diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt index 84217f0c6c..a4b1878719 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt @@ -14,8 +14,13 @@ import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE import com.pinterest.ktlint.core.ast.prevSibling import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.PsiElement +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens.EXTERNAL_KEYWORD import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.psiUtil.allChildren /** * This rule checks if there are any extension functions for the class in the same file, where it is defined @@ -35,11 +40,29 @@ class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule( } } - @Suppress("UnsafeCallOnNullableType") - private fun collectAllClassNames(node: ASTNode): List { - val classes = node.findAllDescendantsWithSpecificType(CLASS) + /** + * Collects all class names in the [file], except those with modifiers from + * the [ignore list][ignoredModifierTypes]. + * + * @throws IllegalArgumentException if [file] is not a + * [FILE][ElementType.FILE] node. + */ + private fun collectAllClassNames(file: ASTNode): List { + require(file.elementType == ElementType.FILE) + + val classes = file.findAllDescendantsWithSpecificType(CLASS) - return classes.map { (it.psi as KtClass).name!! } + return classes.asSequence() + .map(ASTNode::getPsi) + .filterIsInstance(KtClass::class.java) + .filter { clazz -> + clazz.modifierTypes().none { modifierType -> + modifierType in ignoredModifierTypes + } + } + .map(KtClass::getName) + .filterNotNull() + .toList() } private fun fireWarning(node: ASTNode) { @@ -55,5 +78,30 @@ class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule( companion object { const val NAME_ID = "extension-functions-class-file" + + /** + * Types of class/interface modifiers which, if present, don't trigger + * the warning. + * + * @since 1.2.5 + */ + private val ignoredModifierTypes: Array = arrayOf( + EXTERNAL_KEYWORD, + ) + + /** + * @since 1.2.5 + */ + private fun KtClass.modifiers(): Sequence = + modifierList?.allChildren ?: emptySequence() + + /** + * @since 1.2.5 + */ + private fun KtClass.modifierTypes(): Sequence = + modifiers() + .filterIsInstance() + .map(LeafPsiElement::getElementType) + .filterIsInstance() } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt index 4cdff48e26..a2b29affc8 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt @@ -130,4 +130,23 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile """.trimMargin() ) } + + /** + * See [#1586](https://github.com/saveourtool/diktat/issues/1586). + * + * @since 1.2.5 + */ + @Test + @Tag(EXTENSION_FUNCTION_WITH_CLASS) + fun `should raise no warnings for external interfaces`() { + lintMethod( + """ + |internal external interface External { + | val a: Int + |} + | + |fun External.extension() = println(a) + """.trimMargin() + ) + } }