Skip to content

Commit

Permalink
Prevent conflicts with other formatters
Browse files Browse the repository at this point in the history
For background see #323. But do note that other formatters also should follow same rules to prevent conflict with ktlint plugin
  • Loading branch information
Paul Dingemans committed Oct 28, 2023
1 parent e22a8e9 commit 0ecd8c9
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 51 deletions.
21 changes: 15 additions & 6 deletions plugin/src/main/kotlin/com/nbadal/ktlint/KtlintAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ import com.nbadal.ktlint.actions.KtlintRuleSuppressIntention
import com.pinterest.ktlint.rule.engine.api.LintError

class KtlintAnnotator : ExternalAnnotator<KtlintFormatResult, List<LintError>>() {
override fun collectInformation(psiFile: PsiFile, editor: Editor, hasErrors: Boolean): KtlintFormatResult? =
override fun collectInformation(
psiFile: PsiFile,
editor: Editor,
hasErrors: Boolean,
): KtlintFormatResult? =
if (hasErrors) {
null
} else {
// Format the code, but do not write the result as this results in a deadlock (write command may not be invoked
// from read command).
ktlintFormat(psiFile, "KtlintAnnotator", writeFormattedCode = false)
ktlintLint(psiFile, "KtlintAnnotator")
}

override fun doAnnotate(collectedInfo: KtlintFormatResult?): List<LintError>? =
// Ignore errors that can be autocorrected by ktlint to prevent that developer is going to resolve errors
// manually and errors in baseline.
collectedInfo?.canNotBeAutoCorrectedErrors

override fun apply(file: PsiFile, errors: List<LintError>?, holder: AnnotationHolder) {
override fun apply(
file: PsiFile,
errors: List<LintError>?,
holder: AnnotationHolder,
) {
errors?.forEach { lintError ->
holder
.newAnnotation(HighlightSeverity.ERROR, lintError.errorMessage())
Expand All @@ -42,7 +48,10 @@ class KtlintAnnotator : ExternalAnnotator<KtlintFormatResult, List<LintError>>()

private fun LintError.errorMessage(): String = "$detail (${ruleId.value})"

private fun errorTextRange(file: PsiFile, it: LintError): TextRange {
private fun errorTextRange(
file: PsiFile,
it: LintError,
): TextRange {
val doc = file.viewProvider.document!!
val lineStart = doc.getLineStartOffset((it.line - 1).coerceIn(0, doc.lineCount - 1)).coerceIn(0, doc.textLength)
val errorOffset = (lineStart + (it.col - 1)).coerceIn(lineStart, doc.textLength)
Expand Down
96 changes: 54 additions & 42 deletions plugin/src/main/kotlin/com/nbadal/ktlint/KtlintFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.nbadal.ktlint

import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
Expand All @@ -19,13 +20,35 @@ import org.jetbrains.kotlin.utils.addToStdlib.applyIf
import java.nio.file.Files
import java.nio.file.Path

internal fun ktlintLint(
psiFile: PsiFile,
triggeredBy: String,
) = executeKtlintFormat(psiFile, triggeredBy, false)

internal fun ktlintFormat(
psiFile: PsiFile,
triggeredBy: String,
writeFormattedCode: Boolean = true,
) {
val project = psiFile.project
val document = psiFile.viewProvider.document
PsiDocumentManager
.getInstance(project)
.doPostponedOperationsAndUnblockDocument(document)
WriteCommandAction.runWriteCommandAction(project) {
executeKtlintFormat(psiFile, triggeredBy, true)
}
PsiDocumentManager
.getInstance(project)
.doPostponedOperationsAndUnblockDocument(document)
}

private fun executeKtlintFormat(
psiFile: PsiFile,
triggeredBy: String,
writeFormattedCode: Boolean = false,
): KtlintFormatResult {
val virtualFileName = psiFile.virtualFile.name
println("Start ktlintFormat on file '${virtualFileName}' triggered by '$triggeredBy'")
println("Start ktlintFormat on file '$virtualFileName' triggered by '$triggeredBy'")

val project = psiFile.project
if (!project.config().enableKtlint) {
Expand All @@ -45,12 +68,13 @@ internal fun ktlintFormat(

val baselineErrors = loadBaseline(project, filePath)

val ruleProviders = try {
loadRuleProviders(project)
} catch (err: Throwable) {
KtlintNotifier.notifyErrorWithSettings(project, "Error in external ruleset JAR", err.toString())
return EMPTY_KTLINT_FORMAT_RESULT
}
val ruleProviders =
try {
loadRuleProviders(project)
} catch (err: Throwable) {
KtlintNotifier.notifyErrorWithSettings(project, "Error in external ruleset JAR", err.toString())
return EMPTY_KTLINT_FORMAT_RESULT
}

val correctedErrors = mutableListOf<LintError>()
val canNotBeAutoCorrectedErrors = mutableListOf<LintError>()
Expand All @@ -63,10 +87,10 @@ internal fun ktlintFormat(
// TODO: remove when Code.fromSnippet takes a path as parameter in Ktlint 1.1.0.
// Drawback of this method is that it ignores property "root" in '.editorconfig' file.
editorConfigDefaults =
EditorConfigDefaults.load(
path = psiFile.findEditorConfigDirectoryPath(),
propertyTypes = ruleProviders.propertyTypes(),
),
EditorConfigDefaults.load(
path = psiFile.findEditorConfigDirectoryPath(),
propertyTypes = ruleProviders.propertyTypes(),
),
).format(
// The psiFile may contain unsaved changes. So create a snippet based on content of the psiFile *and*
// with the same path as that psiFile so that the correct '.editorconfig' is picked up by ktlint.
Expand All @@ -90,43 +114,30 @@ internal fun ktlintFormat(
}
}
if (writeFormattedCode) {
WriteCommandAction.runWriteCommandAction(
project,
"Format With KtLint",
null,
{
psiFile
.viewProvider
.document
?.apply {
PsiDocumentManager
.getInstance(project)
.doPostponedOperationsAndUnblockDocument(this)
setText(formattedCode)
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
}
},
psiFile,
)
with(psiFile.viewProvider.document) {
setText(formattedCode)
FileDocumentManager.getInstance().saveDocument(this)
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
}
}
} catch (pe: KtLintParseException) {
// TODO: report to rollbar?
println("ktlintFormat on file '${virtualFileName}', KtlintParseException: " + pe.stackTrace)
println("ktlintFormat on file '$virtualFileName', KtlintParseException: " + pe.stackTrace)
return EMPTY_KTLINT_FORMAT_RESULT
} catch (re: KtLintRuleException) {
// No valid rules were passed
println("ktlintFormat on file '${virtualFileName}', KtLintRuleException: " + re.stackTrace)
println("ktlintFormat on file '$virtualFileName', KtLintRuleException: " + re.stackTrace)
return EMPTY_KTLINT_FORMAT_RESULT
}

// TODO: remove
println(
"""
ktlintFormat on file '${virtualFileName}' finished:
ktlintFormat on file '$virtualFileName' finished:
- correctedErrors = ${correctedErrors.size}
- canNotBeAutoCorrectedErrors = ${canNotBeAutoCorrectedErrors.size}
- ignoredErrors = ${ignoredErrors.size}
""".trimIndent()
""".trimIndent(),
)
return KtlintFormatResult(canNotBeAutoCorrectedErrors, correctedErrors, ignoredErrors)
}
Expand All @@ -141,7 +152,7 @@ private fun PsiFile.findEditorConfigDirectoryPath(): Path? {

private fun loadBaseline(
project: Project,
filePath: String
filePath: String,
) = project
.config()
.baselinePath
Expand All @@ -158,13 +169,14 @@ private fun String.pathRelativeTo(projectBasePath: String?): String =
removePrefix(projectBasePath).removePrefix("/")
}

private fun LintError.toCliError(corrected: Boolean): KtlintCliError = KtlintCliError(
line = this.line,
col = this.col,
ruleId = this.ruleId.value,
detail = this.detail.applyIf(corrected) { "$this (cannot be auto-corrected)" },
status = if (corrected) KtlintCliError.Status.FORMAT_IS_AUTOCORRECTED else KtlintCliError.Status.LINT_CAN_NOT_BE_AUTOCORRECTED,
)
private fun LintError.toCliError(corrected: Boolean): KtlintCliError =
KtlintCliError(
line = this.line,
col = this.col,
ruleId = this.ruleId.value,
detail = this.detail.applyIf(corrected) { "$this (cannot be auto-corrected)" },
status = if (corrected) KtlintCliError.Status.FORMAT_IS_AUTOCORRECTED else KtlintCliError.Status.LINT_CAN_NOT_BE_AUTOCORRECTED,
)

data class KtlintFormatResult(
val canNotBeAutoCorrectedErrors: List<LintError>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor

class KtlintPostFormatProcessor : PostFormatProcessor {
override fun processElement(source: PsiElement, settings: CodeStyleSettings) = source // Stub.
override fun processElement(
source: PsiElement,
settings: CodeStyleSettings,
) = source // Stub.

override fun processText(psiFile: PsiFile, rangeToReformat: TextRange, settings: CodeStyleSettings): TextRange {
override fun processText(
psiFile: PsiFile,
rangeToReformat: TextRange,
settings: CodeStyleSettings,
): TextRange {
ktlintFormat(psiFile, "KtlintPostFormatProcessor")

return rangeToReformat
}
}

0 comments on commit 0ecd8c9

Please sign in to comment.