Skip to content

Commit

Permalink
Add new experimental rule no-empty-file #1963 (#2025)
Browse files Browse the repository at this point in the history
* Add new experimental rule `no-empty-file` for all code styles. Kotlin file may not be empty.

Co-authored-by: ao0000 <[email protected]>
  • Loading branch information
paul-dingemans and ao0000 authored May 27, 2023
1 parent 3e56387 commit 1a5790e
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).

### Added

* Add new experimental rule statement-wrapping which ensures function, class, or other blocks statement body doesn't start or end at starting or ending braces of the block ([#1938](https://github.com/pinterest/ktlint/issues/1938))
* Add new experimental rule `no-empty-file` for all code styles. A kotlin (script) file may not be empty ([#1074](https://github.com/pinterest/ktlint/issues/1074))
* Add new experimental rule `statement-wrapping` which ensures function, class, or other blocks statement body doesn't start or end at starting or ending braces of the block ([#1938](https://github.com/pinterest/ktlint/issues/1938))

### Removed

Expand Down
6 changes: 6 additions & 0 deletions documentation/snapshot/docs/rules/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ This rule can also be suppressed with the IntelliJ IDEA inspection suppression `

Rule id: `property-naming` (`standard` rule set)

## No empty file

A kotlin (script) file should not be empty. It needs to contain at least one declaration. Files only contain a package and/or import statements are as of that disallowed.

Rule id: `no-empty-file`

## No single line block comments

A single line block comment should be replaced with an EOL comment when possible.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.NoBlankLinesInChainedMethodCa
import com.pinterest.ktlint.ruleset.standard.rules.NoConsecutiveBlankLinesRule
import com.pinterest.ktlint.ruleset.standard.rules.NoConsecutiveCommentsRule
import com.pinterest.ktlint.ruleset.standard.rules.NoEmptyClassBodyRule
import com.pinterest.ktlint.ruleset.standard.rules.NoEmptyFileRule
import com.pinterest.ktlint.ruleset.standard.rules.NoEmptyFirstLineInClassBodyRule
import com.pinterest.ktlint.ruleset.standard.rules.NoEmptyFirstLineInMethodBlockRule
import com.pinterest.ktlint.ruleset.standard.rules.NoLineBreakAfterElseRule
Expand Down Expand Up @@ -121,6 +122,7 @@ public class StandardRuleSetProvider :
RuleProvider { NoConsecutiveBlankLinesRule() },
RuleProvider { NoConsecutiveCommentsRule() },
RuleProvider { NoEmptyClassBodyRule() },
RuleProvider { NoEmptyFileRule() },
RuleProvider { NoEmptyFirstLineInClassBodyRule() },
RuleProvider { NoEmptyFirstLineInMethodBlockRule() },
RuleProvider { NoLineBreakAfterElseRule() },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType
import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.rule.engine.core.api.children
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment
import com.pinterest.ktlint.rule.engine.core.api.isRoot
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

public class NoEmptyFileRule : StandardRule(id = "no-empty-file"), Rule.Experimental {
override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
node
.takeIf { it.isRoot() }
?.takeIf { it.isEmptyFile() }
?.let { emit(0, "File '${node.getFileName()}' should not be empty", false) }
}

private fun ASTNode.getFileName() =
psi
.containingFile
.virtualFile
.name
.replace("\\", "/") // Ensure compatibility with Windows OS
.substringAfterLast("/")

private fun ASTNode.isEmptyFile(): Boolean =
null ==
children()
.firstOrNull {
!it.isWhiteSpace() &&
!it.isPartOfComment() &&
it.elementType != ElementType.PACKAGE_DIRECTIVE &&
it.elementType != ElementType.IMPORT_LIST &&
!(it.elementType == ElementType.SCRIPT && it.text.isBlank())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
import org.junit.jupiter.api.Test

class NoEmptyFileRuleTest {
private val noEmptyFileRuleAssertThat = assertThatRule { NoEmptyFileRule() }

@Test
fun `Given non-empty kotlin file then ignore the rule for this file`() {
val code =
"""
package foo
fun main() {
println("foo")
}
""".trimIndent()
noEmptyFileRuleAssertThat(code).hasNoLintViolations()
}

@Test
fun `Given an empty kotlin file then do a return lint error`() {
val code = EMPTY_FILE
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kt' should not be empty")
}

@Test
fun `Given an empty kotlin script file then do a return lint error`() {
val code = EMPTY_FILE
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kts")
.asKotlinScript()
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kts' should not be empty")
}

@Test
fun `Given only package statement in kotlin file then do a return lint error`() {
val code =
"""
package foo
""".trimIndent()

noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kt' should not be empty")
}

@Test
fun `Given only import statement in kotlin file then do a return lint error`() {
val code =
"""
import foo.Bar
""".trimIndent()
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kt' should not be empty")
}

@Test
fun `Given only package and import statements in kotlin file then do a return lint error`() {
val code =
"""
package foo
import foo.Bar
""".trimIndent()
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kt' should not be empty")
}

@Test
fun `Given only package, import statements and comments in kotlin file then do a return lint error`() {
val code =
"""
package foo
import foo.Bar
// some comment
/*
* some comment
*/
/**
* some comment
*/
""".trimIndent()
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasLintViolationWithoutAutoCorrect(1, 1, "File 'Tmp.kt' should not be empty")
}

@Test
fun `Given non-empty kotlin file then ignore this file`() {
val code =
"""
package foo
fun main() {
println("foo")
}
""".trimIndent()
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kt")
.hasNoLintViolations()
}

@Test
fun `x x`() {
val code =
"""
plugins {
id("ktlint-publication-library")
}
dependencies {
implementation(projects.ktlintLogger)
implementation(projects.ktlintRuleEngine)
implementation(projects.ktlintCliRulesetCore)
api(libs.assertj)
api(libs.junit5)
api(libs.janino)
api(libs.jimfs)
}
""".trimIndent()
noEmptyFileRuleAssertThat(code)
.asFileWithPath("/some/path/Tmp.kts")
.asKotlinScript()
.hasNoLintViolations()
}

private companion object {
private const val EMPTY_FILE = ""
}
}

0 comments on commit 1a5790e

Please sign in to comment.