/
package.kt
428 lines (383 loc) · 14.7 KB
/
package.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
package com.pinterest.ktlint.core.ast
import com.pinterest.ktlint.core.ast.ElementType.REGULAR_STRING_PART
import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import kotlin.reflect.KClass
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.psi.psiUtil.leaves
import org.jetbrains.kotlin.psi.psiUtil.siblings
public fun ASTNode.nextLeaf(includeEmpty: Boolean = false, skipSubtree: Boolean = false): ASTNode? {
var n = if (skipSubtree) this.lastChildLeafOrSelf().nextLeafAny() else this.nextLeafAny()
if (!includeEmpty) {
while (n != null && n.textLength == 0) {
n = n.nextLeafAny()
}
}
return n
}
public fun ASTNode.nextLeaf(p: (ASTNode) -> Boolean): ASTNode? {
var n = this.nextLeafAny()
while (n != null && !p(n)) {
n = n.nextLeafAny()
}
return n
}
private fun ASTNode.nextLeafAny(): ASTNode? {
var n = this
if (n.firstChildNode != null) {
do {
n = n.firstChildNode
} while (n.firstChildNode != null)
return n
}
return n.nextLeafStrict()
}
private fun ASTNode.nextLeafStrict(): ASTNode? {
val nextSibling: ASTNode? = treeNext
if (nextSibling != null) {
return nextSibling.firstChildLeafOrSelf()
}
return treeParent?.nextLeafStrict()
}
public fun ASTNode.firstChildLeafOrSelf(): ASTNode {
var n = this
if (n.firstChildNode != null) {
do {
n = n.firstChildNode
} while (n.firstChildNode != null)
return n
}
return n
}
public fun ASTNode.prevLeaf(includeEmpty: Boolean = false): ASTNode? {
var n = this.prevLeafAny()
if (!includeEmpty) {
while (n != null && n.textLength == 0) {
n = n.prevLeafAny()
}
}
return n
}
public fun ASTNode.prevLeaf(p: (ASTNode) -> Boolean): ASTNode? {
var n = this.prevLeafAny()
while (n != null && !p(n)) {
n = n.prevLeafAny()
}
return n
}
private fun ASTNode.prevLeafAny(): ASTNode? {
val prevSibling = treePrev
if (prevSibling != null) {
return treePrev.lastChildLeafOrSelf()
}
return treeParent?.prevLeafAny()
}
public fun ASTNode.lastChildLeafOrSelf(): ASTNode {
var n = this
if (n.lastChildNode != null) {
do {
n = n.lastChildNode
} while (n.lastChildNode != null)
return n
}
return n
}
public fun ASTNode.prevCodeLeaf(includeEmpty: Boolean = false): ASTNode? {
var n = prevLeaf(includeEmpty)
while (n != null && (n.elementType == WHITE_SPACE || n.isPartOfComment())) {
n = n.prevLeaf(includeEmpty)
}
return n
}
public fun ASTNode.nextCodeLeaf(includeEmpty: Boolean = false, skipSubtree: Boolean = false): ASTNode? {
var n = nextLeaf(includeEmpty, skipSubtree)
while (n != null && (n.elementType == WHITE_SPACE || n.isPartOfComment())) {
n = n.nextLeaf(includeEmpty, skipSubtree)
}
return n
}
public fun ASTNode.prevCodeSibling(): ASTNode? =
prevSibling { it.elementType != WHITE_SPACE && !it.isPartOfComment() }
public inline fun ASTNode.prevSibling(p: (ASTNode) -> Boolean): ASTNode? {
var n = this.treePrev
while (n != null) {
if (p(n)) {
return n
}
n = n.treePrev
}
return null
}
public fun ASTNode.nextCodeSibling(): ASTNode? =
nextSibling { it.elementType != WHITE_SPACE && !it.isPartOfComment() }
public inline fun ASTNode.nextSibling(p: (ASTNode) -> Boolean): ASTNode? {
var n = this.treeNext
while (n != null) {
if (p(n)) {
return n
}
n = n.treeNext
}
return null
}
/**
* @param elementType [ElementType].*
*/
public fun ASTNode.parent(elementType: IElementType, strict: Boolean = true): ASTNode? {
var n: ASTNode? = if (strict) this.treeParent else this
while (n != null) {
if (n.elementType == elementType) {
return n
}
n = n.treeParent
}
return null
}
public fun ASTNode.parent(p: (ASTNode) -> Boolean, strict: Boolean = true): ASTNode? {
var n: ASTNode? = if (strict) this.treeParent else this
while (n != null) {
if (p(n)) {
return n
}
n = n.treeParent
}
return null
}
/**
* @param elementType [ElementType].*
*/
public fun ASTNode.isPartOf(elementType: IElementType): Boolean =
parent(elementType, strict = false) != null
public fun ASTNode.isPartOf(klass: KClass<out PsiElement>): Boolean {
var n: ASTNode? = this
while (n != null) {
if (klass.java.isInstance(n.psi)) {
return true
}
n = n.treeParent
}
return false
}
public fun ASTNode.isPartOfCompositeElementOfType(iElementType: IElementType): Boolean =
parent(findCompositeElementOfType(iElementType))?.elementType == iElementType
public fun findCompositeElementOfType(iElementType: IElementType): (ASTNode) -> Boolean =
{ it.elementType == iElementType || it !is CompositeElement }
public fun ASTNode.isPartOfString(): Boolean =
parent(STRING_TEMPLATE, strict = false) != null
public fun ASTNode?.isWhiteSpace(): Boolean =
this != null && elementType == WHITE_SPACE
public fun ASTNode?.isWhiteSpaceWithNewline(): Boolean =
this != null && elementType == WHITE_SPACE && textContains('\n')
public fun ASTNode?.isWhiteSpaceWithoutNewline(): Boolean =
this != null && elementType == WHITE_SPACE && !textContains('\n')
public fun ASTNode.isRoot(): Boolean = elementType == ElementType.FILE
public fun ASTNode.isLeaf(): Boolean = firstChildNode == null
public fun ASTNode.isPartOfComment(): Boolean =
parent({ it.psi is PsiComment }, strict = false) != null
public fun ASTNode.children(): Sequence<ASTNode> =
generateSequence(firstChildNode) { node -> node.treeNext }
@Deprecated(message = """Marked for removal in KtLint 0.49. See KDOC""")
/**
* Marked for removal in KtLint 0.49.
*
* Use [ASTNode.upsertWhitespaceBeforeMe] which operates on the [ASTNode] instead of the [LeafElement]. The new method
* handles more edge case and as of that a lot of code can be simplified.
*
* *Code using [LeafElement.upsertWhitespaceBeforeMe]*
* ```
* if (elementType == WHITE_SPACE) {
* (this as LeafPsiElement).rawReplaceWithText("\n${blockCommentNode.lineIndent()}")
* } else {
* (this as LeafPsiElement).upsertWhitespaceBeforeMe("\n${blockCommentNode.lineIndent()}")
* }
* ```
* *Code using [ASTNode.upsertWhitespaceBeforeMe]*
* ```
* this.upsertWhitespaceBeforeMe(text)
* ```
*/
public fun LeafElement.upsertWhitespaceBeforeMe(text: String): LeafElement {
val s = treePrev
return if (s != null && s.elementType == WHITE_SPACE) {
(s.psi as LeafElement).rawReplaceWithText(text)
} else {
PsiWhiteSpaceImpl(text).also { w ->
(psi as LeafElement).rawInsertBeforeMe(w)
}
}
}
@Deprecated(
message =
"Marked for removal in KtLint 0.49. The new insertOrReplaceWhitespaceAfterMe is more versatile as it " +
"operates on an AstNode instead of a LeafElement. In a lot of cases the code can be simplified as it is " +
"no longer needed to check whether the current node is already a whitespace or a leaf element before " +
"calling this method or the rawReplaceWithText.",
ReplaceWith("insertOrReplaceWhitespaceBeforeMe"),
)
public fun LeafElement.upsertWhitespaceAfterMe(text: String): LeafElement {
val s = treeNext
return if (s != null && s.elementType == WHITE_SPACE) {
(s.psi as LeafElement).rawReplaceWithText(text)
} else {
PsiWhiteSpaceImpl(text).also { w ->
(psi as LeafElement).rawInsertAfterMe(w)
}
}
}
/**
* Updates or inserts a new whitespace element with [text] before the given node. If the node itself is a whitespace
* then its contents is replaced with [text]. If the node is a (nested) composite element, the whitespace element is
* added after the previous leaf node.
*/
public fun ASTNode.upsertWhitespaceBeforeMe(text: String) {
if (this is LeafElement) {
if (this.elementType == WHITE_SPACE) {
return replaceWhitespaceWith(text)
}
val previous = treePrev ?: prevLeaf()
if (previous != null && previous.elementType == WHITE_SPACE) {
previous.replaceWhitespaceWith(text)
} else {
PsiWhiteSpaceImpl(text).also { psiWhiteSpace ->
(psi as LeafElement).rawInsertBeforeMe(psiWhiteSpace)
}
}
} else {
val prevLeaf =
requireNotNull(prevLeaf()) {
"Can not upsert a whitespace if the first node is a non-leaf node"
}
prevLeaf.upsertWhitespaceAfterMe(text)
}
}
private fun ASTNode.replaceWhitespaceWith(text: String) {
require(this.elementType == WHITE_SPACE)
if (this.text != text) {
(this.psi as LeafElement).rawReplaceWithText(text)
}
}
/**
* Updates or inserts a new whitespace element with [text] after the given node. If the node itself is a whitespace
* then its contents is replaced with [text]. If the node is a (nested) composite element, the whitespace element is
* added after the last child leaf.
*/
public fun ASTNode.upsertWhitespaceAfterMe(text: String) {
if (this is LeafElement) {
if (this.elementType == WHITE_SPACE) {
return replaceWhitespaceWith(text)
}
val next = treeNext ?: nextLeaf()
if (next != null && next.elementType == WHITE_SPACE) {
next.replaceWhitespaceWith(text)
} else {
PsiWhiteSpaceImpl(text).also { psiWhiteSpace ->
(psi as LeafElement).rawInsertAfterMe(psiWhiteSpace)
}
}
} else {
lastChildLeafOrSelf().upsertWhitespaceAfterMe(text)
}
}
@Deprecated(message = "Marked for removal in Ktlint 0.48. See Ktlint 0.47.0 changelog for more information.")
public fun ASTNode.visit(enter: (node: ASTNode) -> Unit) {
enter(this)
this.getChildren(null).forEach { it.visit(enter) }
}
@Deprecated(message = "Marked for removal in Ktlint 0.48. See Ktlint 0.47.0 changelog for more information.")
public fun ASTNode.visit(enter: (node: ASTNode) -> Unit, exit: (node: ASTNode) -> Unit) {
enter(this)
this.getChildren(null).forEach { it.visit(enter, exit) }
exit(this)
}
/**
* Get the line number of the node in the original code sample. If the AST, prior to the current node, is changed by
* adding or removing a node containing a newline, the lineNumber will not be calculated correctly. Either it results in
* an incorrect lineNumber or an IndexOutOfBoundsException (Wrong offset). is thrown. The rule in which the problem
* manifest does not need to be the same rule which has changed the prior AST. Debugging such problems can be very hard.
*/
@Deprecated(
"Marked for removal in KtLint 0.48. The lineNumber is a calculated field. This calculation is not always " +
"reliable when formatting code.See KDOC for more information.",
)
public fun ASTNode.lineNumber(): Int? =
this.psi.containingFile?.viewProvider?.document?.getLineNumber(this.startOffset)?.let { it + 1 }
public val ASTNode.column: Int
get() {
var leaf = this.prevLeaf()
var offsetToTheLeft = 0
while (leaf != null) {
if ((leaf.elementType == WHITE_SPACE || leaf.elementType == REGULAR_STRING_PART) && leaf.textContains('\n')) {
offsetToTheLeft += leaf.textLength - 1 - leaf.text.lastIndexOf('\n')
break
}
offsetToTheLeft += leaf.textLength
leaf = leaf.prevLeaf()
}
return offsetToTheLeft + 1
}
public fun ASTNode.lineIndent(): String {
var leaf = this.prevLeaf()
while (leaf != null) {
if (leaf.elementType == WHITE_SPACE && leaf.textContains('\n')) {
return leaf.text.substring(leaf.text.lastIndexOf('\n') + 1)
}
leaf = leaf.prevLeaf()
}
return ""
}
/**
* Print content of a node and the element type of the node, its parent and its direct children. Utility is meant to
* be used during development only. Please do not remove.
*/
@Suppress("unused")
public fun ASTNode.logStructure(): ASTNode =
also {
println("Processing ${text.replaceTabAndNewline()} : Type $elementType with parent ${treeParent?.elementType} ")
children()
.toList()
.map {
println(" ${it.text.replaceTabAndNewline()} : Type ${it.elementType}")
}
}
private fun String.replaceTabAndNewline(): String =
replace("\t", "\\t").replace("\n", "\\n")
@Deprecated(
message = "This method is marked for removal in KtLint 0.48.0 as it is not reliable.",
replaceWith = ReplaceWith("hasWhiteSpaceWithNewLineInClosedRange(from, to)"),
)
public fun containsLineBreakInRange(from: PsiElement, to: PsiElement): Boolean =
from.siblings(forward = true, withItself = true)
.takeWhile { !it.isEquivalentTo(to) }
.any { it.textContains('\n') }
/**
* Verifies that a whitespace leaf containing a newline exist in the closed range [from] - [to]. Also, returns true in
* case any of the boundary nodes [from] or [to] is a whitespace leaf containing a newline.
*/
public fun hasWhiteSpaceWithNewLineInClosedRange(from: ASTNode, to: ASTNode): Boolean =
from.isWhiteSpaceWithNewline() ||
leavesInOpenRange(from, to).any { it.isWhiteSpaceWithNewline() } ||
to.isWhiteSpaceWithNewline()
/**
* Verifies that no whitespace leaf contains a newline in the closed range [from] - [to]. Also, the boundary nodes
* [from] and [to] should not be a whitespace leaf containing a newline.
*/
public fun noWhiteSpaceWithNewLineInClosedRange(from: ASTNode, to: ASTNode): Boolean =
!from.isWhiteSpaceWithNewline() &&
leavesInOpenRange(from, to).none { it.isWhiteSpaceWithNewline() } &&
!to.isWhiteSpaceWithNewline()
/**
* Creates a sequence of leaf nodes in the open range [from] - [to]. This means that the boundary nodes are excluded
* from the range in case they would happen to be a leaf node. In case [from] is a [CompositeElement] than the first
* leaf node in the sequence is the first leaf node in this [CompositeElement]. In case [to] is a [CompositeElement]
* than the last node in the sequence is the last leaf node prior to this [CompositeElement].
*/
public fun leavesInOpenRange(from: ASTNode, to: ASTNode): Sequence<ASTNode> =
from
.leaves()
.takeWhile { it != to && it != to.firstChildNode }