diff --git a/.github/workflows/maven-central.yml b/.github/workflows/maven-central.yml index aaeee6c3..c2a0ceb5 100644 --- a/.github/workflows/maven-central.yml +++ b/.github/workflows/maven-central.yml @@ -10,6 +10,11 @@ name: Publish package to the Maven Central Repository on: + workflow_run: + workflows: + - "Test PR" + types: + - completed push: branches: - main diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt index 78fe8b41..bfac7d83 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/clazz/ClassBuilder.kt @@ -4,6 +4,7 @@ import net.theevilreaper.dartpoet.DartModifier import net.theevilreaper.dartpoet.InheritKeyword import net.theevilreaper.dartpoet.annotation.AnnotationSpec import net.theevilreaper.dartpoet.enum.EnumPropertySpec +import net.theevilreaper.dartpoet.function.ConstructorBase import net.theevilreaper.dartpoet.function.FunctionSpec import net.theevilreaper.dartpoet.meta.SpecData import net.theevilreaper.dartpoet.meta.SpecMethods @@ -33,7 +34,7 @@ class ClassBuilder internal constructor( internal val isMixinClass get() = classType == ClassType.MIXIN internal val isAbstract get() = classType == ClassType.ABSTRACT internal val isLibrary get() = classType == ClassType.CLASS - internal val constructorStack: MutableList = mutableListOf() + internal val constructorStack: MutableList = mutableListOf() internal val propertyStack: MutableList = mutableListOf() internal val functionStack: MutableList = mutableListOf() internal val enumPropertyStack: MutableList = mutableListOf() @@ -184,7 +185,7 @@ class ClassBuilder internal constructor( * @param constructor the constructor to add * @return the given instance of an [ClassBuilder] */ - fun constructor(constructor: ConstructorSpec) = apply { + fun constructor(constructor: ConstructorBase) = apply { this.constructorStack += constructor } @@ -193,7 +194,7 @@ class ClassBuilder internal constructor( * @param constructor the constructor to add * @return the given instance of an [ClassBuilder] */ - fun constructor(constructor: () -> ConstructorSpec) = apply { + fun constructor(constructor: () -> ConstructorBase) = apply { this.constructorStack += constructor() } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt index 621852ea..576014d6 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/CodeWriterExtension.kt @@ -5,6 +5,8 @@ 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.function.ConstructorBase +import net.theevilreaper.dartpoet.function.factory.FactorySpec import net.theevilreaper.dartpoet.function.typedef.TypeDefSpec import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec import net.theevilreaper.dartpoet.parameter.ParameterSpec @@ -39,6 +41,7 @@ val Char.isMultiCharNoArgPlaceholder get() = this == '%' internal val String.isPlaceholder get() = (length == 1 && first().isSingleCharNoArgPlaceholder) || (length == 2 && first().isMultiCharNoArgPlaceholder) + fun String.nextPotentialPlaceholderPosition(startIndex: Int) = indexOfAny(NO_ARG_PLACEHOLDERS, startIndex) @@ -82,28 +85,24 @@ internal fun Set.emitAnnotations( } } -internal fun Set.emitConstructors( +internal fun Set.emitConstructors( codeWriter: CodeWriter, - forceNewLines: Boolean = false, leadingNewLine: Boolean = false, - emitBlock: (ConstructorSpec) -> Unit = { it.write(codeWriter) } + emitBlock: (ConstructorBase) -> Unit = { + if (it is ConstructorSpec) { + (it.write(codeWriter)) + } else if (it is FactorySpec) { + (it.write(codeWriter)) + } + } ) = with(codeWriter) { if (isNotEmpty()) { if (leadingNewLine) { codeWriter.emit(NEW_LINE) } - forEachIndexed { index, constructorSpec -> - val emitNewLines = size >= 1 || forceNewLines - - if (index > 0 && emitNewLines) { - codeWriter.emit(NEW_LINE) - } - - emitBlock(constructorSpec) - - if (emitNewLines) { - codeWriter.emit(NEW_LINE) - } + forEach { constructorBase -> + emitBlock(constructorBase) + codeWriter.emit(NEW_LINE) } } } @@ -117,7 +116,7 @@ internal fun List.emitParameters( if (isNotEmpty()) { val emitComma = size > 1 forEachIndexed { index, parameter -> - if (index > 0 && forceNewLines) { + if (index > 0 && forceNewLines) { emit(NEW_LINE) } @@ -143,7 +142,7 @@ internal fun List.emitExtensions( val emitNewLines = size > 1 || forceNewLines forEachIndexed { index, parameter -> - if (index > 0 && emitNewLines) { + if (index > 0 && emitNewLines) { emit(NEW_LINE) } emitBlock(parameter) @@ -155,7 +154,7 @@ internal fun List.emitExtensions( } } -internal fun List.writeImports( +internal fun List.writeImports( writer: CodeWriter, newLineAtBegin: Boolean = true, emitBlock: (T) -> String = { it.asString() } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/DocumentationAppender.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/DocumentationAppender.kt index 4823b427..f0b2fe5b 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/DocumentationAppender.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/DocumentationAppender.kt @@ -16,4 +16,9 @@ internal interface DocumentationAppender { if (docs.isEmpty()) return docs.forEach { writer.emitDoc(it) } } + + fun emitDocumentation(doc: CodeBlock, writer: CodeWriter) { + if (doc.isEmpty()) return + writer.emitDoc(doc) + } } \ No newline at end of file diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstructorWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstructorWriter.kt index 5d099dbf..61d352f9 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstructorWriter.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/ConstructorWriter.kt @@ -19,10 +19,6 @@ internal class ConstructorWriter : Writeable, DocumentationAppe writer.emit("${DartModifier.CONST.identifier}·") } - if (spec.isFactory) { - writer.emit("${DartModifier.FACTORY.identifier}·") - } - writer.emit(spec.name) if (spec.isNamed) { @@ -69,10 +65,6 @@ internal class ConstructorWriter : Writeable, DocumentationAppe return } - if (spec.isFactory) { - writer.emit(" = _${spec.name}") - } - writer.emit(SEMICOLON) } } diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriter.kt b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriter.kt new file mode 100644 index 00000000..e5fc229b --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriter.kt @@ -0,0 +1,110 @@ +package net.theevilreaper.dartpoet.code.writer + +import net.theevilreaper.dartpoet.DartModifier +import net.theevilreaper.dartpoet.code.CodeWriter +import net.theevilreaper.dartpoet.code.DocumentationAppender +import net.theevilreaper.dartpoet.code.InitializerAppender +import net.theevilreaper.dartpoet.code.emitParameters +import net.theevilreaper.dartpoet.function.factory.FactorySpec +import net.theevilreaper.dartpoet.parameter.ParameterSpec +import net.theevilreaper.dartpoet.util.CURLY_CLOSE +import net.theevilreaper.dartpoet.util.CURLY_OPEN +import net.theevilreaper.dartpoet.util.ROUND_CLOSE +import net.theevilreaper.dartpoet.util.ROUND_OPEN +import net.theevilreaper.dartpoet.util.toImmutableList + +internal class FactoryWriter : InitializerAppender, DocumentationAppender { + + fun write(spec: FactorySpec, codeWriter: CodeWriter) { + emitDocumentation(spec.documentation, codeWriter) + if (spec.isConst) { + codeWriter.emitCode("%L·", DartModifier.CONST.identifier) + } + codeWriter.emitCode("%L·%T", DartModifier.FACTORY.identifier, spec.typeName) + + if (spec.hasNamedData) { + codeWriter.emitCode(".%L", spec.named!!) + } + + if (!spec.hasParameters) { + writeSimpleConstructor(spec, codeWriter) + return + } + + codeWriter.emitCode(ROUND_OPEN) + writeParameters(spec, codeWriter) + codeWriter.emitCode(ROUND_CLOSE) + + if (spec.withLambda) { + codeWriter.emitCode("·=>·") + codeWriter.emitCode(spec.initializerBlock) + return + } + + codeWriter.emitCode("·$CURLY_OPEN\n") + codeWriter.indent() + codeWriter.emitCode(spec.initializerBlock) + codeWriter.unindent() + codeWriter.emit("\n$CURLY_CLOSE") + } + + private fun writeSimpleConstructor(spec: FactorySpec, codeWriter: CodeWriter) { + if (spec.withLambda) { + codeWriter.emitCode("·=>·") + codeWriter.emitCode(spec.initializerBlock) + return + } + codeWriter.emitCode("·$CURLY_OPEN\n") + + codeWriter.indent() + codeWriter.emitCode(spec.initializerBlock) + codeWriter.unindent() + codeWriter.emit("\n$CURLY_CLOSE") + } + + private fun writeParameters(spec: FactorySpec, codeWriter: CodeWriter) { + spec.normalParameter.emitParameters(codeWriter, forceNewLines = false) + + if (spec.normalParameter.isNotEmpty() && (spec.hasAdditionalParameters || spec.parametersWithDefaults.isNotEmpty())) { + codeWriter.emit(", ") + } + + if (spec.hasAdditionalParameters) { + emitRequiredAndNamedParameter(spec, codeWriter) + } + + if (spec.parametersWithDefaults.isNotEmpty()) { + codeWriter.emit("[") + spec.parametersWithDefaults.emitParameters(codeWriter, forceNewLines = false) + codeWriter.emit("]") + } + } + + private fun emitRequiredAndNamedParameter(spec: FactorySpec, codeWriter: CodeWriter) { + codeWriter.emit("$CURLY_OPEN") + + val namedRequired = spec.namedParameter.filter { it.isRequired && !it.hasInitializer }.toImmutableList() + + writeParameters(namedRequired, spec.normalParameter.isNotEmpty(), codeWriter) + writeParameters(spec.requiredParameter, namedRequired.isNotEmpty(), codeWriter) + + val test = + spec.namedParameter.minus(namedRequired).filter { it.isNullable || it.hasInitializer }.toImmutableList() + + writeParameters(test, spec.requiredParameter.isNotEmpty() || namedRequired.isNotEmpty(), codeWriter) + + codeWriter.emit("$CURLY_CLOSE") + } + + private fun writeParameters( + parameters: List, + emitSpaceComma: Boolean = false, + codeWriter: CodeWriter, + ) { + if (parameters.isEmpty()) return + if (emitSpaceComma) { + codeWriter.emit(", ") + } + parameters.emitParameters(codeWriter, forceNewLines = false) + } +} diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/function/ConstructorBase.kt b/src/main/kotlin/net/theevilreaper/dartpoet/function/ConstructorBase.kt new file mode 100644 index 00000000..5c54b1df --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/function/ConstructorBase.kt @@ -0,0 +1,7 @@ +package net.theevilreaper.dartpoet.function + + +interface ConstructorBase { + + +} \ No newline at end of file diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorBuilder.kt index 480bc000..f88c9e25 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorBuilder.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorBuilder.kt @@ -12,7 +12,6 @@ class ConstructorBuilder( internal val parameters: MutableList = mutableListOf() internal var lambda: Boolean = false internal val initializer: CodeBlock.Builder = CodeBlock.builder() - internal var factory: Boolean = false internal val modifiers: MutableList = mutableListOf(*modifiers) internal val docs: MutableList = mutableListOf() @@ -42,14 +41,6 @@ class ConstructorBuilder( this.modifiers += modifiers } - /** - * Indicates if the constructor should be generated as factory. - * @param factory True for a factory generation otherwise false - */ - fun asFactory(factory: Boolean) = apply { - this.factory = factory - } - /** * Add a format string with arguments as initializer. * @param format the format for the block diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpec.kt b/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpec.kt index b39fe07f..d518ce75 100644 --- a/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpec.kt +++ b/src/main/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpec.kt @@ -3,18 +3,18 @@ package net.theevilreaper.dartpoet.function.constructor import net.theevilreaper.dartpoet.code.CodeWriter import net.theevilreaper.dartpoet.code.buildCodeString import net.theevilreaper.dartpoet.code.writer.ConstructorWriter +import net.theevilreaper.dartpoet.function.ConstructorBase import net.theevilreaper.dartpoet.util.toImmutableList import net.theevilreaper.dartpoet.util.toImmutableSet class ConstructorSpec( builder: ConstructorBuilder -) { +): ConstructorBase { internal val name = builder.name internal val named = builder.named internal val isNamed = named.orEmpty().trim().isNotEmpty() internal val isLambda = builder.lambda - internal val isFactory = builder.factory internal val initializer = builder.initializer internal val modifiers = builder.modifiers.toImmutableSet() private val modelParameters = builder.parameters.toImmutableSet() @@ -40,7 +40,6 @@ class ConstructorSpec( fun toBuilder(): ConstructorBuilder { val builder = ConstructorBuilder(this.name, this.named) builder.lambda = this.isLambda - builder.factory = this.isFactory builder.modifiers.addAll(this.modifiers) builder.parameters.addAll(this.modelParameters) diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactoryBuilder.kt b/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactoryBuilder.kt new file mode 100644 index 00000000..db3ed735 --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactoryBuilder.kt @@ -0,0 +1,120 @@ +package net.theevilreaper.dartpoet.function.factory + +import net.theevilreaper.dartpoet.annotation.AnnotationSpec +import net.theevilreaper.dartpoet.code.CodeBlock +import net.theevilreaper.dartpoet.parameter.ParameterSpec +import net.theevilreaper.dartpoet.type.TypeName + +/** + * The [FactoryBuilder] is responsible for configuring and assembling details of a factory constructor. + * @param typeName the type name for the factory constructor + * @since 1.0.0 + * @author theEvilReaper + */ +class FactoryBuilder( + val typeName: TypeName, + val const: Boolean = false, +) { + internal val documentation: CodeBlock.Builder = CodeBlock.builder() + internal val annotations: MutableSet = mutableSetOf() + internal val parameters: MutableSet = mutableSetOf() + internal val initializerBlock: CodeBlock.Builder = CodeBlock.builder() + internal var namedString: String? = null + internal var lambda: Boolean = false + internal var private: Boolean = false + + /** + * Add a new content for the documentation. + * @param format the format string + * @param args the arguments for the format string + * @return the current [FactoryBuilder] instance + */ + fun doc(format: String, vararg args: Any) = apply { + documentation.add(format, *args) + } + + /** + * Add a new [AnnotationSpec] to the factory constructor. + * @param annotation the annotation to add + * @return the current [FactoryBuilder] instance + */ + fun annotation(annotation: AnnotationSpec) = apply { + annotations.add(annotation) + } + + /** + * Add a given array of [AnnotationSpec] to the factory constructor. + * @param annotations the annotations to add + * @return the current [FactoryBuilder] instance + */ + fun annotation(vararg annotations: AnnotationSpec) = apply { + this.annotations.addAll(annotations) + } + + /** + * Add a new [ParameterSpec] to the factory constructor. + * @param parameter the parameter to add + * @return the current [FactoryBuilder] instance + */ + fun parameter(parameter: ParameterSpec) = apply { + parameters.add(parameter) + } + + /** + * Add a given array of [ParameterSpec] to the factory constructor. + * @param parameters the parameters to add + * @return the current [FactoryBuilder] instance + */ + fun parameter(vararg parameters: ParameterSpec) = apply { + this.parameters.addAll(parameters) + } + + /** + * Set the indicator if the factory constructor should be generated with a lambda. + * @param lambda true if the factory constructor should be generated with a lambda, false otherwise + * @return the current [FactoryBuilder] instance + */ + fun lambda(lambda: Boolean) = apply { + this.lambda = lambda + } + + fun private(private: Boolean) = apply { + this.private = private + } + + /** + * Add a format string with arguments as initializer. + * @param format the format for the block + * @param args the arguments for the format + */ + fun addCode(format: String, vararg args: Any?) = apply { + initializerBlock.add(format, *args) + } + + /** + * Add a format string with arguments as initializer. + * @param format the format for the block + * @param args the arguments for the format + */ + fun addNamedCode(format: String, args: Map) = apply { + initializerBlock.addNamed(format, args) + } + + fun named(named: String) = apply { + this.namedString = named + } + + /** + * Add a [CodeBlock] which contains the structure for the initializer for a constructor. + * @param codeBlock the block to add + */ + fun addCode(codeBlock: CodeBlock) = apply { + initializerBlock.add(codeBlock) + } + + /** + * Constructs a new [FactorySpec] reference with the current data. + * @return the created [FactorySpec] instance + */ + fun build(): FactorySpec = FactorySpec(this) +} diff --git a/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactorySpec.kt b/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactorySpec.kt new file mode 100644 index 00000000..d3ba731a --- /dev/null +++ b/src/main/kotlin/net/theevilreaper/dartpoet/function/factory/FactorySpec.kt @@ -0,0 +1,72 @@ +package net.theevilreaper.dartpoet.function.factory + +import net.theevilreaper.dartpoet.code.CodeBlock +import net.theevilreaper.dartpoet.code.CodeWriter +import net.theevilreaper.dartpoet.code.buildCodeString +import net.theevilreaper.dartpoet.code.writer.FactoryWriter +import net.theevilreaper.dartpoet.function.ConstructorBase +import net.theevilreaper.dartpoet.function.constructor.ConstructorSpec +import net.theevilreaper.dartpoet.parameter.ParameterSpec +import net.theevilreaper.dartpoet.type.ClassName +import net.theevilreaper.dartpoet.type.TypeName +import net.theevilreaper.dartpoet.util.ParameterFilter +import net.theevilreaper.dartpoet.util.toImmutableList +import net.theevilreaper.dartpoet.util.toImmutableSet + +/** + * The [FactorySpec] represents the factory construct from the language Dart. + * The language allows the definition of factory constructors which works like a normal constructor but with a + * different syntax. + */ +class FactorySpec( + builder: FactoryBuilder +) : ConstructorBase { + val typeName: TypeName = builder.typeName + val isConst: Boolean = builder.const + val isPrivate: Boolean = builder.private + val documentation: CodeBlock = builder.documentation.build() + val parameters: List = builder.parameters.toImmutableList() + val initializerBlock: CodeBlock = builder.initializerBlock.build() + val withLambda: Boolean = builder.lambda + val named: String? = builder.namedString + val hasNamedData = named.orEmpty().trim().isNotEmpty() + val parameterWithoutType: List = builder.parameters.filter { it.type == null }.toMutableList() + val hasParameterWithoutType get() = parameterWithoutType.isNotEmpty() + val hasParameter: Boolean get() = parameters.isNotEmpty() + internal val parametersWithDefaults = + ParameterFilter.filterParameter(parameters) { !it.isRequired && it.hasInitializer } + internal val requiredParameter = + ParameterFilter.filterParameter(parameters) { it.isRequired && !it.isNamed && !it.hasInitializer } + internal val namedParameter = ParameterFilter.filterParameter(parameters) { it.isNamed } + internal val normalParameter = + parameters.minus(parametersWithDefaults).minus(requiredParameter).minus(namedParameter).toImmutableList() + internal val hasParameters = parameters.isNotEmpty() + internal val hasAdditionalParameters = requiredParameter.isNotEmpty() || namedParameter.isNotEmpty() + + init { + check(initializerBlock.isNotEmpty()) { "The initializer block must not be empty" } + } + + internal fun write( + codeWriter: CodeWriter + ) { + FactoryWriter().write(this, codeWriter) + } + + override fun toString() = buildCodeString { write(this) } + + companion object { + + @JvmStatic + fun builder(typeName: TypeName) = FactoryBuilder(typeName) + + @JvmStatic + fun builder(className: ClassName) = FactoryBuilder(className) + + @JvmStatic + fun constBuilder(typeName: TypeName) = FactoryBuilder(typeName, true) + + @JvmStatic + fun constBuilder(className: ClassName) = FactoryBuilder(className, true) + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt index 5ee1dbbb..c7cbdfcf 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/DartFileTest.kt @@ -50,74 +50,6 @@ class DartFileTest { assertContentEquals(dartFileSpec.annotations, specAsBuilder.annotations) } - @Test - fun `write test model with freezed`() { - val freezedMixing = ClassName("_${'$'}VersionModel") - val versionFreezedClass = ClassSpec.builder("VersionModel") - .superClass(freezedMixing, InheritKeyword.MIXIN) - .annotation { AnnotationSpec.builder("freezed").build() } - .constructor { - ConstructorSpec.builder("VersionModel") - .asFactory(true) - .modifier(DartModifier.CONST) - .parameter { - ParameterSpec.builder("version", String::class) - .named(true) - .annotations( - AnnotationSpec.builder("JsonKey") - .content("name: %C", "version").build(), - AnnotationSpec.builder("Default") - .content("%C", "1.0.0").build() - ) - .build() - } - .build() - } - .constructor { - ConstructorSpec.named("VersionModel", "fromJson") - .lambda(true) - .asFactory(true) - .parameter( - ParameterSpec.builder( - "json", - Map::class.parameterizedBy(String::class.asTypeName(), DYNAMIC) - ).build() - ) - .addCode("%L", "_${"$"}VersionModelFromJson(json);") - .build() - } - val versionFile = DartFile.builder("version.dart") - .directives( - DirectiveFactory.create(DirectiveType.IMPORT, "freezed_annotation/freezed_annotation.dart"), - DirectiveFactory.create(DirectiveType.PART, "version.freezed.dart"), - DirectiveFactory.create(DirectiveType.PART, "version.g.dart") - ) - .type( - versionFreezedClass - ) - .build() - assertThat(versionFile.toString()).isEqualTo( - """ - import 'package:freezed_annotation/freezed_annotation.dart'; - - part 'version.freezed.dart'; - part 'version.g.dart'; - - @freezed - class VersionModel with _${'$'}VersionModel { - - const factory VersionModel({ - @JsonKey(name: 'version')@Default('1.0.0') String version - }) = _VersionModel; - - factory VersionModel.fromJson(Map json) => - _${'$'}VersionModelFromJson(json); - - } - """.trimIndent() - ) - } - @Test fun `test library write`() { val libClass = DartFile.builder("testLib") diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriterTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriterTest.kt new file mode 100644 index 00000000..d05214b8 --- /dev/null +++ b/src/test/kotlin/net/theevilreaper/dartpoet/code/writer/FactoryWriterTest.kt @@ -0,0 +1,66 @@ +package net.theevilreaper.dartpoet.code.writer + +import com.google.common.truth.Truth +import net.theevilreaper.dartpoet.function.factory.FactorySpec +import net.theevilreaper.dartpoet.parameter.ParameterSpec +import net.theevilreaper.dartpoet.type.ClassName +import net.theevilreaper.dartpoet.type.ParameterizedTypeName.Companion.parameterizedBy +import net.theevilreaper.dartpoet.type.asTypeName +import org.junit.jupiter.api.Test + +class FactoryWriterTest { + + @Test + fun `test private factory constructor`() { + val constructorName = "Singleton" + val privateFactoryConstructor = FactorySpec.builder(ClassName(constructorName)) + .addCode("return _instance;") + .build() + Truth.assertThat(privateFactoryConstructor.toString()).isEqualTo( + """ + factory $constructorName { + return _instance; + } + """.trimIndent() + ) + } + + @Test + fun `test factory constructor with parameters`() { + val loggerClass = "Logger" + val constructor = FactorySpec.builder(ClassName(loggerClass)) + .parameter(ParameterSpec.builder("name", String::class).build()) + .addCode("return $loggerClass(name);") + .build() + Truth.assertThat(constructor.toString()).isEqualTo( + """ + factory $loggerClass(String name) { + return $loggerClass(name); + } + """.trimIndent() + ) + } + + @Test + fun `test named factory constructor with parameters`() { + val loggerClass = "Logger" + val jsonKey = "json" + val constructor = FactorySpec.builder(ClassName(loggerClass)) + .named("fromName") + .parameter( + ParameterSpec.builder( + jsonKey, + Map::class.parameterizedBy(String::class.asTypeName(), ClassName("Object")) + ).build() + ) + .addCode("return $loggerClass(%L[%C].toString());", jsonKey, "name") + .build() + Truth.assertThat(constructor.toString()).isEqualTo( + """ + factory $loggerClass.fromName(Map json) { + return $loggerClass($jsonKey['name'].toString()); + } + """.trimIndent() + ) + } +} diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/constructor/FactoryFileTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/constructor/FactoryFileTest.kt new file mode 100644 index 00000000..acf4d15c --- /dev/null +++ b/src/test/kotlin/net/theevilreaper/dartpoet/constructor/FactoryFileTest.kt @@ -0,0 +1,93 @@ +package net.theevilreaper.dartpoet.constructor + +import com.google.common.truth.Truth +import net.theevilreaper.dartpoet.DartFile +import net.theevilreaper.dartpoet.InheritKeyword +import net.theevilreaper.dartpoet.annotation.AnnotationSpec +import net.theevilreaper.dartpoet.clazz.ClassSpec +import net.theevilreaper.dartpoet.directive.DirectiveFactory +import net.theevilreaper.dartpoet.directive.DirectiveType +import net.theevilreaper.dartpoet.function.factory.FactorySpec +import net.theevilreaper.dartpoet.parameter.ParameterSpec +import net.theevilreaper.dartpoet.type.ClassName +import net.theevilreaper.dartpoet.type.DYNAMIC +import net.theevilreaper.dartpoet.type.ParameterizedTypeName.Companion.parameterizedBy +import net.theevilreaper.dartpoet.type.asTypeName +import org.junit.jupiter.api.Test + +/** + * The test class contains some test which contains the generation for classes with factory constructors. + * @author theEvilReaper + * @version 1.0.0 + * @since + **/ +class FactoryFileTest { + + private val VERSION_MODEL = "VersionModel" + + @Test + fun `write test class with factory constructors`() { + val versionModelClass = ClassName(VERSION_MODEL) + val freezedMixing = ClassName("_${'$'}$VERSION_MODEL") + val versionFreezedClass = ClassSpec.builder(VERSION_MODEL) + .superClass(freezedMixing, InheritKeyword.MIXIN) + .annotation { AnnotationSpec.builder("freezed").build() } + .constructor { + FactorySpec.constBuilder(versionModelClass) + .parameter( + ParameterSpec.builder("version", String::class) + .named(true) + .annotations( + AnnotationSpec.builder("JsonKey") + .content("name: %C", "version").build(), + AnnotationSpec.builder("Default") + .content("%C", "1.0.0").build() + ) + .build() + ) + .addCode("%L", "_$VERSION_MODEL();") + .build() + } + .constructor { + FactorySpec.builder(versionModelClass) + .lambda(true) + .named("fromJson") + .parameter( + ParameterSpec.builder( + "json", + Map::class.parameterizedBy(String::class.asTypeName(), DYNAMIC) + ).build() + ) + .addCode("%L", "_${"$"}VersionModelFromJson(json);") + .build() + } + val versionFile = DartFile.builder("version.dart") + .directives( + DirectiveFactory.create(DirectiveType.IMPORT, "freezed_annotation/freezed_annotation.dart"), + DirectiveFactory.create(DirectiveType.PART, "version.freezed.dart"), + DirectiveFactory.create(DirectiveType.PART, "version.g.dart") + ) + .type( + versionFreezedClass + ) + .build() + Truth.assertThat(versionFile.toString()).isEqualTo( + """ + import 'package:freezed_annotation/freezed_annotation.dart'; + + part 'version.freezed.dart'; + part 'version.g.dart'; + + @freezed + class VersionModel with _${'$'}VersionModel { + + const factory VersionModel({ + @JsonKey(name: 'version')@Default('1.0.0') String version + }) = _VersionModel; + + factory VersionModel.fromJson(Map json) => _${'$'}VersionModelFromJson(json); + } + """.trimIndent() + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpecTest.kt b/src/test/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpecTest.kt index 1f59fe18..b00eb70a 100644 --- a/src/test/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpecTest.kt +++ b/src/test/kotlin/net/theevilreaper/dartpoet/function/constructor/ConstructorSpecTest.kt @@ -7,10 +7,8 @@ class ConstructorSpecTest { @Test fun `test spec to builder conversation`() { val constructorSpec = ConstructorSpec.builder("TestModel") - .asFactory(true) .build() val specAsBuilder = constructorSpec.toBuilder() assertEquals(constructorSpec.name, specAsBuilder.name) - assertEquals(constructorSpec.isFactory, specAsBuilder.factory) } } \ No newline at end of file