Skip to content

Commit

Permalink
Implement directive alphabetic sorting (#74)
Browse files Browse the repository at this point in the history
* Add class which provides methods to sort directives

* Add test for the import sorting

* Update import handling in the file structure

* Improve directive structure to allow sorting

* Update directive writing

* Move directive tests to another directory

* Improve directive sorting and storing to minimize generation issues

* Add new test which tests the directive ordering at a test class

* Add override generation for functions

* Add documentation

* Improve documentation

* File ends now with an empty line

* Add write support for relative directives

* Move relative directive generation

* Remove print statement

* Add generation of export directives (#75)

* Add support for export directives

* Add variable to store the export directive objects

* Implement cast options

* Add ExportDirectiveTest class

* Remove unused import

* Add file test with export directive
  • Loading branch information
theEvilReaper authored Jan 6, 2024
1 parent c2fec16 commit 57238fe
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 82 deletions.
39 changes: 11 additions & 28 deletions src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import net.theevilreaper.dartpoet.clazz.ClassSpec
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.code.buildCodeString
import net.theevilreaper.dartpoet.code.writer.DartFileWriter
import net.theevilreaper.dartpoet.directive.*
import net.theevilreaper.dartpoet.extension.ExtensionSpec
import net.theevilreaper.dartpoet.function.FunctionSpec
import net.theevilreaper.dartpoet.util.*
import net.theevilreaper.dartpoet.directive.DartDirective
import net.theevilreaper.dartpoet.directive.LibraryDirective
import net.theevilreaper.dartpoet.directive.PartDirective
import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec
import net.theevilreaper.dartpoet.util.DART_FILE_ENDING
import net.theevilreaper.dartpoet.util.isDartConventionFileName
Expand All @@ -30,36 +28,21 @@ class DartFile internal constructor(
internal val types: List<ClassSpec> = builder.specTypes.toImmutableList()
internal val extensions: List<ExtensionSpec> = builder.extensionStack
internal val docs = builder.docs
private val directives = builder.directives.toImmutableList()
internal val constants: Set<ConstantPropertySpec> = builder.constants.toImmutableSet()

internal val imports: List<DartDirective> = if (directives.isEmpty()) {
emptyList()
} else {
builder.directives.filterIsInstance<DartDirective>().toList()
}

internal val partImports: List<PartDirective> = if (directives.isEmpty()) {
emptyList()
} else {
builder.directives.filterIsInstance<PartDirective>().toList()
}

internal val libImport: LibraryDirective? = if (directives.isEmpty()) {
null
} else {
val possibleListImports = directives.filterIsInstance<LibraryDirective>()
if (possibleListImports.isEmpty()) {
null
} else if (possibleListImports.size == 1) {
possibleListImports.first()
} else {
throw Exception("Only one library import is allowed")
}
}
private val directives = builder.directives.toImmutableList()

internal val dartImports = DirectiveOrdering.sortDirectives<DartDirective>(DartDirective::class, directives) { it.contains("dart:") }
internal val packageImports = DirectiveOrdering.sortDirectives<DartDirective>(DartDirective::class, directives) { it.contains("package:")}
internal val partImports = DirectiveOrdering.sortDirectives<PartDirective>(PartDirective::class, directives)
internal val libImport = DirectiveOrdering.sortDirectives<LibraryDirective>(LibraryDirective::class, directives)
internal val exportDirectives = DirectiveOrdering.sortDirectives<ExportDirective>(ExportDirective::class, directives)
internal val relativeImports = DirectiveOrdering.sortDirectives<RelativeDirective>(RelativeDirective::class, directives)
init {
check(name.trim().isNotEmpty()) { "The name of a class can't be empty (ONLY SPACES ARE NOT ALLOWED" }
if (libImport.isNotEmpty()) {
check(libImport.size == 1) { "Only one library directive is allowed" }
}
}

internal fun write(codeWriter: CodeWriter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ internal fun List<ExtensionSpec>.emitExtensions(
internal fun <T: Directive> List<T>.writeImports(
writer: CodeWriter,
newLineAtBegin: Boolean = true,
emitBlock: (T) -> String = { it.toString() }
emitBlock: (T) -> String = { it.asString() }
) {
if (isNotEmpty()) {
if (newLineAtBegin) {
Expand All @@ -174,6 +174,7 @@ internal fun <T: Directive> List<T>.writeImports(
if (index > 0) {
writer.emit(NEW_LINE)
}

writer.emit(emitBlock(import))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package net.theevilreaper.dartpoet.code.writer
import net.theevilreaper.dartpoet.DartFile
import net.theevilreaper.dartpoet.code.*
import net.theevilreaper.dartpoet.code.emitExtensions
import net.theevilreaper.dartpoet.code.writeImports
import net.theevilreaper.dartpoet.directive.Directive
import net.theevilreaper.dartpoet.util.NEW_LINE

internal class DartFileWriter : Writeable<DartFile>, DocumentationAppender {
Expand All @@ -12,30 +12,12 @@ internal class DartFileWriter : Writeable<DartFile>, DocumentationAppender {

override fun write(spec: DartFile, writer: CodeWriter) {
emitDocumentation(spec.docs, writer)
if (spec.libImport != null) {
writer.emit(spec.libImport.toString())
writer.emit(NEW_LINE)

if (spec.imports.isEmpty()) {
writer.emit(NEW_LINE)
}
}

spec.imports.writeImports(writer, newLineAtBegin = spec.libImport != null) {
it.toString()
}

if (spec.imports.isNotEmpty() && spec.partImports.isEmpty()) {
writer.emit(NEW_LINE)
}

spec.partImports.writeImports(writer, newLineAtBegin = spec.imports.isNotEmpty()) {
it.toString()
}

if (spec.partImports.isNotEmpty()) {
writer.emit(NEW_LINE)
}
emitDirectives(writer, spec.libImport)
emitDirectives(writer, spec.dartImports)
emitDirectives(writer, spec.packageImports)
emitDirectives(writer, spec.relativeImports)
emitDirectives(writer, spec.exportDirectives)
emitDirectives(writer, spec.partImports)

spec.constants.emitConstants(writer)

Expand All @@ -52,7 +34,17 @@ internal class DartFileWriter : Writeable<DartFile>, DocumentationAppender {

}
}

spec.extensions.emitExtensions(writer)
}

/**
* Emit a given [List] of [Directive] implementations to a [CodeWriter].
* @param codeWriter the [CodeWriter] instance to append the directives
* @param directives the [List] of [Directive] implementations
*/
private fun emitDirectives(codeWriter: CodeWriter, directives: List<Directive>) {
if (directives.isEmpty()) return
directives.writeImports(codeWriter, newLineAtBegin = false)
codeWriter.emit(NEW_LINE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import net.theevilreaper.dartpoet.code.Writeable
import net.theevilreaper.dartpoet.code.emitParameters
import net.theevilreaper.dartpoet.function.FunctionSpec
import net.theevilreaper.dartpoet.util.EMPTY_STRING
import net.theevilreaper.dartpoet.util.NEW_LINE
import net.theevilreaper.dartpoet.util.SEMICOLON
import net.theevilreaper.dartpoet.util.SPACE
import net.theevilreaper.dartpoet.util.toImmutableSet
Expand All @@ -22,6 +23,11 @@ internal class FunctionWriter : Writeable<FunctionSpec> {
return
}

if (!spec.isTypeDef && spec.annotation.isNotEmpty()) {
spec.annotation.forEach { it.write(writer) }
writer.emit(NEW_LINE)
}

val writeableModifiers = spec.modifiers.filter { it != PRIVATE && it != PUBLIC }.toImmutableSet()
val modifierString = writeableModifiers.joinToString(
separator = SPACE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ abstract class BaseDirective(
* Creates a string representation from a [BaseDirective] implementation.
* @return the created string
*/
override fun toString() = buildCodeString { write(this) }
override fun asString() = buildCodeString { write(this) }

/**
* Makes a comparison with two [Directive] implementation over a [Comparable]
*/
override fun compareTo(other: Directive): Int = toString().compareTo(other.toString())
override fun compareTo(other: Directive): Int = asString().compareTo(other.toString())

/**
* Ensures that the directive path ends with .dart.
Expand All @@ -53,4 +53,10 @@ abstract class BaseDirective(
protected fun isDartImport(): Boolean {
return path.startsWith("dart")
}

/**
* Returns the raw data string from the directive.
* @return the raw data string
*/
override fun getRawPath(): String = this.path
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,17 @@ package net.theevilreaper.dartpoet.directive
* @since 1.0.0
* @author theEvilReaper
*/
sealed interface Directive : Comparable<Directive>
sealed interface Directive : Comparable<Directive> {

/**
* Returns a string representation of the directive.
* @return the string representation
*/
fun asString(): String

/**
* Returns the raw path of the directive.
* @return the raw path
*/
fun getRawPath(): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,41 @@ import net.theevilreaper.dartpoet.util.SEMICOLON
* @author theEvilReaper
*/
class ExportDirective(
private val path: String
private val path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
): 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 (importCast != null) {
check(importCast.trim().isNotEmpty()) { "The importCast can't be empty" }
}
}

/**
* 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) {
writer.emit("export·")
writer.emit("'")
writer.emit(path.ensureDartFileEnding())
writer.emit("'")
writer.emit(SEMICOLON)
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")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ package net.theevilreaper.dartpoet.directive
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.SEMICOLON

/**
* This implementation represents a part directive from dart.
* The main difference to the other variants is that it starts with the word part and not with import.
* @since 1.0.0
* @author theEvilReaper
*/
class PartDirective(
private val path: String
) : BaseDirective(path) {

/**
* Writes the content for a part directive to an instance of an [CodeWriter].
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
writer.emit("part ")
writer.emit("'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ package net.theevilreaper.dartpoet.directive
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.SEMICOLON

/**
* This implementation represents a relative directive from dart.
* The difference to other directive variants is that the path starts with .../ or ../.
* @since 1.0.0
* @author theEvilReaper
*/
class RelativeDirective(
private val path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
): BaseDirective(path) {

/**
* Writes the content for a relative directive to an instance of an [CodeWriter].
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
writer.emit("import ")
writer.emit("'")
Expand Down
76 changes: 76 additions & 0 deletions src/main/kotlin/net/theevilreaper/dartpoet/util/DirectiveOrdere.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package net.theevilreaper.dartpoet.util

import net.theevilreaper.dartpoet.directive.BaseDirective
import net.theevilreaper.dartpoet.directive.Directive
import kotlin.reflect.KClass

/**
* Object responsible for sorting and ordering directives.
*
* This utility object provides functions to sort lists of directives either in alphabetical order based on
* their raw paths or based on a specified subtype of [Directive]. It is designed to work with instances of
* classes that inherit from [Directive].
*
* @since 1.0.0
* @version 1.0.0
* @author theEvilReaper
*/
internal object DirectiveOrdering {

/**
* Sorts a list of directives in alphabetical order based on their raw paths.
*
* This function takes a list of directives, where each directive is a subtype of [BaseDirective],
* and sorts them in ascending alphabetical order according to their raw paths.
*
* @param directives the list of directives to be sorted
* @return a new list containing the sorted directives
*/
internal inline fun <reified T : Directive> sortDirectives(directives: List<T>): List<T> {
if (directives.isEmpty()) return emptyList()
return directives.sortedBy { it.getRawPath() }.toImmutableList()
}

/**
* Sorts a list of directives based on a specified subtype of [Directive].
*
* This function filters the given list of directives to include only instances of the specified
* subtype [T], compares them based on their raw paths, and returns a new list sorted in ascending order.
*
* @param directiveInstance the class type representing the specific subtype [T] to filter the directives
* @param directives the list of directives to be sorted
* @return a new list containing the sorted directives of the specified subtype [T]
*/
internal inline fun <reified T : Directive> sortDirectives(
directiveInstance: KClass<T>,
directives: List<Directive>
): List<Directive> {
if (directives.isEmpty()) return emptyList()
return directives.filter { it::class == directiveInstance }.sortedBy { it.getRawPath() }.toImmutableList()
}

/**
* Sorts a list of directives based on a specified subtype of [Directive] and a predicate.
*
* This function filters the given list of directives to include only instances of the specified
* subtype [T], compares them based on their raw paths, and returns a new list sorted in ascending order.
* The predicate is used to filter the directives based on their raw paths.
*
* @param directiveInstance the class type representing the specific subtype [T] to filter the directives
* @param directives the list of directives to be sorted
* @param predicate the predicate used to filter the directives
* @return a new list containing the sorted directives of the specified subtype [T]
*/
internal inline fun <reified T : Directive> sortDirectives(
directiveInstance: KClass<T>,
directives: List<Directive>,
crossinline predicate: (String) -> Boolean
): List<Directive> {
if (directives.isEmpty()) return emptyList()
return directives
.filter { it::class == directiveInstance }
.sortedBy { it.getRawPath() }
.filter { predicate(it.asString()) }
.toImmutableList()
}
}
Loading

0 comments on commit 57238fe

Please sign in to comment.