Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dev.sdkforge.ktlint

import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision
import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes

class NoImportAllowedInPackageRule(private val forbiddenPackageImports: Map<String, Set<String>>) :
Rule(
ruleId = RuleId("$SDKFORGE_RULE_SET_ID:no-import-allowed-in-package"),
about = About(
maintainer = "azazellj",
repositoryUrl = "https://github.com/SDKForge/template-sdk",
issueTrackerUrl = "https://github.com/SDKForge/template-sdk/issues",
),
),
RuleAutocorrectApproveHandler {
private lateinit var filePackage: KtPackageDirective
private val fileImports = mutableSetOf<ASTNode>()

override fun beforeVisitChildNodes(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
) {
when (node.elementType) {
KtStubElementTypes.PACKAGE_DIRECTIVE -> filePackage = node.psi as KtPackageDirective
KtStubElementTypes.IMPORT_DIRECTIVE -> fileImports += node
else -> Unit
}
}

override fun afterVisitChildNodes(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
) {
if (!::filePackage.isInitialized) return
if (node.elementType != KtStubElementTypes.IMPORT_LIST) return

val forbiddenImports = forbiddenPackageImports.entries.find { filePackage.qualifiedName.contains(it.key) }?.value ?: return

for (fileImport in fileImports) {
val importName = (fileImport.psi as KtImportDirective).importPath?.pathStr.orEmpty()

if (forbiddenImports.none { fileImport.text.contains(it) }) continue

emit(fileImport.startOffset, RULE_VIOLATION_MESSAGE.format(importName, filePackage.qualifiedName), false)
}
}

companion object {
private const val RULE_VIOLATION_MESSAGE = "Importing '%1s', it is not allowed in '%2s'"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleSetId
internal const val SDKFORGE_RULE_SET_ID = "sdkforge-ktlint-rules"

class SdkForgeRuleSetProvider : RuleSetProviderV3(RuleSetId(SDKFORGE_RULE_SET_ID)) {

override fun getRuleProviders(): Set<RuleProvider> = setOf(
RuleProvider { NoTemplateImportRule() },
RuleProvider { NoTemplatePackageRule() },
RuleProvider { NoImportAllowedInPackageRule(FORBIDDEN_PACKAGE_IMPORTS) },
)

companion object {
private val FORBIDDEN_PACKAGE_IMPORTS = mapOf(
"domain" to setOf("data", "ui"),
"data" to setOf("ui", "presentation"),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.sdkforge.ktlint

import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
import com.pinterest.ktlint.test.LintViolation
import kotlin.test.Test

class NoImportAllowedInPackageRuleTest {

@Test
fun `no 'data', 'ui' import in 'domain' rule`() {
val assertThatDataInDomainRule = assertThatRule { NoImportAllowedInPackageRule(mapOf("domain" to setOf("data", "ui"))) }
val code =
"""
package dev.sdkforge.shared.domain

import dev.sdkforge.shared.data
import dev.sdkforge.shared.ui
import dev.sdkforge.shared.presentation
""".trimIndent()
assertThatDataInDomainRule(code).hasLintViolationsWithoutAutoCorrect(
LintViolation(3, 1, "Importing 'dev.sdkforge.shared.data', it is not allowed in 'dev.sdkforge.shared.domain'", false),
LintViolation(4, 1, "Importing 'dev.sdkforge.shared.ui', it is not allowed in 'dev.sdkforge.shared.domain'", false),
)
}

@Test
fun `no 'ui', 'presentation' import in 'data' rule`() {
val assertThatUiDataRule = assertThatRule { NoImportAllowedInPackageRule(mapOf("data" to setOf("ui", "presentation"))) }
val code =
"""
package dev.sdkforge.shared.data

import dev.sdkforge.shared.ui
import dev.sdkforge.shared.presentation
import dev.sdkforge.shared.domain
""".trimIndent()
assertThatUiDataRule(code).hasLintViolationsWithoutAutoCorrect(
LintViolation(3, 1, "Importing 'dev.sdkforge.shared.ui', it is not allowed in 'dev.sdkforge.shared.data'", false),
LintViolation(4, 1, "Importing 'dev.sdkforge.shared.presentation', it is not allowed in 'dev.sdkforge.shared.data'", false),
)
}
}