Repository: analysis-dev/diktat (kotlin)

Issue:
Copyright text for copyright rule should be able to contain patters for current year

Pull request:
### What's done:
* HEADER_MISSING_OR_WRONG_COPYRIGHT - if pattern have obsolete year, also update it like in WRONG_COPYRIGHT_YEAR
* If copyright have old first year, but actual last year - allow such cases
* Add more tests
* Enable multi release for log4j2

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt
index 42699002b2..3f6e403cd2 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt
@@ -25,6 +25,7 @@ import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
 import com.pinterest.ktlint.core.ast.ElementType.KDOC
 import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
 import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
+import com.pinterest.ktlint.core.ast.isWhiteSpace
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
@@ -137,16 +138,22 @@ class HeaderCommentRule(configRules: List<RulesConfig>) : DiktatRule(
 
         // need to make sure that copyright year is consistent with current year
         val copyrightText = configuration.getCopyrightText()
+        val copyrightWithCorrectYear = makeCopyrightCorrectYear(copyrightText)
 
-        if (makeCopyrightCorrectYear(copyrightText).isNotEmpty()) {
-            log.warn("Copyright year is not up do date.")
+        if (copyrightWithCorrectYear.isNotEmpty()) {
+            log.warn("Copyright year in your configuration file is not up to date.")
         }
 
         val headerComment = node.findChildBefore(PACKAGE_DIRECTIVE, BLOCK_COMMENT)
         // Depends only on content and doesn't consider years
+        val isCopyrightMatchesPatternExceptFirstYear = isCopyRightTextMatchesPattern(headerComment, copyrightText) ||
+                isCopyRightTextMatchesPattern(headerComment, copyrightWithCorrectYear)
+
         val isWrongCopyright = headerComment != null &&
-                !headerComment.text.flatten().contains(copyrightText.flatten()) &&
-                !headerComment.text.flatten().contains(makeCopyrightCorrectYear(copyrightText).flatten())
+                !isCopyrightMatchesPatternExceptFirstYear &&
+                !isHeaderCommentContainText(headerComment, copyrightText) &&
+                !isHeaderCommentContainText(headerComment, copyrightWithCorrectYear)
+
         val isMissingCopyright = headerComment == null && configuration.isCopyrightMandatory()
         val isCopyrightInsideKdoc = (node.getAllChildrenWithType(KDOC) + node.getAllChildrenWithType(ElementType.EOL_COMMENT))
             .any { commentNode ->
@@ -162,14 +169,20 @@ class HeaderCommentRule(configRules: List<RulesConfig>) : DiktatRule(
                 else -> error("Should never get to this point")
             }
             HEADER_MISSING_OR_WRONG_COPYRIGHT.warnAndFix(configRules, emitWarn, isFixMode, freeText, node.startOffset, node) {
-                headerComment?.let { node.removeChild(it) }
+                headerComment?.let { copyrightNode ->
+                    // remove node clearly, with trailing whitespace
+                    if (copyrightNode.treeNext.isWhiteSpace()) {
+                        node.removeChild(copyrightNode.treeNext)
+                    }
+                    node.removeChild(copyrightNode)
+                }
                 // do not insert empty line before header kdoc
                 val newLines = node.findChildBefore(PACKAGE_DIRECTIVE, KDOC)?.let { "\n" } ?: "\n\n"
                 node.addChild(PsiWhiteSpaceImpl(newLines), node.firstChildNode)
                 node.addChild(LeafPsiElement(BLOCK_COMMENT,
                     """
                         |/*
-                        |${handleMultilineCopyright(copyrightText)}
+                        |${handleMultilineCopyright(copyrightWithCorrectYear.ifEmpty { copyrightText })}
                         |*/
                     """.trimMargin()),
                     node.firstChildNode
@@ -177,16 +190,34 @@ class HeaderCommentRule(configRules: List<RulesConfig>) : DiktatRule(
             }
         }
 
-        val copyrightWithCorrectYear = makeCopyrightCorrectYear(copyrightText)
-
         // Triggers when there is a copyright, but its year is not updated.
-        if (!isMissingCopyright && copyrightWithCorrectYear.isNotEmpty()) {
+        if (!isMissingCopyright && !isWrongCopyright && copyrightWithCorrectYear.isNotEmpty()) {
             WRONG_COPYRIGHT_YEAR.warnAndFix(configRules, emitWarn, isFixMode, "year should be $curYear", node.startOffset, node) {
                 (headerComment as LeafElement).rawReplaceWithText(headerComment.text.replace(copyrightText, copyrightWithCorrectYear))
             }
         }
     }
 
+    private fun isHeaderCommentContainText(headerComment: ASTNode, text: String): Boolean = if (text.isNotEmpty()) headerComment.text.flatten().contains(text.flatten()) else false
+
+    // Check if provided copyright node differs only in the first date from pattern
+    private fun isCopyRightTextMatchesPattern(copyrightNode: ASTNode?, copyrightPattern: String): Boolean {
+        val copyrightText = copyrightNode?.text?.replace("/*", "")?.replace("*/", "")?.replace("*", "")
+
+        val datesInPattern = hyphenRegex.find(copyrightPattern)?.value
+        val datesInCode = copyrightText?.let { hyphenRegex.find(it)?.value }
+
+        if (datesInPattern == null || datesInCode == null) {
+            return false
+        }
+
+        val patternWithoutDates = copyrightPattern.replace(datesInPattern, "").flatten()
+        val textWithoutDates = copyrightText.replace(datesInCode, "").flatten()
+
+        // Text should be equal, first date could be different, second date should be equal to current year
+        return (patternWithoutDates == textWithoutDates) && (datesInCode.substringAfter("-") == curYear.toString())
+    }
+
     /**
      * Deletes all spaces and newlines
      * Used to compare copyrights in yaml and file
diff --git a/diktat-ruleset/pom.xml b/diktat-ruleset/pom.xml
index 6946cf15d1..5cbaba0ffb 100644
--- a/diktat-ruleset/pom.xml
+++ b/diktat-ruleset/pom.xml
@@ -60,6 +60,11 @@
                     </descriptorRefs>
                     <finalName>diktat-${project.version}</finalName>
                     <appendAssemblyId>false</appendAssemblyId>
+                    <archive>
+                        <manifestEntries>
+                            <Multi-Release>true</Multi-Release>
+                        </manifestEntries>
+                    </archive>
                 </configuration>
                 <executions>
                     <execution>
diff --git a/pom.xml b/pom.xml
index f64f264c32..f653ac95d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -201,6 +201,13 @@
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-assembly-plugin</artifactId>
                     <version>3.3.0</version>
+                    <configuration>
+                        <archive>
+                            <manifestEntries>
+                                <Multi-Release>true</Multi-Release>
+                            </manifestEntries>
+                        </archive>
+                    </configuration>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>


Test patch:
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index d7311cbb4d..55d0f42fe5 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -52,6 +52,7 @@ jobs:
     strategy:
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
+        java-version: [8, 11]
 
     steps:
       - uses: actions/checkout@v2.4.0
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt
index e02c7a386b..c695aaf32a 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt
@@ -83,6 +83,39 @@ class HeaderCommentRuleFixTest : FixTestBase(
         )
     }
 
+    @Test
+    @Tag(WRONG_COPYRIGHT_YEAR)
+    fun `copyright invalid year should be auto-corrected 2`() {
+        fixAndCompare("CopyrightDifferentYearExpected2.kt", "CopyrightDifferentYearTest2.kt",
+            listOf(RulesConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT.name, true, mapOf(
+                "isCopyrightMandatory" to "true",
+                "copyrightText" to "Copyright (c) My Company., Ltd. 2021. All rights reserved."
+            )))
+        )
+    }
+
+    @Test
+    @Tag(WRONG_COPYRIGHT_YEAR)
+    fun `copyright invalid pattern, but valid in code`() {
+        fixAndCompare("CopyrightInvalidPatternValidCodeExpected.kt", "CopyrightInvalidPatternValidCodeTest.kt",
+            listOf(RulesConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT.name, true, mapOf(
+                "isCopyrightMandatory" to "true",
+                "copyrightText" to "Copyright (c) My Company., Ltd. 2012-2019. All rights reserved."
+            )))
+        )
+    }
+
+    @Test
+    @Tag(WRONG_COPYRIGHT_YEAR)
+    fun `copyright invalid pattern, update actual year in it and auto-correct`() {
+        fixAndCompare("CopyrightAbsentInvalidPatternExpected.kt", "CopyrightAbsentInvalidPatternTest.kt",
+            listOf(RulesConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT.name, true, mapOf(
+                "isCopyrightMandatory" to "true",
+                "copyrightText" to "Copyright (c) My Company., Ltd. 2012-2019. All rights reserved."
+            )))
+        )
+    }
+
     @Test
     @Tag(WRONG_COPYRIGHT_YEAR)
     fun `should not raise npe`() {
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt
index 6f35d6fa1c..3693eb93e3 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt
@@ -261,6 +261,52 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) {
         )
     }
 
+    @Test
+    @Tag(WarningNames.WRONG_COPYRIGHT_YEAR)
+    fun `copyright year good 5`() {
+        lintMethod(
+            """
+                /*
+                   Copyright (c) My Company, Ltd. 2021-2021. All rights reserved.
+                 */
+                /**
+                 * Very useful description, why this file has two classes
+                 * foo bar baz
+                 */
+
+                package org.cqfn.diktat.example
+
+                class Example1 { }
+
+                class Example2 { }
+            """.trimIndent(),
+            rulesConfigList = rulesConfigList
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.WRONG_COPYRIGHT_YEAR)
+    fun `copyright year good 6`() {
+        lintMethod(
+            """
+                /*
+                 *  Copyright (c) My Company, Ltd. 2002-2021. All rights reserved.
+                 */
+                /**
+                 * Very useful description, why this file has two classes
+                 * foo bar baz
+                 */
+
+                package org.cqfn.diktat.example
+
+                class Example1 { }
+
+                class Example2 { }
+            """.trimIndent(),
+            rulesConfigList = rulesConfigList
+        )
+    }
+
     @Test
     @Tag(WarningNames.WRONG_COPYRIGHT_YEAR)
     fun `copyright year bad`() {
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/AutoCopyrightApplyPatternTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/AutoCopyrightApplyPatternTest.kt
index 993117dd44..edc24ea458 100644
--- a/diktat-rules/src/test/resources/test/paragraph2/header/AutoCopyrightApplyPatternTest.kt
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/AutoCopyrightApplyPatternTest.kt
@@ -1,3 +1,3 @@
 package test.paragraph2.header
 
-class Example
\ No newline at end of file
+class Example
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternExpected.kt
new file mode 100644
index 0000000000..fe33eb4e52
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternExpected.kt
@@ -0,0 +1,19 @@
+/*
+    Copyright (c) My Company., Ltd. 2012-2021. All rights reserved.
+*/
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternTest.kt
new file mode 100644
index 0000000000..a0e91a476b
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightAbsentInvalidPatternTest.kt
@@ -0,0 +1,16 @@
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected2.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected2.kt
new file mode 100644
index 0000000000..691eef9c9f
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected2.kt
@@ -0,0 +1,19 @@
+/*
+    Copyright (c) My Company., Ltd. 2021. All rights reserved.
+*/
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest2.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest2.kt
new file mode 100644
index 0000000000..5a8264ca59
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest2.kt
@@ -0,0 +1,19 @@
+/*
+    Copyright (c) My Company., Ltd. 2003. All rights reserved.
+*/
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeExpected.kt
new file mode 100644
index 0000000000..b11cc5702f
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeExpected.kt
@@ -0,0 +1,19 @@
+/*
+    Copyright (c) My Company., Ltd. 2021-2021. All rights reserved.
+*/
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeTest.kt
new file mode 100644
index 0000000000..b11cc5702f
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightInvalidPatternValidCodeTest.kt
@@ -0,0 +1,19 @@
+/*
+    Copyright (c) My Company., Ltd. 2021-2021. All rights reserved.
+*/
+/**
+ * Lorem ipsum
+ * dolor sit amet
+ */
+
+package test.paragraph2.header
+
+import org.cqfn.diktat.example.A
+import org.cqfn.diktat.example.B
+
+/**
+ * Example class
+ */
+class Example {
+    lateinit var map: Map<A, B>
+}