From 96dfd85d3c0877796d48f362c79362bcc4b7c89c Mon Sep 17 00:00:00 2001 From: Andrey Shcheglov Date: Thu, 11 Aug 2022 15:28:38 +0300 Subject: [PATCH] `WRONG_INDENTATION`: add the test which reproduces the regression ### What's done: * See #1490. --- .../spaces/ExpectedIndentationError.kt | 48 ++++++-- .../chapter3/spaces/IndentationRuleTest.kt | 16 +++ .../chapter3/spaces/junit/IndentationTest.kt | 12 +- ...ndentationTestInvocationContextProvider.kt | 103 ++++++++++++------ .../junit/IndentationTestWarnExtension.kt | 14 ++- .../IndentationTestWarnInvocationContext.kt | 27 +---- .../diktat/ruleset/junit/ExpectedLintError.kt | 9 ++ .../junit/RuleInvocationContextProvider.kt | 10 +- .../org/cqfn/diktat/util/LintTestBase.kt | 14 ++- 9 files changed, 171 insertions(+), 82 deletions(-) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/ExpectedIndentationError.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/ExpectedIndentationError.kt index 811ac11e22..b90bb5dd29 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/ExpectedIndentationError.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/ExpectedIndentationError.kt @@ -1,18 +1,50 @@ package org.cqfn.diktat.ruleset.chapter3.spaces +import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.junit.ExpectedLintError +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.NAME_ID +import com.pinterest.ktlint.core.LintError /** * The expected indentation error (extracted from annotated code fragments). * * @property line the line number (1-based). * @property column the column number (1-based). - * @property expectedIndent the expected indentation level (in space characters). - * @property actualIndent the actual indentation level (in space characters). */ -data class ExpectedIndentationError( - override val line: Int, - override val column: Int = 1, - val expectedIndent: Int, - val actualIndent: Int -) : ExpectedLintError +class ExpectedIndentationError(override val line: Int, + override val column: Int = 1, + private val message: String +) : ExpectedLintError { + /** + * @param line the line number (1-based). + * @param column the column number (1-based). + * @param expectedIndent the expected indentation level (in space characters). + * @param actualIndent the actual indentation level (in space characters). + */ + constructor(line: Int, + column: Int = 1, + expectedIndent: Int, + actualIndent: Int + ) : this( + line, + column, + warnText(expectedIndent)(actualIndent) + ) + + override fun asLintError(): LintError = + LintError( + line, + column, + "$DIKTAT_RULE_SET_ID:$NAME_ID", + message, + true) + + private companion object { + private val warnText: (Int) -> (Int) -> String = { expectedIndent -> + { actualIndent -> + "${WRONG_INDENTATION.warnText()} expected $expectedIndent but was $actualIndent" + } + } + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTest.kt index 7b7f1605d5..5c8fb9ec64 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTest.kt @@ -255,6 +255,22 @@ class IndentationRuleTest { fun `case 10`() = Unit } + @Nested + @TestMethodOrder(NaturalDisplayName::class) + inner class `String templates` { + /** + * See [#1490](https://github.com/saveourtool/diktat/issues/1490). + */ + @IndentationTest(IndentedSourceCode( + """ + val value = f( + "text ${'$'}variable text".isEmpty() // diktat:WRONG_INDENTATION[message = only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): the same number of indents to the opening and closing quotes was expected] + ) + """), + singleConfiguration = true) + fun `issue #1490`() = Unit + } + /** * See [#1347](https://github.com/saveourtool/diktat/issues/1347). */ diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTest.kt index a5b1434306..3c34aff3a2 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTest.kt @@ -9,8 +9,11 @@ import kotlin.annotation.AnnotationTarget.FUNCTION /** * @property includeWarnTests whether unit tests for the "warn" mode should also - * be generated. If `false`, only fix mode tests get generated. The default is - * `true`. + * be generated. If `false`, the code is allowed to have no expected-error + * annotations, and only fix mode tests get generated. The default is `true`. + * @property singleConfiguration whether only a single code fragment is to be + * analysed. If `true`, the value of [second] is ignored, resulting in fewer + * unit tests being generated. The default is `false`. */ @Target(FUNCTION) @Retention(RUNTIME) @@ -20,6 +23,7 @@ import kotlin.annotation.AnnotationTarget.FUNCTION @Tag(WRONG_INDENTATION) annotation class IndentationTest( val first: IndentedSourceCode, - val second: IndentedSourceCode, - val includeWarnTests: Boolean = true + val second: IndentedSourceCode = IndentedSourceCode(""), + val includeWarnTests: Boolean = true, + val singleConfiguration: Boolean = false, ) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestInvocationContextProvider.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestInvocationContextProvider.kt index 38476f2bb9..30bf592b51 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestInvocationContextProvider.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestInvocationContextProvider.kt @@ -34,6 +34,14 @@ class IndentationTestInvocationContextProvider : RuleInvocationContextProvider ): ExpectedIndentationError { + val message = properties[MESSAGE] + @Suppress("AVOID_NULL_CHECKS") + if (message != null) { + return ExpectedIndentationError( + line = lineNumber, + message = "[$WRONG_INDENTATION] $message") + } + val expectedIndent = properties.expectedIndent() val actualIndent = line.leadingSpaceCount() @@ -58,57 +66,81 @@ class IndentationTestInvocationContextProvider : RuleInvocationContextProvider( - IndentationTestFixInvocationContext(customConfig0, actualCode = code0), - IndentationTestFixInvocationContext(customConfig1, actualCode = code1), - IndentationTestFixInvocationContext(customConfig1, actualCode = code0, expectedCode = code1), - IndentationTestFixInvocationContext(customConfig0, actualCode = code1, expectedCode = code0), - ).let { fixTests -> - when { - includeWarnTests -> concat(fixTests, Stream.of( - IndentationTestWarnInvocationContext(customConfig0, actualCode = code0), - IndentationTestWarnInvocationContext(customConfig1, actualCode = code1), - IndentationTestWarnInvocationContext(customConfig1, actualCode = code0, expectedErrors0), - IndentationTestWarnInvocationContext(customConfig0, actualCode = code1, expectedErrors1), - )) - - else -> fixTests + var contexts: Stream = Stream.of( + IndentationTestFixInvocationContext(customConfig0, actualCode = code0) + ) + + if (includeWarnTests) { + /*- + * In a double-configuration mode (the default), when the code is + * checked against its own configuration, the actual list of errors + * is expected to be empty (it's only used when the code is checked + * against the opposite configuration. + * + * In a single-configuration mode, the opposite configuration is + * empty, so let's allow a non-empty list of expected errors when + * the code is checked against its own configuration. + */ + val expectedErrors = when { + singleConfiguration -> expectedErrors0 + else -> emptyList() + } + contexts += IndentationTestWarnInvocationContext(customConfig0, actualCode = code0, expectedErrors) + } + + if (!singleConfiguration) { + val testInput1 = indentationTest.second.extractTestInput( + supportedTags, + allowEmptyErrors = !includeWarnTests) + val (code1, expectedErrors1, customConfig1) = testInput1 + + assertThat(code0) + .describedAs("Both code fragments are the same") + .isNotEqualTo(code1) + assertThat(customConfig0) + .describedAs("Both custom configs are the same") + .isNotEqualTo(customConfig1) + assertThat(testInput0.effectiveConfig) + .describedAs("Both effective configs are the same") + .isNotEqualTo(testInput1.effectiveConfig) + + contexts += IndentationTestFixInvocationContext(customConfig1, actualCode = code1) + contexts += IndentationTestFixInvocationContext(customConfig1, actualCode = code0, expectedCode = code1) + contexts += IndentationTestFixInvocationContext(customConfig0, actualCode = code1, expectedCode = code0) + + if (includeWarnTests) { + contexts += IndentationTestWarnInvocationContext(customConfig1, actualCode = code1) + contexts += IndentationTestWarnInvocationContext(customConfig1, actualCode = code0, expectedErrors0) + contexts += IndentationTestWarnInvocationContext(customConfig0, actualCode = code1, expectedErrors1) } - }.sorted { left, right -> + } + + return contexts.sorted { left, right -> left.getDisplayName(0).compareTo(right.getDisplayName(0)) } } /** - * @param includeWarnTests whether unit tests for the "warn" mode should also - * be generated. If `false`, only fix mode tests get generated. + * @param allowEmptyErrors whether the list of expected errors is allowed to + * be empty (i.e. the code may contain no known annotations). */ private fun IndentedSourceCode.extractTestInput(supportedTags: List, - includeWarnTests: Boolean): IndentationTestInput { - val (code, expectedErrors) = extractExpectedErrors(code, supportedTags, includeWarnTests) + allowEmptyErrors: Boolean): IndentationTestInput { + val (code, expectedErrors) = extractExpectedErrors(code, supportedTags, allowEmptyErrors) return IndentationTestInput(code, expectedErrors, customConfig()) } private companion object { private const val EXPECTED_INDENT = "expectedIndent" + private const val MESSAGE = "message" @Suppress("WRONG_NEWLINES") // False positives, see #1495. private fun IndentedSourceCode.customConfig(): SortedMap = @@ -140,5 +172,8 @@ class IndentationTestInvocationContextProvider : RuleInvocationContextProvider Stream.plus(value: T): Stream = + concat(this, Stream.of(value)) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnExtension.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnExtension.kt index ab90f576cc..cac665b66a 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnExtension.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnExtension.kt @@ -37,9 +37,17 @@ internal class IndentationTestWarnExtension( val description = NEWLINE + actualCode.annotateWith(actualErrors) + NEWLINE when { - expectedErrors.size == 1 && actualErrors.size == 1 -> assertThat(actualErrors[0]) - .describedAs(description) - .isEqualTo(expectedErrors[0]) + expectedErrors.size == 1 && actualErrors.size == 1 -> { + val actual = actualErrors[0] + val expected = expectedErrors[0] + + assertThat(actual) + .describedAs(description) + .isEqualTo(expected) + assertThat(actual.canBeAutoCorrected) + .describedAs("canBeAutoCorrected") + .isEqualTo(expected.canBeAutoCorrected) + } else -> assertThat(actualErrors) .describedAs(description) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnInvocationContext.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnInvocationContext.kt index 565bf2f89b..ffb81601e0 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnInvocationContext.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/junit/IndentationTestWarnInvocationContext.kt @@ -1,10 +1,7 @@ package org.cqfn.diktat.ruleset.chapter3.spaces.junit -import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.chapter3.spaces.ExpectedIndentationError -import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION -import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule.Companion.NAME_ID -import com.pinterest.ktlint.core.LintError +import org.cqfn.diktat.ruleset.junit.ExpectedLintError import org.intellij.lang.annotations.Language import org.junit.jupiter.api.extension.Extension import java.util.SortedMap @@ -36,25 +33,5 @@ internal class IndentationTestWarnInvocationContext( listOf(IndentationTestWarnExtension( customConfig, actualCode, - expectedErrors.map(asLintError).toTypedArray())) - - private companion object { - private val warnText: (Int) -> (Int) -> String = { expectedIndent -> - { actualIndent -> - "${WRONG_INDENTATION.warnText()} expected $expectedIndent but was $actualIndent" - } - } - - /** - * Converts this instance to a [LintError]. - */ - private val asLintError: ExpectedIndentationError.() -> LintError = { - LintError( - line, - column, - "$DIKTAT_RULE_SET_ID:$NAME_ID", - warnText(expectedIndent)(actualIndent), - true) - } - } + expectedErrors.map(ExpectedLintError::asLintError).toTypedArray())) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/ExpectedLintError.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/ExpectedLintError.kt index 01bacb15e0..2653626092 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/ExpectedLintError.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/ExpectedLintError.kt @@ -1,5 +1,7 @@ package org.cqfn.diktat.ruleset.junit +import com.pinterest.ktlint.core.LintError + /** * The common super-interface for expected lint errors (extracted from the * annotated code). @@ -14,4 +16,11 @@ interface ExpectedLintError { * The column number (1-based). */ val column: Int + + /** + * Converts this instance to a [LintError]. + * + * @return the [LintError] which corresponds to this instance. + */ + fun asLintError(): LintError } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/RuleInvocationContextProvider.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/RuleInvocationContextProvider.kt index 6e25654b59..66fefd7cb4 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/RuleInvocationContextProvider.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/junit/RuleInvocationContextProvider.kt @@ -76,15 +76,15 @@ interface RuleInvocationContextProvider, - includeWarnTests: Boolean + allowEmptyErrors: Boolean ): ExpectedLintErrors { require(supportedTags.isNotEmpty()) { "The list of supported tags is empty" @@ -120,7 +120,7 @@ interface RuleInvocationContextProvider supportedTags[0] else -> "any of $supportedTags" } - if (includeWarnTests) { + if (!allowEmptyErrors) { assertThat(expectedErrors) .describedAs("The code contains no expected-error annotations or an unsupported tag is used (should be $supportedTagsDescription). " + "Please annotate your code or set `includeWarnTests` to `false`:$NEWLINE$filteredCode") @@ -177,7 +177,7 @@ interface RuleInvocationContextProvider$KEY)\h*=\h*(?<$VALUE_GROUP>$VALUE)\h*""") diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt index 917ba5f6d3..ed796dca84 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/LintTestBase.kt @@ -33,9 +33,17 @@ open class LintTestBase(private val ruleSupplier: (rulesConfigList: List assertThat(actualLintErrors[0]) - .describedAs(description) - .isEqualTo(expectedLintErrors[0]) + expectedLintErrors.size == 1 && actualLintErrors.size == 1 -> { + val actual = actualLintErrors[0] + val expected = expectedLintErrors[0] + + assertThat(actual) + .describedAs(description) + .isEqualTo(expected) + assertThat(actual.canBeAutoCorrected) + .describedAs("canBeAutoCorrected") + .isEqualTo(expected.canBeAutoCorrected) + } else -> assertThat(actualLintErrors) .describedAs(description)