diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt index 92663333d4..dd1729f659 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt @@ -1,6 +1,5 @@ package org.cqfn.diktat.ruleset.rules -import com.pinterest.ktlint.core.KtLint.calculateLineColByOffset import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.BLOCK @@ -40,6 +39,7 @@ import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.LONG_LINE import org.cqfn.diktat.ruleset.utils.KotlinParser import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace +import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement @@ -101,7 +101,7 @@ class LineLength(private val configRules: List) : Rule("line-length val newNode = node.psi.findElementAt(offset + configuration.lineLength.toInt())!!.node if ((newNode.elementType != KDOC_TEXT && newNode.elementType != KDOC_MARKDOWN_INLINE_LINK) || !isKDocValid(newNode)) { - positionByOffset = calculateLineColByOffset(node.treeParent.text) + positionByOffset = node.treeParent.calculateLineColByOffset() val fixableType = isFixable(newNode, configuration) LONG_LINE.warnAndFix(configRules, emitWarn, isFixMode, "max line length ${configuration.lineLength}, but was ${it.length}", @@ -394,12 +394,12 @@ class LineLength(private val configRules: List) : Rule("line-length } sealed class LongLineFixableCases { - object None: LongLineFixableCases() - class Comment(val node: ASTNode, val indexLastSpace: Int): LongLineFixableCases() - class Condition(val maximumLineLength: Long, val leftOffset: Int, val binList: MutableList): LongLineFixableCases() - class Fun(val node: ASTNode): LongLineFixableCases() - class Property(val node: ASTNode, val indexLastSpace: Int, val text: String): LongLineFixableCases() + object None : LongLineFixableCases() + class Comment(val node: ASTNode, val indexLastSpace: Int) : LongLineFixableCases() + class Condition(val maximumLineLength: Long, val leftOffset: Int, val binList: MutableList) : LongLineFixableCases() + class Fun(val node: ASTNode) : LongLineFixableCases() + class Property(val node: ASTNode, val indexLastSpace: Int, val text: String) : LongLineFixableCases() class PropertyWithTemplateEntry(val node: ASTNode, val maximumLineLength: Long, - val leftOffset: Int, val binList: MutableList): LongLineFixableCases() + val leftOffset: Int, val binList: MutableList) : LongLineFixableCases() } } 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 e9c7dc1485..92ada383bf 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 @@ -2,7 +2,6 @@ package org.cqfn.diktat.ruleset.utils import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.ast.ElementType -import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.core.ast.ElementType.CONST_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.FILE @@ -515,6 +514,10 @@ fun ASTNode.firstLineOfText(suffix: String = "") = text.lines().run { singleOrNu fun ASTNode.lastLineNumber(isFixMode: Boolean) = getLineNumber(isFixMode)?.plus(text.count { it == '\n' }) +fun ASTNode.calculateLineColByOffset(): (offset: Int) -> Pair { + return buildPositionInTextLocator(text) +} + fun ASTNode.getFileName(): String = getUserData(KtLint.FILE_PATH_USER_DATA_KEY).let { require(it != null) { "File path is not present in user data" } it diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt new file mode 100644 index 0000000000..0b126174c4 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt @@ -0,0 +1,71 @@ +package org.cqfn.diktat.ruleset.utils + +//fixme this code is copy-pasted from ktlint. Change it +internal typealias LineAndColumn = Pair + +/** + * Calculate position in text - line and column based on offset from the text start. + */ +internal fun buildPositionInTextLocator(text: String): (offset: Int) -> LineAndColumn { + val textLength = text.length + val identifierArray = ArrayList() + var endOfLineIndex = -1 + + do { + identifierArray.add(endOfLineIndex + 1) + endOfLineIndex = text.indexOf('\n', endOfLineIndex + 1) + } while (endOfLineIndex != -1) + + identifierArray.add(textLength + if (identifierArray.last() == textLength) 1 else 0) + + val segmentTree = SegmentTree(identifierArray.toIntArray()) + + return { offset -> + val line = segmentTree.indexOf(offset) + if (line != -1) { + val column = offset - segmentTree.get(line).left + line + 1 to column + 1 + } else { + 1 to 1 + } + } +} + +private class SegmentTree(sortedArray: IntArray) { + + init { + require(sortedArray.size > 1) { "At least two data points are required" } + sortedArray.reduce { current, next -> + require(current <= next) { "Data points are not sorted (ASC)" } + next + } + } + + private val segments: List = sortedArray + .dropLast(1) + .mapIndexed { index: Int, element: Int -> + Segment(element, sortedArray[index + 1] - 1) + } + + fun get(index: Int): Segment = segments[index] + + fun indexOf(index: Int): Int = binarySearch(index, 0, segments.size - 1) + + private fun binarySearch(compareElement: Int, left: Int, right: Int): Int = when { + left > right -> -1 + else -> { + val index = left + (right - left) / 2 + val midElement = segments[index] + if (compareElement < midElement.left) { + binarySearch(compareElement, left, index - 1) + } else { + if (midElement.right < compareElement) binarySearch(compareElement, index + 1, right) else index + } + } + } +} + +private data class Segment( + val left: Int, + val right: Int +)