From 7d2b865a1e333a3ea15e59b2a2ad70bf28b42642 Mon Sep 17 00:00:00 2001 From: Steffen Wonning Date: Mon, 9 Oct 2023 12:06:53 +0200 Subject: [PATCH] Fix invalid property usage for const properties and seperate constants from normal properties (#61) * Split the file constant into an own spec structure to reduce complexity * Add test class for the FileConstWriter * Integrate FileConstantSpec to replace the PropertySpec usage * Add test for the new structure * Improve property tests * Update file const write * Improve property writing * Add emitFileConst method * Update ALLOWED_CONST_MODIFIERS content * Improve allowed modifiers check * Replace check with require * Update validation checks and dedicated method for a property as constant * Mark type as nullable * Deprecate ConstClassName * Update type check * Improve property tests * Update content from the ALLOWED_CLASS_CONST_MODIFIERS constant * Update to the new spec structure for constant values * Update method call to write the constants * Rename constant writer * Update constant property access in the test cases * Remove old place for the constant spec structure * Change name of the constant property objects * Fix failing tests --- .../net/theevilreaper/dartpoet/DartFile.kt | 10 +- .../theevilreaper/dartpoet/DartFileBuilder.kt | 7 +- .../dartpoet/clazz/ClassBuilder.kt | 7 +- .../theevilreaper/dartpoet/clazz/ClassSpec.kt | 13 +- .../dartpoet/code/CodeWriterExtension.kt | 18 ++ .../dartpoet/code/writer/ClassWriter.kt | 6 +- .../code/writer/ConstantPropertyWriter.kt | 30 +++ .../dartpoet/code/writer/DartFileWriter.kt | 4 +- .../dartpoet/code/writer/PropertyWriter.kt | 17 +- .../dartpoet/property/PropertyBuilder.kt | 2 +- .../dartpoet/property/PropertySpec.kt | 84 +++++++-- .../consts/ConstantPropertyBuilder.kt | 51 ++++++ .../property/consts/ConstantPropertySpec.kt | 171 ++++++++++++++++++ .../dartpoet/type/ConstClassName.kt | 1 + .../theevilreaper/dartpoet/type/TypeNames.kt | 4 +- .../theevilreaper/dartpoet/util/Constants.kt | 4 +- .../theevilreaper/dartpoet/DartFileTest.kt | 7 +- .../dartpoet/code/writer/ClassWriterTest.kt | 9 +- .../code/writer/ConstantPropertyWriterTest.kt | 95 ++++++++++ .../code/writer/PropertyWriterTest.kt | 28 ++- .../property/ConstantPropertySpecTest.kt | 53 ++++++ .../{ => property}/PropertySpecTest.kt | 42 ++++- 22 files changed, 591 insertions(+), 72 deletions(-) create mode 100644 src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriter.kt create mode 100644 src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertyBuilder.kt create mode 100644 src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertySpec.kt create mode 100644 src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriterTest.kt create mode 100644 src/test/kotlin/net/theevilreaper/dartpoet/property/ConstantPropertySpecTest.kt rename src/test/kotlin/net/theevilreaper/dartpoet/{ => property}/PropertySpecTest.kt (55%) diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt b/src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt index 55cb77f1..ebbe90f5 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt @@ -8,11 +8,10 @@ import net.theevilreaper.dartpoet.code.writer.DartFileWriter import net.theevilreaper.dartpoet.extension.ExtensionSpec import net.theevilreaper.dartpoet.function.FunctionSpec import net.theevilreaper.dartpoet.util.* -import net.theevilreaper.dartpoet.util.ALLOWED_CONST_MODIFIERS import net.theevilreaper.dartpoet.directive.DartDirective import net.theevilreaper.dartpoet.directive.LibraryDirective import net.theevilreaper.dartpoet.directive.PartDirective -import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.util.DART_FILE_ENDING import net.theevilreaper.dartpoet.util.isDartConventionFileName import net.theevilreaper.dartpoet.util.toImmutableList @@ -32,12 +31,7 @@ class DartFile internal constructor( internal val extensions: List = builder.extensionStack internal val docs = builder.docs private val directives = builder.directives.toImmutableList() - internal val constants: Set = builder.constants.onEach { - // Only check modifiers when the size is not zero - if (it.modifiers.isNotEmpty()) { - hasAllowedModifiers(it.modifiers, ALLOWED_CONST_MODIFIERS, "file const") - } - }.toImmutableSet() + internal val constants: Set = builder.constants.toImmutableSet() internal val imports: List = if (directives.isEmpty()) { emptyList() diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/DartFileBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/DartFileBuilder.kt index fbb6a9f6..315ece1a 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/DartFileBuilder.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/DartFileBuilder.kt @@ -6,6 +6,7 @@ import net.theevilreaper.dartpoet.clazz.ClassSpec import net.theevilreaper.dartpoet.code.CodeBlock import net.theevilreaper.dartpoet.extension.ExtensionSpec import net.theevilreaper.dartpoet.directive.Directive +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.property.PropertySpec import net.theevilreaper.dartpoet.util.DEFAULT_INDENT import net.theevilreaper.dartpoet.util.isIndent @@ -18,18 +19,18 @@ class DartFileBuilder( internal val directives: MutableList = mutableListOf() internal val annotations: MutableList = mutableListOf() internal val extensionStack: MutableList = mutableListOf() - internal val constants: MutableSet = mutableSetOf() + internal val constants: MutableSet = mutableSetOf() internal var indent = DEFAULT_INDENT /** * Add a constant [PropertySpec] to the file. * @param constant the property to add */ - fun constant(constant: PropertySpec) = apply { + fun constant(constant: ConstantPropertySpec) = apply { this.constants += constant } - fun constants(vararg constants: PropertySpec) = apply { + fun constants(vararg constants: ConstantPropertySpec) = apply { this.constants += constants } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt index 054908c2..528c1828 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt @@ -9,6 +9,7 @@ import net.theevilreaper.dartpoet.meta.SpecData import net.theevilreaper.dartpoet.meta.SpecMethods import net.theevilreaper.dartpoet.function.constructor.ConstructorSpec import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.type.TypeName import net.theevilreaper.dartpoet.type.asTypeName import java.lang.reflect.Type @@ -30,7 +31,7 @@ class ClassBuilder internal constructor( internal val propertyStack: MutableList = mutableListOf() internal val functionStack: MutableList = mutableListOf() internal val enumPropertyStack: MutableList = mutableListOf() - internal val constantStack: MutableSet = mutableSetOf() + internal val constantStack: MutableSet = mutableSetOf() internal var superClass: TypeName? = null internal var inheritKeyWord: InheritKeyword? = null internal var endWithNewLine = false @@ -40,7 +41,7 @@ class ClassBuilder internal constructor( * Add a constant [PropertySpec] to the file. * @param constant the property to add */ - fun constant(constant: PropertySpec) = apply { + fun constant(constant: ConstantPropertySpec) = apply { this.constantStack += constant } @@ -48,7 +49,7 @@ class ClassBuilder internal constructor( * Add an array of constant [PropertySpec] to the file. * @param constants the array to add */ - fun constants(vararg constants: PropertySpec) = apply { + fun constants(vararg constants: ConstantPropertySpec) = apply { this.constantStack += constants } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassSpec.kt b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassSpec.kt index ef14260e..4094c39b 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassSpec.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassSpec.kt @@ -1,11 +1,9 @@ package net.theevilreaper.dartpoet.clazz -import net.theevilreaper.dartpoet.DartModifier import net.theevilreaper.dartpoet.DartModifier.* import net.theevilreaper.dartpoet.code.CodeWriter import net.theevilreaper.dartpoet.code.buildCodeString import net.theevilreaper.dartpoet.code.writer.ClassWriter -import net.theevilreaper.dartpoet.util.* import net.theevilreaper.dartpoet.util.toImmutableList import net.theevilreaper.dartpoet.util.toImmutableSet @@ -38,10 +36,7 @@ class ClassSpec internal constructor( internal val constructors = builder.constructorStack.toImmutableSet() internal val typeDefStack = builder.functionStack.filter { it.isTypeDef }.toImmutableSet() internal val enumPropertyStack = builder.enumPropertyStack.toImmutableList() - internal val constantStack = builder.constantStack.onEach { - hasAllowedModifiers(it.modifiers, ALLOWED_CLASS_CONST_MODIFIERS, "class constants") - it.modifiers = it.modifiers.sorted().toSet() - }.toImmutableSet() + internal var constantStack = builder.constantStack.toImmutableSet() /** * Returns true when the class has no content to generate. @@ -67,7 +62,9 @@ class ClassSpec internal constructor( * Calls the [ClassWriter] to write the data from the spec into code for dart * @param codeWriter the [CodeWriter] instance to apply the data */ - internal fun write(codeWriter: CodeWriter) { ClassWriter().write(this, codeWriter) } + internal fun write(codeWriter: CodeWriter) { + ClassWriter().write(this, codeWriter) + } /** * Returns a [String] representation from the class spec. @@ -85,7 +82,7 @@ class ClassSpec internal constructor( * @return the created instance */ @JvmStatic - fun builder(name: String) = ClassBuilder(name, ClassType.CLASS, DartModifier.CLASS) + fun builder(name: String) = ClassBuilder(name, ClassType.CLASS, CLASS) /** * Create a new [ClassBuilder] instance for an anonymous dart class. diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt index ad229fa9..a79b6b15 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt @@ -5,6 +5,7 @@ import net.theevilreaper.dartpoet.extension.ExtensionSpec import net.theevilreaper.dartpoet.function.FunctionSpec import net.theevilreaper.dartpoet.function.constructor.ConstructorSpec import net.theevilreaper.dartpoet.directive.Directive +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.parameter.ParameterSpec import net.theevilreaper.dartpoet.property.PropertySpec import net.theevilreaper.dartpoet.util.CURLY_CLOSE @@ -180,6 +181,23 @@ internal fun List.writeImports( } } +fun Set.emitConstants( + codeWriter: CodeWriter, + emitBlock: (ConstantPropertySpec) -> Unit = { it.write(codeWriter) } +) = with(codeWriter) { + if (isNotEmpty()) { + val emitNewLines = size > 1 + + forEachIndexed { index, property -> + if (index > 0) { + emit(if (emitNewLines) NEW_LINE else EMPTY_STRING) + } + emitBlock(property) + } + emit(NEW_LINE) + } +} + fun Set.emitProperties( codeWriter: CodeWriter, forceNewLines: Boolean = false, diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriter.kt index 9d106fa2..e8f00f2e 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriter.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriter.kt @@ -72,7 +72,7 @@ class ClassWriter { codeWriter.emit(NEW_LINE) } - spec.constantStack.emitProperties(codeWriter) + spec.constantStack.emitConstants(codeWriter) if (spec.constantStack.isNotEmpty()) { codeWriter.emit(NEW_LINE) @@ -137,11 +137,13 @@ class ClassWriter { writer.emit(spec.name!!) } } + ClassType.ABSTRACT -> { writer.emit("${type.keyword}·${ClassType.CLASS.keyword}·") writer.emit(if (spec.modifiers.contains(PRIVATE)) PRIVATE.identifier else EMPTY_STRING) writer.emit(spec.name!!) } + else -> { //TODO: Check if a library class needs a header // A library class doesn't have any class header @@ -160,7 +162,7 @@ class ClassWriter { private fun List.emit( codeWriter: CodeWriter, - emitBlock: (EnumPropertySpec) -> Unit = { it.write(codeWriter) } + emitBlock: (EnumPropertySpec) -> Unit = { it.write(codeWriter) } ) = with(codeWriter) { if (isNotEmpty()) { forEachIndexed { index, enumPropertySpec -> diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriter.kt new file mode 100644 index 00000000..2641a2b3 --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriter.kt @@ -0,0 +1,30 @@ +package net.theevilreaper.dartpoet.code.writer + +import net.theevilreaper.dartpoet.DartModifier.* +import net.theevilreaper.dartpoet.code.CodeWriter +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec +import net.theevilreaper.dartpoet.util.SEMICOLON +import net.theevilreaper.dartpoet.util.SPACE + +internal class ConstantPropertyWriter { + + fun emit(spec: ConstantPropertySpec, codeWriter: CodeWriter) { + val modifiersAsString = spec.modifiers.joinToString(separator = SPACE, postfix = SPACE) { it.identifier } + + codeWriter.emit(modifiersAsString) + + if (spec.typeName != null) { + codeWriter.emitCode("%T·", spec.typeName) + } + + if (spec.isPrivat) { + codeWriter.emit(PRIVATE.identifier) + } + + codeWriter.emitCode("%L", spec.name) + + codeWriter.emit("·=·") + codeWriter.emitCode(spec.initializer.build(), isConstantContext = true) + codeWriter.emit(SEMICOLON) + } +} diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/DartFileWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/DartFileWriter.kt index 86de9c81..dda7b92e 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/DartFileWriter.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/DartFileWriter.kt @@ -3,7 +3,7 @@ package net.theevilreaper.dartpoet.code.writer import net.theevilreaper.dartpoet.DartFile import net.theevilreaper.dartpoet.code.CodeWriter import net.theevilreaper.dartpoet.code.emitExtensions -import net.theevilreaper.dartpoet.code.emitProperties +import net.theevilreaper.dartpoet.code.emitConstants import net.theevilreaper.dartpoet.code.writeImports import net.theevilreaper.dartpoet.util.NEW_LINE @@ -40,7 +40,7 @@ class DartFileWriter { writer.emit(NEW_LINE) } - dartFile.constants.emitProperties(writer) { + dartFile.constants.emitConstants(writer) { it.write(writer) } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriter.kt index 2b2a7421..081462f6 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriter.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriter.kt @@ -9,29 +9,26 @@ import net.theevilreaper.dartpoet.util.EMPTY_STRING import net.theevilreaper.dartpoet.util.SEMICOLON import net.theevilreaper.dartpoet.util.SPACE -class PropertyWriter { +internal class PropertyWriter { fun write(property: PropertySpec, writer: CodeWriter) { if (property.hasDocs) { - property.docs.forEach { writer.emitDoc(it) } } property.annotations.emitAnnotations(writer) { it.write(writer, inline = false) } - val modifierString = property.modifiers.joinToString(separator = SPACE) { it.identifier } + val modifierString = property.modifiers.joinToString( + separator = SPACE, + postfix = if (property.modifiers.isNotEmpty()) SPACE else EMPTY_STRING + ) { it.identifier } writer.emit(modifierString) - if (!property.isConst) { - if (property.modifiers.isNotEmpty()) { - writer.emit(SPACE) - } - writer.emitCode("%T", property.type) + if (property.type != null) { + writer.emitCode("%T·", property.type) } - writer.emit("·") - writer.emit(if (property.isPrivate) DartModifier.PRIVATE.identifier else EMPTY_STRING) writer.emit(property.name) emitInitBlock(property.initBlock, writer) diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertyBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertyBuilder.kt index efa145ee..d3398efa 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertyBuilder.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertyBuilder.kt @@ -13,7 +13,7 @@ import net.theevilreaper.dartpoet.type.TypeName **/ class PropertyBuilder internal constructor( var name: String, - var type: TypeName, + var type: TypeName? = null, ) { internal val modifiers: MutableSet = mutableSetOf() internal val annotations: MutableList = mutableListOf() diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertySpec.kt b/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertySpec.kt index 2cc97c35..d44d7c34 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertySpec.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/property/PropertySpec.kt @@ -5,10 +5,10 @@ import net.theevilreaper.dartpoet.annotation.AnnotationSpec import net.theevilreaper.dartpoet.code.CodeWriter import net.theevilreaper.dartpoet.code.writer.PropertyWriter import net.theevilreaper.dartpoet.code.buildCodeString -import net.theevilreaper.dartpoet.type.CONST import net.theevilreaper.dartpoet.type.ClassName import net.theevilreaper.dartpoet.type.TypeName import net.theevilreaper.dartpoet.type.asTypeName +import net.theevilreaper.dartpoet.util.ALLOWED_CONST_MODIFIERS import net.theevilreaper.dartpoet.util.toImmutableSet import net.theevilreaper.dartpoet.util.ALLOWED_PROPERTY_MODIFIERS import net.theevilreaper.dartpoet.util.hasAllowedModifiers @@ -28,19 +28,31 @@ class PropertySpec( internal var annotations: Set = builder.annotations.toImmutableSet() internal var initBlock = builder.initBlock internal var isPrivate = builder.modifiers.contains(DartModifier.PRIVATE) - internal val isConst = builder.type == CONST + internal var isConst = builder.modifiers.contains(DartModifier.CONST) internal val docs = builder.docs internal val hasDocs = builder.docs.isNotEmpty() internal var modifiers: Set = builder.modifiers .also { - hasAllowedModifiers(it, ALLOWED_PROPERTY_MODIFIERS, "property") + if (it.isNotEmpty()) { + hasAllowedModifiers(it, ALLOWED_PROPERTY_MODIFIERS, "property") + if (type == null) { + hasAllowedModifiers(it, ALLOWED_CONST_MODIFIERS, "const property") + it.clear() + it.addAll(ALLOWED_CONST_MODIFIERS) + } + } }.filter { it != DartModifier.PRIVATE && it != DartModifier.PUBLIC }.toImmutableSet() init { - check(name.trim().isNotEmpty()) { "The name of a parameter can't be empty" } - /** check(!modifiers.contains(DartModifier.CONST) && !modifiers.contains(DartModifier.STATIC)) { - "Only" - }**/ + require(name.trim().isNotEmpty()) { "The name of a property can't be empty" } + + if (builder.type == null && !isConst) { + throw IllegalArgumentException("Only a const property can have no type") + } + + if (isConst && this.initBlock.isEmpty()) { + throw IllegalArgumentException("A const variable needs an init block") + } } /** @@ -75,8 +87,12 @@ class PropertySpec( companion object { /** - * Creates a new instance from the [PropertyBuilder]. - * The modifier parameter is optional + * Create a [PropertyBuilder] with the specified property name, [ClassName] type, and optional modifiers. + * + * @param name the name of the property + * @param type the [ClassName] representing the type of the property + * @param modifiers an array of modifiers to apply to the property. Defaults to an empty array if not provided + * @return a [PropertyBuilder] instance configured with the specified parameters */ @JvmStatic fun builder( @@ -87,15 +103,32 @@ class PropertySpec( return PropertyBuilder(name, type).modifiers(*modifiers) } + /** + * Create a [PropertyBuilder] with the specified property name, [TypeName] type, and optional modifiers. + * + * @param name the name of the property + * @param type the [TypeName] representing the type of the property + * @param modifiers an array of modifiers to apply to the property. Defaults to an empty array if not provided + * @return a [PropertyBuilder] instance configured with the specified parameters + */ @JvmStatic fun builder( name: String, type: TypeName, - vararg modifiers: DartModifier + vararg modifiers: DartModifier = emptyArray() ): PropertyBuilder { return PropertyBuilder(name, type).modifiers(*modifiers) } + /** + * Create a [PropertyBuilder] with the specified property name, [KClass] type, and optional modifiers. + * + * @param name the name of the property + * @param type the [KClass] representing the type of the property + * @param modifiers an array of modifiers to apply to the property. Defaults to an empty array if not provided + * @return a [PropertyBuilder] instance configured with the specified parameters + */ + @JvmStatic fun builder( name: String, type: KClass<*>, @@ -104,7 +137,36 @@ class PropertySpec( return PropertyBuilder(name, type.asTypeName()).modifiers(*modifiers) } + /** + * Create a [PropertyBuilder] with only the property name. + * + * @param name the name of the property + * @return a [PropertyBuilder] instance configured with the specified parameters + */ + @JvmStatic + fun constBuilder(name: String): PropertyBuilder = + PropertyBuilder(name).modifiers(*ALLOWED_CONST_MODIFIERS.toTypedArray()) + + /** + * Create a [PropertyBuilder] with only the property name and a given type. + * + * @param name the name of the property + * @param type the [TypeName] representing the type of the property + * @return a [PropertyBuilder] instance + */ + @JvmStatic + fun constBuilder(name: String, type: TypeName): PropertyBuilder = + PropertyBuilder(name, type).modifiers(*ALLOWED_CONST_MODIFIERS.toTypedArray()) + + /** + * Create a [PropertyBuilder] with only the property name and a given type. + * + * @param name the name of the property + * @param type the [KClass] representing the type of the property + * @return a [PropertyBuilder] instance + */ @JvmStatic - fun constBuilder(name: String) = PropertyBuilder(name, CONST).modifier(DartModifier.CONST) + fun constBuilder(name: String, type: KClass<*>): PropertyBuilder = + PropertyBuilder(name, type.asTypeName()).modifiers(*ALLOWED_CONST_MODIFIERS.toTypedArray()) } } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertyBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertyBuilder.kt new file mode 100644 index 00000000..258e6fef --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertyBuilder.kt @@ -0,0 +1,51 @@ +package net.theevilreaper.dartpoet.property.consts + +import net.theevilreaper.dartpoet.DartFileBuilder +import net.theevilreaper.dartpoet.DartModifier +import net.theevilreaper.dartpoet.code.CodeBlock +import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.type.TypeName + +/** + * The [ConstantPropertyBuilder] can be used to construct new [ConstantPropertySpec] object reference which can be set + * into a [DartFileBuilder]. + * @author theEvilReaper + * @since 1.0.0 + */ +class ConstantPropertyBuilder internal constructor( + val name: String, + val typeName: TypeName? = null, + val modifiers: Set +) { + internal var initializer: CodeBlock.Builder = CodeBlock.Builder() + internal var isPrivat: Boolean = false + + /** + * Apply a given format which contains the parts for the init block of the [PropertySpec]. + * @param format the given format + * @param args the arguments for the format + */ + fun initWith(format: String, vararg args: Any?) = apply { + this.initializer.add(format, *args) + } + + /** + * Set the initializer block directly as [CodeBlock.Builder] to the property. + * @param codeFragment the [CodeBlock.Builder] to set + */ + fun initWith(codeFragment: CodeBlock.Builder) = apply { + this.initializer = codeFragment + } + + fun asPrivat(boolean: Boolean) = apply { + this.isPrivat = boolean + } + + /** + * Constructs a [ConstantPropertySpec] reference from the current builder instance + * @return the created [ConstantPropertySpec] instance + */ + fun build(): ConstantPropertySpec { + return ConstantPropertySpec(this) + } +} diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertySpec.kt b/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertySpec.kt new file mode 100644 index 00000000..e037d268 --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/property/consts/ConstantPropertySpec.kt @@ -0,0 +1,171 @@ +package net.theevilreaper.dartpoet.property.consts + +import net.theevilreaper.dartpoet.DartModifier +import net.theevilreaper.dartpoet.code.CodeWriter +import net.theevilreaper.dartpoet.code.buildCodeString +import net.theevilreaper.dartpoet.code.writer.ConstantPropertyWriter +import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.type.ClassName +import net.theevilreaper.dartpoet.type.TypeName +import net.theevilreaper.dartpoet.type.asTypeName +import net.theevilreaper.dartpoet.util.ALLOWED_CLASS_CONST_MODIFIERS +import net.theevilreaper.dartpoet.util.ALLOWED_CONST_MODIFIERS +import net.theevilreaper.dartpoet.util.toImmutableSet +import kotlin.reflect.KClass + +/** + * The [ConstantPropertySpec] is special implementation which contains the same structure as the [PropertySpec] implementation. + * It's separated to avoid any conflicts with the [PropertySpec] implementation. Tge separation also reduces the complexity + * of the writer which is responsible for the generation of the code for properties. + * A file constant can't have the ability to have more modifiers then [DartModifier.CONST]. + * @author theEvilReaper + * @since 1.0.0 + */ +class ConstantPropertySpec( + builder: ConstantPropertyBuilder +) { + internal val name = builder.name + internal val typeName = builder.typeName + internal val initializer = builder.initializer + internal val isPrivat = builder.isPrivat + internal val modifiers = builder.modifiers.toImmutableSet() + + init { + require(name.trim().isNotEmpty()) { "The name of a file constant can't be empty" } + require(initializer.isNotEmpty()) { "The initializer can't be empty" } + + if (this.modifiers.size == 1 && this.modifiers.first() == DartModifier.CONST && isPrivat) { + throw IllegalArgumentException("A file constant can't be private") + } + } + + /** + * Trigger the write process from the [ConstantPropertyWriter] to write the spec into dart code. + * @param codeWriter the [CodeWriter] to apply the content from the spec + */ + internal fun write(codeWriter: CodeWriter) { + ConstantPropertyWriter().emit(this, codeWriter) + } + + /** + * Returns a textual representation of the spec class. + * It calls the [write] method to get the representation + * @return the created representation + */ + override fun toString() = buildCodeString { write(this) } + + /** + * Creates a new instance builder instance with the values from a given [ConstantPropertySpec] reference. + * @return the created instance from the [ConstantPropertySpec] + */ + fun toBuilder(): ConstantPropertyBuilder { + val builder = ConstantPropertyBuilder(name, typeName, modifiers) + builder.initializer = initializer + builder.isPrivat = isPrivat + return builder + } + + companion object { + + /** + * Create a new builder reference to create [ConstantPropertySpec]. + * This method should be used for the constants for a class. + * Adding a [classConst] to a file occurs an error. + * + * @param name the name for the property + * @param type the type for the property provided as [ClassName] + * @param type The type of the constant property. + * @return an instance of [ConstantPropertyBuilder] representing the constant property + */ + @JvmStatic + fun classConst( + name: String, + type: ClassName + ) = ConstantPropertyBuilder(name, type, ALLOWED_CONST_MODIFIERS) + + /** + * Create a new builder reference to create [ConstantPropertySpec]. + * This method should be used for the constants for a class. + * Adding a [classConst] to a file occurs an error. + * + * @param name the name for the property + * @param type the type for the property provided as [TypeName] + * @return an instance of [ConstantPropertyBuilder] representing the constant property + */ + @JvmStatic + fun classConst( + name: String, + type: TypeName + ) = ConstantPropertyBuilder(name, type, ALLOWED_CONST_MODIFIERS) + + /** + * Create a new builder reference to create [ConstantPropertySpec]. + * This method should be used for the constants for a class. + * Adding a [classConst] to a file occurs an error. + * + * @param name the name for the property + * @param type the type for the property provided as [KClass] + * @return an instance of [ConstantPropertyBuilder] representing the constant property. + */ + @JvmStatic + fun classConst( + name: String, + type: KClass<*> + ) = ConstantPropertyBuilder(name, type.asTypeName(), ALLOWED_CONST_MODIFIERS) + + /** + * Create a new builder reference to create [ConstantPropertySpec]. + * This method should be used for the constants for a class. + * Adding a [classConst] to a file occurs an error. + * + * @param name the name for the property + * @return an instance of [ConstantPropertyBuilder] representing the constant property + */ + @JvmStatic + fun classConst( + name: String, + ) = ConstantPropertyBuilder(name, null, ALLOWED_CONST_MODIFIERS) + + /** + * Creates a new instance from the [ConstantPropertyBuilder]. + * @param name the name of the property as [ClassName] + * @return the created instance from the [ConstantPropertyBuilder] + */ + @JvmStatic + fun fileConst( + name: String, + type: ClassName, + ) = ConstantPropertyBuilder(name, type, ALLOWED_CLASS_CONST_MODIFIERS) + + /** + * Creates a new instance from the [ConstantPropertyBuilder]. + * @param name the name of the property + * @param type the type for the property as [TypeName] + * @return the created instance from the [ConstantPropertyBuilder] + */ + @JvmStatic + fun fileConst( + name: String, + type: TypeName, + ) = ConstantPropertyBuilder(name, type, ALLOWED_CLASS_CONST_MODIFIERS) + + /** + * Creates a new instance from the [ConstantPropertyBuilder]. + * @param name the name of the property + * @param type the type for the property as [KClass] + * @return the created instance from the [ConstantPropertyBuilder] + */ + fun fileConst( + name: String, + type: KClass<*>, + ) = ConstantPropertyBuilder(name, type.asTypeName(), ALLOWED_CLASS_CONST_MODIFIERS) + + /** + * Creates a new instance from the [ConstantPropertyBuilder]. + * @param name the name of the property + * @return the created instance from the [ConstantPropertyBuilder] + */ + @JvmStatic + fun fileConst(name: String) = ConstantPropertyBuilder(name, null, ALLOWED_CLASS_CONST_MODIFIERS) + } +} diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/type/ConstClassName.kt b/src/main/kotlin/net/theevilreaper/dartpoet/type/ConstClassName.kt index 0338b00e..7c3a2280 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/type/ConstClassName.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/type/ConstClassName.kt @@ -10,6 +10,7 @@ import net.theevilreaper.dartpoet.DartModifier * @author theEvilReaper * @since 1.0.0 */ +@Deprecated("The usage of the const class name is unclear. For now its deprecated") internal class ConstClassName : ClassName(DartModifier.CONST.identifier) { /** diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/type/TypeNames.kt b/src/main/kotlin/net/theevilreaper/dartpoet/type/TypeNames.kt index f1c612ad..b66d03dc 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/type/TypeNames.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/type/TypeNames.kt @@ -13,7 +13,7 @@ val DOUBLE: ClassName = ClassName("double") val STRING: ClassName = ClassName("String") @JvmField -val CONST: ClassName = ConstClassName() +internal val CONST: ClassName = ConstClassName() @JvmField -val DYNAMIC: ClassName = DynamicClassName() \ No newline at end of file +val DYNAMIC: ClassName = DynamicClassName() diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/util/Constants.kt b/src/main/kotlin/net/theevilreaper/dartpoet/util/Constants.kt index 1cdb3817..399a2e3d 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/util/Constants.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/util/Constants.kt @@ -31,8 +31,8 @@ internal val ALLOWED_FUNCTION_MODIFIERS = setOf(DartModifier.PUBLIC, DartModifier.PRIVATE, DartModifier.STATIC, DartModifier.TYPEDEF) internal val ALLOWED_PROPERTY_MODIFIERS = setOf(DartModifier.PRIVATE, DartModifier.FINAL, DartModifier.LATE, DartModifier.STATIC, DartModifier.CONST) -internal val ALLOWED_CLASS_CONST_MODIFIERS = setOf(DartModifier.STATIC, DartModifier.CONST) -internal val ALLOWED_CONST_MODIFIERS = setOf(DartModifier.CONST) +internal val ALLOWED_CLASS_CONST_MODIFIERS = setOf(DartModifier.CONST) +internal val ALLOWED_CONST_MODIFIERS = setOf(DartModifier.STATIC, DartModifier.CONST) //RegEx private val namePattern: Regex = Regex("[a-z]+|([a-z]+)_+([a-z]+)") diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt index cacb4317..b37abbe1 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt @@ -11,6 +11,7 @@ import net.theevilreaper.dartpoet.directive.DartDirective import net.theevilreaper.dartpoet.directive.CastType import net.theevilreaper.dartpoet.directive.LibraryDirective import net.theevilreaper.dartpoet.directive.PartDirective +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.parameter.ParameterSpec import net.theevilreaper.dartpoet.property.PropertySpec import net.theevilreaper.dartpoet.type.ClassName @@ -338,9 +339,9 @@ class DartFileTest { val classFile = DartFile.builder(name) .directive(DartDirective("dart:html")) .constants( - PropertySpec.constBuilder("typeLive").initWith("1").build(), - PropertySpec.constBuilder("typeTest").initWith("10").build(), - PropertySpec.constBuilder("typeDev").initWith("100").build(), + ConstantPropertySpec.fileConst("typeLive").initWith("1").build(), + ConstantPropertySpec.fileConst("typeTest").initWith("10").build(), + ConstantPropertySpec.fileConst("typeDev").initWith("100").build(), ) .type( ClassSpec.builder(name.replaceFirstChar { it.uppercase() }) diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriterTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriterTest.kt index dc9fb994..7d3323b2 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriterTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ClassWriterTest.kt @@ -1,9 +1,8 @@ package net.theevilreaper.dartpoet.code.writer import com.google.common.truth.Truth.assertThat -import net.theevilreaper.dartpoet.DartModifier import net.theevilreaper.dartpoet.clazz.ClassSpec -import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments @@ -46,12 +45,10 @@ class ClassWriterTest { fun `test class writing with some constants`() { val clazz = ClassSpec.builder("TestClass") .constants( - PropertySpec.builder("test", String::class) - .modifiers(DartModifier.STATIC, DartModifier.CONST) + ConstantPropertySpec.classConst("test", String::class) .initWith("%C", "Test") .build(), - PropertySpec.builder("maxId", Int::class) - .modifiers(DartModifier.CONST, DartModifier.STATIC) + ConstantPropertySpec.classConst("maxId", Int::class) .initWith("%L", "100") .build(), ) diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriterTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriterTest.kt new file mode 100644 index 00000000..223460ce --- /dev/null +++ b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/ConstantPropertyWriterTest.kt @@ -0,0 +1,95 @@ +package net.theevilreaper.dartpoet.code.writer + +import com.google.common.truth.Truth.assertThat +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec +import net.theevilreaper.dartpoet.type.ClassName +import net.theevilreaper.dartpoet.type.ParameterizedTypeName.Companion.parameterizedBy +import net.theevilreaper.dartpoet.type.asClassName +import net.theevilreaper.dartpoet.type.asTypeName +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class ConstantPropertyWriterTest { + + companion object { + + @JvmStatic + private fun testFileConstantWriting() = Stream.of( + Arguments.of( + "const String test = 'Test';", + { ConstantPropertySpec.fileConst("test", String::class).initWith("%C", "Test").build() } + ), + Arguments.of( + "const maxId = 100;", + { ConstantPropertySpec.fileConst("maxId").initWith("%L", "100").build() } + ), + Arguments.of( + "const List strings = [];", + { + ConstantPropertySpec.fileConst( + "strings", + List::class.asClassName().parameterizedBy(String::class.asTypeName()) + ).initWith("[]").build() + } + ), + Arguments.of( + "const TestModel model = TestModel();", + { + ConstantPropertySpec.fileConst("model", ClassName("TestModel")).initWith("%L", "TestModel()") + .build() + } + ) + ) + + @JvmStatic + private fun testClassConstWriting() = Stream.of( + Arguments.of( + "static const String test = 'Test';", + { ConstantPropertySpec.classConst("test", String::class).initWith("%C", "Test").build() } + ), + Arguments.of( + "static const maxId = 100;", + { ConstantPropertySpec.classConst("maxId").initWith("%L", "100").build() } + ), + Arguments.of( + "static const List strings = [];", + { + ConstantPropertySpec.classConst( + "strings", + List::class.asClassName().parameterizedBy(String::class.asTypeName()) + ).initWith("[]").build() + } + ), + Arguments.of( + "static const TestModel model = TestModel();", + { + ConstantPropertySpec.classConst("model", ClassName("TestModel")).initWith("%L", "TestModel()") + .build() + } + ), + Arguments.of( + "static const int _test = 1;", + { + ConstantPropertySpec.classConst("test", Int::class) + .initWith("%L", "1") + .asPrivat(true) + .build() + } + ) + ) + } + + @ParameterizedTest + @MethodSource("testFileConstantWriting") + fun `test basic file const creation`(expected: String, block: () -> ConstantPropertySpec) { + assertThat(block().toString()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("testClassConstWriting") + fun `test basic class const creation`(expected: String, block: () -> ConstantPropertySpec) { + assertThat(block().toString()).isEqualTo(expected) + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriterTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriterTest.kt index a6f1b3f9..21dfb568 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriterTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/PropertyWriterTest.kt @@ -19,8 +19,9 @@ class PropertyWriterTest { @JvmStatic private fun simpleProperties(): Stream = Stream.of( Arguments.of(PropertySpec.builder("id", Int::class).build(), "int id;"), - Arguments.of(PropertySpec.builder("id", String::class.asTypeName().copy(true)) - .build(), + Arguments.of( + PropertySpec.builder("id", String::class.asTypeName().copy(true)) + .build(), "String? id;" ), Arguments.of( @@ -42,6 +43,18 @@ class PropertyWriterTest { "int age = 12;" ) ) + + @JvmStatic + private fun constPropertyWrite() = Stream.of( + Arguments.of( + PropertySpec.constBuilder("value").initWith("%L", "12").build(), + "static const value = 12;" + ), + Arguments.of( + PropertySpec.constBuilder("test", String::class).initWith("%C", "Test").build(), + "static const String test = 'Test';" + ) + ) } @ParameterizedTest @@ -50,13 +63,10 @@ class PropertyWriterTest { assertEquals(expected, propertySpec.toString()) } - @Test - fun `write const property`() { - val property = PropertySpec.builder("maxID", Int::class) - .modifiers(DartModifier.STATIC, DartModifier.CONST) - .initWith("%L", "1000") - .build() - assertEquals("static const int maxID = 1000;", property.toString()) + @ParameterizedTest + @MethodSource("constPropertyWrite") + fun `test const property writing`(propertySpec: PropertySpec, expected: String) { + assertEquals(expected, propertySpec.toString()) } @Test diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/property/ConstantPropertySpecTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/property/ConstantPropertySpecTest.kt new file mode 100644 index 00000000..313d5685 --- /dev/null +++ b/src/test/kotlin/net/theevilreaper/dartpoet/property/ConstantPropertySpecTest.kt @@ -0,0 +1,53 @@ +package net.theevilreaper.dartpoet.property + +import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.lang.IllegalArgumentException +import java.util.stream.Stream + + +class ConstantPropertySpecTest { + + companion object { + + @JvmStatic + private fun invalidFileConstantSpec() = Stream.of( + Arguments.of( + "The name of a file constant can't be empty", + { ConstantPropertySpec.classConst("", String::class).build() } + ), + Arguments.of( + "The initializer can't be empty", + { ConstantPropertySpec.classConst("test", String::class).build() } + ), + Arguments.of( + "A file constant can't be private", + { ConstantPropertySpec.fileConst("test").initWith("%S", "test").asPrivat(true).build() } + ) + ) + } + + @ParameterizedTest + @MethodSource("invalidFileConstantSpec") + fun `test invalid file constant spec`(message: String, block: () -> ConstantPropertySpec) { + val exception = assertThrows { block() } + assertEquals(message, exception.message) + } + + @Test + fun `test toBuilder`() { + val builder = ConstantPropertySpec.classConst("test", String::class) + builder.initializer.add("%S", "test") + val spec = ConstantPropertySpec(builder) + val newBuilder = spec.toBuilder() + assertEquals(builder.name, newBuilder.name) + assertEquals(builder.typeName, newBuilder.typeName) + assertEquals(builder.initializer, newBuilder.initializer) + assertFalse { newBuilder.isPrivat } + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/PropertySpecTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/property/PropertySpecTest.kt similarity index 55% rename from src/test/kotlin/net/theevilreaper/dartpoet/PropertySpecTest.kt rename to src/test/kotlin/net/theevilreaper/dartpoet/property/PropertySpecTest.kt index 83412bf1..ae795aa8 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/PropertySpecTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/property/PropertySpecTest.kt @@ -1,9 +1,12 @@ -package net.theevilreaper.dartpoet +package net.theevilreaper.dartpoet.property import com.google.common.truth.Truth.assertThat -import net.theevilreaper.dartpoet.property.PropertySpec +import net.theevilreaper.dartpoet.DartModifier import net.theevilreaper.dartpoet.type.asTypeName +import net.theevilreaper.dartpoet.util.ALLOWED_CONST_MODIFIERS +import net.theevilreaper.dartpoet.util.ALLOWED_PROPERTY_MODIFIERS import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -40,6 +43,34 @@ class PropertySpecTest { "final String _id;" ) ) + + @JvmStatic + private fun testInvalidPropertyCreation() = Stream.of( + Arguments.of( + { + PropertySpec.builder("test", String::class) + .modifier(DartModifier.REQUIRED) + .build() + }, + "These modifiers [REQUIRED] are not allowed in a property context. Allowed modifiers: $ALLOWED_PROPERTY_MODIFIERS" + ), + Arguments.of( + { PropertySpec.builder("", String::class).build() }, + "The name of a property can't be empty" + ), + Arguments.of( + { PropertySpec.constBuilder("test", String::class).build() }, + "A const variable needs an init block" + ), + Arguments.of( + { + PropertySpec.constBuilder("test") + .modifier(DartModifier.FINAL) + .build() + }, + "These modifiers [FINAL] are not allowed in a const property context. Allowed modifiers: $ALLOWED_CONST_MODIFIERS" + ) + ) } @ParameterizedTest @@ -48,6 +79,13 @@ class PropertySpecTest { assertThat(propertySpec.toString()).isEqualTo(expected) } + @ParameterizedTest + @MethodSource("testInvalidPropertyCreation") + fun `test property creation with invalid values`(block: () -> Unit, exceptionMessage: String) { + val exception = assertThrows { block() } + assertEquals(exceptionMessage, exception.message) + } + @Test fun `test spec to builder conversation`() { val propertySpec = PropertySpec.builder("amount", Int::class.asTypeName().copy(nullable = true))