mirror of
https://github.com/jlengrand/detekt.git
synced 2026-03-10 08:11:23 +00:00
Implement ignoreAnnotated as a core feature (#4102)
* Add missing tests * Add AnnotationSuppressor * Use ignoreAnnotated instead of the custom ones from our rules * Rename LongParameter.ignoreAnnotated to not clash with the general suppression
This commit is contained in:
@@ -20,6 +20,7 @@ import io.gitlab.arturbosch.detekt.core.config.DisabledAutoCorrectConfig
|
||||
import io.gitlab.arturbosch.detekt.core.rules.associateRuleIdsToRuleSetIds
|
||||
import io.gitlab.arturbosch.detekt.core.rules.isActive
|
||||
import io.gitlab.arturbosch.detekt.core.rules.shouldAnalyzeFile
|
||||
import io.gitlab.arturbosch.detekt.core.suppressors.getSuppressors
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -119,7 +120,7 @@ internal class Analyzer(
|
||||
fun executeRules(rules: List<BaseRule>) {
|
||||
for (rule in rules) {
|
||||
rule.visitFile(file, bindingContext, compilerResources)
|
||||
for (finding in rule.findings) {
|
||||
for (finding in filterSuppressedFindings(rule)) {
|
||||
val mappedRuleSet = checkNotNull(ruleIdsToRuleSetIds[finding.id]) {
|
||||
"Mapping for '${finding.id}' expected."
|
||||
}
|
||||
@@ -136,6 +137,15 @@ internal class Analyzer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterSuppressedFindings(rule: BaseRule): List<Finding> {
|
||||
val suppressors = getSuppressors(rule)
|
||||
return if (suppressors.isNotEmpty()) {
|
||||
rule.findings.filter { finding -> !suppressors.any { suppressor -> suppressor(finding) } }
|
||||
} else {
|
||||
rule.findings
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableMap<String, List<Finding>>.mergeSmells(other: Map<String, List<Finding>>) {
|
||||
for ((key, findings) in other.entries) {
|
||||
merge(key, findings) { f1, f2 -> f1.plus(f2) }
|
||||
|
||||
@@ -23,6 +23,7 @@ val DEFAULT_PROPERTY_EXCLUDES = setOf(
|
||||
".*>severity",
|
||||
".*>.*>severity",
|
||||
"build>weights.*",
|
||||
".*>.*>ignoreAnnotated",
|
||||
).joinToString(",")
|
||||
|
||||
fun validateConfig(
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.gitlab.arturbosch.detekt.core.suppressors
|
||||
|
||||
import io.gitlab.arturbosch.detekt.api.ConfigAware
|
||||
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
|
||||
internal fun annotationSuppressorFactory(rule: ConfigAware): Suppressor? {
|
||||
val annotations = rule.valueOrDefault("ignoreAnnotated", emptyList<String>())
|
||||
return if (annotations.isNotEmpty()) {
|
||||
{ finding ->
|
||||
val element = finding.entity.ktElement
|
||||
element != null && annotationSuppressor(element, annotations)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun annotationSuppressor(element: KtElement, annotations: List<String>): Boolean {
|
||||
return element.isAnnotatedWith(annotations)
|
||||
}
|
||||
|
||||
private fun KtElement.isAnnotatedWith(annotationNames: Iterable<String>): Boolean {
|
||||
return if (this is KtAnnotated && annotationEntries.find { it.typeReference?.text in annotationNames } != null) {
|
||||
true
|
||||
} else {
|
||||
getStrictParentOfType<KtAnnotated>()?.isAnnotatedWith(annotationNames) ?: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.gitlab.arturbosch.detekt.core.suppressors
|
||||
|
||||
import io.gitlab.arturbosch.detekt.api.ConfigAware
|
||||
import io.gitlab.arturbosch.detekt.api.Finding
|
||||
import io.gitlab.arturbosch.detekt.api.internal.BaseRule
|
||||
|
||||
/**
|
||||
* Given a Finding it decides if it should be suppressed (`true`) or not (`false`)
|
||||
*/
|
||||
typealias Suppressor = (Finding) -> Boolean
|
||||
|
||||
internal fun getSuppressors(rule: BaseRule): List<Suppressor> {
|
||||
return if (rule is ConfigAware) {
|
||||
listOfNotNull(
|
||||
annotationSuppressorFactory(rule),
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -111,14 +111,13 @@ complexity:
|
||||
LongMethod:
|
||||
active: true
|
||||
threshold: 60
|
||||
ignoreAnnotated: []
|
||||
LongParameterList:
|
||||
active: true
|
||||
functionThreshold: 6
|
||||
constructorThreshold: 7
|
||||
ignoreDefaultParameters: false
|
||||
ignoreDataClasses: true
|
||||
ignoreAnnotated: []
|
||||
ignoreAnnotatedParameter: []
|
||||
MethodOverloading:
|
||||
active: false
|
||||
threshold: 6
|
||||
@@ -440,7 +439,6 @@ naming:
|
||||
functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
ignoreAnnotated: []
|
||||
FunctionParameterNaming:
|
||||
active: true
|
||||
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
|
||||
@@ -555,7 +553,6 @@ potential-bugs:
|
||||
LateinitUsage:
|
||||
active: false
|
||||
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
|
||||
excludeAnnotatedProperties: []
|
||||
ignoreOnClassesPattern: ''
|
||||
MapGetWithNotNullAssertionOperator:
|
||||
active: false
|
||||
@@ -644,7 +641,6 @@ style:
|
||||
ignoreOverridableFunction: true
|
||||
ignoreActualFunction: true
|
||||
excludedFunctions: ''
|
||||
excludeAnnotatedFunction: []
|
||||
LibraryCodeMustSpecifyReturnType:
|
||||
active: true
|
||||
excludes: ['**']
|
||||
@@ -736,7 +732,6 @@ style:
|
||||
acceptableLength: 4
|
||||
UnnecessaryAbstractClass:
|
||||
active: true
|
||||
excludeAnnotatedClasses: []
|
||||
UnnecessaryAnnotationUseSiteTarget:
|
||||
active: false
|
||||
UnnecessaryApply:
|
||||
@@ -766,7 +761,6 @@ style:
|
||||
active: false
|
||||
UseDataClass:
|
||||
active: false
|
||||
excludeAnnotatedClasses: []
|
||||
allowVars: false
|
||||
UseEmptyCounterpart:
|
||||
active: false
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
complexity>LongParameterList>threshold=Use `functionThreshold` and `constructorThreshold` instead
|
||||
empty-blocks>EmptyFunctionBlock>ignoreOverriddenFunctions=Use `ignoreOverridden` instead
|
||||
potential-bugs>LateinitUsage>excludeAnnotatedProperties=Use `ignoreAnnotated` instead
|
||||
naming>FunctionParameterNaming>ignoreOverriddenFunctions=Use `ignoreOverridden` instead
|
||||
naming>MemberNameEqualsClassName>ignoreOverriddenFunction=Use `ignoreOverridden` instead
|
||||
style>FunctionOnlyReturningConstant>excludeAnnotatedFunction=Use `ignoreAnnotated` instead
|
||||
style>UnderscoresInNumericLiterals>acceptableDecimalLength=Use `acceptableLength` instead
|
||||
style>UnnecessaryAbstractClass>excludeAnnotatedClasses=Use `ignoreAnnotated` instead
|
||||
style>UseDataClass>excludeAnnotatedClasses=Use `ignoreAnnotated` instead
|
||||
|
||||
@@ -21,15 +21,6 @@ import java.util.concurrent.CompletionException
|
||||
class AnalyzerSpec : Spek({
|
||||
|
||||
describe("exceptions during analyze()") {
|
||||
|
||||
it("analyze successfully when config has correct value type in config") {
|
||||
val testFile = path.resolve("Test.kt")
|
||||
val settings = createProcessingSettings(testFile, yamlConfig("configs/config-value-type-correct.yml"))
|
||||
val analyzer = Analyzer(settings, listOf(StyleRuleSetProvider()), emptyList())
|
||||
|
||||
assertThat(settings.use { analyzer.run(listOf(compileForTest(testFile))) }).isEmpty()
|
||||
}
|
||||
|
||||
it("throw error explicitly when config has wrong value type in config") {
|
||||
val testFile = path.resolve("Test.kt")
|
||||
val settings = createProcessingSettings(testFile, yamlConfig("configs/config-value-type-wrong.yml"))
|
||||
@@ -59,19 +50,49 @@ class AnalyzerSpec : Spek({
|
||||
.hasCauseInstanceOf(IllegalStateException::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
describe("analyze successfully when config has correct value type in config") {
|
||||
|
||||
it("no findings") {
|
||||
val testFile = path.resolve("Test.kt")
|
||||
val settings = createProcessingSettings(testFile, yamlConfig("configs/config-value-type-correct.yml"))
|
||||
val analyzer = Analyzer(settings, listOf(StyleRuleSetProvider()), emptyList())
|
||||
|
||||
assertThat(settings.use { analyzer.run(listOf(compileForTest(testFile))) }).isEmpty()
|
||||
}
|
||||
|
||||
it("with findings") {
|
||||
val testFile = path.resolve("Test.kt")
|
||||
val settings = createProcessingSettings(testFile, yamlConfig("configs/config-value-type-correct.yml"))
|
||||
val analyzer = Analyzer(settings, listOf(StyleRuleSetProvider(18)), emptyList())
|
||||
|
||||
assertThat(settings.use { analyzer.run(listOf(compileForTest(testFile))) }).hasSize(1)
|
||||
}
|
||||
|
||||
it("with findings but ignored") {
|
||||
val testFile = path.resolve("Test.kt")
|
||||
val settings = createProcessingSettings(
|
||||
testFile,
|
||||
yamlConfig("configs/config-value-type-correct-ignore-annotated.yml")
|
||||
)
|
||||
val analyzer = Analyzer(settings, listOf(StyleRuleSetProvider(18)), emptyList())
|
||||
|
||||
assertThat(settings.use { analyzer.run(listOf(compileForTest(testFile))) }).isEmpty()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private class StyleRuleSetProvider : RuleSetProvider {
|
||||
private class StyleRuleSetProvider(private val threshold: Int? = null) : RuleSetProvider {
|
||||
override val ruleSetId: String = "style"
|
||||
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(MaxLineLength(config)))
|
||||
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(MaxLineLength(config, threshold)))
|
||||
}
|
||||
|
||||
private class MaxLineLength(config: Config) : Rule(config) {
|
||||
private class MaxLineLength(config: Config, threshold: Int?) : Rule(config) {
|
||||
override val issue = Issue(this::class.java.simpleName, Severity.Style, "", Debt.FIVE_MINS)
|
||||
private val lengthThreshold: Int = valueOrDefault("maxLineLength", 100)
|
||||
private val lengthThreshold: Int = threshold ?: valueOrDefault("maxLineLength", 100)
|
||||
override fun visitKtFile(file: KtFile) {
|
||||
super.visitKtFile(file)
|
||||
for (line in file.text.splitToSequence(NL)) {
|
||||
for (line in file.text.lineSequence()) {
|
||||
if (line.length > lengthThreshold) {
|
||||
report(CodeSmell(issue, Entity.atPackageOrFirstDecl(file), issue.description))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
package io.gitlab.arturbosch.detekt.core.suppressors
|
||||
|
||||
import io.github.detekt.psi.FilePath
|
||||
import io.github.detekt.test.utils.compileContentForTest
|
||||
import io.gitlab.arturbosch.detekt.api.CodeSmell
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.ConfigAware
|
||||
import io.gitlab.arturbosch.detekt.api.Debt
|
||||
import io.gitlab.arturbosch.detekt.api.Entity
|
||||
import io.gitlab.arturbosch.detekt.api.Finding
|
||||
import io.gitlab.arturbosch.detekt.api.Issue
|
||||
import io.gitlab.arturbosch.detekt.api.Location
|
||||
import io.gitlab.arturbosch.detekt.api.RuleId
|
||||
import io.gitlab.arturbosch.detekt.api.Severity
|
||||
import io.gitlab.arturbosch.detekt.api.SourceLocation
|
||||
import io.gitlab.arturbosch.detekt.api.TextLocation
|
||||
import io.gitlab.arturbosch.detekt.test.TestConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findFunctionByName
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
import java.nio.file.Paths
|
||||
|
||||
class AnnotationSuppressorSpec : Spek({
|
||||
|
||||
describe("AnnotationSuppressorFactory") {
|
||||
it("Factory returns null if ignoreAnnotated is not set") {
|
||||
val suppressor = annotationSuppressorFactory(buildConfigAware(/* empty */))
|
||||
|
||||
assertThat(suppressor).isNull()
|
||||
}
|
||||
|
||||
it("Factory returns null if ignoreAnnotated is set to empty") {
|
||||
val suppressor = annotationSuppressorFactory(
|
||||
buildConfigAware("ignoreAnnotated" to emptyList<String>())
|
||||
)
|
||||
|
||||
assertThat(suppressor).isNull()
|
||||
}
|
||||
|
||||
it("Factory returns not null if ignoreAnnotated is set to a not empty list") {
|
||||
val suppressor = annotationSuppressorFactory(
|
||||
buildConfigAware("ignoreAnnotated" to listOf("Composable"))
|
||||
)
|
||||
|
||||
assertThat(suppressor).isNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
describe("AnnotationSuppressor") {
|
||||
val suppressor by memoized {
|
||||
annotationSuppressorFactory(buildConfigAware("ignoreAnnotated" to listOf("Composable")))!!
|
||||
}
|
||||
|
||||
it("If KtElement is null it returns false") {
|
||||
assertThat(suppressor(buildFinding(element = null))).isFalse()
|
||||
}
|
||||
|
||||
context("If annotation is at file level") {
|
||||
val root by memoized {
|
||||
compileContentForTest(
|
||||
"""
|
||||
@file:Composable
|
||||
|
||||
class OneClass {
|
||||
fun function(parameter: String) {
|
||||
val a = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun topLevelFunction() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
it("If reports root it returns true") {
|
||||
assertThat(suppressor(buildFinding(element = root))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports class it returns true") {
|
||||
val ktClass = root.findChildByClass(KtClass::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktClass))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports function in class it returns true") {
|
||||
val ktFunction = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports parameter in function in class it returns true") {
|
||||
val ktParameter = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
.findDescendantOfType<KtParameter>()!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktParameter))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports top level function it returns true") {
|
||||
val ktFunction = root.findChildByClass(KtFunction::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
context("If annotation is at function level") {
|
||||
val root by memoized {
|
||||
compileContentForTest(
|
||||
"""
|
||||
class OneClass {
|
||||
@Composable
|
||||
fun function(parameter: String) {
|
||||
val a = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun topLevelFunction() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
it("If reports root it returns false") {
|
||||
assertThat(suppressor(buildFinding(element = root))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports class it returns false") {
|
||||
val ktClass = root.findChildByClass(KtClass::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktClass))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports function in class it returns true") {
|
||||
val ktFunction = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports parameter in function in class it returns true") {
|
||||
val ktParameter = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
.findDescendantOfType<KtParameter>()!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktParameter))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports top level function it returns false") {
|
||||
val ktFunction = root.findChildByClass(KtFunction::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
context("If there is not annotations") {
|
||||
val root by memoized {
|
||||
compileContentForTest(
|
||||
"""
|
||||
class OneClass {
|
||||
fun function(parameter: String) {
|
||||
val a = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun topLevelFunction() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
it("If reports root it returns false") {
|
||||
assertThat(suppressor(buildFinding(element = root))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports class it returns false") {
|
||||
val ktClass = root.findChildByClass(KtClass::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktClass))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports function in class it returns false") {
|
||||
val ktFunction = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports parameter in function in class it returns false") {
|
||||
val ktParameter = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
.findDescendantOfType<KtParameter>()!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktParameter))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports top level function it returns false") {
|
||||
val ktFunction = root.findChildByClass(KtFunction::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
context("If there are other annotations") {
|
||||
val root by memoized {
|
||||
compileContentForTest(
|
||||
"""
|
||||
@file:A
|
||||
|
||||
@B
|
||||
class OneClass {
|
||||
@Composable
|
||||
fun function(@C parameter: String) {
|
||||
@D
|
||||
val a = 0
|
||||
}
|
||||
}
|
||||
|
||||
@E
|
||||
fun topLevelFunction() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
it("If reports root it returns false") {
|
||||
assertThat(suppressor(buildFinding(element = root))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports class it returns false") {
|
||||
val ktClass = root.findChildByClass(KtClass::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktClass))).isFalse()
|
||||
}
|
||||
|
||||
it("If reports function in class it returns true") {
|
||||
val ktFunction = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports parameter in function in class it returns true") {
|
||||
val ktParameter = root.findChildByClass(KtClass::class.java)!!
|
||||
.findFunctionByName("function")!!
|
||||
.findDescendantOfType<KtParameter>()!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktParameter))).isTrue()
|
||||
}
|
||||
|
||||
it("If reports top level function it returns false") {
|
||||
val ktFunction = root.findChildByClass(KtFunction::class.java)!!
|
||||
|
||||
assertThat(suppressor(buildFinding(element = ktFunction))).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun buildFinding(element: KtElement?): Finding = CodeSmell(
|
||||
issue = Issue("RuleName", Severity.CodeSmell, "", Debt.FIVE_MINS),
|
||||
entity = element?.let { Entity.from(element) } ?: buildEmptyEntity(),
|
||||
message = "",
|
||||
)
|
||||
|
||||
private fun buildEmptyEntity(): Entity = Entity(
|
||||
name = "",
|
||||
signature = "",
|
||||
location = Location(SourceLocation(0, 0), TextLocation(0, 0), FilePath.fromAbsolute(Paths.get("/"))),
|
||||
ktElement = null,
|
||||
)
|
||||
|
||||
private fun buildConfigAware(
|
||||
vararg pairs: Pair<String, Any>
|
||||
) = object : ConfigAware {
|
||||
override val ruleId: RuleId = "ruleId"
|
||||
override val ruleSetConfig: Config = TestConfig(*pairs)
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
@file:AnAnnotation
|
||||
|
||||
package cases
|
||||
|
||||
@Suppress("Unused")
|
||||
class Test
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
annotation class AnAnnotation
|
||||
|
||||
@@ -7,3 +7,4 @@ style:
|
||||
autoCorrect: true
|
||||
excludes: []
|
||||
includes: []
|
||||
ignoreAnnotated: []
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
style:
|
||||
MaxLineLength:
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
ignoreAnnotated:
|
||||
- "AnAnnotation"
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.gitlab.arturbosch.detekt.rules.complexity
|
||||
|
||||
import io.github.detekt.metrics.linesOfCode
|
||||
import io.gitlab.arturbosch.detekt.api.AnnotationExcluder
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.Debt
|
||||
import io.gitlab.arturbosch.detekt.api.Entity
|
||||
@@ -39,15 +38,10 @@ class LongMethod(config: Config = Config.empty) : Rule(config) {
|
||||
@Configuration("number of lines in a method to trigger the rule")
|
||||
private val threshold: Int by config(defaultValue = 60)
|
||||
|
||||
@Configuration("ignore long methods in the context of these annotation class names")
|
||||
private val ignoreAnnotated: List<String> by config(emptyList())
|
||||
|
||||
private val functionToLinesCache = HashMap<KtNamedFunction, Int>()
|
||||
private val functionToBodyLinesCache = HashMap<KtNamedFunction, Int>()
|
||||
private val nestedFunctionTracking = IdentityHashMap<KtNamedFunction, HashSet<KtNamedFunction>>()
|
||||
|
||||
private lateinit var annotationExcluder: AnnotationExcluder
|
||||
|
||||
override fun preVisit(root: KtFile) {
|
||||
functionToLinesCache.clear()
|
||||
functionToBodyLinesCache.clear()
|
||||
@@ -77,8 +71,6 @@ class LongMethod(config: Config = Config.empty) : Rule(config) {
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
if (annotationExcluder.shouldExclude(function.annotationEntries)) return
|
||||
|
||||
val parentMethods = function.getStrictParentOfType<KtNamedFunction>()
|
||||
val bodyEntity = function.bodyBlockExpression ?: function.bodyExpression
|
||||
val lines = (if (parentMethods != null) function else bodyEntity)?.linesOfCode() ?: 0
|
||||
@@ -92,11 +84,6 @@ class LongMethod(config: Config = Config.empty) : Rule(config) {
|
||||
?.let { functionToLinesCache[function] = lines - it }
|
||||
}
|
||||
|
||||
override fun visitKtFile(file: KtFile) {
|
||||
annotationExcluder = AnnotationExcluder(file, ignoreAnnotated)
|
||||
super.visitKtFile(file)
|
||||
}
|
||||
|
||||
private fun findAllNestedFunctions(startFunction: KtNamedFunction): Sequence<KtNamedFunction> = sequence {
|
||||
var nestedFunctions = nestedFunctionTracking[startFunction]
|
||||
while (!nestedFunctions.isNullOrEmpty()) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameterList
|
||||
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
|
||||
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
/**
|
||||
* Reports functions and constructors which have more parameters than a certain threshold.
|
||||
@@ -61,27 +60,20 @@ class LongParameterList(config: Config = Config.empty) : Rule(config) {
|
||||
private val ignoreDataClasses: Boolean by config(true)
|
||||
|
||||
@Configuration(
|
||||
"ignore long parameters list for constructors, functions or their parameters in the " +
|
||||
"context of these annotation class names; (e.g. ['Inject', 'Module', 'Suppress', 'Value']); " +
|
||||
"the most common cases are for dependency injection where constructors are annotated with `@Inject` " +
|
||||
"or parameters are annotated with `@Value` and should not be counted for the rule to trigger"
|
||||
"ignore the annotated parameters for the count (e.g. `fun foo(@Value bar: Int)` would not be counted"
|
||||
)
|
||||
private val ignoreAnnotated: List<String> by config(emptyList<String>()) { list ->
|
||||
private val ignoreAnnotatedParameter: List<String> by config(emptyList<String>()) { list ->
|
||||
list.map { it.removePrefix("*").removeSuffix("*") }
|
||||
}
|
||||
|
||||
private lateinit var annotationExcluder: AnnotationExcluder
|
||||
|
||||
override fun visitKtFile(file: KtFile) {
|
||||
annotationExcluder = AnnotationExcluder(file, ignoreAnnotated)
|
||||
annotationExcluder = AnnotationExcluder(file, ignoreAnnotatedParameter)
|
||||
super.visitKtFile(file)
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
val owner = function.containingClassOrObject
|
||||
if (owner is KtClass && owner.isIgnored()) {
|
||||
return
|
||||
}
|
||||
checkLongParameterList(function, functionThreshold, "function ${function.nameAsSafeName}")
|
||||
}
|
||||
|
||||
@@ -105,10 +97,10 @@ class LongParameterList(config: Config = Config.empty) : Rule(config) {
|
||||
checkLongParameterList(constructor, constructorThreshold, "constructor")
|
||||
}
|
||||
|
||||
private fun KtClass.isDataClassOrIgnored() = isIgnored() || ignoreDataClasses && isData()
|
||||
private fun KtClass.isDataClassOrIgnored() = ignoreDataClasses && isData()
|
||||
|
||||
private fun checkLongParameterList(function: KtFunction, threshold: Int, identifier: String) {
|
||||
if (function.isOverride() || function.isIgnored() || function.containingKtFile.isIgnored()) return
|
||||
if (function.isOverride()) return
|
||||
val parameterList = function.valueParameterList ?: return
|
||||
val parameterNumber = parameterList.parameterCount()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.gitlab.arturbosch.detekt.rules.complexity
|
||||
|
||||
import io.gitlab.arturbosch.detekt.api.SourceLocation
|
||||
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
|
||||
import io.gitlab.arturbosch.detekt.test.TestConfig
|
||||
import io.gitlab.arturbosch.detekt.test.assertThat
|
||||
@@ -151,45 +150,4 @@ class LongMethodSpec : Spek({
|
||||
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(5)
|
||||
}
|
||||
}
|
||||
|
||||
describe("annotated functions") {
|
||||
val code = """
|
||||
annotation class Composable
|
||||
annotation class TestAnn
|
||||
|
||||
fun foo() {
|
||||
println()
|
||||
println()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun bar() {
|
||||
println()
|
||||
println()
|
||||
}
|
||||
|
||||
@TestAnn
|
||||
fun baz() {
|
||||
println()
|
||||
println()
|
||||
}
|
||||
"""
|
||||
|
||||
it("does not ignore annotated functions if ignoreAnnotated is empty") {
|
||||
val config = TestConfig(mapOf("threshold" to 2))
|
||||
assertThat(LongMethod(config).compileAndLint(code)).hasSourceLocations(
|
||||
SourceLocation(4, 5),
|
||||
SourceLocation(10, 5),
|
||||
SourceLocation(16, 5)
|
||||
)
|
||||
}
|
||||
|
||||
it("ignores annotated functions if ignoreAnnotated includes the given annotation class") {
|
||||
val config = TestConfig(mapOf("threshold" to 2, "ignoreAnnotated" to listOf("Composable")))
|
||||
assertThat(LongMethod(config).compileAndLint(code)).hasSourceLocations(
|
||||
SourceLocation(4, 5),
|
||||
SourceLocation(16, 5)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -99,7 +99,7 @@ class LongParameterListSpec : Spek({
|
||||
val config by memoized {
|
||||
TestConfig(
|
||||
mapOf(
|
||||
"ignoreAnnotated" to listOf(
|
||||
"ignoreAnnotatedParameter" to listOf(
|
||||
"Generated",
|
||||
"kotlin.Deprecated",
|
||||
"kotlin.jvm.JvmName",
|
||||
@@ -113,54 +113,6 @@ class LongParameterListSpec : Spek({
|
||||
|
||||
val rule by memoized { LongParameterList(config) }
|
||||
|
||||
it("does not report long parameter list for constructors if file is annotated with ignored annotation") {
|
||||
val code = """
|
||||
@file:kotlin.jvm.JvmName("test")
|
||||
class Data(val a: Int)
|
||||
"""
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report long parameter list for functions if file is annotated with ignored annotation") {
|
||||
val code = """
|
||||
@file:kotlin.jvm.JvmName("test")
|
||||
class Data {
|
||||
fun foo(a: Int) {}
|
||||
}
|
||||
"""
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report long parameter list for constructors if class is annotated with ignored annotation") {
|
||||
val code = """
|
||||
annotation class Generated
|
||||
@Generated class Data(val a: Int)
|
||||
"""
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report long parameter list for functions if class is annotated with ignored annotation") {
|
||||
val code = """
|
||||
annotation class Generated
|
||||
@Generated class Data {
|
||||
fun foo(a: Int) {}
|
||||
}
|
||||
"""
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report long parameter list for constructors if constructor is annotated with ignored annotation") {
|
||||
val code = "class Data @kotlin.Deprecated(message = \"\") constructor(val a: Int)"
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report long parameter list for functions if function is annotated with ignored annotation") {
|
||||
val code = """class Data {
|
||||
@kotlin.Deprecated(message = "") fun foo(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int) {} }
|
||||
"""
|
||||
assertThat(rule.compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("reports long parameter list for constructors if constructor parameters are annotated with annotation that is not ignored") {
|
||||
val code = """
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
|
||||
@@ -39,6 +39,7 @@ class LateinitUsage(config: Config = Config.empty) : Rule(config) {
|
||||
)
|
||||
|
||||
@Configuration("Allows you to provide a list of annotations that disable this check.")
|
||||
@Deprecated("Use `ignoreAnnotated` instead")
|
||||
private val excludeAnnotatedProperties: List<String> by config(emptyList<String>()) { list ->
|
||||
list.map { it.removePrefix("*").removeSuffix("*") }
|
||||
}
|
||||
@@ -59,7 +60,7 @@ class LateinitUsage(config: Config = Config.empty) : Rule(config) {
|
||||
|
||||
super.visit(root)
|
||||
|
||||
val annotationExcluder = AnnotationExcluder(root, excludeAnnotatedProperties)
|
||||
val annotationExcluder = AnnotationExcluder(root, @Suppress("DEPRECATION") excludeAnnotatedProperties)
|
||||
|
||||
properties.filterNot { annotationExcluder.shouldExclude(it.annotationEntries) }
|
||||
.filterNot { it.containingClass()?.name?.matches(ignoreOnClassesPattern) == true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.gitlab.arturbosch.detekt.rules.naming
|
||||
|
||||
import io.gitlab.arturbosch.detekt.api.AnnotationExcluder
|
||||
import io.gitlab.arturbosch.detekt.api.CodeSmell
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.Debt
|
||||
@@ -41,13 +40,10 @@ class FunctionNaming(config: Config = Config.empty) : Rule(config) {
|
||||
@Configuration("ignores functions that have the override modifier")
|
||||
private val ignoreOverridden: Boolean by config(true)
|
||||
|
||||
@Configuration("ignore naming for functions in the context of these annotation class names")
|
||||
private val ignoreAnnotated: List<String> by config(emptyList())
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
super.visitNamedFunction(function)
|
||||
|
||||
if (ignoreOverridden && function.isOverride() || shouldAnnotatedFunctionBeExcluded(function)) {
|
||||
if (ignoreOverridden && function.isOverride()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -66,15 +62,9 @@ class FunctionNaming(config: Config = Config.empty) : Rule(config) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldAnnotatedFunctionBeExcluded(function: KtNamedFunction): Boolean {
|
||||
val annotationExcluder = AnnotationExcluder(function.containingKtFile, ignoreAnnotated)
|
||||
return annotationExcluder.shouldExclude(function.annotationEntries)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FUNCTION_PATTERN = "functionPattern"
|
||||
const val EXCLUDE_CLASS_PATTERN = "excludeClassPattern"
|
||||
const val IGNORE_OVERRIDDEN = "ignoreOverridden"
|
||||
const val IGNORE_ANNOTATED = "ignoreAnnotated"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,32 +103,5 @@ class FunctionNamingSpec : Spek({
|
||||
"""
|
||||
assertThat(FunctionNaming().compileAndLint(code)).isEmpty()
|
||||
}
|
||||
|
||||
describe("annotated functions") {
|
||||
val code = """
|
||||
annotation class Composable
|
||||
|
||||
class D {
|
||||
fun SHOULD_BE_FLAGGED() {}
|
||||
}
|
||||
class E {
|
||||
@Suppress
|
||||
fun FLAGGED_IF_NOT_IGNORED() {}
|
||||
}
|
||||
class F {
|
||||
@Composable
|
||||
fun NOT_FLAGGED_BY_DEFAULT() {}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
it("Ignores annotated functions if ignoreAnnotated includes the given annotation class") {
|
||||
val config = TestConfig(mapOf(FunctionNaming.IGNORE_ANNOTATED to listOf("Suppress")))
|
||||
assertThat(FunctionNaming(config).compileAndLint(code)).hasSourceLocations(
|
||||
SourceLocation(4, 9),
|
||||
SourceLocation(12, 9)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -56,6 +56,7 @@ class FunctionOnlyReturningConstant(config: Config = Config.empty) : Rule(config
|
||||
private val excludedFunctions: SplitPattern by config("") { SplitPattern(it) }
|
||||
|
||||
@Configuration("allows to provide a list of annotations that disable this check")
|
||||
@Deprecated("Use `ignoreAnnotated` instead")
|
||||
private val excludeAnnotatedFunction: List<String> by config(emptyList<String>()) { functions ->
|
||||
functions.map { it.removePrefix("*").removeSuffix("*") }
|
||||
}
|
||||
@@ -63,7 +64,7 @@ class FunctionOnlyReturningConstant(config: Config = Config.empty) : Rule(config
|
||||
private lateinit var annotationExcluder: AnnotationExcluder
|
||||
|
||||
override fun visit(root: KtFile) {
|
||||
annotationExcluder = AnnotationExcluder(root, excludeAnnotatedFunction)
|
||||
annotationExcluder = AnnotationExcluder(root, @Suppress("DEPRECATION") excludeAnnotatedFunction)
|
||||
super.visit(root)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class UnnecessaryAbstractClass(config: Config = Config.empty) : Rule(config) {
|
||||
)
|
||||
|
||||
@Configuration("Allows you to provide a list of annotations that disable this check.")
|
||||
@Deprecated("Use `ignoreAnnotated` instead")
|
||||
private val excludeAnnotatedClasses: List<String> by config(emptyList<String>()) { classes ->
|
||||
classes.map { it.removePrefix("*").removeSuffix("*") }
|
||||
}
|
||||
@@ -67,7 +68,7 @@ class UnnecessaryAbstractClass(config: Config = Config.empty) : Rule(config) {
|
||||
private lateinit var annotationExcluder: AnnotationExcluder
|
||||
|
||||
override fun visitKtFile(file: KtFile) {
|
||||
annotationExcluder = AnnotationExcluder(file, excludeAnnotatedClasses)
|
||||
annotationExcluder = AnnotationExcluder(file, @Suppress("DEPRECATION") excludeAnnotatedClasses)
|
||||
super.visitKtFile(file)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class UseDataClass(config: Config = Config.empty) : Rule(config) {
|
||||
)
|
||||
|
||||
@Configuration("allows to provide a list of annotations that disable this check")
|
||||
@Deprecated("Use `ignoreAnnotated` instead")
|
||||
private val excludeAnnotatedClasses: List<String> by config(emptyList<String>()) { classes ->
|
||||
classes.map { it.removePrefix("*").removeSuffix("*") }
|
||||
}
|
||||
@@ -66,7 +67,7 @@ class UseDataClass(config: Config = Config.empty) : Rule(config) {
|
||||
|
||||
override fun visit(root: KtFile) {
|
||||
super.visit(root)
|
||||
val annotationExcluder = AnnotationExcluder(root, excludeAnnotatedClasses)
|
||||
val annotationExcluder = AnnotationExcluder(root, @Suppress("DEPRECATION") excludeAnnotatedClasses)
|
||||
root.forEachDescendantOfType<KtClass> { visitKlass(it, annotationExcluder) }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user