aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStanley Shyiko <stanley.shyiko@gmail.com>2018-05-02 18:43:43 -0700
committerStanley Shyiko <stanley.shyiko@gmail.com>2018-05-02 18:43:43 -0700
commit003fe04879adf5c70ec50be22cb50c033224271f (patch)
tree742089bb3dcedd54da158e235c12f71b4ba40bcc
parent32d3132791d33a1eb11fd317912a15703363e75f (diff)
downloadktlint-003fe04879adf5c70ec50be22cb50c033224271f.tar.gz
Improved filename rule (#194)
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt39
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt63
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt2
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt81
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt129
5 files changed, 193 insertions, 121 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
deleted file mode 100644
index 9622c2bf..00000000
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRule.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.github.shyiko.ktlint.ruleset.standard
-
-import com.github.shyiko.ktlint.core.KtLint
-import com.github.shyiko.ktlint.core.Rule
-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 {
-
- override fun visit(
- node: ASTNode,
- 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 (filePath?.endsWith(".kt") != true) {
- return
- }
-
- val topLevelClassNames = node.getChildren(null)
- .filter { it.elementType == KtStubElementTypes.CLASS }
- .mapNotNull { it.findChildByType(KtTokens.IDENTIFIER)?.text }
-
- val name = Paths.get(filePath).fileName.toString().substringBefore(".")
- if (topLevelClassNames.size == 1 && name != topLevelClassNames.first()) {
- val className = topLevelClassNames.first()
- emit(0,
- "Class $className should be declared in a file named $className.kt",
- false)
- }
- }
-}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
new file mode 100644
index 00000000..8c7169c8
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
@@ -0,0 +1,63 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.KtLint
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+import java.nio.file.Paths
+
+/**
+ * If there is only one top level class/object/typealias in a given file, then its name should match the file's name.
+ */
+class FilenameRule : Rule("filename"), Rule.Modifier.RestrictToRoot {
+
+ private val ignoreSet = setOf<IElementType>(
+ KtStubElementTypes.FILE_ANNOTATION_LIST,
+ KtStubElementTypes.PACKAGE_DIRECTIVE,
+ KtStubElementTypes.IMPORT_LIST,
+ KtTokens.WHITE_SPACE,
+ KtTokens.EOL_COMMENT,
+ KtTokens.BLOCK_COMMENT,
+ KtTokens.DOC_COMMENT,
+ KtTokens.SHEBANG_COMMENT
+ )
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ val filePath = node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)
+ if (filePath?.endsWith(".kt") != true) {
+ // ignore all non ".kt" files (including ".kts")
+ return
+ }
+ var type: String? = null
+ var className: String? = null
+ for (el in node.getChildren(null)) {
+ if (el.elementType == KtStubElementTypes.CLASS ||
+ el.elementType == KtStubElementTypes.OBJECT_DECLARATION ||
+ el.elementType == KtStubElementTypes.TYPEALIAS) {
+ if (className != null) {
+ // more than one class/object/typealias present
+ return
+ }
+ val id = el.findChildByType(KtTokens.IDENTIFIER)
+ type = id?.psi?.getPrevSiblingIgnoringWhitespaceAndComments(false)?.text
+ className = id?.text
+ } else if (!ignoreSet.contains(el.elementType)) {
+ // https://github.com/android/android-ktx/blob/master/src/main/java/androidx/core/graphics/Path.kt case
+ return
+ }
+ }
+ if (className != null) {
+ val name = Paths.get(filePath).fileName.toString().substringBefore(".")
+ if (name != "package" && name != className) {
+ emit(0, "$type $className should be declared in a file named $className.kt", 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 39ba666c..87f06b42 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
@@ -8,7 +8,7 @@ class StandardRuleSetProvider : RuleSetProvider {
override fun get(): RuleSet = RuleSet("standard",
ChainWrappingRule(),
CommentSpacingRule(),
- ClassNameMatchesFileNameRule(),
+ FilenameRule(),
FinalNewlineRule(),
// disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout
// ImportOrderingRule(),
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
deleted file mode 100644
index e57759b8..00000000
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ClassNameMatchesFileNameRuleTest.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.github.shyiko.ktlint.ruleset.standard
-
-import com.github.shyiko.ktlint.core.LintError
-import com.github.shyiko.ktlint.test.lint
-import org.assertj.core.api.Assertions.assertThat
-import org.testng.annotations.Test
-
-class ClassNameMatchesFileNameRuleTest {
-
- @Test
- fun testMatchingSingleClassName() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- class A
- """.trimIndent(),
- fileName("/some/path/A.kt")
- )).isEmpty()
- }
-
- @Test
- fun testNonMatchingSingleClassName() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- class B
- """.trimIndent(),
- fileName("A.kt")
- )).isEqualTo(listOf(
- LintError(1, 1, "class-name-matches-file-name", "Class B should be declared in a file named B.kt")
- ))
- }
-
- @Test
- fun testMultipleTopLevelClasses() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- class B
- class C
- """.trimIndent(),
- fileName("A.kt")
- )).isEmpty()
- }
-
- @Test
- fun testMultipleNonTopLevelClasses() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- class B {
- class C
- class D
- }
- """.trimIndent(),
- fileName("A.kt")
- )).isEqualTo(listOf(
- LintError(1, 1, "class-name-matches-file-name", "Class B should be declared in a file named B.kt")
- ))
- }
-
- @Test
- fun testCaseSensitiveMatching() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- interface Woohoo
- """.trimIndent(),
- fileName("woohoo.kt")
- )).isEqualTo(listOf(
- LintError(1, 1, "class-name-matches-file-name", "Class Woohoo should be declared in a file named Woohoo.kt")
- ))
- }
-
- @Test
- fun testIgnoreKotlinScriptFiles() {
- assertThat(ClassNameMatchesFileNameRule().lint(
- """
- class B
- """.trimIndent(),
- fileName("A.kts")
- )).isEmpty()
- }
-
- private fun fileName(fileName: String) = mapOf("file_path" to fileName)
-}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt
new file mode 100644
index 00000000..84e7ad4a
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt
@@ -0,0 +1,129 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+class FilenameRuleTest {
+
+ @Test
+ fun testMatchingSingleClassName() {
+ for (src in listOf(
+ "class A",
+ "data class A(val v: Int)",
+ "sealed class A",
+ "interface A",
+ "object A",
+ "enum class A {A}",
+ "typealias A = Set<Network.Node>",
+ // >1 declaration case
+ "class B\nfun A.f() {}"
+ )) {
+ assertThat(FilenameRule().lint(
+ """
+ /*
+ * license
+ */
+ @file:JvmName("Foo")
+ package x
+ import y.Z
+ $src
+ //
+ """.trimIndent(),
+ fileName("/some/path/A.kt")
+ )).isEmpty()
+ }
+ }
+
+ @Test
+ fun testNonMatchingSingleClassName() {
+ for (src in mapOf(
+ "class A" to "class",
+ "data class A(val v: Int)" to "class",
+ "sealed class A" to "class",
+ "interface A" to "interface",
+ "object A" to "object",
+ "enum class A {A}" to "class",
+ "typealias A = Set<Network.Node>" to "typealias"
+ )) {
+ assertThat(FilenameRule().lint(
+ """
+ /*
+ * license
+ */
+ @file:JvmName("Foo")
+ package x
+ import y.Z
+ ${src.key}
+ //
+ """.trimIndent(),
+ fileName("/some/path/B.kt")
+ )).isEqualTo(listOf(
+ LintError(1, 1, "filename", "${src.value} A should be declared in a file named A.kt")
+ ))
+ }
+ }
+
+ @Test
+ fun testFileWithoutTopLevelDeclarations() {
+ assertThat(FilenameRule().lint(
+ """
+ /*
+ * copyright
+ */
+ """.trimIndent(),
+ fileName("A.kt")
+ )).isEmpty()
+ }
+
+ @Test
+ fun testMultipleTopLevelClasses() {
+ assertThat(FilenameRule().lint(
+ """
+ class B
+ class C
+ """.trimIndent(),
+ fileName("A.kt")
+ )).isEmpty()
+ }
+
+ @Test
+ fun testMultipleNonTopLevelClasses() {
+ assertThat(FilenameRule().lint(
+ """
+ class B {
+ class C
+ class D
+ }
+ """.trimIndent(),
+ fileName("A.kt")
+ )).isEqualTo(listOf(
+ LintError(1, 1, "filename", "class B should be declared in a file named B.kt")
+ ))
+ }
+
+ @Test
+ fun testCaseSensitiveMatching() {
+ assertThat(FilenameRule().lint(
+ """
+ interface Woohoo
+ """.trimIndent(),
+ fileName("woohoo.kt")
+ )).isEqualTo(listOf(
+ LintError(1, 1, "filename", "interface Woohoo should be declared in a file named Woohoo.kt")
+ ))
+ }
+
+ @Test
+ fun testIgnoreKotlinScriptFiles() {
+ assertThat(FilenameRule().lint(
+ """
+ class B
+ """.trimIndent(),
+ fileName("A.kts")
+ )).isEmpty()
+ }
+
+ private fun fileName(fileName: String) = mapOf("file_path" to fileName)
+}