-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add DependenciesSimplifier recipe.
- Loading branch information
1 parent
2ee7806
commit 09b9d0f
Showing
4 changed files
with
190 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
plugins { | ||
id("cash.lib") | ||
} | ||
|
||
dependencies { | ||
api(project(":core")) | ||
api(project(":grammar")) | ||
api(libs.antlr.runtime) | ||
api(libs.kotlinStdLib) | ||
} |
138 changes: 138 additions & 0 deletions
138
recipes/dependencies/src/main/kotlin/cash/recipes/dependencies/DependenciesSimplifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package cash.recipes.dependencies | ||
|
||
import cash.grammar.kotlindsl.model.DependencyDeclaration | ||
import cash.grammar.kotlindsl.parse.KotlinParseException | ||
import cash.grammar.kotlindsl.parse.Parser | ||
import cash.grammar.kotlindsl.parse.Rewriter | ||
import cash.grammar.kotlindsl.utils.Blocks.isBuildscript | ||
import cash.grammar.kotlindsl.utils.Blocks.isDependencies | ||
import cash.grammar.kotlindsl.utils.CollectingErrorListener | ||
import cash.grammar.kotlindsl.utils.Context.leafRule | ||
import cash.grammar.kotlindsl.utils.DependencyExtractor | ||
import cash.grammar.kotlindsl.utils.Whitespace | ||
import cash.grammar.kotlindsl.utils.Whitespace.trimGently | ||
import cash.grammar.utils.ifNotEmpty | ||
import com.squareup.cash.grammar.KotlinParser.NamedBlockContext | ||
import com.squareup.cash.grammar.KotlinParser.PostfixUnaryExpressionContext | ||
import com.squareup.cash.grammar.KotlinParser.StatementContext | ||
import com.squareup.cash.grammar.KotlinParserBaseListener | ||
import org.antlr.v4.runtime.CharStream | ||
import org.antlr.v4.runtime.CommonTokenStream | ||
import org.antlr.v4.runtime.Token | ||
import java.io.InputStream | ||
import java.nio.file.Path | ||
|
||
public class DependenciesSimplifier private constructor( | ||
private val input: CharStream, | ||
private val tokens: CommonTokenStream, | ||
private val errorListener: CollectingErrorListener, | ||
) : KotlinParserBaseListener() { | ||
|
||
private val rewriter = Rewriter(tokens) | ||
private val indent = Whitespace.computeIndent(tokens, input) | ||
private val terminalNewlines = Whitespace.countTerminalNewlines(tokens) | ||
private val dependencyExtractor = DependencyExtractor(input, tokens, indent) | ||
|
||
private var isInBuildscriptBlock = false | ||
|
||
@Throws(KotlinParseException::class) | ||
public fun rewritten(): String { | ||
errorListener.getErrorMessages().ifNotEmpty { | ||
throw KotlinParseException.withErrors(it) | ||
} | ||
|
||
return rewriter.text.trimGently(terminalNewlines) | ||
} | ||
|
||
override fun enterNamedBlock(ctx: NamedBlockContext) { | ||
dependencyExtractor.onEnterBlock() | ||
|
||
if (ctx.isBuildscript) { | ||
isInBuildscriptBlock = true | ||
} | ||
} | ||
|
||
override fun exitNamedBlock(ctx: NamedBlockContext) { | ||
if (ctx.isDependencies && !isInBuildscriptBlock) { | ||
onExitDependenciesBlock(ctx) | ||
} | ||
|
||
if (ctx.isBuildscript) { | ||
isInBuildscriptBlock = false | ||
} | ||
|
||
dependencyExtractor.onExitBlock() | ||
} | ||
|
||
private fun onExitDependenciesBlock(ctx: NamedBlockContext) { | ||
val container = dependencyExtractor.collectDependencies(ctx) | ||
container.getDependencyDeclarationsWithContext() | ||
// we only care about complex declarations. We will rewrite this in simplified form | ||
.filter { it.declaration.isComplex } | ||
.forEach { element -> | ||
val declaration = element.declaration | ||
val elementCtx = element.statement | ||
|
||
val newText = simplify(declaration) | ||
|
||
if (newText != null) { | ||
rewriter.replace(elementCtx.start, getStop(elementCtx), newText) | ||
} | ||
} | ||
} | ||
|
||
private fun getStop(ctx: StatementContext): Token { | ||
val default = ctx.stop | ||
|
||
val leaf = ctx.leafRule() | ||
if (leaf !is PostfixUnaryExpressionContext) return default | ||
|
||
val postfix = leaf.postfixUnarySuffix().firstOrNull() ?: return default | ||
val preLambda = postfix.callSuffix().valueArguments() | ||
|
||
// we only want to replace everything BEFORE the trailing lambda | ||
return preLambda.stop | ||
} | ||
|
||
private fun simplify(declaration: DependencyDeclaration): String? { | ||
require(declaration.isComplex) { "Expected complex declaration, was $declaration" } | ||
|
||
// TODO(tsr): For now, ignore those that have ext, classifier, producerConfiguration | ||
if (declaration.ext != null || declaration.classifier != null || declaration.producerConfiguration != null) { | ||
return null | ||
} | ||
|
||
return buildString { | ||
append(declaration.configuration) | ||
append("(") | ||
append(declaration.identifier) | ||
append(")") | ||
} | ||
} | ||
|
||
public companion object { | ||
public fun of(buildScript: Path): DependenciesSimplifier { | ||
return of(Parser.readOnlyInputStream(buildScript)) | ||
} | ||
|
||
public fun of(buildScript: String): DependenciesSimplifier { | ||
return of(buildScript.byteInputStream()) | ||
} | ||
|
||
private fun of(buildScript: InputStream): DependenciesSimplifier { | ||
val errorListener = CollectingErrorListener() | ||
|
||
return Parser( | ||
file = buildScript, | ||
errorListener = errorListener, | ||
listenerFactory = { input, tokens, _ -> | ||
DependenciesSimplifier( | ||
input = input, | ||
tokens = tokens, | ||
errorListener = errorListener, | ||
) | ||
} | ||
).listener() | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
recipes/dependencies/src/test/kotlin/cash/recipes/dependencies/DependenciesSimplifierTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package cash.recipes.dependencies | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class DependenciesSimplifierTest { | ||
|
||
@Test fun `can simplify dependency declarations`() { | ||
// Given | ||
val buildScript = """ | ||
dependencies { | ||
implementation(libs.foo) | ||
api("com.foo:bar:1.0") | ||
runtimeOnly(group = "foo", name = "bar", version = "2.0") | ||
compileOnly(group = "foo", name = "bar", version = libs.versions.bar.get()) { | ||
isTransitive = false | ||
} | ||
devImplementation(group = "io.netty", name = "netty-transport-native-kqueue", classifier = "osx-x86_64") | ||
} | ||
""".trimIndent() | ||
|
||
// When | ||
val simplifier = DependenciesSimplifier.of(buildScript) | ||
val rewrittenContent = simplifier.rewritten() | ||
|
||
// Then all declarations are simplified | ||
assertThat(rewrittenContent).isEqualTo( | ||
""" | ||
dependencies { | ||
implementation(libs.foo) | ||
api("com.foo:bar:1.0") | ||
runtimeOnly("foo:bar:2.0") | ||
compileOnly("foo:bar:${'$'}{libs.versions.bar.get()}") { | ||
isTransitive = false | ||
} | ||
devImplementation(group = "io.netty", name = "netty-transport-native-kqueue", classifier = "osx-x86_64") | ||
} | ||
""".trimIndent() | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters