Skip to content

Commit

Permalink
Recognize Kotlin Script when linting and formatting code from stdin
Browse files Browse the repository at this point in the history
… with KtLint CLI

Closes #1832
  • Loading branch information
paul-dingemans committed Mar 16, 2023
1 parent 601b84c commit e4f6b8a
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Previously the default value for `.editorconfig` property `max_line_length` was
* Add new experimental rule `try-catch-finally-spacing` for `ktlint_official` code style. This rule enforces consistent spacing in try-catch, try-finally and try-catch-finally statement. This rule can also be run for other code styles but then its needs to be explicitly enabled.
* Wrap the type or value of a function or class parameter in case the maximum line length is exceeded `parameter-wrapping` ([#1846](https://github.com/pinterest/ktlint/pull/1846))
* Wrap the type or value of a property in case the maximum line length is exceeded `property-wrapping` ([#1846](https://github.com/pinterest/ktlint/pull/1846))
* Recognize Kotlin Script when linting and formatting code from `stdin` with KtLint CLI ([#1832](https://github.com/pinterest/ktlint/issues/1832))

### Removed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,22 @@ internal class KtlintCommandLine {
if (code.fileName != null) {
logger.trace { "Checking ${File(code.fileName!!).location(relative)}" }
}
val result = ArrayList<KtlintCliError>()
if (format) {
val formattedFileContent = try {
ktLintRuleEngine.format(code) { lintError, corrected ->
return if (format) {
format(ktLintRuleEngine, code, baselineLintErrors)
} else {
lint(ktLintRuleEngine, code, baselineLintErrors)
}
}

private fun format(
ktLintRuleEngine: KtLintRuleEngine,
code: Code,
baselineLintErrors: List<KtlintCliError>,
): List<KtlintCliError> {
val ktlintCliErrors = mutableListOf<KtlintCliError>()
try {
ktLintRuleEngine
.format(code) { lintError, corrected ->
val ktlintCliError = KtlintCliError(
line = lintError.line,
col = lintError.col,
Expand All @@ -474,50 +486,88 @@ internal class KtlintCommandLine {
},
)
if (baselineLintErrors.doesNotContain(ktlintCliError)) {
result.add(ktlintCliError)
ktlintCliErrors.add(ktlintCliError)
if (!corrected) {
tripped.set(true)
}
}
}.also { formattedFileContent ->
when {
code.isStdIn -> print(formattedFileContent)
code.content != formattedFileContent ->
code
.filePath
?.toFile()
?.writeText(formattedFileContent, charset("UTF-8"))
}
}
} catch (e: Exception) {
if (code.isStdIn && e is KtLintParseException) {
logger.warn {
"""
Can not parse input from <stdin> as Kotlin, due to error below:
${e.toKtlintCliError(code).detail}
Now, trying to read the input as Kotlin Script.
""".trimIndent()
}
} catch (e: Exception) {
result.add(e.toKtlintCliError(code))
return format(
ktLintRuleEngine = ktLintRuleEngine,
code = Code.fromSnippet(code.content, script = true),
baselineLintErrors = baselineLintErrors,
)
} else {
ktlintCliErrors.add(e.toKtlintCliError(code))
tripped.set(true)
code.content // making sure `cat file | ktlint --stdin > file` is (relatively) safe
}
when {
code.isStdIn -> print(formattedFileContent)
code.content != formattedFileContent ->
code
.filePath
?.toFile()
?.writeText(formattedFileContent, charset("UTF-8"))
}
return ktlintCliErrors.toList()
}

private fun lint(
ktLintRuleEngine: KtLintRuleEngine,
code: Code,
baselineLintErrors: List<KtlintCliError>,
): List<KtlintCliError> {
val ktlintCliErrors = mutableListOf<KtlintCliError>()
try {
ktLintRuleEngine.lint(code) { lintError ->
val ktlintCliError = KtlintCliError(
line = lintError.line,
col = lintError.col,
ruleId = lintError.ruleId,
detail = lintError.detail,
status = if (lintError.canBeAutoCorrected) {
LINT_CAN_BE_AUTOCORRECTED
} else {
LINT_CAN_NOT_BE_AUTOCORRECTED
},
)
if (baselineLintErrors.doesNotContain(ktlintCliError)) {
ktlintCliErrors.add(ktlintCliError)
tripped.set(true)
}
}
} else {
try {
ktLintRuleEngine.lint(code) { lintError ->
val ktlintCliError = KtlintCliError(
line = lintError.line,
col = lintError.col,
ruleId = lintError.ruleId,
detail = lintError.detail,
status = if (lintError.canBeAutoCorrected) {
LINT_CAN_BE_AUTOCORRECTED
} else {
LINT_CAN_NOT_BE_AUTOCORRECTED
},
)
if (baselineLintErrors.doesNotContain(ktlintCliError)) {
result.add(ktlintCliError)
tripped.set(true)
}
} catch (e: Exception) {
if (code.isStdIn && e is KtLintParseException) {
logger.warn {
"""
Can not parse input from <stdin> as Kotlin, due to error below:
${e.toKtlintCliError(code).detail}
Now, trying to read the input as Kotlin Script.
""".trimIndent()
}
} catch (e: Exception) {
result.add(e.toKtlintCliError(code))
return lint(
ktLintRuleEngine = ktLintRuleEngine,
code = Code.fromSnippet(code.content, script = true),
baselineLintErrors = baselineLintErrors,
)
} else {
ktlintCliErrors.add(e.toKtlintCliError(code))
tripped.set(true)
}
}
return result
return ktlintCliErrors.toList()
}

private fun Exception.toKtlintCliError(code: Code): KtlintCliError =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,66 @@ class SimpleCLITest {
assertThat(normalOutput).containsLineMatching(Regex(".*ktlint_standard_filename: disabled.*"))
}
}

@Test
fun `Issue 1832 - Given stdin input containing Kotlin Script resulting in a KtLintParseException when linted as Kotlin code then process the input as Kotlin Script`(
@TempDir
tempDir: Path,
) {
CommandLineTestRunner(tempDir)
.run(
testProjectName = "too-many-empty-lines",
arguments = listOf("--stdin"),
stdin = ByteArrayInputStream(
"""
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
includeBuild("build-logic")
}
""".trimIndent().toByteArray(),
),
) {
assertThat(normalOutput)
.containsLineMatching(Regex(".*Not a valid Kotlin file.*"))
.containsLineMatching(Regex(".*Now, trying to read the input as Kotlin Script.*"))
assertThat(errorOutput)
.containsLineMatching(Regex(".*Needless blank line.*"))
}
}

@Test
fun `Issue 1832 - Given stdin input containing Kotlin Script resulting in a KtLintParseException when formatted as Kotlin code then process the input as Kotlin Script`(
@TempDir
tempDir: Path,
) {
CommandLineTestRunner(tempDir)
.run(
testProjectName = "too-many-empty-lines",
arguments = listOf("--stdin", "--format"),
stdin = ByteArrayInputStream(
"""
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
includeBuild("build-logic")
}
""".trimIndent().toByteArray(),
),
) {
assertThat(normalOutput)
.containsLineMatching(Regex(".*Not a valid Kotlin file.*"))
.containsLineMatching(Regex(".*Now, trying to read the input as Kotlin Script.*"))
assertThat(errorOutput)
.doesNotContainLineMatching(Regex(".*Needless blank line.*"))
}
}
}

0 comments on commit e4f6b8a

Please sign in to comment.