/
FormattingRule.kt
137 lines (117 loc) · 5.36 KB
/
FormattingRule.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package io.gitlab.arturbosch.detekt.formatting
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule.VisitorModifier.RunAsLateAsPossible
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties.codeStyleSetProperty
import com.pinterest.ktlint.core.api.EditorConfigProperties
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import io.github.detekt.psi.fileName
import io.github.detekt.psi.toFilePath
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.CorrectableCodeSmell
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Location
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.SingleAssign
import io.gitlab.arturbosch.detekt.api.SourceLocation
import io.gitlab.arturbosch.detekt.api.TextLocation
import org.ec4j.core.model.Property
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.JavaDummyElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.JavaDummyHolder
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.endOffset
/**
* Rule to detect formatting violations.
*/
abstract class FormattingRule(config: Config) : Rule(config) {
abstract val wrapping: com.pinterest.ktlint.core.Rule
/**
* Should the android style guide be enforced?
* This property is read from the ruleSet config.
*/
protected val isAndroid
get() = FormattingProvider.android.value(ruleSetConfig)
val runAsLateAsPossible
get() = RunAsLateAsPossible in wrapping.visitorModifiers
private var positionByOffset: (offset: Int) -> Pair<Int, Int> by SingleAssign()
private var root: KtFile by SingleAssign()
protected fun issueFor(description: String) =
Issue(javaClass.simpleName, Severity.Style, description, Debt.FIVE_MINS)
override fun visit(root: KtFile) {
this.root = root
positionByOffset = KtLintLineColCalculator
.calculateLineColByOffset(KtLintLineColCalculator.normalizeText(root.text))
root.node.putUserData(KtLint.FILE_PATH_USER_DATA_KEY, root.name)
wrapping.beforeFirstNode(computeEditorConfigProperties())
root.node.visitASTNodes()
wrapping.afterLastNode()
}
open fun overrideEditorConfigProperties(): Map<UsesEditorConfigProperties.EditorConfigProperty<*>, String>? = null
open fun getTextLocationForViolation(node: ASTNode, offset: Int) =
TextLocation(node.startOffset, node.psi.endOffset)
private fun computeEditorConfigProperties(): EditorConfigProperties {
val usesEditorConfigProperties = overrideEditorConfigProperties()?.toMutableMap()
?: mutableMapOf()
if (isAndroid) {
usesEditorConfigProperties[codeStyleSetProperty] = "android"
}
return buildMap {
usesEditorConfigProperties.forEach { (editorConfigProperty, defaultValue) ->
put(
key = editorConfigProperty.type.name,
value = Property.builder()
.name(editorConfigProperty.type.name)
.type(editorConfigProperty.type)
.value(defaultValue)
.build()
)
}
}
}
private fun beforeVisitChildNodes(node: ASTNode) {
wrapping.beforeVisitChildNodes(node, autoCorrect) {
offset, errorMessage, canBeAutoCorrected -> node.emit().invoke(offset, errorMessage, canBeAutoCorrected)
}
}
private fun afterVisitChildNodes(node: ASTNode) {
wrapping.afterVisitChildNodes(node, autoCorrect) {
offset, errorMessage, canBeAutoCorrected -> node.emit().invoke(offset, errorMessage, canBeAutoCorrected)
}
}
private fun ASTNode.emit() = { offset: Int, message: String, _: Boolean ->
val (line, column) = positionByOffset(offset)
val location = Location(
SourceLocation(line, column),
getTextLocationForViolation(this, offset),
root.toFilePath()
)
// Nodes reported by 'NoConsecutiveBlankLines' are dangling whitespace nodes which means they have
// no direct parent which we can use to get the containing file needed to baseline or suppress findings.
// For these reasons we do not report a KtElement which may lead to crashes when postprocessing it
// e.g. reports (html), baseline etc.
val packageName = root.packageFqName.asString()
.takeIf { it.isNotEmpty() }
?.plus(".")
.orEmpty()
val entity = Entity("", "$packageName${root.fileName}:$line", location, root)
report(CorrectableCodeSmell(issue, entity, message, autoCorrectEnabled = autoCorrect))
}
private fun ASTNode.visitASTNodes() {
if (isNotDummyElement()) {
beforeVisitChildNodes(this)
}
getChildren(null).forEach {
it.visitASTNodes()
}
if (isNotDummyElement()) {
afterVisitChildNodes(this)
}
}
private fun ASTNode.isNotDummyElement(): Boolean {
val parent = this.psi?.parent
return parent !is JavaDummyHolder && parent !is JavaDummyElement
}
}