diff --git a/diktat-rules/pom.xml b/diktat-rules/pom.xml
index 59c608d024..6503fb69f3 100644
--- a/diktat-rules/pom.xml
+++ b/diktat-rules/pom.xml
@@ -110,7 +110,8 @@
- src/test/kotlin
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt
index bbbece9074..6f8204bdf2 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt
@@ -88,19 +88,18 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc
private fun handleLocalProperty(property: KtProperty, usages: List) {
val declarationScope = property.getDeclarationScope()
- val firstUsageStatementLine = getFirstUsageStatementOrBlock(usages, declarationScope).node.getLineNumber(isFixMode)!!
- val firstUsage = usages.minBy { it.node.getLineNumber(isFixMode)!! }!!
- checkLineNumbers(property, firstUsageStatementLine, firstUsageLine = firstUsage.node.getLineNumber(isFixMode)!!)
+ val firstUsageStatementLine = getFirstUsageStatementOrBlock(usages, declarationScope).node.getLineNumber()
+ val firstUsage = usages.minBy { it.node.getLineNumber() }!!
+ checkLineNumbers(property, firstUsageStatementLine, firstUsageLine = firstUsage.node.getLineNumber())
}
- @Suppress("UnsafeCallOnNullableType")
private fun handleConsecutiveDeclarations(statement: PsiElement, properties: List) {
// need to check that properties are declared consecutively with only maybe empty lines
properties
- .sortedBy { it.node.getLineNumber(isFixMode)!! }
+ .sortedBy { it.node.getLineNumber() }
.zip(properties.size - 1 downTo 0)
.forEach { (property, offset) ->
- checkLineNumbers(property, statement.node.getLineNumber(isFixMode)!!, offset)
+ checkLineNumbers(property, statement.node.getLineNumber(), offset)
}
}
@@ -117,15 +116,15 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc
siblings
.last()
.node
- .lastLineNumber(isFixMode)!! - siblings
+ .lastLineNumber() - siblings
.first()
.node
- .getLineNumber(isFixMode)!! - 1
+ .getLineNumber() - 1
}
- if (firstUsageStatementLine - numLinesToSkip != property.node.lastLineNumber(isFixMode)!! + 1 + offset) {
+ if (firstUsageStatementLine - numLinesToSkip != property.node.lastLineNumber() + 1 + offset) {
LOCAL_VARIABLE_EARLY_DECLARATION.warn(configRules, emitWarn, isFixMode,
- warnMessage(property.name!!, property.node.getLineNumber(isFixMode)!!, firstUsageLine
+ warnMessage(property.name!!, property.node.getLineNumber(), firstUsageLine
?: firstUsageStatementLine), property.startOffset, property.node)
}
}
@@ -137,7 +136,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc
*/
@Suppress("UnsafeCallOnNullableType", "GENERIC_VARIABLE_WRONG_DECLARATION")
private fun getFirstUsageStatementOrBlock(usages: List, declarationScope: KtBlockExpression?): PsiElement {
- val firstUsage = usages.minBy { it.node.getLineNumber(isFixMode)!! }!!
+ val firstUsage = usages.minBy { it.node.getLineNumber() }!!
val firstUsageScope = firstUsage.getParentOfType(true)
return if (firstUsageScope == declarationScope) {
@@ -147,7 +146,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc
.find { it.parent == declarationScope }!!
} else {
// first usage is in deeper block compared to declaration, need to check how close is declaration to the first line of the block
- usages.minBy { it.node.getLineNumber(isFixMode)!! }!!
+ usages.minBy { it.node.getLineNumber() }!!
.parentsWithSelf
.find { it.parent == declarationScope }!!
}
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
index 3f7fe4a1c8..e7713a5a6c 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.psiUtil.children
+import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.slf4j.Logger
@@ -656,7 +657,7 @@ fun ASTNode.extractLineOfText(): String {
* Checks node has `@Test` annotation
*/
fun ASTNode.hasTestAnnotation() = findChildByType(MODIFIER_LIST)
- ?.getAllChildrenWithType(ElementType.ANNOTATION_ENTRY)
+ ?.getAllChildrenWithType(ANNOTATION_ENTRY)
?.flatMap { it.findAllNodesWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) }
?.any { it.findLeafWithSpecificType(ElementType.IDENTIFIER)?.text == "Test" }
?: false
@@ -683,11 +684,9 @@ fun isLocatedInTest(filePathParts: List, testAnchors: List) = fi
fun ASTNode.firstLineOfText(suffix: String = "") = text.lines().run { singleOrNull() ?: (first() + suffix) }
/**
- * Return the number of the last line in the file
- *
- * @param isFixMode whether autofix mode is on
+ * Return the number in the file of the last line of this node's text
*/
-fun ASTNode.lastLineNumber(isFixMode: Boolean) = getLineNumber(isFixMode)?.plus(text.count { it == '\n' })
+fun ASTNode.lastLineNumber() = getLineNumber() + text.count { it == '\n' }
/**
* copy-pasted method from ktlint to determine line and column number by offset
@@ -720,38 +719,35 @@ data class ReplacementResult(val oldNodes: List, val newNodes: List otherLineNumber)
}
/**
- * Get line number, where this node's content starts
+ * Get line number, where this node's content starts. To avoid `ArrayIndexOutOfBoundsException`s we check whether node's maximum offset is less than
+ * Document's maximum offset, and calculate line number manually if needed.
*
- * @param isFixMode if fix mode is off, then the text can't be altered and we can use cached values for line numbers
* @return line number or null if it cannot be calculated
*/
-fun ASTNode.getLineNumber(isFixMode: Boolean): Int? =
- if (!isFixMode) {
- lineNumber()
- } else {
- calculateLineNumber()
- }
+fun ASTNode.getLineNumber(): Int =
+ psi.containingFile
+ .viewProvider
+ .document
+ ?.takeIf { it.getLineEndOffset(it.lineCount - 1) >= psi.endOffset }
+ ?.let { lineNumber() }
+ ?: calculateLineNumber()
/**
* This function calculates line number instead of using cached values.
* It should be used when AST could be previously mutated by auto fixers.
*/
@Suppress("LOCAL_VARIABLE_EARLY_DECLARATION")
-private fun ASTNode.calculateLineNumber(): Int? {
+private fun ASTNode.calculateLineNumber(): Int {
var count = 0
// todo use runningFold or something similar when we migrate to apiVersion 1.4
- return parents()
- .last()
+ return getRootNode()
.text
.lineSequence()
// calculate offset for every line end, `+1` for `\n` which is trimmed in `lineSequence`
@@ -759,5 +755,8 @@ private fun ASTNode.calculateLineNumber(): Int? {
count += it.length + 1
count > startOffset
}
- .let { if (it == -1) null else it + 1 }
+ .let {
+ require(it >= 0) { "Cannot calculate line number correctly, node's offset $startOffset is greater than file length ${getRootNode().textLength}" }
+ it + 1
+ }
}
diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Expected.kt
index 7fa743a166..21214634b2 100644
--- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Expected.kt
+++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Expected.kt
@@ -9,5 +9,13 @@ fun readFile(foo: Foo) {
class D {
val x = 0
+
+ /**
+ * @return
+ */
+ fun bar(): Bar {
+ val qux = 42
+ return Bar(qux)
+ }
}
diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Test.kt
index 41ddbf1625..5bc52612b6 100644
--- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Test.kt
+++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Bug1Test.kt
@@ -4,4 +4,6 @@ fun readFile(foo: Foo) {
var bar: Bar
}
-class D {val x = 0}
+class D {val x = 0
+fun bar(): Bar {val qux = 42; return Bar(qux)}
+}