Repository: analysis-dev/diktat (kotlin)

Issue:
Null pointer exception at COMMENTED_OUT_CODE
```
package io.reflekt.plugin.scripting

import io.reflekt.plugin.analysis.models.Import
import org.reflections.ReflectionUtils
import org.reflections.Reflections
import org.reflections.scanners.SubTypesScanner
import org.reflections.util.ConfigurationBuilder
import java.io.File
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.net.URLClassLoader

/* Checks if specified imports can be found in classpath. */
@Suppress("ConvertSecondaryConstructorToPrimary")
class ImportChecker {
    /* Fully qualified names of public packages, classes, functions and properties in classpath */
    private val allNames = HashSet<String>()

    constructor(classpath: List<File>) {
        if (classpath.isEmpty()) return

        val urls = classpath.map { it.toURI().toURL() }
        val classLoader = URLClassLoader(urls.toTypedArray())
        // Scan each class in classpath
        val reflections = Reflections(
            ConfigurationBuilder()
                .addClassLoader(classLoader)
                .setUrls(urls)
                .setScanners(SubTypesScanner(false))
        )

        // Get all classes (each class is subtype of java.lang.Object)
        reflections.getSubTypesOf(Object::class.java)
            // Only public classes can be imported
            .filter { Modifier.isPublic(it.modifiers) }
            .filter {
                try {
                    // Skip if no canonical name
                    it.canonicalName != null
                 } catch (e: Throwable) {
                    // Skip if canonical name cannot be resolved
                    false
                }
            }
            .forEach { clazz ->
                // All public methods of class
                val methods = try {
                    ReflectionUtils.getAllMethods(clazz, ReflectionUtils.withModifier(Modifier.PUBLIC))
                } catch (e: Throwable) {
                    emptySet<Method>()
                }
                // All public fields of class
                val fields = try {
                    ReflectionUtils.getAllFields(clazz, ReflectionUtils.withModifier(Modifier.PUBLIC))
                } catch (e: Throwable) {
                    emptySet<Field>()
                }

                // Save method and field names with specified prefix
                val addMembers: (prefix: String) -> Unit = { prefix ->
                    allNames.addAll(methods.map { method -> "$prefix.${method.name}" })
                    allNames.addAll(fields.map { field -> "$prefix.${field.name}" })
                }

                // Full package may be imported
                allNames.add(clazz.packageName)
                // Class may be imported
                allNames.add(clazz.canonicalName)
                // Class methods/fields may be imported
                addMembers(clazz.canonicalName)

                // Top-level functions and properties
                if (clazz.enclosingClass == null && clazz.simpleName.endsWith("Kt")) {
                    addMembers(clazz.packageName)
                }

                // Functions and properties in companion object
                if (clazz.enclosingClass != null && clazz.simpleName == "Companion") {
                    addMembers(clazz.enclosingClass.canonicalName)
                }
            }
    }

    // Checks if specified import may be found in the classpath.
    private fun checkImport(import: Import) = import.fqName in allNames

    // Returns imports that may be found in the classpath.
    fun filterImports(imports: List<Import>) = imports.filter { checkImport(it) }
}
```


```
[ERROR] 2021-11-07 13:28:43 Internal error has occurred in rule [comments]. Please make an issue on this bug at https://github.com/cqfn/diKTat/.
                       As a workaround you can disable these inspections in yml config: <[COMMENTED_OUT_CODE]>.
                       Root cause of the problem is in [/mnt/d/reflekt/reflekt-plugin/src/main/kotlin/io/reflekt/plugin/scripting/ImportChecker.kt] file.
java.lang.NullPointerException
        at org.cqfn.diktat.ruleset.rules.chapter2.comments.CommentsRule.checkCommentedCode(CommentsRule.kt:89)
        at org.cqfn.diktat.ruleset.rules.chapter2.comments.CommentsRule.logic(CommentsRule.kt:35)
        at org.cqfn.diktat.ruleset.rules.DiktatRule.visit(DiktatRule.kt:50)
        at com.pinterest.ktlint.core.KtLint$lint$1.invoke(KtLint.kt:81)
        at com.pinterest.ktlint.core.KtLint$lint$1.invoke(KtLint.kt:23)
        at com.pinterest.ktlint.core.KtLint$visitor$2$1.invoke(KtLint.kt:206)
        at com.pinterest.ktlint.core.KtLint$visitor$2$1.invoke(KtLint.kt:23)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:224)
        at com.pinterest.ktlint.core.KtLint$visitor$2.invoke(KtLint.kt:204)
        at com.pinterest.ktlint.core.KtLint$visitor$2.invoke(KtLint.kt:23)
        at com.pinterest.ktlint.core.KtLint.lint(KtLint.kt:74)
```

Pull request:
… as code

### What's done:
* Fix in logic
* Add tests

## Which rule and warnings did you add?

This pull request closes #1091

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt
index a97fb7f4a2..3b85d7b49d 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt
@@ -5,6 +5,7 @@ import org.cqfn.diktat.ruleset.constants.ListOfPairs
 import org.cqfn.diktat.ruleset.constants.Warnings.COMMENTED_OUT_CODE
 import org.cqfn.diktat.ruleset.rules.DiktatRule
 import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType
+import org.cqfn.diktat.ruleset.utils.getFilePath
 
 import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
 import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
@@ -17,6 +18,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
 import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.psi.KtPsiFactory
 import org.jetbrains.kotlin.resolve.ImportPath
+import org.slf4j.LoggerFactory
 
 /**
  * This rule performs checks if there is any commented code.
@@ -47,7 +49,11 @@ class CommentsRule(configRules: List<RulesConfig>) : DiktatRule(
      *    with '// ' with whitespace, while automatic commenting in, e.g., IDEA creates slashes in the beginning of the line
      *
      */
-    @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION")
+    @Suppress(
+        "UnsafeCallOnNullableType",
+        "TOO_LONG_FUNCTION",
+        "AVOID_NULL_CHECKS"
+    )
     private fun checkCommentedCode(node: ASTNode) {
         val errorNodesWithText: ListOfPairs = mutableListOf()
         val eolCommentsOffsetToText = getOffsetsToTextBlocksFromEolComments(node, errorNodesWithText)
@@ -68,10 +74,10 @@ class CommentsRule(configRules: List<RulesConfig>) : DiktatRule(
             .map { (offset, text) -> offset to text.trim() }
             .mapNotNull { (offset, text) ->
                 when {
-                    text.contains(importKeyword) ->
-                        offset to ktPsiFactory.createImportDirective(ImportPath.fromString(text.substringAfter("$importKeyword "))).node
-                    text.contains(packageKeyword) ->
-                        offset to ktPsiFactory.createPackageDirective(FqName(text.substringAfter("$packageKeyword "))).node
+                    text.isPossibleImport() ->
+                        offset to ktPsiFactory.createImportDirective(ImportPath.fromString(text.substringAfter(importKeywordWithSpace, ""))).node
+                    text.trimStart().startsWith(packageKeywordWithSpace) ->
+                        offset to ktPsiFactory.createPackageDirective(FqName(text.substringAfter(packageKeywordWithSpace, ""))).node
                     else -> if (isContainingRequiredPartOfCode(text)) {
                         offset to ktPsiFactory.createBlockCodeFragment(text, null).node
                     } else {
@@ -85,8 +91,23 @@ class CommentsRule(configRules: List<RulesConfig>) : DiktatRule(
                     .isEmpty()
             }
             .forEach { (offset, parsedNode) ->
-                COMMENTED_OUT_CODE.warn(configRules, emitWarn, isFixMode, parsedNode.text.substringBefore("\n").trim(), offset,
-                    errorNodesWithText.find { it.second.trim().contains(parsedNode.text, false) || parsedNode.text.contains(it.second.trim(), false) }?.first!!)
+                val invalidNode = errorNodesWithText.find {
+                    it.second.trim().contains(parsedNode.text, false) ||
+                            parsedNode.text.contains(it.second.trim(), false)
+                }?.first
+                if (invalidNode == null) {
+                    logger.warn("Text [${parsedNode.text}] is a piece of code, created from comment; " +
+                            "but no matching text in comments has been found in the file ${node.getFilePath()}")
+                } else {
+                    COMMENTED_OUT_CODE.warn(
+                        configRules,
+                        emitWarn,
+                        isFixMode,
+                        parsedNode.text.substringBefore("\n").trim(),
+                        offset,
+                        invalidNode
+                    )
+                }
             }
     }
 
@@ -139,11 +160,22 @@ class CommentsRule(configRules: List<RulesConfig>) : DiktatRule(
     private fun isContainingRequiredPartOfCode(text: String): Boolean =
             text.contains("val ", true) || text.contains("var ", true) || text.contains("=", true) || (text.contains("{", true) && text.substringAfter("{").contains("}", true))
 
+    /**
+     * Some weak checks to see if this string can be used as a part of import statement.
+     * Only string surrounded in backticks or a dot-qualified expression (i.e., containing words maybe separated by dots)
+     * are considered for this case.
+     */
+    private fun String.isPossibleImport(): Boolean = trimStart().startsWith(importKeywordWithSpace) &&
+            substringAfter(importKeywordWithSpace, "").run {
+                startsWith('`') && endsWith('`') || !contains(' ')
+            }
+
     @Suppress("MaxLineLength")
     companion object {
-        private val importKeyword = KtTokens.IMPORT_KEYWORD.value
-        private val packageKeyword = KtTokens.PACKAGE_KEYWORD.value
-        private val importOrPackage = """($importKeyword|$packageKeyword) """.toRegex()
+        private val logger = LoggerFactory.getLogger(CommentsRule::class.java)
+        private val importKeywordWithSpace = "${KtTokens.IMPORT_KEYWORD.value} "
+        private val packageKeywordWithSpace = "${KtTokens.PACKAGE_KEYWORD.value} "
+        private val importOrPackage = """($importKeywordWithSpace|$packageKeywordWithSpace)""".toRegex()
         private val classRegex =
                 """^\s*(public|private|protected)*\s*(internal)*\s*(open|data|sealed)*\s*(internal)*\s*(class|object)\s+(\w+)(\(.*\))*(\s*:\s*\w+(\(.*\))*)?\s*\{*$""".toRegex()
         private val importOrPackageRegex = """^(import|package)?\s+([a-zA-Z.])+;*$""".toRegex()


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
index 2be054be4c..e2de27195b 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
@@ -453,4 +453,21 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) {
             """.trimMargin()
         )
     }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should not trigger on 'imports'`() {
+        lintMethod(
+            """
+            /* Checks if specified imports can be found in classpath. */
+            class Example
+            
+            /* Checks if specified import can be found in classpath. */
+            class Example2
+            
+            /* import this and you died. */
+            class Example3
+            """.trimMargin()
+        )
+    }
 }