aboutsummaryrefslogtreecommitdiff
path: root/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
diff options
context:
space:
mode:
Diffstat (limited to 'ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt')
-rw-r--r--ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt209
1 files changed, 143 insertions, 66 deletions
diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
index 13c7ea4e..977b97d7 100644
--- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
+++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
@@ -1,10 +1,13 @@
package com.github.shyiko.ktlint.core
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
+import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger
import org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPoint
import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions.getArea
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
@@ -28,18 +31,30 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
import sun.reflect.ReflectionFactory
import java.util.ArrayList
import java.util.HashSet
+import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger as DiagnosticLogger
object KtLint {
val EDITOR_CONFIG_USER_DATA_KEY = Key<EditorConfig>("EDITOR_CONFIG")
val ANDROID_USER_DATA_KEY = Key<Boolean>("ANDROID")
+ val FILE_PATH_USER_DATA_KEY = Key<String>("FILE_PATH")
private val psiFileFactory: PsiFileFactory
private val nullSuppression = { _: Int, _: String -> false }
init {
+ // do not print anything to the stderr when lexer is unable to match input
+ class LoggerFactory : DiagnosticLogger.Factory {
+ override fun getLoggerInstance(p: String): DiagnosticLogger = object : DefaultLogger(null) {
+ override fun warn(message: String?, t: Throwable?) {}
+ override fun error(message: String?, vararg details: String?) {}
+ }
+ }
+ DiagnosticLogger.setFactory(LoggerFactory::class.java)
+ val compilerConfiguration = CompilerConfiguration()
+ compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
val project = KotlinCoreEnvironment.createForProduction(Disposable {},
- CompilerConfiguration(), EnvironmentConfigFiles.EMPTY).project
+ compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES).project
// everything below (up to PsiFileFactory.getInstance(...)) is to get AST mutations (`ktlint -F ...`) working
// otherwise it's not needed
val pomModel: PomModel = object : UserDataHolderBase(), PomModel {
@@ -129,43 +144,100 @@ object KtLint {
throw ParseException(line, col, errorElement.errorDescription)
}
val rootNode = psiFile.node
- rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android"))
+ rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android" - "file_path"))
rootNode.putUserData(ANDROID_USER_DATA_KEY, userData["android"]?.toBoolean() ?: false)
+ rootNode.putUserData(FILE_PATH_USER_DATA_KEY, userData["file_path"])
val isSuppressed = calculateSuppressedRegions(rootNode)
- val r = flatten(ruleSets)
- rootNode.visit { node ->
- r.forEach { (id, rule) ->
- if (!isSuppressed(node.startOffset, id)) {
- try {
- rule.visit(node, false) { offset, errorMessage, _ ->
- val (line, col) = positionByOffset(offset)
- cb(LintError(line, col, id, errorMessage))
- }
- } catch (e: Exception) {
- val (line, col) = positionByOffset(node.startOffset)
- throw RuleExecutionException(line, col, id, e)
+ visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
+ // fixme: enforcing suppression based on node.startOffset is wrong
+ // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+ if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
+ try {
+ rule.visit(node, false) { offset, errorMessage, _ ->
+ val (line, col) = positionByOffset(offset)
+ cb(LintError(line, col, fqRuleId, errorMessage))
}
+ } catch (e: Exception) {
+ val (line, col) = positionByOffset(node.startOffset)
+ throw RuleExecutionException(line, col, fqRuleId, e)
}
}
}
}
- private fun flatten(ruleSets: Iterable<RuleSet>) = ArrayList<Pair<String, Rule>>().apply {
- ruleSets.forEach { ruleSet ->
+ private fun visitor(
+ rootNode: ASTNode,
+ ruleSets: Iterable<RuleSet>,
+ concurrent: Boolean = true,
+ filter: (fqRuleId: String) -> Boolean = { true }
+ ): ((node: ASTNode, rule: Rule, fqRuleId: String) -> Unit) -> Unit {
+ val fqrsRestrictedToRoot = mutableListOf<Pair<String, Rule>>()
+ val fqrs = mutableListOf<Pair<String, Rule>>()
+ val fqrsExpectedToBeExecutedLastOnRoot = mutableListOf<Pair<String, Rule>>()
+ val fqrsExpectedToBeExecutedLast = mutableListOf<Pair<String, Rule>>()
+ for (ruleSet in ruleSets) {
val prefix = if (ruleSet.id === "standard") "" else "${ruleSet.id}:"
- ruleSet.forEach { rule -> add("$prefix${rule.id}" to rule) }
+ for (rule in ruleSet) {
+ val fqRuleId = "$prefix${rule.id}"
+ if (!filter(fqRuleId)) {
+ continue
+ }
+ val fqr = fqRuleId to rule
+ when {
+ rule is Rule.Modifier.Last -> fqrsExpectedToBeExecutedLast.add(fqr)
+ rule is Rule.Modifier.RestrictToRootLast -> fqrsExpectedToBeExecutedLastOnRoot.add(fqr)
+ rule is Rule.Modifier.RestrictToRoot -> fqrsRestrictedToRoot.add(fqr)
+ else -> fqrs.add(fqr)
+ }
+ }
+ }
+ return { visit ->
+ for ((fqRuleId, rule) in fqrsRestrictedToRoot) {
+ visit(rootNode, rule, fqRuleId)
+ }
+ if (concurrent) {
+ rootNode.visit { node ->
+ for ((fqRuleId, rule) in fqrs) {
+ visit(node, rule, fqRuleId)
+ }
+ }
+ } else {
+ for ((fqRuleId, rule) in fqrs) {
+ rootNode.visit { node ->
+ visit(node, rule, fqRuleId)
+ }
+ }
+ }
+ for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLastOnRoot) {
+ visit(rootNode, rule, fqRuleId)
+ }
+ if (!fqrsExpectedToBeExecutedLast.isEmpty()) {
+ if (concurrent) {
+ rootNode.visit { node ->
+ for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
+ visit(node, rule, fqRuleId)
+ }
+ }
+ } else {
+ for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
+ rootNode.visit { node ->
+ visit(node, rule, fqRuleId)
+ }
+ }
+ }
+ }
}
}
private fun calculateLineColByOffset(text: String): (offset: Int) -> Pair<Int, Int> {
- var i = 0
+ var i = -1
val e = text.length
val arr = ArrayList<Int>()
do {
- arr.add(i)
- i = text.indexOf('\n', i) + 1
- } while (i != 0 && i != e)
- arr.add(e)
+ arr.add(i + 1)
+ i = text.indexOf('\n', i + 1)
+ } while (i != -1)
+ arr.add(e + if (arr.last() == e) 1 else 0)
val segmentTree = SegmentTree(arr.toTypedArray())
return { offset ->
val line = segmentTree.indexOf(offset)
@@ -199,8 +271,12 @@ object KtLint {
fun format(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError, corrected: Boolean) -> Unit): String =
format(text, ruleSets, emptyMap<String, String>(), cb, script = false)
- fun format(text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
- cb: (e: LintError, corrected: Boolean) -> Unit): String = format(text, ruleSets, userData, cb, script = false)
+ fun format(
+ text: String,
+ ruleSets: Iterable<RuleSet>,
+ userData: Map<String, String>,
+ cb: (e: LintError, corrected: Boolean) -> Unit
+ ): String = format(text, ruleSets, userData, cb, script = false)
/**
* Fix style violations.
@@ -215,8 +291,12 @@ object KtLint {
fun formatScript(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError, corrected: Boolean) -> Unit): String =
format(text, ruleSets, emptyMap(), cb, script = true)
- fun formatScript(text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
- cb: (e: LintError, corrected: Boolean) -> Unit): String = format(text, ruleSets, userData, cb, script = true)
+ fun formatScript(
+ text: String,
+ ruleSets: Iterable<RuleSet>,
+ userData: Map<String, String>,
+ cb: (e: LintError, corrected: Boolean) -> Unit
+ ): String = format(text, ruleSets, userData, cb, script = true)
private fun format(
text: String,
@@ -238,55 +318,57 @@ object KtLint {
throw ParseException(line, col, errorElement.errorDescription)
}
val rootNode = psiFile.node
- rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android"))
+ rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android" - "file_path"))
rootNode.putUserData(ANDROID_USER_DATA_KEY, userData["android"]?.toBoolean() ?: false)
+ rootNode.putUserData(FILE_PATH_USER_DATA_KEY, userData["file_path"])
var isSuppressed = calculateSuppressedRegions(rootNode)
- val r = flatten(ruleSets)
- var autoCorrect = false
- rootNode.visit { node ->
- r.forEach { (id, rule) ->
- if (!isSuppressed(node.startOffset, id)) {
+ var tripped = false
+ var mutated = false
+ visitor(rootNode, ruleSets, concurrent = false)
+ .invoke { node, rule, fqRuleId ->
+ // fixme: enforcing suppression based on node.startOffset is wrong
+ // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+ if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
try {
- rule.visit(node, false) { offset, errorMessage, canBeAutoCorrected ->
+ rule.visit(node, true) { _, _, canBeAutoCorrected ->
+ tripped = true
if (canBeAutoCorrected) {
- autoCorrect = true
+ mutated = true
+ if (isSuppressed !== nullSuppression) {
+ isSuppressed = calculateSuppressedRegions(rootNode)
+ }
}
- val (line, col) = positionByOffset(offset)
- cb(LintError(line, col, id, errorMessage), canBeAutoCorrected)
}
} catch (e: Exception) {
- val (line, col) = positionByOffset(node.startOffset)
- throw RuleExecutionException(line, col, id, e)
+ // line/col cannot be reliably mapped as exception might originate from a node not present
+ // in the original AST
+ throw RuleExecutionException(0, 0, fqRuleId, e)
}
}
}
- }
- if (autoCorrect) {
- rootNode.visit { node ->
- r.forEach { (id, rule) ->
- if (!isSuppressed(node.startOffset, id)) {
- try {
- rule.visit(node, true) { _, _, canBeAutoCorrected ->
- if (canBeAutoCorrected && isSuppressed !== nullSuppression) {
- isSuppressed = calculateSuppressedRegions(rootNode)
- }
- }
- } catch (e: Exception) {
- // line/col cannot be reliably mapped as exception might originate from a node not present
- // in the original AST
- throw RuleExecutionException(0, 0, id, e)
+ if (tripped) {
+ visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
+ // fixme: enforcing suppression based on node.startOffset is wrong
+ // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+ if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
+ try {
+ rule.visit(node, false) { offset, errorMessage, _ ->
+ val (line, col) = positionByOffset(offset)
+ cb(LintError(line, col, fqRuleId, errorMessage), false)
}
+ } catch (e: Exception) {
+ val (line, col) = positionByOffset(node.startOffset)
+ throw RuleExecutionException(line, col, fqRuleId, e)
}
}
}
- return rootNode.text.replace("\n", determineLineSeparator(text))
}
- return text
+ return if (mutated) rootNode.text.replace("\n", determineLineSeparator(text)) else text
}
private fun calculateLineBreakOffset(fileContent: String): (offset: Int) -> Int {
val arr = ArrayList<Int>()
- var i: Int = 0
+ var i = 0
do {
arr.add(i)
i = fileContent.indexOf("\r\n", i + 1)
@@ -296,13 +378,8 @@ object KtLint {
SegmentTree(arr.toTypedArray()).let { return { offset -> it.indexOf(offset) } } else { _ -> 0 }
}
- private fun determineLineSeparator(fileContent: String): String {
- val i = fileContent.lastIndexOf('\n')
- if (i == -1) {
- return if (fileContent.lastIndexOf('\r') == -1) System.getProperty("line.separator") else "\r"
- }
- return if (i != 0 && fileContent[i] == '\r') "\r\n" else "\n"
- }
+ private fun determineLineSeparator(fileContent: String) =
+ if (fileContent.lastIndexOf('\r') != -1) "\r\n" else "\n"
/**
* @param range zero-based range of lines where lint errors should be suppressed
@@ -329,8 +406,8 @@ object KtLint {
val commentText = text.removePrefix("/*").removeSuffix("*/").trim()
parseHintArgs(commentText, "ktlint-disable")?.apply {
open.add(SuppressionHint(IntRange(node.startOffset, node.startOffset), HashSet(this)))
- } ?:
- parseHintArgs(commentText, "ktlint-enable")?.apply {
+ }
+ ?: parseHintArgs(commentText, "ktlint-enable")?.apply {
// match open hint
val disabledRules = HashSet(this)
val openHintIndex = open.indexOfLast { it.disabledRules == disabledRules }
@@ -363,7 +440,7 @@ object KtLint {
private fun splitCommentBySpace(comment: String) =
comment.replace(Regex("\\s"), " ").replace(" {2,}", " ").split(" ")
- private fun <T>List<T>.tail() = this.subList(1, this.size)
+ private fun <T> List<T>.tail() = this.subList(1, this.size)
}
}