Skip to content

Commit

Permalink
Accept filename which is based on the fully qualified receiver class …
Browse files Browse the repository at this point in the history
…plus name of an extension function when it is the only top level declaration in a file

Closes pinterest#1521
  • Loading branch information
paul-dingemans committed Jun 27, 2022
1 parent a4358e7 commit 31cad30
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ public class FilenameRule : Rule(
topLevelDeclaration
.identifier
.toPascalCase()
fileName.shouldMatchFileName(pascalCaseIdentifier, emit)
val pascalCaseQualifier =
topLevelDeclaration
.qualifier
?.toPascalCase()
fileName.shouldMatchFileName(pascalCaseQualifier, pascalCaseIdentifier, emit)
} else {
fileName.shouldMatchPascalCase(emit)
}
Expand Down Expand Up @@ -139,15 +143,23 @@ public class FilenameRule : Rule(
replaceFirstChar { it.uppercaseChar() }

private fun String.shouldMatchFileName(
qualifier: String?,
filename: String,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (this != filename) {
emit(
0,
"File '$this.kt' contains a single top level declaration and should be named '$filename.kt'",
false
)
when {
qualifier == null && this != filename ->
emit(
0,
"File '$this.kt' contains a single top level declaration and should be named '$filename.kt'",
false
)
qualifier != null && this != "$qualifier$filename" ->
emit(
0,
"File '$this.kt' contains a single top level declaration and should be named '$filename.kt' or '$qualifier$filename.kt'",
false
)
}
}

Expand All @@ -161,14 +173,26 @@ public class FilenameRule : Rule(

private data class TopLevelDeclaration(
val elementType: IElementType,
val identifier: String
val identifier: String,
val qualifier: String? = null
)

private fun ASTNode.toTopLevelDeclaration(): TopLevelDeclaration? =
findChildByType(IDENTIFIER)
?.text
?.removeSurrounding("`")
?.let { TopLevelDeclaration(elementType, it) }
?.let {
if (this.elementType == FUN) {
val typeReference =
this
.findChildByType(TYPE_REFERENCE)
?.text
?.replace(".", "")
TopLevelDeclaration(elementType, it, typeReference)
} else {
TopLevelDeclaration(elementType, it)
}
}

private companion object {
val pascalCaseRegEx = Regex("""^[A-Z][A-Za-z\d]*$""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ class FilenameRuleTest {
strings = [
"object Foo",
"typealias Foo = String",
"fun String.foo() = {}",
"fun foo() = {}"
]
)
Expand All @@ -94,6 +93,29 @@ class FilenameRuleTest {
.hasLintViolationWithoutAutoCorrect(1, 1, "File '$UNEXPECTED_FILE_NAME' contains a single top level declaration and should be named 'Foo.kt'")
}

@Test
fun `Issue 1521 - Given a file containing a single toplevel extension function on a standard receiver class defined in another file then allow the file to be named based upon the fully qualified receiver suffixed with the function name`() {
val code =
"""
fun String.foo() {}
""".trimIndent()
fileNameRuleAssertThat(code)
.asFileWithPath(UNEXPECTED_FILE_NAME)
.hasLintViolationWithoutAutoCorrect(1, 1, "File '$UNEXPECTED_FILE_NAME' contains a single top level declaration and should be named 'Foo.kt' or 'StringFoo.kt'")
}

@Test
fun `Issue 1521 - Given a file containing a single toplevel extension function on a custom receiver class defined in another file then allow the file to be named based upon the fully qualified receiver suffixed with the function name`() {
val code =
"""
fun Foo.Status.image() {}
""".trimIndent()
fileNameRuleAssertThat(code)
.asFileWithPath(UNEXPECTED_FILE_NAME)
.hasLintViolationWithoutAutoCorrect(1, 1, "File '$UNEXPECTED_FILE_NAME' contains a single top level declaration and should be named 'Image.kt' or 'FooStatusImage.kt'")
}


@ParameterizedTest(name = "Top level declaration: {0}")
@ValueSource(
strings = [
Expand Down

0 comments on commit 31cad30

Please sign in to comment.