From dfbe86ce39a714926ca4445cbcc34de96a8e6034 Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Sat, 21 Apr 2018 15:47:22 -0700 Subject: [PATCH 1/6] Create ASTNodeExtensions for common utilities --- .../com/github/shyiko/ktlint/core/ASTNodeExtensions.kt | 8 ++++++++ .../main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt | 5 ----- .../ktlint/ruleset/standard/NoMultipleSpacesRule.kt | 6 +----- .../shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt | 6 +----- .../ktlint/ruleset/standard/ParameterListWrappingRule.kt | 6 +----- 5 files changed, 11 insertions(+), 20 deletions(-) create mode 100644 ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt new file mode 100644 index 0000000000..ed42b46de4 --- /dev/null +++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt @@ -0,0 +1,8 @@ +package com.github.shyiko.ktlint.core + +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { + cb(this) + this.getChildren(null).forEach { it.visit(cb) } +} diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt index fd3b6d3b01..a1d572a29b 100644 --- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt +++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt @@ -487,9 +487,4 @@ object KtLint { } return null } - - private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { - cb(this) - this.getChildren(null).forEach { it.visit(cb) } - } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt index 84287e4c44..8137e92306 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt @@ -1,6 +1,7 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule +import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.com.intellij.psi.PsiComment @@ -78,9 +79,4 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") { } } } - - private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { - cb(this) - this.getChildren(null).forEach { it.visit(cb) } - } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt index 7973044d50..bbe0932d27 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt @@ -1,6 +1,7 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule +import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.kdoc.lexer.KDocTokens @@ -84,9 +85,4 @@ class NoUnusedImportsRule : Rule("no-unused-imports") { } private fun String.isComponentN() = componentNRegex.matches(this) - - private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { - cb(this) - this.getChildren(null).forEach { it.visit(cb) } - } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt index dc75a4ae06..1afd24e7ff 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt @@ -1,6 +1,7 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule +import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.lang.FileASTNode @@ -115,11 +116,6 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { return offsetToTheLeft + 1 } - private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { - cb(this) - this.getChildren(null).forEach { it.visit(cb) } - } - private fun errorMessage(node: ASTNode) = when (node.elementType) { KtStubElementTypes.VALUE_PARAMETER -> From 9dc16deca161a0a9329873b525477c79ad20204f Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Sat, 21 Apr 2018 16:24:29 -0700 Subject: [PATCH 2/6] Add rule to check that a single top level class name matches the file name --- .../standard/ClassNameMatchesFileNameRule.kt | 42 +++++++++++++++++++ .../standard/StandardRuleSetProvider.kt | 1 + 2 files changed, 43 insertions(+) create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt new file mode 100644 index 0000000000..7706c763bf --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt @@ -0,0 +1,42 @@ +package com.github.shyiko.ktlint.ruleset.standard + +import com.github.shyiko.ktlint.core.Rule +import com.github.shyiko.ktlint.core.visit +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes +import java.nio.file.Paths + +/** + * If there is only one top level class in a given file, then its name should match the file's name + */ +class ClassNameMatchesFileNameRule : Rule("class-name-matches-file-name"), Rule.Modifier.RestrictToRoot { + + private class NameWithOffset(val name: String, val offset: Int) + + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + fileName: String, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + val topLevelClassNames = mutableListOf() + + node.visit { + if (it.elementType == KtStubElementTypes.CLASS && it.treeParent.elementType == KtStubElementTypes.FILE) { + val ktClass = it.findChildByType(KtTokens.IDENTIFIER) + ktClass?.let { + val className = it.text + topLevelClassNames.add(NameWithOffset(className, it.startOffset)) + } + } + } + + val name = Paths.get(fileName).fileName.toString().substringBefore(".") + if (topLevelClassNames.size == 1 && name != topLevelClassNames.first().name) { + emit(topLevelClassNames.first().offset, + "Single top level class name [${topLevelClassNames.first().name}] does not match file name", + false) + } + } +} diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt index 17a2ddd8fa..57cf907a10 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt @@ -7,6 +7,7 @@ class StandardRuleSetProvider : RuleSetProvider { override fun get(): RuleSet = RuleSet("standard", ChainWrappingRule(), + ClassNameMatchesFileNameRule(), FinalNewlineRule(), // disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout // ImportOrderingRule(), From 83282c18869b2b4f892ec5849e919fa5145a6fbf Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Sat, 21 Apr 2018 16:48:05 -0700 Subject: [PATCH 3/6] Fix new violations introduced by ClassNameMatchesFileNameRule --- .../com/github/shyiko/ktlint/test/{package.kt => DumpAST.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/{package.kt => DumpAST.kt} (100%) diff --git a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt similarity index 100% rename from ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt rename to ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt From 790710e3d8337250a7d2e76297c2be37d94a3362 Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Sat, 21 Apr 2018 17:02:30 -0700 Subject: [PATCH 4/6] Fix a couple minor issues found in PR --- .../standard/ClassNameMatchesFileNameRule.kt | 4 +- .../ClassNameMatchesFileNameRuleTest.kt | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt index 7706c763bf..d008fe7595 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt @@ -24,8 +24,8 @@ class ClassNameMatchesFileNameRule : Rule("class-name-matches-file-name"), Rule. node.visit { if (it.elementType == KtStubElementTypes.CLASS && it.treeParent.elementType == KtStubElementTypes.FILE) { - val ktClass = it.findChildByType(KtTokens.IDENTIFIER) - ktClass?.let { + val classIdentifier = it.findChildByType(KtTokens.IDENTIFIER) + classIdentifier?.let { val className = it.text topLevelClassNames.add(NameWithOffset(className, it.startOffset)) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt new file mode 100644 index 0000000000..6c93070b8c --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt @@ -0,0 +1,64 @@ +package com.github.shyiko.ktlint.ruleset.standard + +import com.github.shyiko.ktlint.core.LintError +import com.github.shyiko.ktlint.test.lintWithFileName +import org.assertj.core.api.Assertions.assertThat +import org.testng.annotations.Test + +class ClassNameMatchesFileNameRuleTest { + + @Test + fun testMatchingSingleClassName() { + assertThat(ClassNameMatchesFileNameRule().lintWithFileName("/some/path/A.kt", + """ + class A + """.trimIndent() + )).isEmpty() + } + + @Test + fun testNonMatchingSingleClassName() { + assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + """ + class B + """.trimIndent() + )).isEqualTo(listOf( + LintError(1, 7, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + )) + } + + @Test + fun testMultipleTopLevelClasses() { + assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + """ + class B + class C + """.trimIndent() + )).isEmpty() + } + + @Test + fun testMultipleNonTopLevelClasses() { + assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + """ + class B { + class C + class D + } + """.trimIndent() + )).isEqualTo(listOf( + LintError(1, 7, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + )) + } + + @Test + fun testCaseSensitiveMatching() { + assertThat(ClassNameMatchesFileNameRule().lintWithFileName("woohoo.kt", + """ + interface Woohoo + """.trimIndent() + )).isEqualTo(listOf( + LintError(1, 11, "class-name-matches-file-name", "Single top level class name [Woohoo] does not match file name") + )) + } +} From 1025de12f72bef7a1e69aff508097b07d1aec439 Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Sun, 29 Apr 2018 17:05:00 -0700 Subject: [PATCH 5/6] Address feedback on ClassNameMatchesFileNameRule PR - Get filepath from userData - Ignore non .kt files - Change offset to 0 for errror reporting - Removed ASTNodeExtensions, moved visit() method to internal util file --- .../shyiko/ktlint/core/ASTNodeExtensions.kt | 8 ---- .../com/github/shyiko/ktlint/core/KtLint.kt | 5 +++ .../standard/ClassNameMatchesFileNameRule.kt | 22 ++++----- .../ruleset/standard/NoMultipleSpacesRule.kt | 1 - .../ruleset/standard/NoUnusedImportsRule.kt | 1 - .../standard/ParameterListWrappingRule.kt | 1 - .../shyiko/ktlint/ruleset/standard/package.kt | 5 +++ .../ClassNameMatchesFileNameRuleTest.kt | 45 +++++++++++++------ 8 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt deleted file mode 100644 index ed42b46de4..0000000000 --- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ASTNodeExtensions.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.shyiko.ktlint.core - -import org.jetbrains.kotlin.com.intellij.lang.ASTNode - -fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { - cb(this) - this.getChildren(null).forEach { it.visit(cb) } -} diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt index a1d572a29b..fd3b6d3b01 100644 --- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt +++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt @@ -487,4 +487,9 @@ object KtLint { } return null } + + private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { + cb(this) + this.getChildren(null).forEach { it.visit(cb) } + } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt index d008fe7595..5740b86415 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt @@ -1,7 +1,7 @@ package com.github.shyiko.ktlint.ruleset.standard +import com.github.shyiko.ktlint.core.KtLint import com.github.shyiko.ktlint.core.Rule -import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes @@ -12,30 +12,32 @@ import java.nio.file.Paths */ class ClassNameMatchesFileNameRule : Rule("class-name-matches-file-name"), Rule.Modifier.RestrictToRoot { - private class NameWithOffset(val name: String, val offset: Int) - override fun visit( node: ASTNode, autoCorrect: Boolean, - fileName: String, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { - val topLevelClassNames = mutableListOf() + // Ignore all non ".kt" files (including ".kts") + if (node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)?.endsWith(".kt") != true) { + return + } + + val topLevelClassNames = mutableListOf() node.visit { if (it.elementType == KtStubElementTypes.CLASS && it.treeParent.elementType == KtStubElementTypes.FILE) { val classIdentifier = it.findChildByType(KtTokens.IDENTIFIER) classIdentifier?.let { val className = it.text - topLevelClassNames.add(NameWithOffset(className, it.startOffset)) + topLevelClassNames.add(className) } } } - val name = Paths.get(fileName).fileName.toString().substringBefore(".") - if (topLevelClassNames.size == 1 && name != topLevelClassNames.first().name) { - emit(topLevelClassNames.first().offset, - "Single top level class name [${topLevelClassNames.first().name}] does not match file name", + val name = Paths.get(node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)).fileName.toString().substringBefore(".") + if (topLevelClassNames.size == 1 && name != topLevelClassNames.first()) { + emit(0, + "Single top level class name [${topLevelClassNames.first()}] does not match file name", false) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt index 8137e92306..15aa1a2ad5 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt @@ -1,7 +1,6 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule -import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.com.intellij.psi.PsiComment diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt index bbe0932d27..3b0e1b8f4c 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt @@ -1,7 +1,6 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule -import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.kdoc.lexer.KDocTokens diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt index 1afd24e7ff..96c41bdd65 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt @@ -1,7 +1,6 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.Rule -import com.github.shyiko.ktlint.core.visit import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.lang.FileASTNode diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt index 087a1934d1..1d64c651f8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt @@ -1,5 +1,6 @@ package com.github.shyiko.ktlint.ruleset.standard +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.com.intellij.psi.util.PsiTreeUtil @@ -11,6 +12,10 @@ internal fun PsiElement.isPartOf(clazz: KClass) = getNonStrictPa internal fun PsiElement.isPartOfString() = isPartOf(KtStringTemplateEntry::class) internal fun PsiElement.prevLeaf(): LeafPsiElement? = PsiTreeUtil.prevLeaf(this) as LeafPsiElement? internal fun PsiElement.nextLeaf(): LeafPsiElement? = PsiTreeUtil.nextLeaf(this) as LeafPsiElement? +internal fun ASTNode.visit(cb: (node: ASTNode) -> Unit) { + cb(this) + this.getChildren(null).forEach { it.visit(cb) } +} internal fun List.head() = this.subList(0, this.size - 1) internal fun List.tail() = this.subList(1, this.size) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt index 6c93070b8c..675f2d48d0 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt @@ -1,7 +1,7 @@ package com.github.shyiko.ktlint.ruleset.standard import com.github.shyiko.ktlint.core.LintError -import com.github.shyiko.ktlint.test.lintWithFileName +import com.github.shyiko.ktlint.test.lint import org.assertj.core.api.Assertions.assertThat import org.testng.annotations.Test @@ -9,56 +9,73 @@ class ClassNameMatchesFileNameRuleTest { @Test fun testMatchingSingleClassName() { - assertThat(ClassNameMatchesFileNameRule().lintWithFileName("/some/path/A.kt", + assertThat(ClassNameMatchesFileNameRule().lint( """ class A - """.trimIndent() + """.trimIndent(), + fileName("/some/path/A.kt") )).isEmpty() } @Test fun testNonMatchingSingleClassName() { - assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + assertThat(ClassNameMatchesFileNameRule().lint( """ class B - """.trimIndent() + """.trimIndent(), + fileName("A.kt") )).isEqualTo(listOf( - LintError(1, 7, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Single top level class name [B] does not match file name") )) } @Test fun testMultipleTopLevelClasses() { - assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + assertThat(ClassNameMatchesFileNameRule().lint( """ class B class C - """.trimIndent() + """.trimIndent(), + fileName("A.kt") )).isEmpty() } @Test fun testMultipleNonTopLevelClasses() { - assertThat(ClassNameMatchesFileNameRule().lintWithFileName("A.kt", + assertThat(ClassNameMatchesFileNameRule().lint( """ class B { class C class D } - """.trimIndent() + """.trimIndent(), + fileName("A.kt") )).isEqualTo(listOf( - LintError(1, 7, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Single top level class name [B] does not match file name") )) } @Test fun testCaseSensitiveMatching() { - assertThat(ClassNameMatchesFileNameRule().lintWithFileName("woohoo.kt", + assertThat(ClassNameMatchesFileNameRule().lint( """ interface Woohoo - """.trimIndent() + """.trimIndent(), + fileName("woohoo.kt") )).isEqualTo(listOf( - LintError(1, 11, "class-name-matches-file-name", "Single top level class name [Woohoo] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Single top level class name [Woohoo] does not match file name") )) } + + @Test + fun testIgnoreKotlinScriptFiles() { + assertThat(ClassNameMatchesFileNameRule().lint( + """ + class B + """.trimIndent(), + fileName("A.kts") + )).isEmpty() + } + + private fun fileName(fileName: String) = mapOf("file_path" to fileName) } From 1e3823cf77c4be8ab165814a13d2a986c6024da2 Mon Sep 17 00:00:00 2001 From: JelloRanger Date: Tue, 1 May 2018 00:24:58 -0700 Subject: [PATCH 6/6] Address additional feedback on ClassNameMatchesFileNameRule PR - Filter immediate children to get top level classes - Changed error message --- .../standard/ClassNameMatchesFileNameRule.kt | 23 ++++++++----------- .../ClassNameMatchesFileNameRuleTest.kt | 6 ++--- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt index 5740b86415..9622c2bfe2 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt @@ -17,27 +17,22 @@ class ClassNameMatchesFileNameRule : Rule("class-name-matches-file-name"), Rule. autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { + val filePath = node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY) + // Ignore all non ".kt" files (including ".kts") - if (node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)?.endsWith(".kt") != true) { + if (filePath?.endsWith(".kt") != true) { return } - val topLevelClassNames = mutableListOf() - - node.visit { - if (it.elementType == KtStubElementTypes.CLASS && it.treeParent.elementType == KtStubElementTypes.FILE) { - val classIdentifier = it.findChildByType(KtTokens.IDENTIFIER) - classIdentifier?.let { - val className = it.text - topLevelClassNames.add(className) - } - } - } + val topLevelClassNames = node.getChildren(null) + .filter { it.elementType == KtStubElementTypes.CLASS } + .mapNotNull { it.findChildByType(KtTokens.IDENTIFIER)?.text } - val name = Paths.get(node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)).fileName.toString().substringBefore(".") + val name = Paths.get(filePath).fileName.toString().substringBefore(".") if (topLevelClassNames.size == 1 && name != topLevelClassNames.first()) { + val className = topLevelClassNames.first() emit(0, - "Single top level class name [${topLevelClassNames.first()}] does not match file name", + "Class $className should be declared in a file named $className.kt", false) } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt index 675f2d48d0..e57759b8c4 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt @@ -25,7 +25,7 @@ class ClassNameMatchesFileNameRuleTest { """.trimIndent(), fileName("A.kt") )).isEqualTo(listOf( - LintError(1, 1, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Class B should be declared in a file named B.kt") )) } @@ -51,7 +51,7 @@ class ClassNameMatchesFileNameRuleTest { """.trimIndent(), fileName("A.kt") )).isEqualTo(listOf( - LintError(1, 1, "class-name-matches-file-name", "Single top level class name [B] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Class B should be declared in a file named B.kt") )) } @@ -63,7 +63,7 @@ class ClassNameMatchesFileNameRuleTest { """.trimIndent(), fileName("woohoo.kt") )).isEqualTo(listOf( - LintError(1, 1, "class-name-matches-file-name", "Single top level class name [Woohoo] does not match file name") + LintError(1, 1, "class-name-matches-file-name", "Class Woohoo should be declared in a file named Woohoo.kt") )) }