Skip to content

Commit

Permalink
Reduce code complexity which generates the directives (#79)
Browse files Browse the repository at this point in the history
* Improve documentation

* Update basic directive tests

* Reduce complexity from the directive write process

* Update library keyword usage
  • Loading branch information
theEvilReaper authored Jan 14, 2024
1 parent dea9d8b commit b0c52ff
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import net.theevilreaper.dartpoet.code.buildCodeString
import net.theevilreaper.dartpoet.util.DART_FILE_ENDING

/**
* Represents the base implementation for each [Directive].
* The class represents the basic implementation which are used from all directive implementations.
* @param path the path for the directive
* @author theEvilReaper
* @since 1.0.0
*/
abstract class BaseDirective(
private val path: String
): Directive {
) : Directive {

init {
check(path.trim().isNotEmpty()) { "The path of an directive can't be empty" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import net.theevilreaper.dartpoet.util.IMPORT
import net.theevilreaper.dartpoet.util.SEMICOLON

/**
* The class represents a normal import from dart.
* An import statement can look like this in dart:
* <ol>
* <li>'package:flutter/material.dart'</li>
* <li>'../../model/item_model.dart'<li>
* </ol>
* Dart also allows to add a prefix to an import which means that an import can look like that:
* -> import ../../model/item_model.dart as itemModel;
* Represents an import directive from dart which usual starts with `dart` or `package`.
*
* @author theEvilReaper
* @since 1.0.0
* @param path the path to the Dart file or package being imported.
* @param castType the optional cast type for the imported directive, used when casting the directive
* @param importCast the optional import cast, specifying a cast expression for the imported directive.
*
* @throws IllegalArgumentException if [importCast] is provided and is empty or consists only of whitespace.
*
* @constructor Creates a Dart import directive with the specified path as [String], a cast type as [CastType], and a importCast a [String].
*/
class DartDirective(
private val path: String,
Expand All @@ -27,32 +25,29 @@ class DartDirective(
* Check if some conditions are false and throw an exception.
*/
init {
if (castType != null && importCast != null) {
if (importCast != null) {
check(importCast.trim().isNotEmpty()) { "The importCast can't be empty" }
}

if ((castType != null && importCast == null) || (castType == null && importCast != null)) {
throw IllegalStateException("The castType and importCast must be set together or must be null. A mixed state is not allowed")
}
}

/**
* Writes the given data from the directive to the provided [CodeWriter].
*
* @param writer the writer instance to append the directive
*/
override fun write(writer: CodeWriter) {
writer.emit("$IMPORT ")
if (importCast == null && castType == null) {
if (isDartImport()) {
writer.emit("'${path.ensureDartFileEnding()}'")
} else {
writer.emit("'")
writer.emit("package:")
writer.emit(path.ensureDartFileEnding())
writer.emit("'")
}
writer.emit(SEMICOLON)
} else if (importCast != null && castType != null) {
writer.emit("'")
if (!isDartImport()) {
writer.emit("package:")
}
writer.emit("${path.ensureDartFileEnding()}' ${castType.identifier} $importCast")
writer.emit(SEMICOLON)
} else {
throw Error("Something went wrong during the DartDirective write prozess")
val ensuredPath = path.ensureDartFileEnding()
val pathToWrite = if (isDartImport()) ensuredPath else "package:$ensuredPath"
writer.emit("'$pathToWrite'")

if (importCast != null && castType != null) {
writer.emit(" ${castType.identifier} $importCast")
}
writer.emit(SEMICOLON)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,38 @@ class ExportDirective(
private val path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
): BaseDirective(path) {
) : BaseDirective(path) {

private val export = "export"
private val invalidCastType = arrayOf(CastType.DEFERRED, CastType.AS)

init {
if (castType != null) {
if (castType in invalidCastType) {
throw IllegalStateException("The following cast types are not allowed for an export directive: [${invalidCastType.joinToString()}]")
}
if (castType != null && castType in invalidCastType) {
throw IllegalStateException("The following cast types are not allowed for an export directive: [${invalidCastType.joinToString()}]")
}

if (importCast != null) {
check(importCast.trim().isNotEmpty()) { "The importCast can't be empty" }
}

if ((castType != null && importCast == null) || (castType == null && importCast != null)) {
throw IllegalStateException("The castType and importCast must be set together or must be null. A mixed state is not allowed")
}
}

/**
* Writes an [ExportDirective] with the right syntax to an [CodeWriter] instance.
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
val ensuredPath = path.ensureDartFileEnding()
writer.emit("$export·'")
if (importCast == null && castType == null) {
writer.emit(path.ensureDartFileEnding())
writer.emit("'$SEMICOLON")
} else if (importCast != null && castType != null) {
writer.emit(path.ensureDartFileEnding())
writer.emit("' ${castType.identifier} $importCast")
writer.emit(SEMICOLON)
} else {
throw Error("Something went wrong during the ExportDirective write process")
writer.emit(ensuredPath)
writer.emit("'")

if (importCast != null && castType != null) {
writer.emit("·${castType.identifier} $importCast")
}
writer.emit(SEMICOLON)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.SEMICOLON

/**
*
* The [LibraryDirective] represents the library directive from dart.
* @since 1.0.0
* @author theEvilReaper
*/
class LibraryDirective(
private val path: String,
private val asPartOf: Boolean = false
): BaseDirective(path) {
) : BaseDirective(path) {

/**
* Writes the data from the [LibraryDirective] to a given instance from a [CodeWriter].
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
if (asPartOf) {
writer.emit("part of ")
} else {
writer.emit("${LIBRARY.identifier} ")
}
val baseString = if (asPartOf) "part of" else LIBRARY.identifier
writer.emit("$baseString·")
writer.emit(path)
writer.emit(SEMICOLON)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ class RelativeDirective(
private val path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
): BaseDirective(path) {
) : BaseDirective(path) {

init {
if (importCast != null) {
check(importCast.trim().isNotEmpty()) { "The importCast can't be empty" }
}

if ((castType != null && importCast == null) || (castType == null && importCast != null)) {
throw IllegalStateException("The castType and importCast must be set together or must be null. A mixed state is not allowed")
}
}

/**
* Writes the content for a relative directive to an instance of an [CodeWriter].
Expand All @@ -28,7 +38,6 @@ class RelativeDirective(
if (castType != null && importCast != null) {
writer.emit(" ${castType.identifier} $importCast")
}

writer.emit(SEMICOLON)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package net.theevilreaper.dartpoet.directive

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Assertions.assertEquals
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
Expand All @@ -14,6 +15,24 @@ class DirectiveTest {
companion object {
private const val CAST_VALUE = "item"
private const val TEST_IMPORT = "../../model/item_model.dart"
private const val EMPTY_NAME_MESSAGE = "The path of an directive can't be empty"
private const val INVALID_CAST_USAGE =
"The castType and importCast must be set together or must be null. A mixed state is not allowed"

@JvmStatic
private fun directivesWhichThrowsException() = Stream.of(
Arguments.of({ DartDirective(" ") }, EMPTY_NAME_MESSAGE),
Arguments.of({ LibraryDirective("") }, EMPTY_NAME_MESSAGE),
Arguments.of({ PartDirective("") }, EMPTY_NAME_MESSAGE),
Arguments.of({ RelativeDirective("") }, EMPTY_NAME_MESSAGE),
Arguments.of({ LibraryDirective("") }, EMPTY_NAME_MESSAGE),
Arguments.of({ DartDirective("flutter/material.dart", CastType.AS, null) }, INVALID_CAST_USAGE),
Arguments.of({ DartDirective("flutter/material.dart", null, "test") }, INVALID_CAST_USAGE),
Arguments.of({ RelativeDirective("flutter/material.dart", CastType.AS, null) }, INVALID_CAST_USAGE),
Arguments.of({ RelativeDirective("flutter/material.dart", null, "test") }, INVALID_CAST_USAGE),
Arguments.of({ ExportDirective("test.dart", CastType.HIDE, null) }, INVALID_CAST_USAGE),
Arguments.of({ ExportDirective("test.dart", null, "test") }, INVALID_CAST_USAGE)
)

@JvmStatic
private fun libDirectives() = Stream.of(
Expand All @@ -30,10 +49,22 @@ class DirectiveTest {
@JvmStatic
private fun relativeDirectives() = Stream.of(
Arguments.of(RelativeDirective(TEST_IMPORT), "import '../../model/item_model.dart';"),
Arguments.of(RelativeDirective(TEST_IMPORT, CastType.AS, CAST_VALUE), "import '../../model/item_model.dart' as item;"),
Arguments.of(RelativeDirective(TEST_IMPORT, CastType.DEFERRED, CAST_VALUE), "import '../../model/item_model.dart' deferred as item;"),
Arguments.of(RelativeDirective(TEST_IMPORT, CastType.HIDE, CAST_VALUE), "import '../../model/item_model.dart' hide item;"),
Arguments.of(RelativeDirective(TEST_IMPORT, CastType.SHOW, CAST_VALUE), "import '../../model/item_model.dart' show item;")
Arguments.of(
RelativeDirective(TEST_IMPORT, CastType.AS, CAST_VALUE),
"import '../../model/item_model.dart' as item;"
),
Arguments.of(
RelativeDirective(TEST_IMPORT, CastType.DEFERRED, CAST_VALUE),
"import '../../model/item_model.dart' deferred as item;"
),
Arguments.of(
RelativeDirective(TEST_IMPORT, CastType.HIDE, CAST_VALUE),
"import '../../model/item_model.dart' hide item;"
),
Arguments.of(
RelativeDirective(TEST_IMPORT, CastType.SHOW, CAST_VALUE),
"import '../../model/item_model.dart' show item;"
)
)

@JvmStatic
Expand All @@ -43,6 +74,14 @@ class DirectiveTest {
)
}

@ParameterizedTest
@MethodSource("directivesWhichThrowsException")
fun `test directives which throws exception`(current: () -> Directive, expectedMessage: String) {
val exception = assertThrows<IllegalStateException> { current() }
assertEquals(IllegalStateException::class.java, exception.javaClass)
assertEquals(expectedMessage, exception.message)
}

@ParameterizedTest
@MethodSource("libDirectives")
fun `test library imports`(current: Directive, expected: String) {
Expand All @@ -67,35 +106,6 @@ class DirectiveTest {
assertEquals(expected, current.asString())
}

@Test
fun `test import with empty path`() {
assertThrows(
IllegalStateException::class.java,
{ DartDirective(" ") },
"The path of an Import can't be empty"
)
assertThrows(
IllegalStateException::class.java,
{ DartDirective("") },
"The path of an Import can't be empty"
)
}

@Test
fun `test cast import with empty cast`() {
assertThrows(
IllegalStateException::class.java,
{ DartDirective("flutter/material.dart", CastType.AS, " ") },
"The importCast can't be empty"
)
assertThrows(
IllegalStateException::class.java,
{ DartDirective("flutter/material.dart", CastType.AS, "") },
"The importCast can't be empty"
)
}


@Test
fun `test package import`() {
val import = DartDirective("flutter/material.dart")
Expand Down

0 comments on commit b0c52ff

Please sign in to comment.