diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..073fb6a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_imports_layout = * diff --git a/build.gradle b/build.gradle index 63eea4f..fc41d5c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,12 +15,7 @@ spotless { kotlin { target "**/*.kt" targetExclude "**/build/grammar/**/*.*", "**/generated/**/*.*", "src/test/fixtures/**/build/grammars/**/*.*" - ktlint('0.49.1').editorConfigOverride([ - "indent_size": "2", - "disabled_rules": "package-name", - "ij_kotlin_allow_trailing_comma": "true", - "ij_kotlin_allow_trailing_comma_on_call_site": "true", - ]) + ktfmt(libs.ktfmt.get().version).googleStyle() trimTrailingWhitespace() endWithNewline() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66f7d83..00daaff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ kotlinPoet = { module = "com.squareup:kotlinpoet", version = "2.2.0" } grammarKitPlugin = { module = "org.jetbrains.intellij.plugins:gradle-grammarkit-plugin", version = "2023.3.0.1" } intelliJ = { module = "com.jetbrains.intellij.platform:analysis-impl", version.ref = "intelliJ" } coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2" +ktfmt = "com.facebook:ktfmt:0.59" [plugins] kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "2.2.21" } diff --git a/src/main/kotlin/com/alecstrong/grammar/kit/composer/BnfExtenderTask.kt b/src/main/kotlin/com/alecstrong/grammar/kit/composer/BnfExtenderTask.kt index efdc454..60398ff 100644 --- a/src/main/kotlin/com/alecstrong/grammar/kit/composer/BnfExtenderTask.kt +++ b/src/main/kotlin/com/alecstrong/grammar/kit/composer/BnfExtenderTask.kt @@ -7,6 +7,8 @@ import com.squareup.kotlinpoet.LambdaTypeName import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asTypeName +import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty @@ -20,8 +22,6 @@ import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.workers.WorkAction import org.gradle.workers.WorkerExecutor -import java.io.File -import javax.inject.Inject @CacheableTask abstract class BnfExtenderTask : DefaultTask() { @@ -29,14 +29,11 @@ abstract class BnfExtenderTask : DefaultTask() { @get:InputFile abstract val bnfFile: RegularFileProperty - @get:Internal - abstract val root: Property + @get:Internal abstract val root: Property - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Inject - abstract val workerExecutor: WorkerExecutor + @get:Inject abstract val workerExecutor: WorkerExecutor @TaskAction fun execute() { @@ -64,17 +61,12 @@ internal abstract class ComposeGrammar : WorkAction { override fun execute() { val outputs: Outputs = parameters - GrammarFile( - file = outputs.inputFile.asFile.get(), - outputs = outputs, - ).generateComposableGrammarFile() + GrammarFile(file = outputs.inputFile.asFile.get(), outputs = outputs) + .generateComposableGrammarFile() } } -private class GrammarFile( - private val file: File, - private val outputs: Outputs, -) { +private class GrammarFile(private val file: File, private val outputs: Outputs) { private var overrides: ClassName? = null fun generateComposableGrammarFile() { @@ -117,7 +109,8 @@ private class GrammarFile( val unextendableRules = unextendableRules(header, rules.keys) val rulesToExtend = rules.filterNot { it.key in unextendableRules } - val privateRules = rules.keys.filter { it.startsWith("private") }.map { it.substringAfter("private ") } + val privateRules = + rules.keys.filter { it.startsWith("private") }.map { it.substringAfter("private ") } val imports = mutableSetOf() if (overrides != null) { @@ -126,42 +119,60 @@ private class GrammarFile( val newRules = generateRules(firstRule, rulesToExtend, imports, privateRules) - val keyFinder = Regex("([^a-zA-Z_]|^)(${unextendableSubclasses(header, rules.keys).joinToString("|")})([^a-zA-Z_]|$)") - val unextendableRuleDefinitions = rules.filterKeys { it in unextendableRules } - .map { - if (it.key.startsWith("private")) { - generateRule(it.key, it.value, rulesToExtend, privateRules, imports) - } else { - "${it.key} ::= ${it.value.subclassReplacements(keyFinder)}" + val keyFinder = + Regex( + "([^a-zA-Z_]|^)(${unextendableSubclasses(header, rules.keys).joinToString("|")})([^a-zA-Z_]|$)" + ) + val unextendableRuleDefinitions = + rules + .filterKeys { it in unextendableRules } + .map { + if (it.key.startsWith("private")) { + generateRule(it.key, it.value, rulesToExtend, privateRules, imports) + } else { + "${it.key} ::= ${it.value.subclassReplacements(keyFinder)}" + } } - } - .joinToString("\n") + .joinToString("\n") header = header.lines().filterNot { it.contains("parserUtilClass=") }.drop(1).joinToString("\n") - header = if (header.contains("parserImports=[")) { - header.replace("parserImports=[", imports.joinToString(separator = "\n", prefix = "parserImports = [\n").prependIndent(" ")) - } else { - imports.joinToString(separator = "\n", prefix = "parserImports = [\n", postfix = "\n]\n").prependIndent(" ") + header - } - - header = "{\n parserUtilClass=\"${outputs.outputPackage.get()}.${file.parserUtilName()}\"\n" + - " parserClass=\"${outputs.parserClass}\"\n" + - " elementTypeHolderClass=\"${outputs.psiPackage.get()}.${file.elementTypeHolderName()}\"\n" + - " psiPackage=\"${outputs.psiPackage.get()}\"\n" + - " psiImplPackage=\"${outputs.psiPackage.get()}.impl\"\n" + - header + header = + if (header.contains("parserImports=[")) { + header.replace( + "parserImports=[", + imports.joinToString(separator = "\n", prefix = "parserImports = [\n").prependIndent(" "), + ) + } else { + imports + .joinToString(separator = "\n", prefix = "parserImports = [\n", postfix = "\n]\n") + .prependIndent(" ") + header + } - outputs.outputFile.get().asFile.createIfAbsent() + header = + "{\n parserUtilClass=\"${outputs.outputPackage.get()}.${file.parserUtilName()}\"\n" + + " parserClass=\"${outputs.parserClass}\"\n" + + " elementTypeHolderClass=\"${outputs.psiPackage.get()}.${file.elementTypeHolderName()}\"\n" + + " psiPackage=\"${outputs.psiPackage.get()}\"\n" + + " psiImplPackage=\"${outputs.psiPackage.get()}.impl\"\n" + + header + + outputs.outputFile + .get() + .asFile + .createIfAbsent() .writeText("$header\n$newRules\n$unextendableRuleDefinitions") - outputs.outputDirectory.file("${file.parserUtilName()}.kt").get().asFile + outputs.outputDirectory + .file("${file.parserUtilName()}.kt") + .get() + .asFile .createIfAbsent() .writeText( generateParserUtil( rules = rulesToExtend, inputFile = file, superclass = generatedUtilSuperclass, - ), + ) ) } @@ -178,9 +189,11 @@ private class GrammarFile( imports: MutableSet, privateRules: Collection, ): String { - val keyFinder = Regex("([^a-zA-Z_]|^)(${(rules.keys + privateRules).joinToString("|")})([^a-zA-Z_0-9]|$)") + val keyFinder = + Regex("([^a-zA-Z_]|^)(${(rules.keys + privateRules).joinToString("|")})([^a-zA-Z_0-9]|$)") - val builder = StringBuilder("root ::= ${firstRule.extensionReplacements(keyFinder, privateRules)}\n") + val builder = + StringBuilder("root ::= ${firstRule.extensionReplacements(keyFinder, privateRules)}\n") for ((rule, definition) in rules) { builder.append(generateRule(rule, definition, rules, privateRules, imports)) } @@ -194,24 +207,25 @@ private class GrammarFile( privateRules: Collection, imports: MutableSet, ): String { - val keyFinder = Regex("([^a-zA-Z_]|^)(${(rules.keys + privateRules).joinToString("|")})([^a-zA-Z_0-9]|$)") + val keyFinder = + Regex("([^a-zA-Z_]|^)(${(rules.keys + privateRules).joinToString("|")})([^a-zA-Z_0-9]|$)") val pinFinder = Regex("pin[\\s\\S]+=[^\n\r]*") val recoverWhileFinder = Regex("[\\s\\S]+recoverWhile *= *([a-zA-Z_]*)[\\s\\S]+") val builder = StringBuilder() - val definition = definition.replace(Regex("\\{([a-zA-Z_]*)}")) { - val externalRule = it.groupValues[1] - imports.add("\"static ${overrides}Util.${externalRule.toFunctionName()}\"") - return@replace "<<${externalRule.toFunctionName()} <<${externalRule}_real>>>>" - } + val definition = + definition.replace(Regex("\\{([a-zA-Z_]*)}")) { + val externalRule = it.groupValues[1] + imports.add("\"static ${overrides}Util.${externalRule.toFunctionName()}\"") + return@replace "<<${externalRule.toFunctionName()} <<${externalRule}_real>>>>" + } - builder.append("fake $rule ::= $definition\n") + builder + .append("fake $rule ::= $definition\n") .append( "${rule}_real ::= ${definition.extensionReplacements(keyFinder, privateRules)} {\n" + - " elementType = ${rule.substringAfter("private ")}\n", + " elementType = ${rule.substringAfter("private ")}\n" ) - pinFinder.find(definition)?.groupValues?.getOrNull(0)?.let { - builder.append(" $it\n") - } + pinFinder.find(definition)?.groupValues?.getOrNull(0)?.let { builder.append(" $it\n") } recoverWhileFinder.matchEntire(definition)?.groupValues?.getOrNull(1)?.let { builder.append(" recoverWhile=${it.extensionReplacements(keyFinder, privateRules)}\n") } @@ -220,14 +234,18 @@ private class GrammarFile( return builder.toString() } - private fun String.extensionReplacements(keysRegex: Regex, privateRules: Collection): String { - fun String.matcher() = replace(keysRegex) { match -> - if (match.groupValues[2] in privateRules) { - "${match.groupValues[1]}${match.groupValues[2]}_real${match.groupValues[3]}" - } else { - "${match.groupValues[1]}<<${match.groupValues[2].toFunctionName()} ${match.groupValues[2]}_real>>${match.groupValues[3]}" + private fun String.extensionReplacements( + keysRegex: Regex, + privateRules: Collection, + ): String { + fun String.matcher() = + replace(keysRegex) { match -> + if (match.groupValues[2] in privateRules) { + "${match.groupValues[1]}${match.groupValues[2]}_real${match.groupValues[3]}" + } else { + "${match.groupValues[1]}<<${match.groupValues[2].toFunctionName()} ${match.groupValues[2]}_real>>${match.groupValues[3]}" + } } - } if (trim().endsWith("}")) { return substring(0, lastIndexOf("{") - 1).matcher().matcher() } @@ -236,9 +254,10 @@ private class GrammarFile( } private fun String.subclassReplacements(keysRegex: Regex): String { - fun String.matcher() = replace(keysRegex) { match -> - "${match.groupValues[1]}${match.groupValues[2]}_real${match.groupValues[3]}" - } + fun String.matcher() = + replace(keysRegex) { match -> + "${match.groupValues[1]}${match.groupValues[2]}_real${match.groupValues[3]}" + } // We have to do it twice because the matcher doesn't catch three adjacent rules. return matcher().matcher() } @@ -248,33 +267,44 @@ private class GrammarFile( } private fun String.toCustomFunction(): String { - return replace(snakeCaseRegex) { matchResult -> matchResult.value.trim('_').replaceFirstChar { it.titlecase() } } + return replace(snakeCaseRegex) { matchResult -> + matchResult.value.trim('_').replaceFirstChar { it.titlecase() } + } } private fun unextendableRules(headerText: String, rules: Collection): Sequence { val keyFinder = Regex("extends\\(\"([^)\"]+)\"\\)=([a-zA-Z_]+)\n") - return keyFinder.findAll(headerText).map { it.groupValues[2] }.asSequence() + rules.filter { it.startsWith("private ") } + return keyFinder.findAll(headerText).map { it.groupValues[2] }.asSequence() + + rules.filter { it.startsWith("private ") } } - private fun unextendableSubclasses(headerText: String, rules: Collection): Sequence { + private fun unextendableSubclasses( + headerText: String, + rules: Collection, + ): Sequence { val keyFinder = Regex("extends\\(\"([^)\"]+)\"\\)=([a-zA-Z_]+)\n") - return keyFinder.findAll(headerText).flatMap { - val pattern = Regex(it.groupValues[1]) - return@flatMap (rules.filter { it.matches(pattern) }).asSequence() - }.distinct() + return keyFinder + .findAll(headerText) + .flatMap { + val pattern = Regex(it.groupValues[1]) + return@flatMap (rules.filter { it.matches(pattern) }).asSequence() + } + .distinct() } - private fun File.parserUtilName() = "${nameWithoutExtension.replaceFirstChar { it.titlecase() }}ParserUtil" + private fun File.parserUtilName() = + "${nameWithoutExtension.replaceFirstChar { it.titlecase() }}ParserUtil" - private fun File.elementTypeHolderName() = "${nameWithoutExtension.replaceFirstChar { it.titlecase() }}Types" + private fun File.elementTypeHolderName() = + "${nameWithoutExtension.replaceFirstChar { it.titlecase() }}Types" private fun generateParserUtil( rules: Map, inputFile: File, superclass: ClassName, ): String { - val parserType = ClassName("com.intellij.lang.parser", "GeneratedParserUtilBase") - .nestedClass("Parser") + val parserType = + ClassName("com.intellij.lang.parser", "GeneratedParserUtilBase").nestedClass("Parser") val elementTypeHolder = ClassName(outputs.psiPackage.get(), inputFile.elementTypeHolderName()) val astNodeType = ClassName("com.intellij.lang", "ASTNode") val psiElementType = ClassName("com.intellij.psi", "PsiElement") @@ -283,8 +313,9 @@ private class GrammarFile( val overrideFinder = Regex("[\\s\\S]+override *= *([a-zA-Z_]*)[\\s\\S]+") fun ClassName.util() = ClassName(packageName, "${simpleName}Util") - val resetMethod = FunSpec.builder("reset") - .addStatement("createElement = { %T.Factory.createElement(it) }", elementTypeHolder) + val resetMethod = + FunSpec.builder("reset") + .addStatement("createElement = { %T.Factory.createElement(it) }", elementTypeHolder) return FileSpec.builder(outputs.outputPackage.get(), inputFile.parserUtilName()) .addType( @@ -292,15 +323,13 @@ private class GrammarFile( .superclass(superclass) .addProperty( PropertySpec.builder( - name = "createElement", - type = LambdaTypeName.get( - parameters = arrayOf(astNodeType), - returnType = psiElementType, - ), - ) + name = "createElement", + type = + LambdaTypeName.get(parameters = arrayOf(astNodeType), returnType = psiElementType), + ) .mutable(true) .initializer("{ %T.Factory.createElement(it) }", elementTypeHolder) - .build(), + .build() ) .apply { rules.forEach { (key, definition) -> @@ -308,7 +337,7 @@ private class GrammarFile( PropertySpec.builder(key, parserType.copy(nullable = true)) .mutable(true) .initializer("null") - .build(), + .build() ) resetMethod.addStatement("$key = null") @@ -321,23 +350,21 @@ private class GrammarFile( .addParameter(key, parserType) .returns(Boolean::class) .addStatement("return (this.$key ?: $key).parse(builder, level)") - .build(), + .build() ) overrides?.let { overrides -> - overrideFinder.matchEntire(definition) - ?.groupValues?.getOrNull(1) - ?.let { - if (it == "true") { - overrideMethod.addStatement( - "%T.$key = Parser { psiBuilder, i -> " + - "$key?.parse(psiBuilder, i) ?: %T.${key}_real(psiBuilder, i)" + - " }", - overrides.util(), - outputs.parserClass, - ) - } + overrideFinder.matchEntire(definition)?.groupValues?.getOrNull(1)?.let { + if (it == "true") { + overrideMethod.addStatement( + "%T.$key = Parser { psiBuilder, i -> " + + "$key?.parse(psiBuilder, i) ?: %T.${key}_real(psiBuilder, i)" + + " }", + overrides.util(), + outputs.parserClass, + ) } + } } } @@ -346,15 +373,16 @@ private class GrammarFile( overrides?.let { overrides -> overrideMethod.addCode( """ - val currentCreateElement = %T.createElement - %T.createElement = { - try { - createElement(it) - } catch(e: %T) { - currentCreateElement(it) - } - } - """.trimIndent(), + val currentCreateElement = %T.createElement + %T.createElement = { + try { + createElement(it) + } catch(e: %T) { + currentCreateElement(it) + } + } + """ + .trimIndent(), overrides.util(), overrides.util(), AssertionError::class.asTypeName(), @@ -363,7 +391,7 @@ private class GrammarFile( addFunction(overrideMethod.build()) } } - .build(), + .build() ) .build() .toString() diff --git a/src/main/kotlin/com/alecstrong/grammar/kit/composer/GrammarKitComposerPlugin.kt b/src/main/kotlin/com/alecstrong/grammar/kit/composer/GrammarKitComposerPlugin.kt index 115b85a..4b09c38 100644 --- a/src/main/kotlin/com/alecstrong/grammar/kit/composer/GrammarKitComposerPlugin.kt +++ b/src/main/kotlin/com/alecstrong/grammar/kit/composer/GrammarKitComposerPlugin.kt @@ -1,79 +1,87 @@ package com.alecstrong.grammar.kit.composer +import java.io.File import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.tasks.SourceSetContainer import org.jetbrains.grammarkit.GrammarKitPlugin import org.jetbrains.grammarkit.tasks.GenerateParserTask -import java.io.File open class GrammarKitComposerPlugin : Plugin { override fun apply(project: Project) { project.pluginManager.apply(GrammarKitPlugin::class.java) // https://youtrack.jetbrains.com/issue/IDEA-301677 - val grammar = project.configurations.register("grammar") { - it.isCanBeResolved = true - it.isCanBeConsumed = false - it.isVisible = false - it.defaultDependencies { - it.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")) + val grammar = + project.configurations.register("grammar") { + it.isCanBeResolved = true + it.isCanBeConsumed = false + it.isVisible = false + it.defaultDependencies { + it.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")) + } } - } - val rootDir = project.layout.projectDirectory.dir("src${File.separatorChar}main${File.separatorChar}kotlin") + val rootDir = + project.layout.projectDirectory.dir("src${File.separatorChar}main${File.separatorChar}kotlin") rootDir.forBnfFiles { bnfFile -> - val name = bnfFile.toRelativeString(rootDir.asFile).replace(File.separatorChar, '_').dropLast(4) + val name = + bnfFile.toRelativeString(rootDir.asFile).replace(File.separatorChar, '_').dropLast(4) val outputDirectory = project.layout.buildDirectory.dir("grammars${File.separatorChar}$name") - val compose = project.tasks.register("createComposable${name}Grammar", BnfExtenderTask::class.java) { - it.bnfFile.set(bnfFile) - it.root.set(rootDir.asFile.absolutePath) - it.outputDirectory.set(outputDirectory) - it.group = "grammar" - it.description = "Generate composable grammars from .bnf files." - } + val compose = + project.tasks.register("createComposable${name}Grammar", BnfExtenderTask::class.java) { + it.bnfFile.set(bnfFile) + it.root.set(rootDir.asFile.absolutePath) + it.outputDirectory.set(outputDirectory) + it.group = "grammar" + it.description = "Generate composable grammars from .bnf files." + } - val gen = project.tasks.register("generate${name}Parser", GenerateParserTask::class.java) { generateParserTask -> - val outputs = getOutputs( - bnf = bnfFile, - outputDirectory = outputDirectory, - root = rootDir.asFile.absolutePath, - ) + val gen = + project.tasks.register("generate${name}Parser", GenerateParserTask::class.java) { + generateParserTask -> + val outputs = + getOutputs( + bnf = bnfFile, + outputDirectory = outputDirectory, + root = rootDir.asFile.absolutePath, + ) - generateParserTask.dependsOn(compose) - generateParserTask.sourceFile.set(outputs.outputFile) - generateParserTask.targetRootOutputDir.set(outputDirectory) - generateParserTask.pathToParser.set(outputs.parserClassString) - generateParserTask.pathToPsiRoot.set(outputs.psiPackage) - generateParserTask.purgeOldFiles.set(true) - generateParserTask.group = "grammar" + generateParserTask.dependsOn(compose) + generateParserTask.sourceFile.set(outputs.outputFile) + generateParserTask.targetRootOutputDir.set(outputDirectory) + generateParserTask.pathToParser.set(outputs.parserClassString) + generateParserTask.pathToPsiRoot.set(outputs.psiPackage) + generateParserTask.purgeOldFiles.set(true) + generateParserTask.group = "grammar" - generateParserTask.classpath(grammar) - } + generateParserTask.classpath(grammar) + } project.pluginManager.withPlugin("org.gradle.java") { (project.extensions.getByName("sourceSets") as SourceSetContainer) - .getByName("main").java.srcDir(outputDirectory.map { it.asFile.relativeTo(project.projectDir) }) + .getByName("main") + .java + .srcDir(outputDirectory.map { it.asFile.relativeTo(project.projectDir) }) } project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { - project.tasks.named("compileKotlin").configure { - it.dependsOn(gen) - } + project.tasks.named("compileKotlin").configure { it.dependsOn(gen) } } project.pluginManager.withPlugin("com.google.devtools.ksp") { - project.afterEvaluate { - project.tasks.named("kspKotlin").configure { - it.dependsOn(gen) - } - } + project.afterEvaluate { project.tasks.named("kspKotlin").configure { it.dependsOn(gen) } } } project.tasks.configureEach { - if (it.name.contains("dokka") || it.name == "sourcesJar" || it.name == "kotlinSourcesJar" || it.name == "javaSourcesJar") { + if ( + it.name.contains("dokka") || + it.name == "sourcesJar" || + it.name == "kotlinSourcesJar" || + it.name == "javaSourcesJar" + ) { it.dependsOn(gen, compose) } } @@ -81,8 +89,6 @@ open class GrammarKitComposerPlugin : Plugin { } private fun Directory.forBnfFiles(action: (bnfFile: File) -> Unit) { - asFileTree.filter { - it.extension == "bnf" - }.forEach(action) + asFileTree.filter { it.extension == "bnf" }.forEach(action) } } diff --git a/src/main/kotlin/com/alecstrong/grammar/kit/composer/Outputs.kt b/src/main/kotlin/com/alecstrong/grammar/kit/composer/Outputs.kt index b3d1ea0..af1ba93 100644 --- a/src/main/kotlin/com/alecstrong/grammar/kit/composer/Outputs.kt +++ b/src/main/kotlin/com/alecstrong/grammar/kit/composer/Outputs.kt @@ -1,6 +1,7 @@ package com.alecstrong.grammar.kit.composer import com.squareup.kotlinpoet.ClassName +import java.io.File import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile @@ -8,7 +9,6 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.workers.WorkParameters -import java.io.File internal interface Outputs : WorkParameters { val inputFile: RegularFileProperty @@ -20,12 +20,11 @@ internal interface Outputs : WorkParameters { val outputDirectory: DirectoryProperty } -internal val Outputs.parserClass get() = getParserClass(outputPackage.get(), bnfFileName.get()) +internal val Outputs.parserClass + get() = getParserClass(outputPackage.get(), bnfFileName.get()) -private fun getParserClass( - outputPackage: String, - bnfFileName: String, -) = ClassName(outputPackage, "${bnfFileName.replaceFirstChar { it.titlecase() }}Parser") +private fun getParserClass(outputPackage: String, bnfFileName: String) = + ClassName(outputPackage, "${bnfFileName.replaceFirstChar { it.titlecase() }}Parser") internal fun getOutputs( outputDirectory: Provider, @@ -35,12 +34,16 @@ internal fun getOutputs( val outputPackage = bnf.outputPackage(root) return ParserOutputs( outputFile = outputDirectory.map { it.file(bnf.generatedBnfFile) }, - parserClassString = getParserClass(outputPackage, bnf.nameWithoutExtension).toString().replace('.', File.separatorChar), + parserClassString = + getParserClass(outputPackage, bnf.nameWithoutExtension) + .toString() + .replace('.', File.separatorChar), psiPackage = outputPackage.psi.replace('.', File.separatorChar), ) } -internal val String.psi get() = "$this.psi" +internal val String.psi + get() = "$this.psi" internal data class ParserOutputs( val outputFile: Provider, @@ -48,6 +51,8 @@ internal data class ParserOutputs( val psiPackage: String, ) -internal val File.generatedBnfFile get() = "${nameWithoutExtension}_gen.bnf" +internal val File.generatedBnfFile + get() = "${nameWithoutExtension}_gen.bnf" -internal fun File.outputPackage(root: String): String = parentFile.toRelativeString(File(root)).replace(File.separatorChar, '.') +internal fun File.outputPackage(root: String): String = + parentFile.toRelativeString(File(root)).replace(File.separatorChar, '.') diff --git a/src/test/kotlin/com/alecstrong/grammar/kit/composer/PluginTest.kt b/src/test/kotlin/com/alecstrong/grammar/kit/composer/PluginTest.kt index 17c4071..287d3a0 100644 --- a/src/test/kotlin/com/alecstrong/grammar/kit/composer/PluginTest.kt +++ b/src/test/kotlin/com/alecstrong/grammar/kit/composer/PluginTest.kt @@ -1,44 +1,40 @@ package com.alecstrong.grammar.kit.composer -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome import java.io.File import kotlin.test.Test import kotlin.test.assertEquals +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome class PluginTest { - private val String.fixtureDir: File get() = File("src/test/fixtures/$this") - - private fun testing(path: String, vararg tasks: String, clean: Boolean = true): Pair { + private val String.fixtureDir: File + get() = File("src/test/fixtures/$this") + + private fun testing( + path: String, + vararg tasks: String, + clean: Boolean = true, + ): Pair { val fixtureDir = path.fixtureDir val gradleRoot = File(fixtureDir, "gradle").also { it.mkdir() } File("gradle/wrapper").copyRecursively(File(gradleRoot, "wrapper"), true) - val runner = GradleRunner.create() - .withProjectDir(fixtureDir) - .withPluginClasspath() - .forwardOutput() + val runner = + GradleRunner.create().withProjectDir(fixtureDir).withPluginClasspath().forwardOutput() if (clean) { runner.withArguments("clean").build() } - val firstRun = runner.withArguments( - *tasks, - "--stacktrace", - "--configuration-cache", - ).build() + val firstRun = runner.withArguments(*tasks, "--stacktrace", "--configuration-cache").build() for (task in tasks) { assertEquals(TaskOutcome.SUCCESS, firstRun.task(task)?.outcome) } - val secondCachedRun = runner.withArguments( - *tasks, - "--stacktrace", - "--configuration-cache", - ).build() + val secondCachedRun = + runner.withArguments(*tasks, "--stacktrace", "--configuration-cache").build() for (task in tasks) { assertEquals(TaskOutcome.UP_TO_DATE, secondCachedRun.task(task)?.outcome) @@ -47,11 +43,16 @@ class PluginTest { return firstRun to secondCachedRun } - @Test fun `unchanged grammar files are up to date`() { + @Test + fun `unchanged grammar files are up to date`() { val fixture = "multiple-bnf-files" testing(fixture, ":assemble") val bnfFile = File(fixture.fixtureDir, "src/main/kotlin/com/example/bar.bnf") - val originalBar = bnfFile.copyTo(File(bnfFile.parent, "${bnfFile.nameWithoutExtension}.oldbnf"), overwrite = true) + val originalBar = + bnfFile.copyTo( + File(bnfFile.parent, "${bnfFile.nameWithoutExtension}.oldbnf"), + overwrite = true, + ) bnfFile.appendText("testingIndependentChanges ::= 'SUCCESS'\n") @@ -59,17 +60,25 @@ class PluginTest { originalBar.copyTo(bnfFile, overwrite = true) originalBar.delete() - assertEquals(TaskOutcome.SUCCESS, firstRun.task(":createComposablecom_example_barGrammar")?.outcome) + assertEquals( + TaskOutcome.SUCCESS, + firstRun.task(":createComposablecom_example_barGrammar")?.outcome, + ) assertEquals(TaskOutcome.SUCCESS, firstRun.task(":generatecom_example_barParser")?.outcome) - assertEquals(TaskOutcome.UP_TO_DATE, firstRun.task(":createComposablecom_example_fooGrammar")?.outcome) + assertEquals( + TaskOutcome.UP_TO_DATE, + firstRun.task(":createComposablecom_example_fooGrammar")?.outcome, + ) assertEquals(TaskOutcome.UP_TO_DATE, firstRun.task(":generatecom_example_fooParser")?.outcome) } - @Test fun composing() { + @Test + fun composing() { testing("composing", ":assemble") } - @Test fun applying() { + @Test + fun applying() { testing("applying", ":assemble") } }