diff --git a/core/src/main/kotlin/cash/grammar/kotlindsl/utils/Comments.kt b/core/src/main/kotlin/cash/grammar/kotlindsl/utils/Comments.kt index b066dc1..16849f6 100644 --- a/core/src/main/kotlin/cash/grammar/kotlindsl/utils/Comments.kt +++ b/core/src/main/kotlin/cash/grammar/kotlindsl/utils/Comments.kt @@ -57,16 +57,16 @@ public class Comments( } public fun getCommentsInBlock(ctx: KotlinParser.NamedBlockContext): List { - val comments = ArrayDeque() - var index = ctx.stop.tokenIndex + val comments = mutableListOf() + var index = ctx.start.tokenIndex - while (index > ctx.start.tokenIndex) { - var token = tokens.get(index) + while (index <= ctx.stop.tokenIndex) { + val token = tokens.get(index) if (token.isComment()) { - comments.addFirst(token) + comments.add(token) } - --index + ++index } return comments diff --git a/core/src/main/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemover.kt b/core/src/main/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemover.kt new file mode 100644 index 0000000..6751fac --- /dev/null +++ b/core/src/main/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemover.kt @@ -0,0 +1,113 @@ +package cash.grammar.kotlindsl.utils + +import cash.grammar.kotlindsl.parse.KotlinParseException +import cash.grammar.kotlindsl.parse.Parser +import cash.grammar.kotlindsl.parse.Rewriter +import cash.grammar.kotlindsl.utils.Context.leafRule +import cash.grammar.kotlindsl.utils.Whitespace.trimGently +import cash.grammar.utils.ifNotEmpty +import com.squareup.cash.grammar.KotlinParser +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 + +/** + * Removes comments from a specified block in a build script. + * + * Example: + * ``` + * dependencies { + * /* This is a block comment + * that spans multiple lines */ + * implementation("org.jetbrains.kotlin:kotlin-stdlib") // This is an inline comment + * // This is a single-line comment + * testImplementation("org.junit.jupiter:junit-jupiter") + * } + * ``` + * + * The above script would be rewritten to: + * ``` + * dependencies { + * implementation("org.jetbrains.kotlin:kotlin-stdlib") + * testImplementation("org.junit.jupiter:junit-jupiter") + * } + * ``` + */ +public class CommentsInBlockRemover private constructor( + private val input: CharStream, + private val tokens: CommonTokenStream, + private val errorListener: CollectingErrorListener, + private val blockName: String, +) : KotlinParserBaseListener() { + private var terminalNewlines = 0 + private val rewriter = Rewriter(tokens) + private val indent = Whitespace.computeIndent(tokens, input) + private val comments = Comments(tokens, indent) + + @Throws(KotlinParseException::class) + public fun rewritten(): String { + errorListener.getErrorMessages().ifNotEmpty { + throw KotlinParseException.withErrors(it) + } + + return rewriter.text.trimGently(terminalNewlines) + } + + override fun exitNamedBlock(ctx: KotlinParser.NamedBlockContext) { + if (ctx.name().text == blockName) { + // Delete inline comments (a comment after a statement) + val allInlineComments = mutableListOf() + ctx.statements().statement().forEach { + val leafRule = it.leafRule() + val inlineComments = rewriter.deleteCommentsAndBlankSpaceToRight(leafRule.stop).orEmpty() + allInlineComments += inlineComments + } + + val nonInlineComments = comments.getCommentsInBlock(ctx).subtract(allInlineComments) + nonInlineComments.forEach { token -> + rewriter.deleteWhitespaceToLeft(token) + rewriter.deleteNewlineToRight(token) + rewriter.delete(token) + } + } + } + + public companion object { + public fun of( + buildScript: Path, + blockName: String, + ): CommentsInBlockRemover { + return of(Parser.readOnlyInputStream(buildScript), blockName) + } + + public fun of( + buildScript: String, + blockName: String, + ): CommentsInBlockRemover { + return of(buildScript.byteInputStream(), blockName) + } + + private fun of( + buildScript: InputStream, + blockName: String, + ): CommentsInBlockRemover { + val errorListener = CollectingErrorListener() + + return Parser( + file = buildScript, + errorListener = errorListener, + listenerFactory = { input, tokens, _ -> + CommentsInBlockRemover( + input = input, + tokens = tokens, + errorListener = errorListener, + blockName = blockName, + ) + }, + ).listener() + } + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemoverTest.kt b/core/src/test/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemoverTest.kt new file mode 100644 index 0000000..59d73a5 --- /dev/null +++ b/core/src/test/kotlin/cash/grammar/kotlindsl/utils/CommentsInBlockRemoverTest.kt @@ -0,0 +1,46 @@ +package cash.grammar.kotlindsl.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class CommentsInBlockRemoverTest { + @Test + fun `remove all comments in the given block`() { + // Given + val buildScript = """ + |dependencies { + | /* This is a block comment + | that spans multiple lines */ + | implementation("org.jetbrains.kotlin:kotlin-stdlib") // This is an line comment + | // This is a single-line comment + | testImplementation("org.junit.jupiter:junit-jupiter") + | // This is another single-line comment + |} + | + |// This is project bar + |project.name = bar + | + |otherBlock { + | // More comments + |} + """.trimMargin() + + // When + val rewrittenBuildScript = CommentsInBlockRemover.of(buildScript, "dependencies").rewritten() + + // Then + assertThat(rewrittenBuildScript).isEqualTo(""" + |dependencies { + | implementation("org.jetbrains.kotlin:kotlin-stdlib") + | testImplementation("org.junit.jupiter:junit-jupiter") + |} + | + |// This is project bar + |project.name = bar + | + |otherBlock { + | // More comments + |} + """.trimMargin()) + } +} \ No newline at end of file