diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f05039b3f5..c9ffaf8beb 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -85,6 +85,12 @@ jobs:
cp diktat-rules/src/main/resources/diktat-analysis.yml $(dirname $file)
done
next_snapshot_version=$(printf 'VERSION=${project.version}\n0\n' | mvn help:evaluate | grep '^VERSION' | cut -d= -f2)
+ # Update the version in `examples/maven/pom.xml`
+ # (which is not a part of the multi-module project).
+ for file in examples/maven/pom.xml
+ do
+ sed -i "s|\(\)[[:digit:]]\(\.[[:digit:]]\)\+-SNAPSHOT\(\)|\1${next_snapshot_version}\3|g" "${file}" || echo "File ${file} hasn't been updated (2nd sed pass)"
+ done
echo "version=$next_snapshot_version" > info/buildSrc/gradle.properties
- name: Create pull request
uses: peter-evans/create-pull-request@v4
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt
index 800004bf8d..fb95cec57f 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt
@@ -233,11 +233,12 @@ class CommentsFormatting(configRules: List) : DiktatRule(
}
if (node.elementType == BLOCK_COMMENT &&
- (node
- .text
- .trim('/', '*')
- .takeWhile { it == ' ' }
- .length == configuration.maxSpacesInComment ||
+ (node.isIndentStyleComment() ||
+ node
+ .text
+ .trim('/', '*')
+ .takeWhile { it == ' ' }
+ .length == configuration.maxSpacesInComment ||
node
.text
.trim('/', '*')
@@ -343,6 +344,30 @@ class CommentsFormatting(configRules: List) : DiktatRule(
private fun ASTNode.isChildOfBlockOrClassBody(): Boolean = treeParent.elementType == BLOCK || treeParent.elementType == CLASS_BODY
+ /**
+ * Returns whether this block comment is a `indent`-style comment.
+ *
+ * `indent(1)` is a source code formatting utility for C-like languages.
+ * Historically, source code formatters are permitted to reformat and reflow
+ * the content of block comments, except for those comments which start with
+ * "/*-".
+ *
+ * See also:
+ * - [5.1.1 Block Comments](https://www.oracle.com/java/technologies/javase/codeconventions-comments.html)
+ * - [`indent(1)`](https://man.openbsd.org/indent.1)
+ * - [`style(9)`](https://www.freebsd.org/cgi/man.cgi?query=style&sektion=9)
+ *
+ * @return `true` if this block comment is a `indent`-style comment, `false`
+ * otherwise.
+ */
+ private fun ASTNode.isIndentStyleComment(): Boolean {
+ require(elementType == BLOCK_COMMENT) {
+ "The elementType of this node is $elementType while $BLOCK_COMMENT expected"
+ }
+
+ return text.matches(indentCommentMarker)
+ }
+
/**
* [RuleConfiguration] for [CommentsFormatting] rule
*/
@@ -361,5 +386,10 @@ class CommentsFormatting(configRules: List) : DiktatRule(
private const val APPROPRIATE_COMMENT_SPACES = 1
private const val MAX_SPACES = 1
const val NAME_ID = "kdoc-comments-codeblocks-formatting"
+
+ /**
+ * "/*-" followed by anything but `*` or `-`.
+ */
+ private val indentCommentMarker = Regex("""(?s)^\Q/*-\E[^*-].*?""")
}
}
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 5ab0f78d31..7d8457cdee 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
@@ -213,18 +213,33 @@ internal class DotCallChecker(config: IndentationConfig) : CustomIndentationChec
} || nextNode.isCommentBeforeDot()) && whiteSpace.parents.none { it.node.elementType == LONG_STRING_TEMPLATE_ENTRY }
}
?.let { node ->
+ val indentIncrement = (if (configuration.extendedIndentBeforeDot) 2 else 1) * configuration.indentationSize
if (node.isFromStringTemplate()) {
return CheckResult.from(indentError.actual, indentError.expected +
- (if (configuration.extendedIndentBeforeDot) 2 else 1) * configuration.indentationSize, true)
+ indentIncrement, true)
}
// we need to get indent before the first expression in calls chain
- return CheckResult.from(indentError.actual, (whiteSpace.run {
+ /*-
+ * If the parent indent (the one before a `DOT_QUALIFIED_EXPRESSION`
+ * or a `SAFE_ACCESS_EXPRESSION`) is `null`, then use 0 as the
+ * fallback value.
+ *
+ * If `indentError.expected` is used as a fallback (pre-1.2.2
+ * behaviour), this breaks chained dot-qualified or safe-access
+ * expressions (see #1336), e.g.:
+ *
+ * ```kotlin
+ * val a = first()
+ * .second()
+ * .third()
+ * ```
+ */
+ val parentIndent = whiteSpace.run {
parents.takeWhile { it is KtDotQualifiedExpression || it is KtSafeQualifiedExpression }.lastOrNull() ?: this
- }
- .parentIndent()
- ?: indentError.expected) +
- (if (configuration.extendedIndentBeforeDot) 2 else 1) * configuration.indentationSize, true)
+ }.parentIndent() ?: 0
+ val expectedIndent = parentIndent + indentIncrement
+ return CheckResult.from(indentError.actual, expectedIndent, true)
}
return null
}
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt
index 53d0f3a217..997e1ed293 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt
@@ -1,5 +1,7 @@
package org.cqfn.diktat.ruleset.chapter2
+import org.cqfn.diktat.ruleset.chapter2.CommentsFormattingTest.Companion.indentStyleComment
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.describe
import org.cqfn.diktat.ruleset.rules.chapter2.kdoc.CommentsFormatting
import org.cqfn.diktat.util.FixTestBase
@@ -7,9 +9,13 @@ import generated.WarningNames.COMMENT_WHITE_SPACE
import generated.WarningNames.FIRST_COMMENT_NO_BLANK_LINE
import generated.WarningNames.IF_ELSE_COMMENTS
import generated.WarningNames.WRONG_NEWLINES_AROUND_KDOC
+import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+
+import java.nio.file.Path
class CommentsFormattingFixTest : FixTestBase("test/paragraph2/kdoc/", ::CommentsFormatting) {
@Test
@@ -40,4 +46,16 @@ class CommentsFormattingFixTest : FixTestBase("test/paragraph2/kdoc/", ::Comment
fun `regression - should not insert newline before the first comment in a file`() {
fixAndCompare("NoPackageNoImportExpected.kt", "NoPackageNoImportTest.kt")
}
+
+ /**
+ * `indent(1)` and `style(9)` style comments.
+ */
+ @Test
+ @Tag(COMMENT_WHITE_SPACE)
+ fun `indent-style header in a block comment should be preserved`(@TempDir tempDir: Path) {
+ val lintResult = fixAndCompareContent(indentStyleComment, tempDir = tempDir)
+ assertThat(lintResult.actualContent)
+ .describedAs("lint result for ${indentStyleComment.describe()}")
+ .isEqualTo(lintResult.expectedContent)
+ }
}
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingTest.kt
index 758f87243e..8b87bbc735 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingTest.kt
@@ -8,6 +8,7 @@ import org.cqfn.diktat.util.LintTestBase
import com.pinterest.ktlint.core.LintError
import generated.WarningNames
+import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
@@ -491,4 +492,35 @@ class CommentsFormattingTest : LintTestBase(::CommentsFormatting) {
lintMethod(code)
}
+
+ /**
+ * `indent(1)` and `style(9)` style comments.
+ */
+ @Test
+ @Tag(WarningNames.COMMENT_WHITE_SPACE)
+ fun `indent-style header in a block comment should produce no warnings`() =
+ lintMethod(indentStyleComment)
+
+ internal companion object {
+ @Language("kotlin")
+ internal val indentStyleComment = """
+ |/*-
+ | * This is an indent-style comment, and it's different from regular
+ | * block comments in C-like languages.
+ | *
+ | * Code formatters should not wrap or reflow its content, so you can
+ | * safely insert code fragments:
+ | *
+ | * ```
+ | * int i = 42;
+ | * ```
+ | *
+ | * or ASCII diagrams:
+ | *
+ | * +-----+
+ | * | Box |
+ | * +-----+
+ | */
+ """.trimMargin()
+ }
}
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 2117f95773..9e744752b9 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
@@ -8,6 +8,7 @@ import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.assertNo
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.describe
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.extendedIndent
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.withCustomParameters
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.dotQualifiedExpressions
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctions
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperator
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.parenthesesSurroundedInfixExpressions
@@ -248,4 +249,38 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
rulesConfigList = customConfig.asRulesConfigList())
}
}
+
+ /**
+ * See [#1336](https://github.com/saveourtool/diktat/issues/1336).
+ */
+ @Nested
+ @TestMethodOrder(DisplayName::class)
+ inner class `Dot- and safe-qualified expressions` {
+ @ParameterizedTest(name = "extendedIndentBeforeDot = {0}")
+ @ValueSource(booleans = [false, true])
+ @Tag(WarningNames.WRONG_INDENTATION)
+ fun `should be properly indented`(extendedIndentBeforeDot: Boolean, @TempDir tempDir: Path) {
+ val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+ val customConfig = defaultConfig.withCustomParameters("extendedIndentBeforeDot" to extendedIndentBeforeDot)
+
+ lintMultipleMethods(
+ dotQualifiedExpressions[extendedIndentBeforeDot].assertNotNull(),
+ tempDir = tempDir,
+ rulesConfigList = customConfig.asRulesConfigList())
+ }
+
+ @ParameterizedTest(name = "extendedIndentBeforeDot = {0}")
+ @ValueSource(booleans = [false, true])
+ @Tag(WarningNames.WRONG_INDENTATION)
+ fun `should be reformatted if mis-indented`(extendedIndentBeforeDot: Boolean, @TempDir tempDir: Path) {
+ val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+ val customConfig = defaultConfig.withCustomParameters("extendedIndentBeforeDot" to extendedIndentBeforeDot)
+
+ lintMultipleMethods(
+ actualContent = dotQualifiedExpressions[!extendedIndentBeforeDot].assertNotNull(),
+ expectedContent = dotQualifiedExpressions[extendedIndentBeforeDot].assertNotNull(),
+ tempDir = tempDir,
+ rulesConfigList = customConfig.asRulesConfigList())
+ }
+ }
}
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
index 9cacd40b54..277b96917f 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
@@ -979,6 +979,161 @@ internal object IndentationRuleTestResources {
false to parenthesesSurroundedInfixExpressionsSingleIndent,
true to parenthesesSurroundedInfixExpressionsContinuationIndent)
+ /**
+ * Dot-qualified and safe-access expressions, single indent
+ * (`extendedIndentBeforeDot` is **off**).
+ *
+ * When adding new code fragments to this list, be sure to also add their
+ * counterparts (preserving order) to
+ * [dotQualifiedExpressionsContinuationIndent].
+ *
+ * See [#1336](https://github.com/saveourtool/diktat/issues/1336).
+ *
+ * @see dotQualifiedExpressionsContinuationIndent
+ */
+ @Language("kotlin")
+ val dotQualifiedExpressionsSingleIndent = arrayOf(
+ """
+ |fun LocalDateTime.updateTime(
+ | hour: Int? = null,
+ | minute: Int? = null,
+ | second: Int? = null,
+ |): LocalDateTime = withHour(hour ?: getHour())
+ | .withMinute(minute ?: getMinute())
+ | .withSecond(second ?: getSecond())
+ """.trimMargin(),
+
+ """
+ |fun f() {
+ | first()
+ | .second()
+ | .third()
+ |}
+ """.trimMargin(),
+
+ """
+ |val a = first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |val b = first()
+ | ?.second()
+ | ?.third()
+ """.trimMargin(),
+
+ """
+ |fun f1() = first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |fun f2() =
+ | first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |fun f3() = g(first()
+ | .second()
+ | .third()
+ | .fourth())
+ """.trimMargin(),
+
+ """
+ |fun f4() = g(
+ | first()
+ | .second()
+ | .third()
+ | .fourth())
+ """.trimMargin(),
+ )
+
+ /**
+ * Dot-qualified and safe-access expressions, continuation indent
+ * (`extendedIndentBeforeDot` is **on**).
+ *
+ * When adding new code fragments to this list, be sure to also add their
+ * counterparts (preserving order) to
+ * [dotQualifiedExpressionsSingleIndent].
+ *
+ * See [#1336](https://github.com/saveourtool/diktat/issues/1336).
+ *
+ * @see dotQualifiedExpressionsSingleIndent
+ */
+ @Language("kotlin")
+ val dotQualifiedExpressionsContinuationIndent = arrayOf(
+ """
+ |fun LocalDateTime.updateTime(
+ | hour: Int? = null,
+ | minute: Int? = null,
+ | second: Int? = null,
+ |): LocalDateTime = withHour(hour ?: getHour())
+ | .withMinute(minute ?: getMinute())
+ | .withSecond(second ?: getSecond())
+ """.trimMargin(),
+
+ """
+ |fun f() {
+ | first()
+ | .second()
+ | .third()
+ |}
+ """.trimMargin(),
+
+ """
+ |val a = first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |val b = first()
+ | ?.second()
+ | ?.third()
+ """.trimMargin(),
+
+ """
+ |fun f1() = first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |fun f2() =
+ | first()
+ | .second()
+ | .third()
+ """.trimMargin(),
+
+ """
+ |fun f3() = g(first()
+ | .second()
+ | .third()
+ | .fourth())
+ """.trimMargin(),
+
+ """
+ |fun f4() = g(
+ | first()
+ | .second()
+ | .third()
+ | .fourth())
+ """.trimMargin(),
+ )
+
+ /**
+ * Dot-qualified and safe-access expressions.
+ *
+ * See [#1336](https://github.com/saveourtool/diktat/issues/1336).
+ */
+ val dotQualifiedExpressions = mapOf(
+ false to dotQualifiedExpressionsSingleIndent,
+ true to dotQualifiedExpressionsContinuationIndent)
+
@Language("kotlin")
@Suppress("COMMENT_WHITE_SPACE")
private val ifExpressionsSingleIndent = arrayOf(
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 2892cfde57..fdda961e50 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
@@ -8,6 +8,7 @@ import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.asSequen
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.assertNotNull
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.describe
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.withCustomParameters
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.dotQualifiedExpressions
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctions
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperator
import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.ifExpressions
@@ -15,6 +16,7 @@ import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.pare
import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION
import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule
import org.cqfn.diktat.util.LintTestBase
+
import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import org.assertj.core.api.AbstractSoftAssertions
@@ -29,6 +31,7 @@ import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.opentest4j.MultipleFailuresError
+
import java.util.function.Consumer
@Suppress("LargeClass")
@@ -936,6 +939,48 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
}
}
+ /**
+ * See [#1336](https://github.com/saveourtool/diktat/issues/1336).
+ */
+ @Nested
+ @TestMethodOrder(DisplayName::class)
+ inner class `Dot- and safe-qualified expressions` {
+ @ParameterizedTest(name = "extendedIndentBeforeDot = {0}")
+ @ValueSource(booleans = [false, true])
+ @Tag(WarningNames.WRONG_INDENTATION)
+ fun `should be properly indented`(extendedIndentBeforeDot: Boolean) {
+ val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+ val customConfig = defaultConfig.withCustomParameters("extendedIndentBeforeDot" to extendedIndentBeforeDot)
+
+ lintMultipleMethods(
+ dotQualifiedExpressions[extendedIndentBeforeDot].assertNotNull(),
+ lintErrors = emptyArray(),
+ customConfig.asRulesConfigList()
+ )
+ }
+
+ @ParameterizedTest(name = "extendedIndentBeforeDot = {0}")
+ @ValueSource(booleans = [false, true])
+ @Tag(WarningNames.WRONG_INDENTATION)
+ fun `should be reported if mis-indented`(extendedIndentBeforeDot: Boolean) {
+ val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+ val customConfig = defaultConfig.withCustomParameters("extendedIndentBeforeDot" to extendedIndentBeforeDot)
+
+ assertSoftly { softly ->
+ dotQualifiedExpressions[!extendedIndentBeforeDot].assertNotNull().forEach { code ->
+ softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
+ .describedAs("lint result for ${code.describe()}")
+ .isNotEmpty
+ .hasSizeBetween(2, 3).allSatisfy(Consumer { lintError ->
+ assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId)
+ assertThat(lintError.canBeAutoCorrected).describedAs("canBeAutoCorrected").isTrue
+ assertThat(lintError.detail).matches(warnTextRegex)
+ })
+ }
+ }
+ }
+ }
+
@Nested
@TestMethodOrder(DisplayName::class)
inner class `If expressions` {
diff --git a/examples/maven/pom.xml b/examples/maven/pom.xml
index de15d4fc23..d47946c445 100644
--- a/examples/maven/pom.xml
+++ b/examples/maven/pom.xml
@@ -5,7 +5,7 @@
org.cqfn.diktat
diktat-examples-maven
pom
- 1.2.1-SNAPSHOT
+ 1.2.2-SNAPSHOT
1.2.1
diff --git a/pom.xml b/pom.xml
index 00b18b7101..7f3f124069 100644
--- a/pom.xml
+++ b/pom.xml
@@ -254,7 +254,7 @@
org.apache.maven.plugins
maven-assembly-plugin
- 3.4.0
+ 3.4.1