Skip to content

Add blank line check between package and import statements#3266

Open
maheen8q wants to merge 24 commits intopinterest:masterfrom
maheen8q:package-import-blank-line
Open

Add blank line check between package and import statements#3266
maheen8q wants to merge 24 commits intopinterest:masterfrom
maheen8q:package-import-blank-line

Conversation

@maheen8q
Copy link

@maheen8q maheen8q commented Mar 15, 2026

Description

Add blank line check between package and import statements

The Android Kotlin Style Guide requires exactly one blank line between
the package statement and import statements:
https://developer.android.com/kotlin/style-guide#structure

ktlint was not enforcing this rule. This PR extends the existing
PackageNameRule to also check for the missing blank line, with
autocorrect support.

Fixes #3251

Checklist

Before submitting the PR, please check following (checks which are not relevant may be ignored):

  • [✅] Commit message are well written. In addition to a short title, the commit message also explain why a change is made.
  • [✅] At least one commit message contains a reference Closes #<xxx> or Fixes #<xxx> (replace<xxx> with issue number)
  • [✅] Tests are added
  • KtLint format has been applied on source code itself and violations are fixed
  • [✅] PR title is short and clear (it is used as description in the release changelog)
  • [✅] PR description added (background information)

Documentation is updated. See difference between snapshot and release documentation

  • [] Snapshot documentation in case documentation is to be released together with a code change
  • Release documentation in case documentation is related to a released version of ktlint and has to be published as soon as the change is merged to master
paul-dingemans and others added 5 commits March 14, 2026 16:09
Remove deprecated methods which were marked for removal in Ktlint 2.0, and resolve TODO's
If a files starts with the UTF BOM character, it is removed before the file is formatted, and restored after formatting. When the UTF BOM character is not at the start of the file, it should not be removed before formatting, and has to be preserved in the formatted file. The existing test for a file starting with UTF BOM character was not effective because the file was not modified at all. In that case, the unmodified input is returned by ktlint format. Closes pinterest#3220
Copy link
Collaborator

@paul-dingemans paul-dingemans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tnx for giving this a try so quickly. Please try to adhere more with existing code patterns and standards in current ktlint code.

}
}
// https://developer.android.com/kotlin/style-guide#structure
if (node.elementType == IMPORT_DIRECTIVE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking backwards. (or up) in the AST to find the spot where to fix the AST causes problems. You need to find the PACKAGE statement, and then look forwards whether it is followed by a blank line instead.

if (newlineCount < 2) {
val decision = emit(
node.startOffset,
"Missing blank line between package statement and import statements",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the existing patterns in other rules, and use the .ifAutocorrectAllowed extension to fix the violation.

package com.example
import foo.bar
""".trimIndent()
val formattedCode = """
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that you did not apply Ktlint formatting.

}

@Test
fun `passes when blank line exists between package and import`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use naming patterns consisting with rest of ktlint, eg Given ... then (all tests)

}

@Test
fun `autocorrects missing blank line between package and import`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be merged with previous test. There is no need to have separate tests for report and fixing.

Comment on lines +111 to +114
.hasLintViolation(
line = 2,
col = 1,
detail = "Missing blank line between package statement and import statements",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don use named arguments for hasLintViolation

""".trimIndent()
packageNameRuleAssertThat(code)
.hasLintViolation(2, 1, "Missing blank line between package statement and import statements")
.isFixedTo(formattedCode)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a non-existing extension function in the Ktlint AssertThat library. Probably you meant isFormattedAs/

@maheen8q
Copy link
Author

Review comments addressed, please take another look

@maheen8q maheen8q requested a review from paul-dingemans March 15, 2026 19:01
…terest#3267) Fix backwards compatibility with Ktlint 1.x rulesets by readding the RuleAutocorrectApproveHandler, and overriding the methods of the RuleV2 method to fallback to the actual implementation of the Rule (V1). In Ktlint CLI ensure that custom ruleset with RuleSetProviderV3 interface is loaded successful, and a deprecation warning is printed. Fix Ktlint CLI to load the custom rule sets only once by initialized the ruleProviders lazily. Remove the main,kt.test file from the custom-ruleset resource directory as it is not used.
Copy link
Collaborator

@paul-dingemans paul-dingemans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tnx for follow-up. I have some more remarks.

val nextCodeSibling = packageNode.nextCodeSibling20
if (nextCodeSibling?.elementType == IMPORT_DIRECTIVE) {
val newlineCount = nextSibling.text.count { it == '\n' }
if (newlineCount < 2) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style guide describes that there should be exactly 1 blank line between package and imports. So test on on "newLineCount != 2, and add a test:

package foo import bar.* 

Also, tests corner case with comment above imports

package foo // Some comment import bar.* 
}
}

// https://developer.android.com/kotlin/style-guide#structure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hindsight this should be in separate rule. I asked you change extend the package rule if it existed. When I wrote that comment, I did not have access to the repository to check that. Now I see that the new check is added to the PackageNameRule which might be confusing.

}
}

// FIX: Renamed using "Given ... then" pattern
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all "FIX" comments

@maheen8q
Copy link
Author

Hi @paul-dingemans , thanks for the feedback! I've moved the blank line check into a separate PackageImportSpacingRule as suggested. Here's a summary of all changes made:

Moved blank line between package and import check to new PackageImportSpacingRule
Cleaned up PackageNameRule to only handle package naming validation
Used newlineCount != 2 to enforce exactly 1 blank line
Created PackageImportSpacingRuleTest with all test cases including:

No blank line between package and import
Too many blank lines between package and import
Comment above import edge case

Removed blank line tests from PackageNameRuleTest

Please take another look, happy to make any further changes!

@maheen8q maheen8q requested a review from paul-dingemans March 16, 2026 15:17
* https://developer.android.com/kotlin/style-guide#structure
*/
@SinceKtlint("1.x", EXPERIMENTAL)
public class PackageImportSpacingRule : StandardRule("package-import-spacing") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have resulted in a failing test. The rule is only annotated with an EXPERIMENTAL tag, but has not implemented the marker interface Experimental.

Please change to:

@SinceKtlint("2.,0", EXPERIMENTAL) public class PackageImportSpacingRule : StandardRule("package-import-spacing"), RuleV2.Experimental { 
Comment on lines +40 to +42
val VALID_PACKAGE_NAME_REGEXP =
"[a-z_][a-zA-Z\\d_]*(\\.[a-z_][a-zA-Z\\d_]*)*"
.regExIgnoringDiacriticsAndStrokesOnLetters()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert all changes on the PackageName rule.

) {
node
.takeIf { node.elementType == PACKAGE_DIRECTIVE }
.takeIf { it.elementType == PACKAGE_DIRECTIVE }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert all changes on the PackageName rule.

Comment on lines -31 to -33
// The Kotlin inspections (preference > Editor > Inspections) for 'Package naming convention' allows
// underscores as well. But as this has been forbidden by KtLint since early versions, this is still
// prohibited.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert all changes on the PackageName rule.

@maheen8q
Copy link
Author

Hi Paul, thank you for the detailed feedback and the AST explanation!
I have updated the PR with the following changes:
PackageImportSpacingRule:
Implemented the RuleV2.Experimental marker interface as requested.
Updated the @SinceKtlint annotation to ("2.0", EXPERIMENTAL).
Refactored the logic to use the siblings() and takeWhile { it.elementType != IMPORT_LIST } approach. This is much cleaner and handles intermediate comments correctly.
PackageNameRule:
Reverted all changes to this rule, including the regex and the it.elementType refactor, to keep this PR focused and consistent with the existing codebase.
Let me know if there is anything else you'd like me to adjust.

…blank-line' into pr/3266 # Conflicts: #	ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageImportSpacingRule.kt #	ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageImportSpacingRuleTest.kt
@paul-dingemans
Copy link
Collaborator

Hmm, I had to make quite some changes to make it actually work. Did you run the tests on your local machine? Also, documentation was missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants