mirror of
https://github.com/jlengrand/detekt.git
synced 2026-03-10 08:11:23 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ?: "" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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\")"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,8 @@ class CliOptionsPrinter {
|
||||
}
|
||||
|
||||
fun print(filePath: Path) {
|
||||
Files.write(filePath,
|
||||
Files.write(
|
||||
filePath,
|
||||
buildString {
|
||||
appendLine("```")
|
||||
jCommander.usageFormatter.usage(this)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }})" }
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -198,7 +198,6 @@ private object Xml10EscapeSymbolsInitializer {
|
||||
}
|
||||
|
||||
fun initializeXml10(): XmlEscapeSymbols {
|
||||
|
||||
val xml10References = XmlEscapeSymbols.References()
|
||||
|
||||
/*
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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("*") }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user