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:
Brais Gabín
2021-10-02 10:47:12 +02:00
committed by GitHub
parent ee0ae169f8
commit f12697d700
21 changed files with 410 additions and 181 deletions

View File

@@ -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) }

View File

@@ -23,6 +23,7 @@ val DEFAULT_PROPERTY_EXCLUDES = setOf(
".*>severity",
".*>.*>severity",
"build>weights.*",
".*>.*>ignoreAnnotated",
).joinToString(",")
fun validateConfig(

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -1,4 +1,9 @@
@file:AnAnnotation
package cases
@Suppress("Unused")
class Test
@Target(AnnotationTarget.FILE)
annotation class AnAnnotation

View File

@@ -7,3 +7,4 @@ style:
autoCorrect: true
excludes: []
includes: []
ignoreAnnotated: []

View File

@@ -0,0 +1,6 @@
style:
MaxLineLength:
active: true
maxLineLength: 120
ignoreAnnotated:
- "AnAnnotation"

View File

@@ -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()) {

View File

@@ -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()

View File

@@ -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)
)
}
}
})

View File

@@ -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)

View File

@@ -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 }

View File

@@ -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"
}
}

View File

@@ -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)
)
}
}
}
})

View File

@@ -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)
}

View File

@@ -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)
}

View 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) }
}