Apply more formatting rules to our code (#3615)

* Update formatting configuration

* Apply autocorrect to the project

* Format using ktlint 0.41 --experimental

* Minor manual improvements
This commit is contained in:
Brais Gabín
2021-03-28 19:00:03 +02:00
committed by GitHub
parent 84009f24af
commit 92b6a01903
285 changed files with 3103 additions and 1739 deletions

View File

@@ -10,6 +10,7 @@ import java.io.File
open class UpdateVersionInFileTask : DefaultTask(), Runnable {
private val fileProp: RegularFileProperty = project.objects.fileProperty()
@get:InputFile
var fileToUpdate: File
get() = fileProp.get().asFile

View File

@@ -64,10 +64,30 @@ formatting:
active: true
android: false
autoCorrect: true
AnnotationSpacing:
active: true
EnumEntryNameCase:
active: true
ImportOrdering:
active: true
Indentation:
active: true
MaximumLineLength:
active: false
NoEmptyFirstLineInMethodBlock:
active: true
ParameterListWrapping:
active: false
SpacingAroundAngleBrackets:
active: true
SpacingAroundDoubleColon:
active: true
SpacingAroundUnaryOperator:
active: true
SpacingBetweenDeclarationsWithAnnotations:
active: true
SpacingBetweenDeclarationsWithComments:
active: true
potential-bugs:
UnsafeCast:

View File

@@ -100,11 +100,13 @@ class SpekTestDiscovery(config: Config = Config.empty) : Rule(config) {
val fqType = initExpr?.getType(bindingContext)
?.getJetTypeFqName(false)
if (fqType != null && fqType !in allowedTypes) {
report(CodeSmell(
issue,
Entity.atName(property),
"Variable declarations which do not met the allowed types should be memoized."
))
report(
CodeSmell(
issue,
Entity.atName(property),
"Variable declarations which do not met the allowed types should be memoized."
)
)
}
}
}

View File

@@ -18,11 +18,13 @@ class SpekTestDiscoverySpec : Spek({
context("top level scope") {
it("allows strings, paths and files by default") {
val code = createSpekCode("""
val code = createSpekCode(
"""
val s = "simple"
val p = Paths.get("")
val f = File("")
""")
"""
)
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@@ -44,14 +46,16 @@ class SpekTestDiscoverySpec : Spek({
setOf("describe", "context").forEach { name ->
it("allows strings, files and paths by default") {
val code = createSpekCode("""
val code = createSpekCode(
"""
$name("group") {
val s = "simple"
val p = Paths.get("")
val f = File("")
val m by memoized { Any() }
}
""")
"""
)
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@@ -59,11 +63,13 @@ class SpekTestDiscoverySpec : Spek({
setOf("describe", "context").forEach { name ->
it("disallows non memoized declarations") {
val code = createSpekCode("""
val code = createSpekCode(
"""
$name("group") {
val complex = Any()
}
""")
"""
)
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}

View File

@@ -86,7 +86,11 @@ open class ThresholdedCodeSmell(
message: String,
references: List<Entity> = emptyList()
) : CodeSmell(
issue, entity, message, metrics = listOf(metric), references = references
issue,
entity,
message,
metrics = listOf(metric),
references = references
) {
val value: Int

View File

@@ -65,8 +65,10 @@ data class Location @Deprecated("Consider relative path by passing a [FilePath]"
fun startLineAndColumn(element: PsiElement, offset: Int = 0): PsiDiagnosticUtils.LineAndColumn {
return try {
val range = element.textRange
DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile,
TextRange(range.startOffset + offset, range.endOffset + offset))
DiagnosticUtils.getLineAndColumnInPsiFile(
element.containingFile,
TextRange(range.startOffset + offset, range.endOffset + offset)
)
} catch (e: IndexOutOfBoundsException) {
// #3317 If any rule mutates the PsiElement, searching the original PsiElement may throw exception.
PsiDiagnosticUtils.LineAndColumn(-1, -1, null)

View File

@@ -11,31 +11,38 @@ enum class Severity {
* Represents clean coding violations which may lead to maintainability issues.
*/
CodeSmell,
/**
* Inspections in this category detect violations of code syntax styles.
*/
Style,
/**
* Corresponds to issues that do not prevent the code from working,
* but may nevertheless represent coding inefficiencies.
*/
Warning,
/**
* Corresponds to coding mistakes which could lead to unwanted behavior.
*/
Defect,
/**
* Represents code quality issues which only slightly impact the code quality.
*/
Minor,
/**
* Issues in this category make the source code confusing and difficult to maintain.
*/
Maintainability,
/**
* Places in the source code that can be exploited and possibly result in significant damage.
*/
Security,
/**
* Places in the source code which degrade the performance of the application.
*/

View File

@@ -26,7 +26,6 @@ class PathFilters internal constructor(
* return false iff [path] matches any [includes] and [path] does not match any [excludes].
*/
fun isIgnored(path: Path): Boolean {
fun isIncluded() = includes?.any { it.matches(path) }
fun isExcluded() = excludes?.any { it.matches(path) }

View File

@@ -10,7 +10,6 @@ import java.nio.file.PathMatcher
* Internally a globbing pattern is transformed to a regex respecting the Windows file system.
*/
fun pathMatcher(pattern: String): PathMatcher {
val result = when (pattern.substringBefore(":")) {
"glob" -> pattern
"regex" -> throw IllegalArgumentException(USE_GLOB_MSG)

View File

@@ -6,7 +6,6 @@ private val identifierRegex = Regex("[aA-zZ]+([-][aA-zZ]+)*")
* Checks if given string matches the criteria of an id - [aA-zZ]+([-][aA-zZ]+)* .
*/
internal fun validateIdentifier(id: String) {
require(id.matches(identifierRegex)) {
"id '$id' must match ${identifierRegex.pattern}"
}

View File

@@ -18,11 +18,13 @@ class AnnotationExcluderSpec : Spek({
val sinceKotlinAnnotation by memoized { psiFactory.createAnnotationEntry("@SinceKotlin") }
val file by memoized {
compileContentForTest("""
compileContentForTest(
"""
package foo
import kotlin.jvm.JvmField
""".trimIndent())
""".trimIndent()
)
}
it("should exclude when the annotation was found") {

View File

@@ -16,7 +16,8 @@ class EntitySpec : Spek({
describe("entity signatures") {
val path = Paths.get("/full/path/to/Test.kt")
val code by memoized(CachingMode.SCOPE) {
compileContentForTest("""
compileContentForTest(
"""
package test
class C : Any() {
@@ -25,7 +26,8 @@ class EntitySpec : Spek({
}
fun topLevelFun(number: Int) = Unit
""".trimIndent(), path.toString()
""".trimIndent(),
path.toString()
)
}

View File

@@ -25,10 +25,13 @@ class PropertiesAwareSpec : Spek({
register("string", "test")
register("number", 5)
register("set", setOf(1, 2, 3))
register("any", object : Any() {
override fun equals(other: Any?): Boolean = hashCode() == other.hashCode()
override fun hashCode(): Int = hash
})
register(
"any",
object : Any() {
override fun equals(other: Any?): Boolean = hashCode() == other.hashCode()
override fun hashCode(): Int = hash
}
)
}
}

View File

@@ -70,9 +70,12 @@ class InclusionExclusionPatternsSpec : Spek({
describe("rule should only run on included files") {
it("should only run on dummies") {
val config = TestConfig(mapOf(
Config.INCLUDES_KEY to "**Dummy*.kt",
Config.EXCLUDES_KEY to "**/library/**"))
val config = TestConfig(
mapOf(
Config.INCLUDES_KEY to "**Dummy*.kt",
Config.EXCLUDES_KEY to "**/library/**"
)
)
OnlyLibraryTrackingRule(config).apply {
Files.walk(resourceAsPath("library/Library.kt").parent)
@@ -84,9 +87,12 @@ class InclusionExclusionPatternsSpec : Spek({
}
it("should only run on library file") {
val config = TestConfig(mapOf(
Config.INCLUDES_KEY to "**Library.kt",
Config.EXCLUDES_KEY to "**/library/**"))
val config = TestConfig(
mapOf(
Config.INCLUDES_KEY to "**Library.kt",
Config.EXCLUDES_KEY to "**/library/**"
)
)
OnlyLibraryTrackingRule(config).apply {
Files.walk(resourceAsPath("library/Library.kt").parent)

View File

@@ -123,11 +123,11 @@ class CliArgs {
)
var allRules: Boolean = false
// nullable for 1.x.x to prefer maxIssues from config file
@Parameter(
names = ["--max-issues"],
description = "Return exit code 0 only when found issues count does not exceed specified issues count."
)
// nullable for 1.x.x to prefer maxIssues from config file
var maxIssues: Int? = null
@Parameter(
@@ -147,7 +147,8 @@ class CliArgs {
@Parameter(
names = ["--help", "-h"],
help = true, description = "Shows the usage."
help = true,
description = "Shows the usage."
)
var help: Boolean = false

View File

@@ -5,7 +5,8 @@ import io.github.detekt.tooling.internal.NotApiButProbablyUsedByUsers
import io.gitlab.arturbosch.detekt.api.Config
@NotApiButProbablyUsedByUsers
@Deprecated("""
@Deprecated(
"""
Exposes internal resource name. There should not be a case were just the resource name is needed.
Please use the DefaultConfigurationProvider to get a default config instance.
"""

View File

@@ -7,7 +7,6 @@ import io.gitlab.arturbosch.detekt.api.commaSeparatedPattern
internal fun CliArgs.createSpec(output: Appendable, error: Appendable): ProcessingSpec {
val args = this
return ProcessingSpec {
logging {
debug = args.debug
outputChannel = output

View File

@@ -25,9 +25,12 @@ class RunnerSpec : Spek({
val tmpReport = createTempFileForTest("RunnerSpec", ".txt")
executeDetekt(
"--input", inputPath.toString(),
"--report", "txt:$tmpReport",
"--config-resource", "/configs/max-issues-2.yml"
"--input",
inputPath.toString(),
"--report",
"txt:$tmpReport",
"--config-resource",
"/configs/max-issues-2.yml"
)
assertThat(Files.readAllLines(tmpReport)).hasSize(1)
@@ -36,8 +39,10 @@ class RunnerSpec : Spek({
it("should throw on maxIssues=0") {
assertThatThrownBy {
executeDetekt(
"--input", inputPath.toString(),
"--config-resource", "/configs/max-issues-0.yml"
"--input",
inputPath.toString(),
"--config-resource",
"/configs/max-issues-0.yml"
)
}.isExactlyInstanceOf(MaxIssuesReached::class.java)
}
@@ -46,9 +51,12 @@ class RunnerSpec : Spek({
val tmpReport = createTempFileForTest("RunnerSpec", ".txt")
executeDetekt(
"--input", inputPath.toString(),
"--report", "txt:$tmpReport",
"--config-resource", "/configs/max-issues--1.yml"
"--input",
inputPath.toString(),
"--report",
"txt:$tmpReport",
"--config-resource",
"/configs/max-issues--1.yml"
)
assertThat(Files.readAllLines(tmpReport)).hasSize(1)
@@ -60,10 +68,14 @@ class RunnerSpec : Spek({
val tmpReport = createTempFileForTest("RunnerSpec", ".txt")
executeDetekt(
"--input", inputPath.toString(),
"--report", "txt:$tmpReport",
"--config-resource", "/configs/max-issues-0.yml",
"--baseline", resourceAsPath("configs/baseline-with-two-excludes.xml").toString()
"--input",
inputPath.toString(),
"--report",
"txt:$tmpReport",
"--config-resource",
"/configs/max-issues-0.yml",
"--baseline",
resourceAsPath("configs/baseline-with-two-excludes.xml").toString()
)
assertThat(Files.readAllLines(tmpReport)).isEmpty()
@@ -117,8 +129,10 @@ class RunnerSpec : Spek({
beforeEachTest {
val args = parseArguments(
arrayOf(
"--input", inputPath.toString(),
"--config-resource", "/configs/max-issues-0.yml"
"--input",
inputPath.toString(),
"--config-resource",
"/configs/max-issues-0.yml"
)
)
@@ -143,8 +157,10 @@ class RunnerSpec : Spek({
it("should throw on invalid config property when validation=true") {
assertThatThrownBy {
executeDetekt(
"--input", path.toString(),
"--config-resource", "/configs/invalid-config.yml"
"--input",
path.toString(),
"--config-resource",
"/configs/invalid-config.yml"
)
}.isExactlyInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("property")
@@ -153,8 +169,10 @@ class RunnerSpec : Spek({
it("should throw on invalid config properties when validation=true") {
assertThatThrownBy {
executeDetekt(
"--input", path.toString(),
"--config-resource", "/configs/invalid-configs.yml"
"--input",
path.toString(),
"--config-resource",
"/configs/invalid-configs.yml"
)
}.isExactlyInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("properties")
@@ -163,8 +181,10 @@ class RunnerSpec : Spek({
it("should not throw on invalid config property when validation=false") {
assertThatCode {
executeDetekt(
"--input", path.toString(),
"--config-resource", "/configs/invalid-config_no-validation.yml"
"--input",
path.toString(),
"--config-resource",
"/configs/invalid-config_no-validation.yml"
)
}.doesNotThrowAnyException()
}
@@ -172,8 +192,10 @@ class RunnerSpec : Spek({
it("should not throw on deprecation warnings") {
assertThatCode {
executeDetekt(
"--input", path.toString(),
"--config-resource", "/configs/deprecated-property.yml"
"--input",
path.toString(),
"--config-resource",
"/configs/deprecated-property.yml"
)
}.doesNotThrowAnyException()
}
@@ -186,9 +208,12 @@ class RunnerSpec : Spek({
assertThatThrownBy {
executeDetekt(
"--input", inputPath.toString(),
"--report", "txt:$tmp",
"--run-rule", "test:test"
"--input",
inputPath.toString(),
"--report",
"txt:$tmp",
"--run-rule",
"test:test"
)
}.isExactlyInstanceOf(MaxIssuesReached::class.java)
assertThat(Files.readAllLines(tmp)).hasSize(1)
@@ -222,9 +247,12 @@ class RunnerSpec : Spek({
it("does fail via cli flag even if config>maxIssues is specified") {
assertThatThrownBy {
executeDetekt(
"--input", inputPath.toString(),
"--max-issues", "0",
"--config-resource", "configs/max-issues--1.yml" // allow any
"--input",
inputPath.toString(),
"--max-issues",
"0",
"--config-resource",
"configs/max-issues--1.yml" // allow any
)
}.isExactlyInstanceOf(MaxIssuesReached::class.java)
.hasMessage("Build failed with 1 weighted issues.")

View File

@@ -14,9 +14,9 @@ import io.gitlab.arturbosch.detekt.api.internal.CompilerResources
import io.gitlab.arturbosch.detekt.api.internal.whichDetekt
import io.gitlab.arturbosch.detekt.api.internal.whichJava
import io.gitlab.arturbosch.detekt.api.internal.whichOS
import io.gitlab.arturbosch.detekt.core.config.AllRulesConfig
import io.gitlab.arturbosch.detekt.core.config.DefaultConfig
import io.gitlab.arturbosch.detekt.core.config.DisabledAutoCorrectConfig
import io.gitlab.arturbosch.detekt.core.config.AllRulesConfig
import io.gitlab.arturbosch.detekt.core.rules.associateRuleIdsToRuleSetIds
import io.gitlab.arturbosch.detekt.core.rules.isActive
import io.gitlab.arturbosch.detekt.core.rules.shouldAnalyzeFile

View File

@@ -28,8 +28,11 @@ internal fun generateBindingContext(
)
analyzer.analyzeAndReport(files) {
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
environment.project, files, NoScopeRecordCliBindingTrace(),
environment.configuration, environment::createPackagePartProvider,
environment.project,
files,
NoScopeRecordCliBindingTrace(),
environment.configuration,
environment::createPackagePartProvider,
::FileBasedDeclarationProviderFactory
)
}

View File

@@ -28,7 +28,8 @@ import java.net.URI
class ProcessingSettings(
val spec: ProcessingSpec,
override val config: Config
) : AutoCloseable, Closeable,
) : AutoCloseable,
Closeable,
LoggingAware by LoggingFacade(spec.loggingSpec),
PropertiesAware by PropertiesFacade(),
EnvironmentAware by EnvironmentFacade(spec.projectSpec, spec.compilerSpec),

View File

@@ -22,10 +22,9 @@ class BaselineResultMapping : ReportingExtension {
override fun transformFindings(findings: Map<RuleSetId, List<Finding>>): Map<RuleSetId, List<Finding>> {
val baselineFile = baselineFile
require(
!createBaseline ||
(createBaseline && baselineFile != null)
) { "Invalid baseline options invariant." }
require(!createBaseline || (createBaseline && baselineFile != null)) {
"Invalid baseline options invariant."
}
return baselineFile?.let { findings.transformWithBaseline(it) } ?: findings
}

View File

@@ -26,11 +26,15 @@ fun Config.valueOrDefaultInternal(
default
}
} catch (_: ClassCastException) {
error("Value \"$result\" set for config parameter \"${keySequence(key)}\" is not of" +
" required type ${default::class.simpleName}.")
error(
"Value \"$result\" set for config parameter \"${keySequence(key)}\" is not of" +
" required type ${default::class.simpleName}."
)
} catch (_: NumberFormatException) {
error("Value \"$result\" set for config parameter \"${keySequence(key)}\" is not of" +
" required type ${default::class.simpleName}.")
error(
"Value \"$result\" set for config parameter \"${keySequence(key)}\" is not of" +
" required type ${default::class.simpleName}."
)
}
}

View File

@@ -74,7 +74,6 @@ internal fun validateConfig(
fun testKeys(current: Map<String, Any>, base: Map<String, Any>, parentPath: String?) {
for (prop in current.keys) {
val propertyPath = "${if (parentPath == null) "" else "$parentPath>"}$prop"
val deprecationWarning = DEPRECATED_PROPERTIES

View File

@@ -50,11 +50,13 @@ class YamlConfig internal constructor(
* and point to a readable file.
*/
fun load(path: Path): Config =
load(path.toFile().apply {
require(exists()) { "Configuration does not exist: $path" }
require(isFile) { "Configuration must be a file: $path" }
require(canRead()) { "Configuration must be readable: $path" }
}.reader())
load(
path.toFile().apply {
require(exists()) { "Configuration does not exist: $path" }
require(isFile) { "Configuration must be a file: $path" }
require(canRead()) { "Configuration must be readable: $path" }
}.reader()
)
/**
* Factory method to load a yaml configuration from a URL.

View File

@@ -25,8 +25,8 @@ class FileProcessorLocatorSpec : Spek({
assertThat(processorClasses).isNotEmpty
processorClasses
.filter { clazz -> processors.firstOrNull { clazz == it.javaClass } == null }
.forEach { fail("$it processor is not loaded by the FileProcessorLocator") }
.filter { clazz -> processors.firstOrNull { clazz == it.javaClass } == null }
.forEach { fail("$it processor is not loaded by the FileProcessorLocator") }
}
it("has disabled processors") {
@@ -39,6 +39,6 @@ class FileProcessorLocatorSpec : Spek({
private fun getProcessorClasses(): List<Class<out FileProcessListener>> {
return Reflections("io.github.detekt.metrics.processors")
.getSubTypesOf(FileProcessListener::class.java)
.filter { !Modifier.isAbstract(it.modifiers) }
.getSubTypesOf(FileProcessListener::class.java)
.filter { !Modifier.isAbstract(it.modifiers) }
}

View File

@@ -133,7 +133,8 @@ internal class SuppressionSpec : Spek({
}
it("rule should be suppressed by detekt prefix in uppercase with dot separator") {
val ktFile = compileContentForTest("""
val ktFile = compileContentForTest(
"""
@file:Suppress("Detekt.ALL")
object SuppressedWithDetektPrefix {
@@ -141,14 +142,16 @@ internal class SuppressionSpec : Spek({
println("FAILED TEST")
}
}
""")
"""
)
val rule = TestRule()
rule.visitFile(ktFile)
assertThat(rule.expected).isNotNull()
}
it("rule should be suppressed by detekt prefix in lowercase with colon separator") {
val ktFile = compileContentForTest("""
val ktFile = compileContentForTest(
"""
@file:Suppress("detekt:ALL")
object SuppressedWithDetektPrefix {
@@ -156,14 +159,16 @@ internal class SuppressionSpec : Spek({
println("FAILED TEST")
}
}
""")
"""
)
val rule = TestRule()
rule.visitFile(ktFile)
assertThat(rule.expected).isNotNull()
}
it("rule should be suppressed by detekt prefix in all caps with colon separator") {
val ktFile = compileContentForTest("""
val ktFile = compileContentForTest(
"""
@file:Suppress("DETEKT:ALL")
object SuppressedWithDetektPrefix {
@@ -171,7 +176,8 @@ internal class SuppressionSpec : Spek({
println("FAILED TEST")
}
}
""")
"""
)
val rule = TestRule()
rule.visitFile(ktFile)
assertThat(rule.expected).isNotNull()
@@ -181,7 +187,8 @@ internal class SuppressionSpec : Spek({
describe("suppression based on aliases from config property") {
it("allows to declare") {
val ktFile = compileContentForTest("""
val ktFile = compileContentForTest(
"""
@file:Suppress("detekt:MyTest")
object SuppressedWithDetektPrefixAndCustomConfigBasedPrefix {
@@ -189,7 +196,8 @@ internal class SuppressionSpec : Spek({
println("FAILED TEST")
}
}
""")
"""
)
val rule = TestRule(TestConfig(mutableMapOf("aliases" to "[MyTest]")))
rule.visitFile(ktFile)
assertThat(rule.expected).isNotNull()

View File

@@ -21,7 +21,8 @@ class SupportConfigValidationSpec : Spek({
val testDir by memoized { createTempDirectoryForTest("detekt-sample") }
it("fails when unknown properties are found") {
val config = yamlConfigFromContent("""
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
@@ -31,7 +32,8 @@ class SupportConfigValidationSpec : Spek({
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
""")
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it) }
.isInstanceOf(InvalidConfig::class.java)
@@ -41,13 +43,15 @@ class SupportConfigValidationSpec : Spek({
}
it("fails due to custom config validator want active to be booleans") {
val config = yamlConfigFromContent("""
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
# This property is tested via the SampleConfigValidator
active: 1 # should be true
""")
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it) }
.isInstanceOf(InvalidConfig::class.java)
@@ -56,7 +60,8 @@ class SupportConfigValidationSpec : Spek({
}
it("passes with excluded new properties") {
val config = yamlConfigFromContent("""
val config = yamlConfigFromContent(
"""
config:
validation: true
# Additional properties can be useful when writing custom extensions.
@@ -74,7 +79,8 @@ class SupportConfigValidationSpec : Spek({
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
""")
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it) }.doesNotThrowAnyException()
}

View File

@@ -13,10 +13,12 @@ class CompositeConfigTest : Spek({
val first by memoized { yamlConfig("detekt.yml") }
val compositeConfig by memoized { CompositeConfig(second, first) }
it("""
it(
"""
should have style sub config with active false which is overridden
in `second` config regardless of default value
""") {
"""
) {
val styleConfig = compositeConfig.subConfig("style").subConfig("WildcardImport")
assertThat(styleConfig.valueOrDefault("active", true)).isEqualTo(false)
assertThat(styleConfig.valueOrDefault("active", false)).isEqualTo(false)

View File

@@ -186,13 +186,15 @@ internal class ConfigValidationSpec : Spek({
).forEach { (testName, warningsAsErrors) ->
it(testName) {
val config = yamlConfigFromContent("""
val config = yamlConfigFromContent(
"""
config:
warningsAsErrors: $warningsAsErrors
naming:
FunctionParameterNaming:
ignoreOverriddenFunctions: ''
""".trimIndent())
""".trimIndent()
)
val result = validateConfig(config, config)

View File

@@ -60,10 +60,12 @@ internal class MaxIssueCheckTest : Spek({
describe("based on config") {
val config by memoized {
yamlConfigFromContent("""
yamlConfigFromContent(
"""
build:
maxIssues: 1
""".trimIndent())
""".trimIndent()
)
}
it("uses the config for max issues when MaxIssuePolicy == NonSpecified") {

View File

@@ -114,10 +114,12 @@ class YamlConfigSpec : Spek({
it("throws InvalidConfigurationError on invalid structured yaml files") {
assertThatCode {
yamlConfigFromContent("""
yamlConfigFromContent(
"""
map:
{}map
""".trimIndent())
""".trimIndent()
)
}.isInstanceOf(Config.InvalidConfigurationError::class.java)
.hasMessageContaining("Provided configuration file is invalid")
.hasCauseInstanceOf(ParserException::class.java)

View File

@@ -74,13 +74,21 @@ internal class OutputReportsSpec : Spek({
}
it("should recognize custom output format") {
assertThat(reports).haveExactly(1,
Condition(Predicate { it.type == reportUnderTest },
"Corresponds exactly to the test output report."))
assertThat(reports).haveExactly(
1,
Condition(
Predicate { it.type == reportUnderTest },
"Corresponds exactly to the test output report."
)
)
assertThat(extensions).haveExactly(1,
Condition(Predicate { it is TestOutputReport && it.ending == "yml" },
"Is exactly the test output report."))
assertThat(extensions).haveExactly(
1,
Condition(
Predicate { it is TestOutputReport && it.ending == "yml" },
"Is exactly the test output report."
)
)
}
}
}

View File

@@ -52,7 +52,8 @@ class FindingsReportSpec : Spek({
it("reports no findings with rule set containing no smells") {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
Pair("Ruleset", emptyList()))
Pair("Ruleset", emptyList())
)
}
assertThat(subject.render(detektion)).isNull()
}

View File

@@ -16,7 +16,9 @@ class ProjectStatisticsReportSpec : Spek({
val expected = "Project Statistics:\n\t- M2: 2\n\t- M1: 1\n"
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 1, priority = 1), ProjectMetric("M2", 2, priority = 2))
ProjectMetric("M1", 1, priority = 1),
ProjectMetric("M2", 2, priority = 2)
)
}
assertThat(subject.render(detektion)).isEqualTo(expected)
}

View File

@@ -40,11 +40,13 @@ internal class SingleRuleProviderSpec : Spek({
arrayOf("true", "false").forEach { value ->
it("configures rule with active=$value") {
val config = yamlConfigFromContent("""
val config = yamlConfigFromContent(
"""
style:
MagicNumber:
active: $value
""".trimIndent())
""".trimIndent()
)
assertThat(produceRule(provider, config).active).isEqualTo(value.toBoolean())
}

View File

@@ -22,6 +22,5 @@ class FormattingProvider : RuleSetProvider {
override val ruleSetId: String = "formatting"
override fun instance(config: Config) =
RuleSet(ruleSetId, listOf(KtLintMultiRule(config)))
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(KtLintMultiRule(config)))
}

View File

@@ -20,20 +20,24 @@ class FinalNewlineSpec : Spek({
}
it("should not report as new line is present") {
val findings = FinalNewline(Config.empty).lint("""
val findings = FinalNewline(Config.empty).lint(
"""
fun main() = Unit
""")
"""
)
assertThat(findings).isEmpty()
}
it("should report new line when configured") {
val findings = FinalNewline(TestConfig(INSERT_FINAL_NEWLINE to "false"))
.lint("""
.lint(
"""
fun main() = Unit
""")
"""
)
assertThat(findings).hasSize(1)
}

View File

@@ -15,21 +15,25 @@ class FormattingRuleSpec : Spek({
describe("formatting rules can be suppressed") {
it("does support suppression only on file level") {
val findings = subject.lint("""
val findings = subject.lint(
"""
@file:Suppress("NoLineBreakBeforeAssignment")
fun main()
= Unit
""".trimIndent())
""".trimIndent()
)
assertThat(findings).isEmpty()
}
it("does not support suppression on node level") {
val findings = subject.lint("""
val findings = subject.lint(
"""
@Suppress("NoLineBreakBeforeAssignment")
fun main()
= Unit
""".trimIndent())
""".trimIndent()
)
assertThat(findings).hasSize(1)
}
@@ -38,20 +42,24 @@ class FormattingRuleSpec : Spek({
describe("formatting rules have a signature") {
it("has no package name") {
val findings = subject.lint("""
val findings = subject.lint(
"""
fun main()
= Unit
""".trimIndent())
""".trimIndent()
)
assertThat(findings.first().signature).isEqualTo("Test.kt:2")
}
it("has a package name") {
val findings = subject.lint("""
val findings = subject.lint(
"""
package test.test.test
fun main()
= Unit
""".trimIndent())
""".trimIndent()
)
assertThat(findings.first().signature).isEqualTo("test.test.test.Test.kt:3")
}

View File

@@ -19,7 +19,8 @@ class ImportOrderingSpec : Spek({
describe("different import ordering layouts") {
it("defaults to the idea layout") {
val findings = ImportOrdering(Config.empty).lint("""
val findings = ImportOrdering(Config.empty).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
@@ -31,7 +32,8 @@ class ImportOrderingSpec : Spek({
import kotlin.io.Closeable
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent())
""".trimIndent()
)
assertThat(findings).isEmpty()
}
@@ -77,7 +79,8 @@ class ImportOrderingSpec : Spek({
describe("supports custom patterns") {
it("misses a empty line between aliases and other imports") {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint("""
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
@@ -85,12 +88,14 @@ class ImportOrderingSpec : Spek({
import kotlin.concurrent.Thread
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent())
""".trimIndent()
)
assertThat(findings).hasSize(1)
}
it("passes for empty line between aliases and other imports") {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint("""
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
@@ -99,7 +104,8 @@ class ImportOrderingSpec : Spek({
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent())
""".trimIndent()
)
assertThat(findings).isEmpty()
}

View File

@@ -70,7 +70,9 @@ val verifyGeneratorOutput by tasks.registering(Exec::class) {
standardOutput = configDiff
if (configDiff.toString().isNotEmpty()) {
throw GradleException("The default-detekt-config.yml is not up-to-date. " +
"You can execute the generateDocumentation Gradle task to update it and commit the changed files.")
throw GradleException(
"The default-detekt-config.yml is not up-to-date. " +
"You can execute the generateDocumentation Gradle task to update it and commit the changed files."
)
}
}

View File

@@ -7,28 +7,39 @@ import java.nio.file.Paths
class GeneratorArgs {
@Parameter(names = ["--input", "-i"],
@Parameter(
names = ["--input", "-i"],
required = true,
description = "Input paths to analyze.")
description = "Input paths to analyze."
)
private var input: String? = null
@Parameter(names = ["--documentation", "-d"],
@Parameter(
names = ["--documentation", "-d"],
required = true,
description = "Output path for generated documentation.")
description = "Output path for generated documentation."
)
private var documentation: String? = null
@Parameter(names = ["--config", "-c"],
@Parameter(
names = ["--config", "-c"],
required = true,
description = "Output path for generated detekt config.")
description = "Output path for generated detekt config."
)
private var config: String? = null
@Parameter(names = ["--cli-options"],
@Parameter(
names = ["--cli-options"],
required = true,
description = "Output path for generated cli options page.")
description = "Output path for generated cli options page."
)
private var cliOptions: String? = null
@Parameter(names = ["--help", "-h"],
help = true, description = "Shows the usage.")
@Parameter(
names = ["--help", "-h"],
help = true,
description = "Shows the usage."
)
var help: Boolean = false
val inputPath: List<Path> by lazy {
@@ -41,15 +52,19 @@ class GeneratorArgs {
.toList()
}
val documentationPath: Path
get() = Paths.get(checkNotNull(documentation) {
"Documentation output path was not initialized by jcommander!"
})
get() = Paths.get(
checkNotNull(documentation) {
"Documentation output path was not initialized by jcommander!"
}
)
val configPath: Path
get() = Paths.get(checkNotNull(config) { "Configuration output path was not initialized by jcommander!" })
val cliOptionsPath: Path
get() = Paths.get(checkNotNull(cliOptions) {
"Cli options output path was not initialized by jcommander!"
})
get() = Paths.get(
checkNotNull(cliOptions) {
"Cli options output path was not initialized by jcommander!"
}
)
}

View File

@@ -49,11 +49,11 @@ class DetektCollector : Collector<RuleSetPage> {
private fun List<Rule>.resolveParentRule(rules: List<Rule>) {
this.filter { it.debt.isEmpty() && it.severity.isEmpty() }
.forEach {
val parentRule = rules.findRuleByName(it.parent)
it.debt = parentRule.debt
it.severity = parentRule.severity
}
.forEach {
val parentRule = rules.findRuleByName(it.parent)
it.debt = parentRule.debt
it.severity = parentRule.severity
}
}
override fun visit(file: KtFile) {

View File

@@ -48,7 +48,7 @@ class MultiRuleVisitor : DetektVisitor() {
val rules = mutableListOf<String>()
val ruleProperties = rulesVisitor.ruleProperties
.mapNotNull { properties[it] }
.mapNotNull { properties[it] }
rules.addAll(ruleProperties)
rules.addAll(rulesVisitor.ruleNames)
@@ -63,8 +63,8 @@ class MultiRuleVisitor : DetektVisitor() {
override fun visitSuperTypeList(list: KtSuperTypeList) {
val isMultiRule = list.entries
?.mapNotNull { it.typeAsUserType?.referencedName }
?.any { it == multiRule } ?: false
?.mapNotNull { it.typeAsUserType?.referencedName }
?.any { it == multiRule } ?: false
val containingClass = list.containingClass()
val className = containingClass?.name
@@ -112,13 +112,17 @@ class RuleListVisitor : DetektVisitor() {
val argumentExpressions = list.arguments.map { it.getArgumentExpression() }
// Call Expression = Constructor of rule
ruleNames.addAll(argumentExpressions
ruleNames.addAll(
argumentExpressions
.filterIsInstance<KtCallExpression>()
.map { it.calleeExpression?.text ?: "" })
.map { it.calleeExpression?.text ?: "" }
)
// Reference Expression = variable we need to search for
ruleProperties.addAll(argumentExpressions
ruleProperties.addAll(
argumentExpressions
.filterIsInstance<KtReferenceExpression>()
.map { it.text ?: "" })
.map { it.text ?: "" }
)
}
}

View File

@@ -80,8 +80,10 @@ class RuleSetProviderVisitor : DetektVisitor() {
super.visitProperty(property)
if (property.isOverride() && property.name != null && property.name == PROPERTY_RULE_SET_ID) {
name = (property.initializer as? KtStringTemplateExpression)?.entries?.get(0)?.text
?: throw InvalidDocumentationException("RuleSetProvider class " +
"${property.containingClass()?.name ?: ""} doesn't provide list of rules.")
?: throw InvalidDocumentationException(
"RuleSetProvider class " +
"${property.containingClass()?.name ?: ""} doesn't provide list of rules."
)
}
}
@@ -90,14 +92,14 @@ class RuleSetProviderVisitor : DetektVisitor() {
if (expression.calleeExpression?.text == "RuleSet") {
val ruleListExpression = expression.valueArguments
.map { it.getArgumentExpression() }
.firstOrNull { it?.referenceExpression()?.text == "listOf" }
.map { it.getArgumentExpression() }
.firstOrNull { it?.referenceExpression()?.text == "listOf" }
?: throw InvalidDocumentationException("RuleSetProvider $name doesn't provide list of rules.")
val ruleArgumentNames = (ruleListExpression as? KtCallExpression)
?.valueArguments
?.map { it.getArgumentExpression() }
?.mapNotNull { it?.referenceExpression()?.text }
?.valueArguments
?.map { it.getArgumentExpression() }
?.mapNotNull { it?.referenceExpression()?.text }
?: emptyList()
ruleNames.addAll(ruleArgumentNames)

View File

@@ -1,5 +1,5 @@
package io.gitlab.arturbosch.detekt.generator.collection.exception
class InvalidAliasesDeclaration : RuntimeException(
"Invalid aliases declaration. Example: override val defaultRuleIdAliases = setOf(\"Name1\", \"Name2\")"
"Invalid aliases declaration. Example: override val defaultRuleIdAliases = setOf(\"Name1\", \"Name2\")"
)

View File

@@ -12,7 +12,8 @@ class CliOptionsPrinter {
}
fun print(filePath: Path) {
Files.write(filePath,
Files.write(
filePath,
buildString {
appendLine("```")
jCommander.usageFormatter.usage(this)

View File

@@ -40,12 +40,12 @@ object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
}
ruleSet.configuration
.forEach { configuration ->
if (configuration.defaultValue.isYamlList()) {
list(configuration.name, configuration.defaultValue.toList())
} else {
keyValue { configuration.name to configuration.defaultValue }
if (configuration.defaultValue.isYamlList()) {
list(configuration.name, configuration.defaultValue.toList())
} else {
keyValue { configuration.name to configuration.defaultValue }
}
}
}
rules.forEach { rule ->
node(rule.name) {
keyValue { Config.ACTIVE_KEY to "${rule.active}" }
@@ -58,12 +58,12 @@ object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
}
rule.configuration
.forEach { configuration ->
if (configuration.defaultValue.isYamlList()) {
list(configuration.name, configuration.defaultValue.toList())
} else if (configuration.deprecated == null) {
keyValue { configuration.name to configuration.defaultValue }
if (configuration.defaultValue.isYamlList()) {
list(configuration.name, configuration.defaultValue.toList())
} else if (configuration.deprecated == null) {
keyValue { configuration.name to configuration.defaultValue }
}
}
}
}
}
emptyLine()

View File

@@ -23,7 +23,7 @@ abstract class Exclusions {
private object TestExclusions : Exclusions() {
override val pattern =
"['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']"
"['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']"
override val ruleSets = setOf("comments")
override val rules = setOf(
"NamingRules",

View File

@@ -42,7 +42,7 @@ object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {
paragraph {
"${bold { "Active by default" }}: ${if (rule.active) "Yes" else "No"}" +
(rule.activeSince?.let { " - Since $it" } ?: "")
(rule.activeSince?.let { " - Since $it" } ?: "")
}
if (rule.requiresTypeResolution) {
@@ -75,7 +75,8 @@ object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {
} else {
val defaultValues = it.defaultValue.lines()
val defaultValuesString = defaultValues.joinToString {
value -> value.trim().removePrefix("- ")
value ->
value.trim().removePrefix("- ")
}
item { "${code { it.name }} (default: ${code { defaultValuesString }})" }
}

View File

@@ -56,7 +56,7 @@ class RuleSetProviderCollectorSpec : Spek({
"""
it("throws an exception") {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
.isThrownBy { subject.run(code) }
}
}
@@ -73,7 +73,7 @@ class RuleSetProviderCollectorSpec : Spek({
it("throws an exception") {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
.isThrownBy { subject.run(code) }
}
}
@@ -179,7 +179,7 @@ class RuleSetProviderCollectorSpec : Spek({
it("throws an exception") {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
.isThrownBy { subject.run(code) }
}
}
@@ -202,7 +202,7 @@ class RuleSetProviderCollectorSpec : Spek({
it("throws an exception") {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
.isThrownBy { subject.run(code) }
}
}
@@ -222,7 +222,7 @@ class RuleSetProviderCollectorSpec : Spek({
it("throws an exception") {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
.isThrownBy { subject.run(code) }
}
}

View File

@@ -32,10 +32,12 @@ dependencies {
constraints {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.0") {
because("""Android Gradle Plugin 4.1.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.
because(
"""Android Gradle Plugin 4.1.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.
This constraint should be lifted on Android Gradle Plugin 4.2.0. See
https://dl.google.com/android/maven2/com/android/tools/build/gradle/4.2.0-beta02/gradle-4.2.0-beta02.pom
""")
"""
)
}
}
}
@@ -76,13 +78,17 @@ pluginBundle {
}
tasks.processResources {
filter<org.apache.tools.ant.filters.ReplaceTokens>("tokens" to mapOf(
"detektVersion" to project.version as String
))
filter<org.apache.tools.ant.filters.ReplaceTokens>(
"tokens" to mapOf(
"detektVersion" to project.version as String
)
)
}
tasks.processTestResources {
filter<org.apache.tools.ant.filters.ReplaceTokens>("tokens" to mapOf(
"detektVersion" to project.version as String
))
filter<org.apache.tools.ant.filters.ReplaceTokens>(
"tokens" to mapOf(
"detektVersion" to project.version as String
)
)
}

View File

@@ -122,6 +122,7 @@ open class Detekt @Inject constructor(
@get:Internal
internal val failFastProp: Property<Boolean> = project.objects.property(Boolean::class.javaObjectType)
@Deprecated("Please use the buildUponDefaultConfig and allRules flags instead.", ReplaceWith("allRules"))
var failFast: Boolean
@Input
@@ -212,8 +213,9 @@ open class Detekt @Inject constructor(
@TaskAction
fun check() {
if (failFastProp.getOrElse(false)) {
project.logger.warn("'failFast' is deprecated. Please use " +
"'buildUponDefaultConfig' together with 'allRules'.")
project.logger.warn(
"'failFast' is deprecated. Please use 'buildUponDefaultConfig' together with 'allRules'."
)
}
val arguments = mutableListOf(

View File

@@ -113,8 +113,9 @@ open class DetektCreateBaselineTask : SourceTask() {
@TaskAction
fun baseline() {
if (failFast.getOrElse(false)) {
project.logger.warn("'failFast' is deprecated. Please use " +
"'buildUponDefaultConfig' together with 'allRules'.")
project.logger.warn(
"'failFast' is deprecated. Please use 'buildUponDefaultConfig' together with 'allRules'."
)
}
val arguments = mutableListOf(

View File

@@ -12,6 +12,7 @@ open class DetektExtension @Inject constructor(objects: ObjectFactory) : CodeQua
var ignoreFailures: Boolean
@JvmName("ignoreFailures_")
get() = isIgnoreFailures
@JvmName("ignoreFailures_")
set(value) {
isIgnoreFailures = value

View File

@@ -24,7 +24,7 @@ internal class DetektAndroid(private val project: Project) {
project.tasks.register("${DetektPlugin.DETEKT_TASK_NAME}Main") {
it.group = "verification"
it.description = "EXPERIMENTAL: Run detekt analysis for production classes across " +
"all variants with type resolution"
"all variants with type resolution"
}
}
@@ -32,7 +32,7 @@ internal class DetektAndroid(private val project: Project) {
project.tasks.register("${DetektPlugin.DETEKT_TASK_NAME}Test") {
it.group = "verification"
it.description = "EXPERIMENTAL: Run detekt analysis for test classes across " +
"all variants with type resolution"
"all variants with type resolution"
}
}
@@ -40,7 +40,7 @@ internal class DetektAndroid(private val project: Project) {
project.tasks.register("${DetektPlugin.BASELINE_TASK_NAME}Main") {
it.group = "verification"
it.description = "EXPERIMENTAL: Creates detekt baseline files for production classes across " +
"all variants with type resolution"
"all variants with type resolution"
}
}
@@ -48,7 +48,7 @@ internal class DetektAndroid(private val project: Project) {
project.tasks.register("${DetektPlugin.BASELINE_TASK_NAME}Test") {
it.group = "verification"
it.description = "EXPERIMENTAL: Creates detekt baseline files for test classes across " +
"all variants with type resolution"
"all variants with type resolution"
}
}
@@ -100,8 +100,8 @@ internal class DetektAndroid(private val project: Project) {
private fun DetektExtension.matchesIgnoredConfiguration(variant: BaseVariant): Boolean =
ignoredVariants.contains(variant.name) ||
ignoredBuildTypes.contains(variant.buildType.name) ||
ignoredFlavors.contains(variant.flavorName)
ignoredBuildTypes.contains(variant.buildType.name) ||
ignoredFlavors.contains(variant.flavorName)
private fun Project.registerAndroidDetektTask(
bootClasspath: FileCollection,

View File

@@ -40,9 +40,14 @@ internal data class InputArgument(val fileCollection: FileCollection) : CliArgum
}
internal data class ClasspathArgument(val fileCollection: FileCollection) : CliArgument() {
override fun toArgument() = if (!fileCollection.isEmpty) listOf(
CLASSPATH_PARAMETER,
fileCollection.joinToString(File.pathSeparator) { it.absolutePath }) else emptyList()
override fun toArgument() = if (!fileCollection.isEmpty) {
listOf(
CLASSPATH_PARAMETER,
fileCollection.joinToString(File.pathSeparator) { it.absolutePath }
)
} else {
emptyList()
}
}
internal data class LanguageVersionArgument(val languageVersion: String?) : CliArgument() {
@@ -89,11 +94,14 @@ internal sealed class BoolCliArgument(open val value: Boolean, val configSwitch:
internal data class DebugArgument(override val value: Boolean) : BoolCliArgument(value, DEBUG_PARAMETER)
internal data class ParallelArgument(override val value: Boolean) : BoolCliArgument(value, PARALLEL_PARAMETER)
internal data class DisableDefaultRuleSetArgument(override val value: Boolean) :
BoolCliArgument(value, DISABLE_DEFAULT_RULESETS_PARAMETER)
internal data class BuildUponDefaultConfigArgument(override val value: Boolean) :
BoolCliArgument(value, BUILD_UPON_DEFAULT_CONFIG_PARAMETER)
internal data class DisableDefaultRuleSetArgument(
override val value: Boolean
) : BoolCliArgument(value, DISABLE_DEFAULT_RULESETS_PARAMETER)
internal data class BuildUponDefaultConfigArgument(
override val value: Boolean
) : BoolCliArgument(value, BUILD_UPON_DEFAULT_CONFIG_PARAMETER)
internal data class FailFastArgument(override val value: Boolean) : BoolCliArgument(value, FAIL_FAST_PARAMETER)

View File

@@ -48,7 +48,8 @@ internal class DefaultCliInvoker(
try {
val loader = classLoaderCache.getOrCreate(classpath)
val clazz = loader.loadClass("io.gitlab.arturbosch.detekt.cli.Main")
val runner = clazz.getMethod("buildRunner",
val runner = clazz.getMethod(
"buildRunner",
Array<String>::class.java,
PrintStream::class.java,
PrintStream::class.java

View File

@@ -1,8 +1,8 @@
package io.gitlab.arturbosch.detekt.report
import io.github.detekt.sarif4j.SarifSchema210
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import io.github.detekt.sarif4j.SarifSchema210
import java.io.File
/**

View File

@@ -33,20 +33,24 @@ object DetektAndroidTest : Spek({
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":app:detektMain",
":app:detektDebug"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektMain",
":app:detektDebug"
)
)
}
gradleRunner.runTasksAndCheckResult(":app:detektTest") { buildResult ->
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":app:detektDebugUnitTest",
":app:detektDebugAndroidTest"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektDebugUnitTest",
":app:detektDebugAndroidTest"
)
)
}
gradleRunner.runTasksAndCheckResult(":app:check") { buildResult ->
@@ -102,20 +106,24 @@ object DetektAndroidTest : Spek({
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektMain",
":lib:detektDebug"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektMain",
":lib:detektDebug"
)
)
}
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektDebugUnitTest",
":lib:detektDebugAndroidTest"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektDebugUnitTest",
":lib:detektDebugAndroidTest"
)
)
}
gradleRunner.runTasksAndCheckResult(":lib:check") { buildResult ->
@@ -145,21 +153,25 @@ object DetektAndroidTest : Spek({
gradleRunner.writeProjectFile("lib/src/main/AndroidManifest.xml", MANIFEST_CONTENT)
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
)
}
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryReleaseUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
))
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryReleaseUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
)
}
}
@@ -184,21 +196,25 @@ object DetektAndroidTest : Spek({
gradleRunner.writeProjectFile("lib/src/main/AndroidManifest.xml", MANIFEST_CONTENT)
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug"
)
).doesNotContain(
":lib:detektOldHarryRelease"
)
}
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
).doesNotContain(
":lib:detektOldHarryReleaseUnitTest"
)
}
@@ -225,19 +241,23 @@ object DetektAndroidTest : Spek({
gradleRunner.writeProjectFile("lib/src/main/AndroidManifest.xml", MANIFEST_CONTENT)
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektOldHarryDebug"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug"
)
).doesNotContain(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryRelease"
)
}
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest"
)
).doesNotContain(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
@@ -266,20 +286,24 @@ object DetektAndroidTest : Spek({
gradleRunner.writeProjectFile("lib/src/main/AndroidManifest.xml", MANIFEST_CONTENT)
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
).doesNotContain(
":lib:detektYoungHarryDebug"
)
}
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)).doesNotContain(
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)
).doesNotContain(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest"
)

View File

@@ -13,7 +13,10 @@ class DetektMultiplatformTest : Spek({
describe("multiplatform projects - Common target") {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
$KMM_PLUGIN_BLOCK
kotlin {
@@ -45,7 +48,10 @@ class DetektMultiplatformTest : Spek({
describe("multiplatform projects - detekt plain only if user opts out") {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
$KMM_PLUGIN_BLOCK
kotlin {
@@ -73,7 +79,10 @@ class DetektMultiplatformTest : Spek({
describe("multiplatform projects - JVM target") {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
$KMM_PLUGIN_BLOCK
kotlin {
@@ -119,9 +128,13 @@ class DetektMultiplatformTest : Spek({
describe(
"multiplatform projects - Android target",
skip = if (isAndroidSdkInstalled()) Skip.No else Skip.Yes("No android sdk.")) {
skip = if (isAndroidSdkInstalled()) Skip.No else Skip.Yes("No android sdk.")
) {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
plugins {
id "kotlin-multiplatform"
@@ -180,7 +193,10 @@ class DetektMultiplatformTest : Spek({
describe("multiplatform projects - JS target") {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
$KMM_PLUGIN_BLOCK
kotlin {
@@ -221,7 +237,8 @@ class DetektMultiplatformTest : Spek({
}
}
describe("multiplatform projects - iOS target",
describe(
"multiplatform projects - iOS target",
skip = if (isMacOs()) {
Skip.No
} else {
@@ -229,7 +246,10 @@ class DetektMultiplatformTest : Spek({
}
) {
val gradleRunner = setupProject {
addSubmodule("shared", 1, 1,
addSubmodule(
"shared",
1,
1,
buildFileContent = """
$KMM_PLUGIN_BLOCK
kotlin {

View File

@@ -17,10 +17,12 @@ internal class DetektTaskMultiModuleIntegrationTest : Spek({
describe("using ${builder.gradleBuildName}") {
it("""
it(
"""
|is applied with defaults to all subprojects individually
|without sources in root project using the subprojects block
""".trimMargin()) {
""".trimMargin()
) {
val projectLayout = ProjectLayout(0).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
@@ -57,10 +59,12 @@ internal class DetektTaskMultiModuleIntegrationTest : Spek({
}
}
it("""
it(
"""
|is applied with defaults to main project
|and subprojects individually using the allprojects block
""".trimMargin()) {
""".trimMargin()
) {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)

View File

@@ -24,11 +24,13 @@ class GenerateConfigTaskTest : Spek({
}
it("chooses the last config file when configured") {
val gradleRunner = builder.withDetektConfig("""
val gradleRunner = builder.withDetektConfig(
"""
|detekt {
| config = files("config/detekt/detekt.yml", "config/other/detekt.yml")
|}
""").build()
"""
).build()
gradleRunner.runTasksAndCheckResult("detektGenerateConfig") { result ->
assertThat(result.task(":detektGenerateConfig")?.outcome).isEqualTo(TaskOutcome.SUCCESS)

View File

@@ -13,24 +13,28 @@ internal class XmlReportMergerSpec : Spek({
it("passes for same files") {
val file1 = File.createTempFile("detekt1", "xml").apply {
writeText("""
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent())
""".trimIndent()
)
}
val file2 = File.createTempFile("detekt2", "xml").apply {
writeText("""
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent())
""".trimIndent()
)
}
val output = File.createTempFile("output", "xml")
XmlReportMerger.merge(setOf(file1, file2), output)

View File

@@ -81,7 +81,8 @@ class DslGradleRunner @Suppress("LongParameterList") constructor(
writeKtFile(
dir = File(moduleRoot, moduleSourceDir),
className = "My$srcDirIdx${submodule.name}${it}Class",
withCodeSmell = withCodeSmell)
withCodeSmell = withCodeSmell
)
}
}
}

View File

@@ -18,8 +18,8 @@ abstract class AbstractProcessor : FileProcessListener {
override fun onFinish(files: List<KtFile>, result: Detektion, bindingContext: BindingContext) {
val count = files
.mapNotNull { it.getUserData(key) }
.sum()
.mapNotNull { it.getUserData(key) }
.sum()
result.addData(key, count)
}
}

View File

@@ -11,8 +11,8 @@ abstract class AbstractProjectMetricProcessor : AbstractProcessor() {
override fun onFinish(files: List<KtFile>, result: Detektion, bindingContext: BindingContext) {
val count = files
.mapNotNull { it.getUserData(key) }
.sum()
.mapNotNull { it.getUserData(key) }
.sum()
result.add(ProjectMetric(type, count))
}
}

View File

@@ -19,9 +19,9 @@ class PackageCountProcessor : FileProcessListener {
override fun onFinish(files: List<KtFile>, result: Detektion, bindingContext: BindingContext) {
val count = files
.mapNotNull { it.getUserData(key) }
.distinct()
.size
.mapNotNull { it.getUserData(key) }
.distinct()
.size
result.add(ProjectMetric(key.toString(), count))
}
}

View File

@@ -1,7 +1,7 @@
package io.github.detekt.metrics.processors
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.github.detekt.metrics.processors.util.LLOC
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

View File

@@ -24,9 +24,9 @@ class SLOCVisitor : DetektVisitor() {
fun count(lines: List<String>): Int {
return lines
.map { it.trim() }
.filter { trim -> trim.isNotEmpty() && !comments.any { trim.startsWith(it) } }
.size
.map { it.trim() }
.filter { trim -> trim.isNotEmpty() && !comments.any { trim.startsWith(it) } }
.size
}
}
}

View File

@@ -25,7 +25,6 @@ object LLOC {
@Suppress("LoopWithTooManyJumpStatements")
fun run(): Int {
for (line in lines) {
val trimmed = line.trim()
if (trimmed.isEmpty()) {
@@ -83,7 +82,6 @@ object LLOC {
}
private fun frequency(source: String, part: String): Int {
if (source.isEmpty() || part.isEmpty()) {
return 0
}

View File

@@ -10,7 +10,8 @@ class CognitiveComplexitySpec : Spek({
describe("cognitive complexity") {
it("sums seven for sumOfPrimes example") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun sumOfPrimes(max: Int): Int {
var total = 0
next@ for (i in 1..max) {
@@ -24,20 +25,23 @@ class CognitiveComplexitySpec : Spek({
}
return total
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(7)
}
it("sums one for getWords example for a single when expression") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun getWords(number: Int): String = when (number) {
1 -> "one"
2 -> "a couple"
3 -> "a few"
else -> "lots"
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
@@ -45,62 +49,73 @@ class CognitiveComplexitySpec : Spek({
describe("recursion") {
it("adds one for recursion inside class") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
class A {
fun factorial(n: Int): Int =
if (n >= 1) n * this.factorial(n - 1) else 1
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
it("adds one for top level recursion") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun factorial(n: Int): Int =
if (n >= 1) n * factorial(n - 1) else 1
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
it("does not add as it is only the same name") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
object O { fun factorial(i: Int): Int = i - 1 }
fun factorial(n: Int): Int =
if (n >= 1) n * O.factorial(n - 1) else 1
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
}
it("ignores shorthand operators") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun parse(args: Array<String>): Nothing = TODO()
fun main(args: Array<String>) {
args.takeIf { it.size > 3 }?.let(::parse) ?: error("not enough arguments")
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(0)
}
it("adds one per catch clause") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun main() {
try {
} catch (e: IllegalArgumentException) {
} catch (e: IllegalStateException) {
} catch (e: Throwable) {}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
it("adds extra complexity for nesting") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun main() {
try {
if (true) { // +1
@@ -114,23 +129,28 @@ class CognitiveComplexitySpec : Spek({
do {} while(true) // +2
}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(9)
}
it("adds nesting for lambdas but not complexity") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun main() { run { if (true) {} } }
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
it("adds nesting for nested functions but not complexity") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun main() { fun run() { if (true) {} } }
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@@ -138,9 +158,11 @@ class CognitiveComplexitySpec : Spek({
describe("binary expressions") {
it("does not increment on just a condition") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) = !cond
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(0)
}
@@ -148,31 +170,38 @@ class CognitiveComplexitySpec : Spek({
describe("increments for every non-like operator") {
it("adds one for just a &&") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) = !cond && !cond
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
it("adds only one for repeated &&") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) = !cond && !cond && !cond
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
it("adds one per logical alternate operator") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) = !cond && !cond || cond
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
it("adds one per logical alternate operator with like operators in between") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) {
if ( // +1
!cond
@@ -181,39 +210,45 @@ class CognitiveComplexitySpec : Spek({
&& cond // +1
) {}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(4)
}
it("adds one for negated but similar operators") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) {
if ( // +1
!cond
&& !(cond && cond) // +2
) {}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
it("adds only one for a negated chain of similar operators") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) {
if ( // +1
!cond
&& !(cond && cond && cond) // +2
) {}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
it("adds one for every negated similar operator chain") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(cond: Boolean) {
if ( // +1
!cond
@@ -221,7 +256,8 @@ class CognitiveComplexitySpec : Spek({
|| !(cond || cond) // +2
) {}
}
""")
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(5)
}

View File

@@ -13,9 +13,11 @@ class CyclomaticComplexitySpec : Spek({
describe("basic function expressions are tested") {
it("counts for safe navigation") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test() = null as? String ?: ""
""")
"""
)
val actual = CyclomaticComplexity.calculate(code)
@@ -23,9 +25,11 @@ class CyclomaticComplexitySpec : Spek({
}
it("counts if and && and || expressions") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test() = if (true || true && false) 1 else 0
""")
"""
)
val actual = CyclomaticComplexity.calculate(code)
@@ -33,7 +37,8 @@ class CyclomaticComplexitySpec : Spek({
}
it("counts while, continue and break") {
val code = compileContentForTest("""
val code = compileContentForTest(
"""
fun test(i: Int) {
var j = i
while(true) { // 1
@@ -47,7 +52,8 @@ class CyclomaticComplexitySpec : Spek({
}
println("finished")
}
""")
"""
)
val actual = CyclomaticComplexity.calculate(code)
@@ -58,7 +64,8 @@ class CyclomaticComplexitySpec : Spek({
describe("counts function calls used for nesting") {
val code by memoized {
compileContentForTest("""
compileContentForTest(
"""
fun test(i: Int) {
(1..10).forEach { println(it) }
}
@@ -100,7 +107,8 @@ class CyclomaticComplexitySpec : Spek({
describe("ignoreSimpleWhenEntries is false") {
it("counts simple when branches as 1") {
val function = compileContentForTest("""
val function = compileContentForTest(
"""
fun test() {
when (System.currentTimeMillis()) {
0 -> println("Epoch!")
@@ -108,7 +116,8 @@ class CyclomaticComplexitySpec : Spek({
else -> println("Meh")
}
}
""").getFunctionByName("test")
"""
).getFunctionByName("test")
val actual = CyclomaticComplexity.calculate(function) {
ignoreSimpleWhenEntries = false
@@ -118,7 +127,8 @@ class CyclomaticComplexitySpec : Spek({
}
it("counts block when branches as 1") {
val function = compileContentForTest("""
val function = compileContentForTest(
"""
fun test() {
when (System.currentTimeMillis()) {
0 -> {
@@ -128,7 +138,8 @@ class CyclomaticComplexitySpec : Spek({
else -> println("Meh")
}
}
""").getFunctionByName("test")
"""
).getFunctionByName("test")
val actual = CyclomaticComplexity.calculate(function) {
ignoreSimpleWhenEntries = false
@@ -141,7 +152,8 @@ class CyclomaticComplexitySpec : Spek({
describe("ignoreSimpleWhenEntries is true") {
it("counts a when with only simple branches as 1") {
val function = compileContentForTest("""
val function = compileContentForTest(
"""
fun test() {
when (System.currentTimeMillis()) {
0 -> println("Epoch!")
@@ -149,7 +161,8 @@ class CyclomaticComplexitySpec : Spek({
else -> println("Meh")
}
}
""").getFunctionByName("test")
"""
).getFunctionByName("test")
val actual = CyclomaticComplexity.calculate(function) {
ignoreSimpleWhenEntries = true
@@ -159,7 +172,8 @@ class CyclomaticComplexitySpec : Spek({
}
it("does not count simple when branches") {
val function = compileContentForTest("""
val function = compileContentForTest(
"""
fun test() {
when (System.currentTimeMillis()) {
0 -> {
@@ -172,7 +186,8 @@ class CyclomaticComplexitySpec : Spek({
else -> println("Meh")
}
}
""").getFunctionByName("test")
"""
).getFunctionByName("test")
val actual = CyclomaticComplexity.calculate(function) {
ignoreSimpleWhenEntries = true
@@ -182,7 +197,8 @@ class CyclomaticComplexitySpec : Spek({
}
it("counts block when branches as 1") {
val function = compileContentForTest("""
val function = compileContentForTest(
"""
fun test() {
when (System.currentTimeMillis()) {
0 -> {
@@ -197,7 +213,8 @@ class CyclomaticComplexitySpec : Spek({
else -> println("Meh")
}
}
""").getFunctionByName("test")
"""
).getFunctionByName("test")
val actual = CyclomaticComplexity.calculate(function) {
ignoreSimpleWhenEntries = true

View File

@@ -68,7 +68,6 @@ fun createCompilerConfiguration(
languageVersion: LanguageVersion?,
jvmTarget: JvmTarget
): CompilerConfiguration {
val javaFiles = pathsToAnalyze.flatMap { path ->
path.toFile().walk()
.filter { it.isFile && it.extension.equals("java", true) }

View File

@@ -35,9 +35,10 @@ data class FilePath constructor(
) {
init {
require(basePath == null ||
relativePath == null ||
absolutePath == basePath.resolve(relativePath).normalize()
require(
basePath == null ||
relativePath == null ||
absolutePath == basePath.resolve(relativePath).normalize()
) {
"Absolute path = $absolutePath much match base path = $basePath and relative path = $relativePath"
}

View File

@@ -29,7 +29,7 @@ inline fun <reified T : KtExpression> KtExpression.isGuardClause(): Boolean {
fun <T : KtExpression> KtExpression.isIfConditionGuardClause(descendantExpr: T): Boolean {
val ifExpr = this as? KtIfExpression ?: return false
return ifExpr.`else` == null &&
descendantExpr === ifExpr.then?.lastBlockStatementOrThis()
descendantExpr === ifExpr.then?.lastBlockStatementOrThis()
}
fun KtExpression.isElvisOperatorGuardClause(): Boolean {

View File

@@ -42,6 +42,8 @@ inline fun <reified T : Any> Any.safeAs(): T? = this as? T
fun KtCallExpression.receiverIsUsed(context: BindingContext): Boolean =
(parent as? KtQualifiedExpression)?.let {
val scopeOfApplyCall = parent.parent
!((scopeOfApplyCall == null || scopeOfApplyCall is KtBlockExpression) &&
(context == BindingContext.EMPTY || !it.isUsedAsExpression(context)))
!(
(scopeOfApplyCall == null || scopeOfApplyCall is KtBlockExpression) &&
(context == BindingContext.EMPTY || !it.isUsedAsExpression(context))
)
} ?: true

View File

@@ -10,9 +10,9 @@ fun KtAnnotated.hasAnnotation(
val names = annotationNames.toHashSet()
val predicate: (KtAnnotationEntry) -> Boolean = {
it.typeReference
?.typeElement
?.safeAs<KtUserType>()
?.referencedName in names
?.typeElement
?.safeAs<KtUserType>()
?.referencedName in names
}
return annotationEntries.any(predicate)
}

View File

@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
fun KtCallExpression.isCalling(fqName: FqName, bindingContext: BindingContext): Boolean {
return bindingContext != BindingContext.EMPTY &&
calleeExpression?.text == fqName.shortName().asString() &&
getResolvedCall(bindingContext)?.resultingDescriptor?.fqNameSafe == fqName
calleeExpression?.text == fqName.shortName().asString() &&
getResolvedCall(bindingContext)?.resultingDescriptor?.fqNameSafe == fqName
}
fun KtCallExpression.isCallingWithNonNullCheckArgument(

View File

@@ -7,7 +7,7 @@ import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.psiUtil.isPublic
fun KtModifierListOwner.isPublicNotOverridden() =
isPublic && !isOverride()
isPublic && !isOverride()
fun KtModifierListOwner.isAbstract() = hasModifier(KtTokens.ABSTRACT_KEYWORD)

View File

@@ -5,14 +5,14 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
fun KtFunction.isEqualsFunction() =
this.name == "equals" && this.isOverride() && hasCorrectEqualsParameter()
this.name == "equals" && this.isOverride() && hasCorrectEqualsParameter()
fun KtFunction.isHashCodeFunction() =
this.name == "hashCode" && this.isOverride() && this.valueParameters.isEmpty()
this.name == "hashCode" && this.isOverride() && this.valueParameters.isEmpty()
private val knownAnys = setOf("Any?", "kotlin.Any?")
fun KtFunction.hasCorrectEqualsParameter() =
this.valueParameters.singleOrNull()?.typeReference?.text in knownAnys
this.valueParameters.singleOrNull()?.typeReference?.text in knownAnys
fun KtNamedFunction.isMainFunction() = hasMainSignature() && (this.isTopLevel || isMainInsideObject())
@@ -37,7 +37,7 @@ private fun KtNamedFunction.hasMainParameter() =
valueParameters.isEmpty() || valueParameters.size == 1 && valueParameters[0].typeReference?.text == "Array<String>"
private fun KtNamedFunction.isMainInsideObject() =
this.name == "main" &&
this.isPublicNotOverridden() &&
this.parent?.parent is KtObjectDeclaration &&
this.hasAnnotation("JvmStatic", "kotlin.jvm.JvmStatic")
this.name == "main" &&
this.isPublicNotOverridden() &&
this.parent?.parent is KtObjectDeclaration &&
this.hasAnnotation("JvmStatic", "kotlin.jvm.JvmStatic")

View File

@@ -33,7 +33,7 @@ class MethodSignatureSpec : Spek({
),
TestCase(
testDescription = "should return method name and params list for full signature method with multiple params " +
"where method name has spaces and special characters",
"where method name has spaces and special characters",
methodSignature = "io.gitlab.arturbosch.detekt.SomeClass.`some , method`(kotlin.String)",
expectedMethodName = "io.gitlab.arturbosch.detekt.SomeClass.some , method",
expectedParams = listOf("kotlin.String")

View File

@@ -164,13 +164,13 @@ private class SUMMARY(
initialAttributes: Map<String, String>,
override val consumer: TagConsumer<*>
) : HTMLTag(
"summary",
consumer,
initialAttributes,
null,
false,
false
),
"summary",
consumer,
initialAttributes,
null,
false,
false
),
CommonAttributeGroupFacadeFlowInteractiveContent
private fun TextLocation.length(): Int = end - start

View File

@@ -112,7 +112,8 @@ class HtmlOutputReportSpec : Spek({
it("renders a metric report correctly") {
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 10000), ProjectMetric("M2", 2)
ProjectMetric("M1", 10000),
ProjectMetric("M2", 2)
)
}
val result = htmlReport.render(detektion)
@@ -174,7 +175,6 @@ private fun mockKtElement(): KtElement {
}
private fun createTestDetektionWithMultipleSmells(): Detektion {
val entity1 = createEntity("src/main/com/sample/Sample1.kt", 11 to 1, 10..14, mockKtElement())
val entity2 = createEntity("src/main/com/sample/Sample2.kt", 22 to 2)
val entity3 = createEntity("src/main/com/sample/Sample3.kt", 33 to 3)

View File

@@ -36,7 +36,8 @@ class HtmlUtilsSpec : Spek({
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 1), 34)
}
assertThat(snippet).isEqualTo("""
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
@@ -57,7 +58,8 @@ class HtmlUtilsSpec : Spek({
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 26)
}
assertThat(snippet).isEqualTo("""
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
@@ -78,7 +80,8 @@ class HtmlUtilsSpec : Spek({
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 66)
}
assertThat(snippet).isEqualTo("""
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment

View File

@@ -27,20 +27,22 @@ fun SarifSchema210.withDetektRun(config: Config, init: Run.() -> Unit) {
runs.add(
Run()
.withResults(ArrayList())
.withTool(tool {
driver = component {
guid = "022ca8c2-f6a2-4c95-b107-bb72c43263f3"
name = "detekt"
fullName = name
organization = name
language = "en"
version = VersionProvider.load().current()
semanticVersion = version
downloadUri = URI.create("https://github.com/detekt/detekt/releases/download/v$version/detekt")
informationUri = URI.create("https://detekt.github.io/detekt")
rules = ruleDescriptors(config).values.toSet()
.withTool(
tool {
driver = component {
guid = "022ca8c2-f6a2-4c95-b107-bb72c43263f3"
name = "detekt"
fullName = name
organization = name
language = "en"
version = VersionProvider.load().current()
semanticVersion = version
downloadUri = URI.create("https://github.com/detekt/detekt/releases/download/v$version/detekt")
informationUri = URI.create("https://detekt.github.io/detekt")
rules = ruleDescriptors(config).values.toSet()
}
}
})
)
.apply(init)
)
}

View File

@@ -75,22 +75,24 @@ private fun Finding.toResult(ruleSetId: RuleSetId): Result = result {
ruleId = "detekt.$ruleSetId.$id"
level = severity.toResultLevel()
for (location in listOf(location) + references.map { it.location }) {
locations.add(Location().apply {
physicalLocation = PhysicalLocation().apply {
region = Region().apply {
startLine = location.source.line
startColumn = location.source.column
}
artifactLocation = ArtifactLocation().apply {
if (location.filePath.relativePath != null) {
uri = location.filePath.relativePath?.toUnifiedString()
uriBaseId = SARIF_SRCROOT_PROPERTY
} else {
uri = location.filePath.absolutePath.toUnifiedString()
locations.add(
Location().apply {
physicalLocation = PhysicalLocation().apply {
region = Region().apply {
startLine = location.source.line
startColumn = location.source.column
}
artifactLocation = ArtifactLocation().apply {
if (location.filePath.relativePath != null) {
uri = location.filePath.relativePath?.toUnifiedString()
uriBaseId = SARIF_SRCROOT_PROPERTY
} else {
uri = location.filePath.absolutePath.toUnifiedString()
}
}
}
}
})
)
}
message = Message().apply { text = messageOrDescription() }
}

View File

@@ -33,7 +33,8 @@ class SarifOutputReportSpec : Spek({
.stripWhitespace()
assertThat(report).isEqualTo(
readResourceContent("vanilla.sarif.json").stripWhitespace())
readResourceContent("vanilla.sarif.json").stripWhitespace()
)
}
it("renders multiple issues with relative path") {
@@ -46,9 +47,11 @@ class SarifOutputReportSpec : Spek({
val report = SarifOutputReport()
.apply {
init(EmptySetupContext().apply {
register(DETEKT_OUTPUT_REPORT_BASE_PATH_KEY, Paths.get(basePath))
})
init(
EmptySetupContext().apply {
register(DETEKT_OUTPUT_REPORT_BASE_PATH_KEY, Paths.get(basePath))
}
)
}
.render(result)
.stripWhitespace()

View File

@@ -29,7 +29,8 @@ class TxtOutputReportSpec : Spek({
val detektion = TestDetektion(
createFinding(ruleName = "TestSmellA"),
createFinding(ruleName = "TestSmellB"),
createFinding(ruleName = "TestSmellC"))
createFinding(ruleName = "TestSmellC")
)
val renderedText = """
TestSmellA - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
TestSmellB - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature

View File

@@ -198,7 +198,6 @@ private object Xml10EscapeSymbolsInitializer {
}
fun initializeXml10(): XmlEscapeSymbols {
val xml10References = XmlEscapeSymbols.References()
/*

View File

@@ -28,18 +28,18 @@ class XmlOutputReport : OutputReport() {
smells.groupBy { it.location.filePath.relativePath ?: it.location.filePath.absolutePath }
.forEach { (filePath, findings) ->
lines += "<file name=\"${filePath.toUnifiedString().toXmlString()}\">"
findings.forEach {
lines += arrayOf(
lines += "<file name=\"${filePath.toUnifiedString().toXmlString()}\">"
findings.forEach {
lines += arrayOf(
"\t<error line=\"${it.location.source.line.toXmlString()}\"",
"column=\"${it.location.source.column.toXmlString()}\"",
"severity=\"${it.severityLabel.toXmlString()}\"",
"message=\"${it.messageOrDescription().toXmlString()}\"",
"source=\"${"detekt.${it.id.toXmlString()}"}\" />"
).joinToString(separator = " ")
).joinToString(separator = " ")
}
lines += "</file>"
}
lines += "</file>"
}
lines += "</checkstyle>"
return lines.joinToString(separator = "\n")

View File

@@ -23,14 +23,26 @@ private const val TAB = "\t"
class XmlOutputFormatSpec : Spek({
val entity1 by memoized {
Entity("Sample1", "",
Location(SourceLocation(11, 1), TextLocation(0, 10),
"src/main/com/sample/Sample1.kt"))
Entity(
"Sample1",
"",
Location(
SourceLocation(11, 1),
TextLocation(0, 10),
"src/main/com/sample/Sample1.kt"
)
)
}
val entity2 by memoized {
Entity("Sample2", "",
Location(SourceLocation(22, 2), TextLocation(0, 20),
"src/main/com/sample/Sample2.kt"))
Entity(
"Sample2",
"",
Location(
SourceLocation(22, 2),
TextLocation(0, 20),
"src/main/com/sample/Sample2.kt"
)
)
}
val outputFormat by memoized { XmlOutputReport() }
@@ -40,10 +52,13 @@ class XmlOutputFormatSpec : Spek({
it("renders empty report") {
val result = outputFormat.render(TestDetektion())
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
it("renders one reported issue in single file") {
@@ -51,13 +66,16 @@ class XmlOutputFormatSpec : Spek({
val result = outputFormat.render(TestDetektion(smell))
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
it("renders two reported issues in single file") {
@@ -66,14 +84,17 @@ class XmlOutputFormatSpec : Spek({
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
it("renders one reported issue across multiple files") {
@@ -82,7 +103,8 @@ class XmlOutputFormatSpec : Spek({
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
@@ -91,7 +113,9 @@ class XmlOutputFormatSpec : Spek({
<file name="src/main/com/sample/Sample2.kt">
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
it("renders issues with relative path") {
@@ -108,7 +132,8 @@ class XmlOutputFormatSpec : Spek({
val result = outputFormat.render(TestDetektion(findingA, findingB))
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
@@ -117,7 +142,9 @@ class XmlOutputFormatSpec : Spek({
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
it("renders two reported issues across multiple files") {
@@ -135,7 +162,8 @@ class XmlOutputFormatSpec : Spek({
)
)
assertThat(result).isEqualTo("""
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
@@ -146,7 +174,9 @@ class XmlOutputFormatSpec : Spek({
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>""".trimIndent())
</checkstyle>
""".trimIndent()
)
}
describe("severity level conversion") {

View File

@@ -45,9 +45,12 @@ class ComplexCondition(
threshold: Int = DEFAULT_CONDITIONS_COUNT
) : ThresholdRule(config, threshold) {
override val issue = Issue("ComplexCondition", Severity.Maintainability,
"Complex conditions should be simplified and extracted into well-named methods if necessary.",
Debt.TWENTY_MINS)
override val issue = Issue(
"ComplexCondition",
Severity.Maintainability,
"Complex conditions should be simplified and extracted into well-named methods if necessary.",
Debt.TWENTY_MINS
)
override fun visitIfExpression(expression: KtIfExpression) {
val condition = expression.condition
@@ -77,17 +80,20 @@ class ComplexCondition(
val conditionString = longestBinExpr.text
val count = frequency(conditionString, "&&") + frequency(conditionString, "||") + 1
if (count >= threshold) {
report(ThresholdedCodeSmell(issue,
report(
ThresholdedCodeSmell(
issue,
Entity.from(condition),
Metric("SIZE", count, threshold),
"This condition is too complex ($count). " +
"Defined complexity threshold for conditions is set to '$threshold'"))
"Defined complexity threshold for conditions is set to '$threshold'"
)
)
}
}
}
private fun frequency(source: String, part: String): Int {
if (source.isEmpty() || part.isEmpty()) {
return 0
}

View File

@@ -35,12 +35,15 @@ class ComplexInterface(
threshold: Int = DEFAULT_LARGE_INTERFACE_COUNT
) : ThresholdRule(config, threshold) {
override val issue = Issue(javaClass.simpleName, Severity.Maintainability,
"An interface contains too many functions and properties. " +
"Large classes tend to handle many things at once. " +
"An interface should have one responsibility. " +
"Split up large interfaces into smaller ones that are easier to understand.",
Debt.TWENTY_MINS)
override val issue = Issue(
javaClass.simpleName,
Severity.Maintainability,
"An interface contains too many functions and properties. " +
"Large classes tend to handle many things at once. " +
"An interface should have one responsibility. " +
"Split up large interfaces into smaller ones that are easier to understand.",
Debt.TWENTY_MINS
)
private val includeStaticDeclarations = valueOrDefault(INCLUDE_STATIC_DECLARATIONS, false)
private val includePrivateDeclarations = valueOrDefault(INCLUDE_PRIVATE_DECLARATIONS, false)
@@ -54,10 +57,12 @@ class ComplexInterface(
}
if (size >= threshold) {
report(
ThresholdedCodeSmell(issue,
ThresholdedCodeSmell(
issue,
Entity.atName(klass),
Metric("SIZE: ", size, threshold),
"The interface ${klass.name} is too complex. Consider splitting it up.")
"The interface ${klass.name} is too complex. Consider splitting it up."
)
)
}
}
@@ -71,7 +76,7 @@ class ComplexInterface(
private fun calculateMembers(body: KtClassBody): Int {
fun PsiElement.considerPrivate() = includePrivateDeclarations ||
this is KtTypeParameterListOwner && !this.isPrivate()
this is KtTypeParameterListOwner && !this.isPrivate()
fun PsiElement.isMember() = this is KtNamedFunction || this is KtProperty

View File

@@ -52,10 +52,12 @@ class ComplexMethod(
threshold: Int = DEFAULT_THRESHOLD_METHOD_COMPLEXITY
) : ThresholdRule(config, threshold) {
override val issue = Issue("ComplexMethod",
override val issue = Issue(
"ComplexMethod",
Severity.Maintainability,
"Prefer splitting up complex methods into smaller, easier to understand methods.",
Debt.TWENTY_MINS)
Debt.TWENTY_MINS
)
private val ignoreSingleWhenExpression = valueOrDefault(IGNORE_SINGLE_WHEN_EXPRESSION, false)
private val ignoreSimpleWhenEntries = valueOrDefault(IGNORE_SIMPLE_WHEN_ENTRIES, false)
@@ -81,7 +83,7 @@ class ComplexMethod(
Entity.atName(function),
Metric("MCC", complexity, threshold),
"The function ${function.nameAsSafeName} appears to be too complex ($complexity). " +
"Defined complexity threshold for methods is set to '$threshold'"
"Defined complexity threshold for methods is set to '$threshold'"
)
)
}

View File

@@ -63,10 +63,12 @@ import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
*/
class LabeledExpression(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue("LabeledExpression",
Severity.Maintainability,
"Expression with labels increase complexity and affect maintainability.",
Debt.TWENTY_MINS)
override val issue: Issue = Issue(
"LabeledExpression",
Severity.Maintainability,
"Expression with labels increase complexity and affect maintainability.",
Debt.TWENTY_MINS
)
private val ignoredLabels = valueOrDefaultCommaSeparated(IGNORED_LABELS, emptyList())
.map { it.removePrefix("*").removeSuffix("*") }

View File

@@ -30,11 +30,13 @@ class LargeClass(
threshold: Int = DEFAULT_THRESHOLD_CLASS_LENGTH
) : ThresholdRule(config, threshold) {
override val issue = Issue("LargeClass",
Severity.Maintainability,
"One class should have one responsibility. Large classes tend to handle many things at once. " +
"Split up large classes into smaller classes that are easier to understand.",
Debt.TWENTY_MINS)
override val issue = Issue(
"LargeClass",
Severity.Maintainability,
"One class should have one responsibility. Large classes tend to handle many things at once. " +
"Split up large classes into smaller classes that are easier to understand.",
Debt.TWENTY_MINS
)
private val classToLinesCache = IdentityHashMap<KtClassOrObject, Int>()
private val nestedClassTracking = IdentityHashMap<KtClassOrObject, HashSet<KtClassOrObject>>()
@@ -52,7 +54,8 @@ class LargeClass(
issue,
Entity.atName(clazz),
Metric("SIZE", lines, threshold),
"Class ${clazz.name} is too large. Consider splitting it into smaller pieces.")
"Class ${clazz.name} is too large. Consider splitting it into smaller pieces."
)
)
}
}
@@ -62,12 +65,12 @@ class LargeClass(
val lines = classOrObject.linesOfCode()
classToLinesCache[classOrObject] = lines
classOrObject.getStrictParentOfType<KtClassOrObject>()
?.let { nestedClassTracking.getOrPut(it) { HashSet() }.add(classOrObject) }
?.let { nestedClassTracking.getOrPut(it) { HashSet() }.add(classOrObject) }
super.visitClassOrObject(classOrObject)
findAllNestedClasses(classOrObject)
.fold(0) { acc, next -> acc + (classToLinesCache[next] ?: 0) }
.takeIf { it > 0 }
?.let { classToLinesCache[classOrObject] = lines - it }
.fold(0) { acc, next -> acc + (classToLinesCache[next] ?: 0) }
.takeIf { it > 0 }
?.let { classToLinesCache[classOrObject] = lines - it }
}
private fun findAllNestedClasses(startClass: KtClassOrObject): Sequence<KtClassOrObject> = sequence {

View File

@@ -30,11 +30,13 @@ class LongMethod(
threshold: Int = DEFAULT_THRESHOLD_METHOD_LENGTH
) : ThresholdRule(config, threshold) {
override val issue = Issue("LongMethod",
Severity.Maintainability,
"One method should have one responsibility. Long methods tend to handle many things at once. " +
"Prefer smaller methods to make them easier to understand.",
Debt.TWENTY_MINS)
override val issue = Issue(
"LongMethod",
Severity.Maintainability,
"One method should have one responsibility. Long methods tend to handle many things at once. " +
"Prefer smaller methods to make them easier to understand.",
Debt.TWENTY_MINS
)
private val functionToLinesCache = HashMap<KtNamedFunction, Int>()
private val functionToBodyLinesCache = HashMap<KtNamedFunction, Int>()
@@ -77,9 +79,9 @@ class LongMethod(
parentMethods?.let { nestedFunctionTracking.getOrPut(it) { HashSet() }.add(function) }
super.visitNamedFunction(function)
findAllNestedFunctions(function)
.fold(0) { acc, next -> acc + (functionToLinesCache[next] ?: 0) }
.takeIf { it > 0 }
?.let { functionToLinesCache[function] = lines - it }
.fold(0) { acc, next -> acc + (functionToLinesCache[next] ?: 0) }
.takeIf { it > 0 }
?.let { functionToLinesCache[function] = lines - it }
}
private fun findAllNestedFunctions(startFunction: KtNamedFunction): Sequence<KtNamedFunction> = sequence {

View File

@@ -41,12 +41,14 @@ class LongParameterList(
config: Config = Config.empty
) : Rule(config) {
override val issue = Issue("LongParameterList",
Severity.Maintainability,
"The more parameters a function has the more complex it is. Long parameter lists are often " +
"used to control complex algorithms and violate the Single Responsibility Principle. " +
"Prefer functions with short parameter lists.",
Debt.TWENTY_MINS)
override val issue = Issue(
"LongParameterList",
Severity.Maintainability,
"The more parameters a function has the more complex it is. Long parameter lists are often " +
"used to control complex algorithms and violate the Single Responsibility Principle. " +
"Prefer functions with short parameter lists.",
Debt.TWENTY_MINS
)
private val functionThreshold: Int =
valueOrDefault(FUNCTION_THRESHOLD, valueOrDefault(THRESHOLD, DEFAULT_FUNCTION_THRESHOLD))
@@ -105,14 +107,18 @@ class LongParameterList(
if (parameterNumber >= threshold) {
val parameterPrint = function.valueParameters.joinToString(separator = ", ") {
it.nameAsSafeName.identifier + ": " + it.typeReference?.text
it.nameAsSafeName.identifier + ": " + it.typeReference?.text
}
report(ThresholdedCodeSmell(issue,
report(
ThresholdedCodeSmell(
issue,
Entity.from(parameterList),
Metric("SIZE", parameterNumber, threshold),
"The $identifier($parameterPrint) has too many parameters. " +
"The current threshold is set to $threshold."))
"The current threshold is set to $threshold."
)
)
}
}

View File

@@ -31,11 +31,14 @@ class MethodOverloading(
threshold: Int = DEFAULT_THRESHOLD_OVERLOAD_COUNT
) : ThresholdRule(config, threshold) {
override val issue = Issue("MethodOverloading", Severity.Maintainability,
"Methods which are overloaded often might be harder to maintain. " +
"Furthermore, these methods are tightly coupled. " +
"Refactor these methods and try to use optional parameters.",
Debt.TWENTY_MINS)
override val issue = Issue(
"MethodOverloading",
Severity.Maintainability,
"Methods which are overloaded often might be harder to maintain. " +
"Furthermore, these methods are tightly coupled. " +
"Refactor these methods and try to use optional parameters.",
Debt.TWENTY_MINS
)
override fun visitKtFile(file: KtFile) {
val visitor = OverloadedMethodVisitor()

Some files were not shown because too many files have changed in this diff Show More