forked from pinterest/ktlint
/
CommentWrappingRule.kt
127 lines (116 loc) · 5.4 KB
/
CommentWrappingRule.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
package com.pinterest.ktlint.ruleset.experimental
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.lineIndent
import com.pinterest.ktlint.core.ast.lineNumber
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiCommentImpl
/**
* Checks external wrapping of block comments. Wrapping inside the comment is not altered. A block comment following
* another element on the same line is replaced with an EOL comment, if possible.
*/
public class CommentWrappingRule :
Rule("$experimentalRulesetId:comment-wrapping"),
UsesEditorConfigProperties {
override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> =
listOf(
DefaultEditorConfigProperties.indentSizeProperty,
DefaultEditorConfigProperties.indentStyleProperty,
)
override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
if (node.elementType == BLOCK_COMMENT) {
val nonIndentLeafOnSameLinePrecedingBlockComment =
node
.prevLeaf()
?.takeIf { isNonIndentLeafOnSameLine(it) }
val nonIndentLeafOnSameLineFollowingBlockComment =
node
.nextLeaf()
?.takeIf { isNonIndentLeafOnSameLine(it) }
if (nonIndentLeafOnSameLinePrecedingBlockComment != null &&
nonIndentLeafOnSameLineFollowingBlockComment != null
) {
if (nonIndentLeafOnSameLinePrecedingBlockComment.lineNumber() == nonIndentLeafOnSameLineFollowingBlockComment.lineNumber()) {
// Do not try to fix constructs like below:
// val foo /* some comment */ = "foo"
emit(
node.startOffset,
"A block comment in between other elements on the same line is disallowed",
false,
)
} else {
// Do not try to fix constructs like below:
// val foo = "foo" /*
// some comment
// */ val bar = "bar"
emit(
node.startOffset,
"A block comment starting on same line as another element and ending on another line before another element is disallowed",
false,
)
}
return
}
nonIndentLeafOnSameLinePrecedingBlockComment
?.precedesBlockCommentOnSameLine(node, emit, autoCorrect)
nonIndentLeafOnSameLineFollowingBlockComment
?.followsBlockCommentOnSameLine(node, emit, autoCorrect)
}
}
private fun ASTNode.precedesBlockCommentOnSameLine(
blockCommentNode: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
autoCorrect: Boolean,
) {
val leafAfterBlockComment = blockCommentNode.nextLeaf()
if (!blockCommentNode.textContains('\n') && leafAfterBlockComment.isLastElementOnLine()) {
emit(
startOffset,
"A single line block comment after a code element on the same line must be replaced with an EOL comment",
true,
)
if (autoCorrect) {
blockCommentNode.replaceWithEndOfLineComment()
}
} else {
// It can not be autocorrected as it might depend on the situation and code style what is preferred.
emit(
blockCommentNode.startOffset,
"A block comment after any other element on the same line must be separated by a new line",
false,
)
}
}
private fun ASTNode.replaceWithEndOfLineComment() {
val content = text.removeSurrounding("/*", "*/").trim()
val eolComment = PsiCommentImpl(EOL_COMMENT, "// $content")
(this as LeafPsiElement).rawInsertBeforeMe(eolComment)
rawRemove()
}
private fun ASTNode.followsBlockCommentOnSameLine(
blockCommentNode: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
autoCorrect: Boolean,
) {
emit(startOffset, "A block comment may not be followed by any other element on that same line", true)
if (autoCorrect) {
this.upsertWhitespaceBeforeMe("\n${blockCommentNode.lineIndent()}")
}
}
private fun isNonIndentLeafOnSameLine(it: ASTNode) =
it.elementType != WHITE_SPACE || !it.textContains('\n')
private fun ASTNode?.isLastElementOnLine() =
this == null || (elementType == WHITE_SPACE && textContains('\n'))
}