From 050cf5473b551d22697c2d6aa998cd18c3d1f145 Mon Sep 17 00:00:00 2001 From: Andrey Shcheglov Date: Fri, 17 Jun 2022 16:36:55 +0300 Subject: [PATCH 1/2] [#1347] Clean up the code ### What's done: * Code cleaned up in preparation to settle #1347. * No logic is modified with this change. --- .../rules/chapter3/files/IndentationRule.kt | 239 ++++++++++++++---- .../utils/indentation/IndentationConfig.kt | 2 +- .../chapter3/spaces/IndentationRuleFixTest.kt | 2 +- .../spaces/IndentationRuleTestMixin.kt | 20 ++ .../spaces/IndentationRuleWarnTest.kt | 4 +- 5 files changed, 208 insertions(+), 59 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt index 30bab7fc64..5c638b0234 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt @@ -8,7 +8,14 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.* +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.NEWLINE +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.SPACE +import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getAllLeafsWithSpecificType +import org.cqfn.diktat.ruleset.utils.getFilePath +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.indentBy import org.cqfn.diktat.ruleset.utils.indentation.ArrowInWhenChecker import org.cqfn.diktat.ruleset.utils.indentation.AssignmentOperatorChecker import org.cqfn.diktat.ruleset.utils.indentation.ConditionalsAndLoopsWithoutBracesChecker @@ -20,6 +27,7 @@ import org.cqfn.diktat.ruleset.utils.indentation.IndentationConfig import org.cqfn.diktat.ruleset.utils.indentation.KdocIndentationChecker import org.cqfn.diktat.ruleset.utils.indentation.SuperTypeListChecker import org.cqfn.diktat.ruleset.utils.indentation.ValueParameterListChecker +import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CLOSING_QUOTE @@ -35,6 +43,7 @@ import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_START import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACKET +import com.pinterest.ktlint.core.ast.ElementType.REGULAR_STRING_PART import com.pinterest.ktlint.core.ast.ElementType.RPAR import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.SHORT_STRING_TEMPLATE_ENTRY @@ -58,8 +67,6 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.slf4j.LoggerFactory -import java.lang.StringBuilder - import kotlin.math.abs /** @@ -117,15 +124,15 @@ class IndentationRule(configRules: List) : DiktatRule( val whiteSpaceNodes: MutableList = mutableListOf() node.getAllLeafsWithSpecificType(WHITE_SPACE, whiteSpaceNodes) whiteSpaceNodes - .filter { it.textContains('\t') } + .filter { it.textContains(TAB) } .apply { if (isEmpty()) { return true } } .forEach { - WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "tabs are not allowed for indentation", it.startOffset + it.text.indexOf('\t'), it) { - (it as LeafPsiElement).rawReplaceWithText(it.text.replace("\t", " ".repeat(configuration.indentationSize))) + WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "tabs are not allowed for indentation", it.startOffset + it.text.indexOf(TAB), it) { + (it as LeafPsiElement).rawReplaceWithText(it.text.replace(TAB.toString(), configuration.indentationSize.spaces)) } } return isFixMode // true if we changed all tabs to spaces @@ -137,7 +144,7 @@ class IndentationRule(configRules: List) : DiktatRule( private fun checkNewlineAtEnd(node: ASTNode) { if (configuration.newlineAtEnd) { val lastChild = generateSequence(node) { it.lastChildNode }.last() - val numBlankLinesAfter = lastChild.text.count { it == '\n' } + val numBlankLinesAfter = lastChild.text.count { it == NEWLINE } if (lastChild.elementType != WHITE_SPACE || numBlankLinesAfter != 1) { val warnText = if (lastChild.elementType != WHITE_SPACE || numBlankLinesAfter == 0) "no newline" else "too many blank lines" val fileName = filePath.substringAfterLast(File.separator) @@ -145,10 +152,10 @@ class IndentationRule(configRules: List) : DiktatRule( // however, the text length does not consider it, since it's blank and line appeared only because of `\n` // But ktlint synthetically increase length in aim to have ability to point to this line, so in this case // offset will be `node.textLength`, otherwise we will point to the last symbol, i.e `node.textLength - 1` - val offset = if (lastChild.elementType == WHITE_SPACE && lastChild.textContains('\n')) node.textLength else node.textLength - 1 + val offset = if (lastChild.elementType == WHITE_SPACE && lastChild.textContains(NEWLINE)) node.textLength else node.textLength - 1 WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "$warnText at the end of file $fileName", offset, node) { if (lastChild.elementType != WHITE_SPACE) { - node.addChild(PsiWhiteSpaceImpl("\n"), null) + node.addChild(PsiWhiteSpaceImpl(NEWLINE.toString()), null) } else { lastChild.leaveOnlyOneNewLine() } @@ -166,10 +173,10 @@ class IndentationRule(configRules: List) : DiktatRule( context.checkAndReset(astNode) if (astNode.elementType in increasingTokens) { context.storeIncrementingToken(astNode.elementType) - } else if (astNode.elementType in decreasingTokens && !astNode.treePrev.let { it.elementType == WHITE_SPACE && it.textContains('\n') }) { + } else if (astNode.elementType in decreasingTokens && !astNode.treePrev.let { it.elementType == WHITE_SPACE && it.textContains(NEWLINE) }) { // if decreasing token is after WHITE_SPACE with \n, indents are corrected in visitWhiteSpace method context.dec(astNode.elementType) - } else if (astNode.elementType == WHITE_SPACE && astNode.textContains('\n') && astNode.treeNext != null) { + } else if (astNode.elementType == WHITE_SPACE && astNode.textContains(NEWLINE) && astNode.treeNext != null) { // we check only WHITE_SPACE nodes with newlines, other than the last line in file; correctness of newlines should be checked elsewhere visitWhiteSpace(astNode, context) } @@ -226,7 +233,7 @@ class IndentationRule(configRules: List) : DiktatRule( "expected $expectedIndent but was ${indentError.actual}" } WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, warnText, - whiteSpace.startOffset + whiteSpace.text.lastIndexOf('\n') + 1, whiteSpace.node) { + whiteSpace.startOffset + whiteSpace.text.lastIndexOf(NEWLINE) + 1, whiteSpace.node) { checkStringLiteral(whiteSpace, expectedIndent, indentError.actual) whiteSpace.node.indentBy(expectedIndent) } @@ -255,7 +262,23 @@ class IndentationRule(configRules: List) : DiktatRule( } /** - * If it is triple-quoted string template we need to indent all its parts + * Indents each [entry][LITERAL_STRING_TEMPLATE_ENTRY] in a (potentially, + * multi-line) triple-quoted [string template][STRING_TEMPLATE]. + * + * String templates usually have the following structure: + * + * * `STRING_TEMPLATE` + * * `OPEN_QUOTE` + * * `LITERAL_STRING_TEMPLATE_ENTRY` + * * `REGULAR_STRING_PART` + * * … + * * `LITERAL_STRING_TEMPLATE_ENTRY` + * * `REGULAR_STRING_PART` + * * `CLOSING_QUOTE` + * + * @param stringTemplate the string template. + * @see STRING_TEMPLATE + * @see LITERAL_STRING_TEMPLATE_ENTRY */ @Suppress("LOCAL_VARIABLE_EARLY_DECLARATION") private fun fixStringLiteral( @@ -263,19 +286,42 @@ class IndentationRule(configRules: List) : DiktatRule( expectedIndent: Int, actualIndent: Int ) { - val textIndent = " ".repeat(expectedIndent + INDENT_SIZE) val templateEntries = stringTemplate.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY) - templateEntries.forEach { node -> - if (!node.text.contains("\n")) { - fixFirstTemplateEntries(node, textIndent, actualIndent) + templateEntries.asSequence().filterIndexed { index, templateEntry -> + val text = templateEntry.text + val containsNewline = text.contains(NEWLINE) + + if (containsNewline) { + /* + * In real-life cases observed, whenever a `LITERAL_STRING_TEMPLATE_ENTRY` + * _contains_ a newline character, it is _exactly_ a newline character. + */ + check(text.length == 1) { + val escapedText = text.replace(NEWLINE.toString(), "\\n") + + "A LITERAL_STRING_TEMPLATE_ENTRY at index $index contains extra characters in addition to the newline, " + + "entry: \"$escapedText\", " + + "string template: ${stringTemplate.text}" + } } + + !containsNewline + }.forEach { templateEntry -> + fixFirstTemplateEntries( + templateEntry, + expectedIndent = expectedIndent, + actualIndent = actualIndent) + } + + /* + * This is the last string template fragment which is usually followed + * with the closing `"""` and the `.trimIndent()` or `.trimMargin()` call. + */ + val lastRegularStringPart = templateEntries.last().firstChildNode as LeafPsiElement + lastRegularStringPart.checkRegularStringPart().apply { + val textWithoutIndent = text.trimStart() + rawReplaceWithText(expectedIndent.spaces + textWithoutIndent) } - (templateEntries.last().firstChildNode as LeafPsiElement) - .rawReplaceWithText(" ".repeat(expectedIndent) + templateEntries - .last() - .firstChildNode - .text - .trim()) } private fun getNextDotExpression(node: ASTNode) = if (node.elementType == DOT_QUALIFIED_EXPRESSION) { @@ -285,41 +331,70 @@ class IndentationRule(configRules: List) : DiktatRule( } /** - * This method fixes all lines of string template except the last one - * Also it considers $foo insertions in string + * Modifies [templateEntry] by correcting its indentation level. + * + * This method can be used to fix all [lines][LITERAL_STRING_TEMPLATE_ENTRY] + * of a [string template][STRING_TEMPLATE] except for the last one. + * + * Also, it considers `$foo` insertions in a string. + * + * @param templateEntry a [LITERAL_STRING_TEMPLATE_ENTRY] node. + * @param expectedIndent the expected indent level, as returned by + * [IndentationError.expected]. + * @param actualIndent the actual indent level, as returned by + * [IndentationError.actual]. */ private fun fixFirstTemplateEntries( - node: ASTNode, - textIndent: String, + templateEntry: ASTNode, + expectedIndent: Int, actualIndent: Int ) { - val correctedText = StringBuilder() - // shift of the node depending on its initial string template indent - val nodeStartIndent = if (node.firstChildNode - .text - .takeWhile { it == ' ' } - .count() - actualIndent - INDENT_SIZE > 0) { - node.firstChildNode - .text - .takeWhile { it == ' ' } - .count() - actualIndent - INDENT_SIZE - } else { - 0 + require(templateEntry.elementType == LITERAL_STRING_TEMPLATE_ENTRY) { + "The elementType of this node is ${templateEntry.elementType} while $LITERAL_STRING_TEMPLATE_ENTRY expected" } - val isPrevStringTemplate = node.treePrev.elementType in stringLiteralTokens - val isNextStringTemplate = node.treeNext.elementType in stringLiteralTokens - when { - // if string template is before literal_string - isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(node.firstChildNode.text.trimEnd()) - // if string template is after literal_string - !isPrevStringTemplate && isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trimStart()) - // if there is no string template in literal_string - !isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trim()) - isPrevStringTemplate && isNextStringTemplate -> correctedText.append(node.firstChildNode.text) - node.text.isBlank() -> correctedText.append(textIndent) - else -> {} + + /* + * Quite possible, do nothing in this case. + */ + if (expectedIndent == actualIndent) { + return } - (node.firstChildNode as LeafPsiElement).rawReplaceWithText(correctedText.toString()) + + /* + * A `REGULAR_STRING_PART`. + */ + val regularStringPart = templateEntry.firstChildNode as LeafPsiElement + val regularStringPartText = regularStringPart.checkRegularStringPart().text + val nodeStartIndentOrNegative = regularStringPartText.leadingSpaceCount() - actualIndent - DEFAULT_INDENT_SIZE + // shift of the node depending on its initial string template indent + val nodeStartIndent = nodeStartIndentOrNegative.zeroIfNegative() + + val isPrevStringTemplate = templateEntry.treePrev.elementType in stringLiteralTokens + val isNextStringTemplate = templateEntry.treeNext.elementType in stringLiteralTokens + + val correctedText = when { + isPrevStringTemplate -> when { + isNextStringTemplate -> regularStringPartText + + // if string template is before literal_string + else -> regularStringPartText.trimEnd() + + } + + else -> { + val textIndent = (expectedIndent + DEFAULT_INDENT_SIZE).spaces + + when { + // if string template is after literal_string + isNextStringTemplate -> textIndent + nodeStartIndent.spaces + regularStringPartText.trimStart() + + // if there is no string template in literal_string + else -> textIndent + nodeStartIndent.spaces + regularStringPartText.trim() + } + } + } + + regularStringPart.rawReplaceWithText(correctedText) } private fun ASTNode.getExceptionalIndentInitiator() = treeParent.let { parent -> @@ -395,7 +470,7 @@ class IndentationRule(configRules: List) : DiktatRule( ) = exceptionalIndents.add(ExceptionalIndent(initiator, indent, includeLastChild)) /** - * @param astNode the node which is used to determine whether exceptinoal indents are still active + * @param astNode the node which is used to determine whether exceptional indents are still active * @return boolean result */ fun checkAndReset(astNode: ASTNode) = exceptionalIndents.retainAll { it.isActive(astNode) } @@ -424,12 +499,63 @@ class IndentationRule(configRules: List) : DiktatRule( companion object { private val log = LoggerFactory.getLogger(IndentationRule::class.java) - const val INDENT_SIZE = 4 + + /** + * The default indent size (space characters), configurable via + * `indentationSize`. + */ + const val DEFAULT_INDENT_SIZE = 4 const val NAME_ID = "zct-indentation" + internal const val NEWLINE = '\n' + internal const val SPACE = ' ' + internal const val TAB = '\t' private val increasingTokens = listOf(LPAR, LBRACE, LBRACKET, LONG_TEMPLATE_ENTRY_START) private val decreasingTokens = listOf(RPAR, RBRACE, RBRACKET, LONG_TEMPLATE_ENTRY_END) private val matchingTokens = increasingTokens.zip(decreasingTokens) private val stringLiteralTokens = listOf(SHORT_STRING_TEMPLATE_ENTRY, LONG_STRING_TEMPLATE_ENTRY) + + /** + * @return a string which consists of `N` [space][SPACE] characters. + */ + @Suppress("CUSTOM_GETTERS_SETTERS") + private val Int.spaces: String + get() = + SPACE.toString().repeat(n = this) + + /** + * Checks this [REGULAR_STRING_PART] child of a [LITERAL_STRING_TEMPLATE_ENTRY]. + * + * @return this `REGULAR_STRING_PART` PSI element. + */ + private fun LeafPsiElement.checkRegularStringPart(): LeafPsiElement { + val lastRegularStringPartType = elementType + + check(lastRegularStringPartType == REGULAR_STRING_PART) { + "Unexpected type of the 1st child of the string template entry, " + + "expected: $REGULAR_STRING_PART, " + + "actual: $lastRegularStringPartType, " + + "string template: ${parent.parent.text}" + } + + return this + } + + /** + * @return the number of leading space characters in this string. + */ + private fun String.leadingSpaceCount(): Int = + asSequence() + .takeWhile(::isSpaceCharacter) + .count() + + /** + * @return this very integer if non-negative, 0 otherwise. + */ + private fun Int.zeroIfNegative(): Int = + when { + this > 0 -> this + else -> 0 + } } } @@ -442,4 +568,7 @@ internal data class IndentationError(val expected: Int, val actual: Int) /** * @return indentation of the last line of this string */ -internal fun String.lastIndent() = substringAfterLast('\n').count { it == ' ' } +internal fun String.lastIndent() = substringAfterLast(NEWLINE).count(::isSpaceCharacter) + +private fun isSpaceCharacter(ch: Char): Boolean = + ch == SPACE diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt index 184ac437b5..a8eefe9436 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt @@ -37,5 +37,5 @@ internal class IndentationConfig(config: Map) : RuleConfiguratio /** * The indentation size for each file */ - val indentationSize = config["indentationSize"]?.toInt() ?: IndentationRule.INDENT_SIZE + val indentationSize = config["indentationSize"]?.toInt() ?: IndentationRule.DEFAULT_INDENT_SIZE } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt index 6195a34387..be35a8afaa 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt @@ -241,7 +241,7 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation", if (!lintResult.isSuccessful) { softly.assertThat(lintResult.actualContent) - .describedAs("lint result for \"$actual\"") + .describedAs("lint result for ${actual.describe()}") .isEqualTo(lintResult.expectedContent) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt index b329f68ba8..a68af9f3b5 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt @@ -284,6 +284,26 @@ internal interface IndentationRuleTestMixin { } } + /** + * @return a brief description of this code fragment. + */ + fun String.describe(): String { + val lines = splitToSequence('\n') + + var first: String? = null + + val count = lines.onEachIndexed { index, line -> + if (index == 0) { + first = line + } + }.count() + + return when (count) { + 1 -> "\"$this\"" + else -> "\"$first\u2026\" ($count line(s))" + } + } + /** * @return `true` if known-to-fail unit tests can be muted on the CI server. */ diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt index 7dda79cdaf..66ef42b840 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt @@ -757,7 +757,7 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule), IndentationRule assertSoftly { softly -> expressionBodyFunctionsSingleIndent.forEach { code -> softly.assertThat(lintResult(code, customConfig.asRulesConfigList())) - .describedAs("lint result for \"$code\"") + .describedAs("lint result for ${code.describe()}") .isNotEmpty .hasSizeBetween(1, 3).allSatisfy(Consumer { lintError -> assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId) @@ -782,7 +782,7 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule), IndentationRule assertSoftly { softly -> expressionBodyFunctionsContinuationIndent.forEach { code -> softly.assertThat(lintResult(code, customConfig.asRulesConfigList())) - .describedAs("lint result for \"$code\"") + .describedAs("lint result for ${code.describe()}") .isNotEmpty .hasSizeBetween(1, 3).allSatisfy(Consumer { lintError -> assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId) From 0a3a32f648c6a9935b35ed59df411876dff7f013 Mon Sep 17 00:00:00 2001 From: Andrey Shcheglov Date: Mon, 20 Jun 2022 18:12:41 +0300 Subject: [PATCH 2/2] [#1347] Clean up the code ### What's done: * `IndentationError` moved to a separate source file. * Top-level extensions moved from `IndentationRule.kt` to `StringUtils.kt`. --- .../rules/chapter3/files/IndentationError.kt | 7 ++++++ .../rules/chapter3/files/IndentationRule.kt | 24 ++++--------------- .../cqfn/diktat/ruleset/utils/StringUtils.kt | 18 ++++++++++++++ .../ruleset/utils/indentation/Checkers.kt | 2 +- .../diktat/util/DiktatRuleSetProvider4Test.kt | 12 +++++++--- 5 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt new file mode 100644 index 0000000000..ff00e2b5cd --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt @@ -0,0 +1,7 @@ +package org.cqfn.diktat.ruleset.rules.chapter3.files + +/** + * @property expected expected indentation as a number of spaces + * @property actual actual indentation as a number of spaces + */ +internal data class IndentationError(val expected: Int, val actual: Int) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt index 5c638b0234..1571166f63 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt @@ -8,8 +8,9 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.NEWLINE -import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.SPACE +import org.cqfn.diktat.ruleset.utils.NEWLINE +import org.cqfn.diktat.ruleset.utils.SPACE +import org.cqfn.diktat.ruleset.utils.TAB import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getAllLeafsWithSpecificType @@ -27,6 +28,8 @@ import org.cqfn.diktat.ruleset.utils.indentation.IndentationConfig import org.cqfn.diktat.ruleset.utils.indentation.KdocIndentationChecker import org.cqfn.diktat.ruleset.utils.indentation.SuperTypeListChecker import org.cqfn.diktat.ruleset.utils.indentation.ValueParameterListChecker +import org.cqfn.diktat.ruleset.utils.isSpaceCharacter +import org.cqfn.diktat.ruleset.utils.lastIndent import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION @@ -506,9 +509,6 @@ class IndentationRule(configRules: List) : DiktatRule( */ const val DEFAULT_INDENT_SIZE = 4 const val NAME_ID = "zct-indentation" - internal const val NEWLINE = '\n' - internal const val SPACE = ' ' - internal const val TAB = '\t' private val increasingTokens = listOf(LPAR, LBRACE, LBRACKET, LONG_TEMPLATE_ENTRY_START) private val decreasingTokens = listOf(RPAR, RBRACE, RBRACKET, LONG_TEMPLATE_ENTRY_END) private val matchingTokens = increasingTokens.zip(decreasingTokens) @@ -558,17 +558,3 @@ class IndentationRule(configRules: List) : DiktatRule( } } } - -/** - * @property expected expected indentation as a number of spaces - * @property actual actual indentation as a number of spaces - */ -internal data class IndentationError(val expected: Int, val actual: Int) - -/** - * @return indentation of the last line of this string - */ -internal fun String.lastIndent() = substringAfterLast(NEWLINE).count(::isSpaceCharacter) - -private fun isSpaceCharacter(ch: Char): Boolean = - ch == SPACE diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt index 5550ae8e43..cf1eb0416c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt @@ -6,6 +6,12 @@ package org.cqfn.diktat.ruleset.utils import org.jetbrains.kotlin.lexer.KtTokens +internal const val NEWLINE = '\n' + +internal const val SPACE = ' ' + +internal const val TAB = '\t' + @Suppress("VARIABLE_NAME_INCORRECT_FORMAT") val JAVA = arrayOf("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", @@ -97,3 +103,15 @@ fun String.removePrefix(): String { } return this } + +/** + * @return the indentation of the last line of this string. + */ +internal fun String.lastIndent() = substringAfterLast(NEWLINE).count(::isSpaceCharacter) + +/** + * @param ch the character to examine. + * @return `true` if [ch] is a [SPACE], `false` otherwise. + */ +internal fun isSpaceCharacter(ch: Char): Boolean = + ch == SPACE diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt index 019c0abb06..5ab0f78d31 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt @@ -5,8 +5,8 @@ package org.cqfn.diktat.ruleset.utils.indentation import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationError -import org.cqfn.diktat.ruleset.rules.chapter3.files.lastIndent import org.cqfn.diktat.ruleset.utils.hasParent +import org.cqfn.diktat.ruleset.utils.lastIndent import com.pinterest.ktlint.core.ast.ElementType.ARROW import com.pinterest.ktlint.core.ast.ElementType.AS_KEYWORD diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt index 1007c609ec..8f440abf57 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt @@ -13,6 +13,7 @@ import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider +import org.assertj.core.api.Assertions.assertThat import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -43,6 +44,7 @@ class DiktatRuleSetProviderTest { .map { it.nameWithoutExtension } .filterNot { it in ignoreFile } val rulesName = DiktatRuleSetProvider().get() + .asSequence() .onEachIndexed { index, rule -> if (index != 0) { Assertions.assertTrue( @@ -53,8 +55,9 @@ class DiktatRuleSetProviderTest { } .map { (it as? DiktatRuleSetProvider.OrderedRule)?.rule ?: it } .map { it::class.simpleName!! } - .filter { it != "DummyWarning" } - Assertions.assertEquals(filesName.sorted().toList(), rulesName.sorted()) + .filterNot { it == "DummyWarning" } + .toList() + assertThat(rulesName.sorted()).containsExactlyElementsOf(filesName.sorted().toList()) } @Test @@ -122,6 +125,9 @@ class DiktatRuleSetProviderTest { } companion object { - private val ignoreFile = listOf("DiktatRuleSetProvider", "DiktatRule") + private val ignoreFile = listOf( + "DiktatRuleSetProvider", + "DiktatRule", + "IndentationError") } }