Meet detekt, a static code analysis tool for the Kotlin programming language. It operates on the abstract syntax tree provided by the Kotlin compiler.
- code smell analysis for your kotlin projects
- complexity report based on logical lines of code, McCabe complexity and amount of code smells
- highly configurable (rule set or rule level)
- suppress findings with Kotlin's @Suppress and Java's @SuppressWarnings annotations
- specify code smell thresholds to break your build or print a warning
- extensible by own rule sets and
FileProcessListener's
- format your code with the formatting rule set
- code Smell baseline and ignore lists for legacy projects
- gradle plugin for code analysis, formatting and import migration
- NEW - gradle tasks to use local
intellij
distribution for formatting and inspecting kotlin code - NEW - optionally configure detekt for each sub module by using profiles (gradle-plugin)
- NEW - sonarqube integration
- Commandline interface
- Gradle plugin
- Standalone gradle task
- Standalone maven task
- Rule sets
- Rule set configuration
- Suppress rules
- Build failure
- Custom rule sets
- Formatting - Code Style
- Black- and Whitelist code smells
- Contributors
git clone https://github.com/arturbosch/detekt
cd detekt
./gradlew build shadow
java -jar detekt-cli/build/libs/detekt-cli-[version]-all.jar [parameters]*
The CLI uses jcommander for argument parsing. Following parameters are shown when --help
is entered:
The following option is required: --input, -i
Usage: detekt [options]
Options:
--baseline, -b
Treats current analysis findings as a smell baseline for further detekt
runs. If a baseline xml file is passed in, only new code smells not in
the baseline are printed in the console.
--config, -c
Path to the config file (path/to/config.yml).
--config-resource, -cr
Path to the config resource on detekt's classpath (path/to/config.yml).
--create-baseline, -cb
Treats current analysis findings as a smell baseline for further detekt runs.
Default: false
--debug, -d
Debugs given ktFile by printing its elements.
Default: false
--disable-default-rulesets, -dd
Disables default rule sets.
Default: false
--filters, -f
Path filters defined through regex with separator ';' (".*test.*").
--format
Enables formatting of source code. Cannot be used together with
--config.
Default: false
--generate-config, -gc
Export default config to default-detekt-config.yml.
Default: false
--help, -h
Shows the usage.
--output, -o
Specify the file to output to.
--output-format, -of
Specify the output format.
Default: XML
Possible Values: [PLAIN, XML]
--parallel
Enables parallel compilation of source files. Should only be used if the
analyzing project has more than ~200 kotlin files.
Default: false
* --input, -i
Input path to analyze (path/to/project).
--rules, -r
Extra paths to ruleset jars separated by ';'.
--useTabs
Tells the formatter that indentation with tabs are valid.
Default: false
--input
can either be a directory or a single Kotlin file.
The currently only supported configuration format is yaml. --config
should point to one. Generating a default configuration file is as easy as using the --generate-config
parameter.
filters
can be used for example to exclude all test directories.
With rules
you can point to additional ruleset.jar's creating by yourself or others.
More on this topic see section Custom RuleSets.
Use the groovy or kotlin dsl of gradle and configure the detekt closure as described here.
For gradle version >= 2.1
buildscript {
repositories {
jcenter()
}
}
plugins {
id "io.gitlab.arturbosch.detekt" version "1.0.0.[version]"
}
detekt {
version = "1.0.0.[version]"
profile("main") {
input = "$projectDir/src/main/kotlin"
config = "$projectDir/detekt.yml"
filters = ".*test.*,.*/resources/.*,.*/tmp/.*"
}
}
For all gradle versions:
buildscript {
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0.[version]"
}
}
apply plugin: "io.gitlab.arturbosch.detekt"
detekt {
version = "1.0.0.[version]"
profile("main") {
input = "$projectDir/src/main/kotlin"
config = "$projectDir/detekt.yml"
filters = ".*test.*,.*/resources/.*,.*/tmp/.*"
}
}
For gradle version >= 3.5
import io.gitlab.arturbosch.detekt.DetektExtension
buildscript {
repositories {
jcenter()
}
}
plugins {
id("io.gitlab.arturbosch.detekt").version("1.0.0.[version]")
}
configure<DetektExtension> {
version = "1.0.0.[version]"
profile("main") {
input = "$projectDir/src/main/kotlin"
config = "$projectDir/detekt.yml"
filters = ".*test.*,.*/resources/.*,.*/tmp/.*"
}
}
detektCheck
- Runs a detekt analysis and complexity report. Configure the analysis inside thedetekt-closure
. By default the standard rule set is used without output report or black- and whitelist checks.detektGenerateConfig
- Generates a default detekt config file into your projects location.detektFormat
- Formats your kotlin code by using the formatting rule set. Specify which rules should be turned on/off and which should be auto corrected. (see Formatting - Code Style). Pay attention that this formatting rule set is not as powerful asintellij's inspection
and never will be. It is recommend to use thedetektIdeaFormat
task which needs some pre configurations.detektBaseline
- LikedetektCheck
, but creates a code smell baseline. Further detekt runs will only feature new smells not in this list.detektMigrate
- Experimental feature for now. Just supports migration of specified imports. See migration section.detektIdeaFormat
- Uses a localidea
installation to format your kotlin (and other) code according to the specifiedcode-style.xml
.detektIdeaInspect
Uses a localidea
installation to run inspections on your kotlin (and other) code according to the specifiedinspections.xml
profile.
detekt {
version = "1.0.0.[version]" // When unspecified the latest detekt version found, will be used. Override to stay on the same version.
// A profile basically abstracts over the argument vector passed to detekt.
// Different profiles can be specified and used for different sub modules or testing code.
profile("main") {
input = "$projectDir/src/main/kotlin" // Which part of your project should be analyzed?
config = "$projectDir/detekt.yml" // Use $project.projectDir or to navigate inside your project
configResource = "/detekt.yml" // Use this parameter instead of config if your detekt yaml file is inside your resources. Is needed for multi project maven tasks.
filters = ".*test.*, .*/resources/.*" // What paths to exclude? Use comma oder semicolon to separate
ruleSets = "other/optional/ruleset.jar" // Custom rule sets can be linked to this, use comma oder semicolon to separate, remove if unused.
disableDefaultRuleSets = false // Disables the default rule set. Just use detekt as the detection engine with your custom rule sets.
output = "$project.projectDir/reports/detekt.xml" // If present, prints all findings into that file.
outputFormat = "xml" // Can be either 'xml' or 'plain', default is 'xml'
baseline = "$project.projectDir/reports/baseline.xml" // If present all current findings are saved in a baseline.xml to only consider new code smells for further runs.
parallel = true // Use this flag if your project has more than 200 files.
useTabs = false // Turns off the indentation check for spaces if true, default is false and does not need to be specified
}
// Definines a secondary profile `gradle detektCheck -Ddetekt.profile=override` will use this profile.
// The main profile gets always loaded but specified profiles override main profiles parameters.
profile("override") {
config = "$projectDir/detekt-test-config.yml"
}
}
- download the community edition of intellij
- extract the file to your preferred location eg.
~/.idea
- let detekt know about idea inside the
detekt-closure
- extract
code-style.xml
andinpect.xml
from idea settings (Settings>CodeStyle>Scheme
andSettings>Inspections>Profile
) - run
detektIdeaFormat
ordetektIdeaInspect
- all parameters in the following detekt-closure are mandatory for both tasks
String USER_HOME = System.getProperty("user.home")
detekt {
profile("main") {
input = "$projectDir/src/main/kotlin"
output = "$projectDir/reports/report.xml"
outputFormat = "xml"
}
idea {
path = "$USER_HOME/.idea"
codeStyleScheme = "$USER_HOME/.idea/idea-code-style.xml"
inspectionsProfile = "$USER_HOME/.idea/inspect.xml"
report = "project.projectDir/reports"
mask = "*.kt,"
}
}
For more information on using idea as a headless formatting/inspection tool see here.
Migration rules can be configured in your detekt.yml
file. For now only migration of imports is supported.
# *experimental feature*
# Migration rules can be defined in the same config file or a new one
migration:
active: true
imports:
# your.package.Class: new.package.or.Class
# for example:
# io.gitlab.arturbosch.detekt.api.Rule: io.gitlab.arturbosch.detekt.rule.Rule
- Add following lines to your build.gradle file.
- Run
gradle detekt
- Add
check.dependsOn detekt
if you want to run detekt on everybuild
repositories {
jcenter()
}
configurations {
detekt
}
task detekt(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt.yml"
def filters = ".*test.*"
def rulesets = ""
def params = [ '-p', input, '-c', config, '-f', filters, '-r', rulesets]
args(params)
}
dependencies {
detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.0.[version]'
detekt 'io.gitlab.arturbosch.detekt:detekt-formatting:1.0.0.[version]'
}
Attention Android Developers! the dependencies section must be at the bottom, after the repository, configurations and task sections!
- Add following lines to your pom.xml.
- Run
mvn verify
(when using the verify phase as I did here)
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<!-- This can be run separately with mvn antrun:run@detekt -->
<id>detekt</id>
<phase>verify</phase>
<configuration>
<target name="detekt">
<java taskname="detekt" dir="${basedir}" fork="true" failonerror="true"
classname="io.gitlab.arturbosch.detekt.cli.Main" classpathref="maven.plugin.classpath">
<arg value="-p"/>
<arg value="${basedir}/src"/>
<arg value="-f"/>
<arg value=".*test.*"/>
<arg value="--useTabs"/>
</java>
</target>
</configuration>
<goals><goal>run</goal></goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>io.gitlab.arturbosch.detekt</groupId>
<artifactId>detekt-cli</artifactId>
<version>1.0.0.[CURRENT_MILESTONE]</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>arturbosch-code-analysis</id>
<name>arturbosch-code-analysis (for detekt)</name>
<url>https://dl.bintray.com/arturbosch/code-analysis/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
Currently there are seven rule sets which are used per default when running the cli.
- complexity - has rules to detect LongMethod, LongParameterList, LargeClass, ComplexMethod ... smells
- code-smell - other rules which can be classified as code smells but do not fit into the complexity category
- style - detects wildcard imports and naming violations
- comments - has rules to detect missing KDoc over public members and unnecessary KDoc over private members
- exceptions - too general exceptions are used in throw and catch statements like RuntimeException, Error or Throwable
- empty - finds empty block statements
- potential-bugs - code is structured in a way it can lead to bugs like 'only equals but not hashcode is implemented' or explicit garbage collection calls
- performance - finds potential performance issues
- formatting: detects indentation, spacing problems and optional semicolons in code
As of milestone six, the formatting rule set is shipped as an standalone plugin which must be linked to a detekt run through the --rules "path/to/jar" parameter or via gradle/maven classpath setup.
To turn off specific rules/rule sets or change threshold values for certain rules a yaml configuration file can be used.
Export the default config with the --generate-config
flag or copy and modify the detekt-cli/src/main/resources/default-detekt-config.yml
for your needs.
complexity:
active: true
LongMethod:
threshold: 20
LongParameterList:
threshold: 5
LargeClass:
threshold: 150
ComplexMethod:
threshold: 10
TooManyFunctions:
threshold: 10
ComplexCondition:
threshold: 3
style:
active: true
WildcardImport:
active: true
NamingConventionViolation:
active: true
variablePattern: '^(_)?[a-z$][a-zA-Z$0-9]*$'
constantPattern: '^([A-Z_]*|serialVersionUID)$'
methodPattern: '^[a-z$][a-zA-Z$0-9]*$'
classPattern: '[A-Z$][a-zA-Z$]*'
enumEntryPattern: '^[A-Z$][a-zA-Z_$]*$'
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: false
excludeImportStatements: false
comments:
active: true
CommentOverPrivateMethod:
active: true
CommentOverPrivateProperty:
active: true
NoDocOverPublicClass:
active: false
NoDocOverPublicMethod:
active: false
potential-bugs:
active: true
DuplicateCaseInWhenExpression:
active: true
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
LateinitUsage:
active: false
performance:
active: true
ForEachOnRange:
active: true
exceptions:
active: true
empty-blocks:
active: true
autoCorrect: true
formatting:
ConsecutiveBlankLines:
autoCorrect: true
...
Every rule of the default rule sets can be turned off. Thresholded code-smell rules can have an additional field threshold
.
Formatting
rules can be configured to autoCorrect
the style mistake.
Active
keyword is only needed if you want to turn off the rule. Active
on the rule set level turn off whole rule set.
autoCorrect
on the top level must be set to true or else all configured formatting rules are ignored.
This is done to prevent you from changing your project files if your not 100% sure about it.
detekt supports the Java (@SuppressWarnings
) and Kotlin (@Suppress
) style suppression. If both annotations are present, only Kotlin's annotation is used! To suppress a rule, the id of the rule must be written inside the values field of the annotation e.g. @Suppress("LongMethod", "LongParameterList", ...)
detekt now can throw a BuildFailure(Exception) and let the build fail with following config parameters:
build:
warningThreshold: 5 // Five weighted findings
failThreshold: 10 // Ten weighted smells to fail the build
weights:
complexity: 2 // Whole complexity rule should add two for each finding.
LongParameterList: 1 // The specific rule should not add two.
comments: 0 // Comments should not fail the build at all?!
Every rule and rule set can be attached with an integer value which is the weight of the finding. For example: If you have 5 findings of the category code-smell, then your failThreshold of 10 is reached as 5 x 2 = 10.
The formula for weights: RuleID > RuleSetID > 1. Only integer values are supported.
detekt uses a ServiceLoader to collect all instances of RuleSetProvider-interfaces. So it is possible
to define rules/rule sets and enhance detekt with your own flavor.
Attention: You need a resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
file which
has as content the fully qualified name of your RuleSetProvider e.g. io.gitlab.arturbosch.detekt.sampleruleset.SampleProvider.
The easiest way to define an rule set is to clone the provided detekt-sample-ruleset project.
Own rules have to extend the abstract Rule class and override the visitXXX
functions from the AST.
A RuleSetProvider
must be implemented which declares a RuleSet
in the instance
method.
To allow your rule to be configurable, pass it a Config object from within your rule set provider.
You can also specify a Severity type for your rule.
Example of a custom rule:
class TooManyFunctions : Rule("TooManyFunctions") {
private var amount: Int = 0
override fun visitFile(file: PsiFile) {
super.visitFile(file)
if (amount > 10) {
addFindings(CodeSmell(id, Entity.from(file)))
}
}
override fun visitNamedFunction(function: KtNamedFunction) {
amount++
}
}
Example of a much preciser rule in terms of more specific CodeSmell constructor and Rule attributes:
class TooManyFunctions2(config: Config) : Rule("TooManyFunctionsTwo", Severity.Maintainability, config) {
private var amount: Int = 0
override fun visitFile(file: PsiFile) {
super.visitFile(file)
if (amount > 10) {
addFindings(CodeSmell(
id = id, entity = Entity.from(file),
description = "Too many functions can make the maintainability of a file more costly",
metrics = listOf(Metric(type = "SIZE", value = amount, threshold = 10)),
references = listOf())
)
}
}
override fun visitNamedFunction(function: KtNamedFunction) {
amount++
}
}
If you want your rule to be configurable, write down your properties inside the detekt.yml file
and use the withConfig
function:
MyRuleSet:
MyRule:
MyMetric: 5
threshold: 10
OtherRule:
active: false
By specifying the rule set and rule ids, detekt will use the sub configuration of MyRule:
val threshold = withConfig { valueOrDefault("threshold") { threshold } }
If your using maven to build rule sets or use detekt as a dependency, you have to run the additional task install
To test your rules you need a KtFile object and use it's visit method. There are two predefined methods to help obtaining a KtFile:
- compileContentForTest(content: String): KtFile
- compileForTest(path: Path): KtFile
New with M3 there is a special detekt-test module, which specifies above two methods but also Rule extension functions that allow allow to skip compilation, ktFile and visit procedures.
- Rule.lint(StringContent/Path) returns just the findings for given content
- Rule.format(StringContent/Path) returns just the new modified content for given content
As of
M11
thedetektIdeaFormat
-task makes the formatting rule set obsolete to a big part. It is highly advised to use that gradle task for indentation and spacing. Rules like removing OptionalUnit and converting to ExpressionBodySyntax for single return statements can still be useful.
KtLint was first to support auto correct formatting according to the kotlin coding conventions. In detekt I made an effort to port over all available formatting rules to detect style violations and auto correct them.
Following configuration I use to check the style for detekt. If your like me who prefer tabs over spaces, use useTabs
in the
rule set level to turn off indentation check for spaces (or simple turn off Indentation
rule).
formatting:
active: true
useTabs: true
Indentation:
active: false
indentSize: 4
ConsecutiveBlankLines:
active: true
autoCorrect: true
MultipleSpaces:
active: true
autoCorrect: true
SpacingAfterComma:
active: true
autoCorrect: true
SpacingAfterKeyword:
active: true
autoCorrect: true
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundCurlyBraces:
active: true
autoCorrect: true
SpacingAroundOperator:
active: true
autoCorrect: true
TrailingSpaces:
active: true
autoCorrect: true
UnusedImports:
active: true
autoCorrect: true
OptionalSemicolon:
active: true
autoCorrect: true
OptionalUnit:
active: true
autoCorrect: true
ExpressionBodySyntax:
active: false
autoCorrect: true
ExpressionBodySyntaxLineBreaks:
active: false
autoCorrect: true
Specify a report output with --output
parameter and specify its format with --output-format
.
Now you can generate a report which holds all findings of current analysis.
With --baseline
you generate a baseline.xml
where code smells are white- or blacklisted.
<SmellBaseline>
<Blacklist timestamp="1483388204705">
<ID>CatchRuntimeException:Junk.kt$e: RuntimeException</ID>
</Blacklist>
<Whitelist timestamp="1496432564542">
<ID>NestedBlockDepth:Indentation.kt$Indentation$override fun procedure(node: ASTNode)</ID>
<ID>ComplexCondition:SpacingAroundOperator.kt$SpacingAroundOperator$tokenSet.contains(node.elementType) && node is LeafPsiElement && !node.isPartOf(KtPrefixExpression::class) && // not unary !node.isPartOf(KtTypeParameterList::class) && // fun <T>fn(): T {} !node.isPartOf(KtTypeArgumentList::class) && // C<T> !node.isPartOf(KtValueArgument::class) && // fn(*array) !node.isPartOf(KtImportDirective::class) && // import * !node.isPartOf(KtSuperExpression::class)</ID>
<ID>TooManyFunctions:LargeClass.kt$io.gitlab.arturbosch.detekt.rules.complexity.LargeClass.kt</ID>
<ID>ComplexMethod:DetektExtension.kt$DetektExtension$fun convertToArguments(): MutableList<String></ID>
</Whitelist>
</SmellBaseline>
The intention of a whitelist is that only new code smells are printed on further analysis. The blacklist can be used
to write down false positive detections. The ID
node must be build of <RuleID>:<Signature>
. Both values can be found
inside the report file.
- Artur Bosch - Maintainer
- lummax - Cli enhancements
- Svyatoslav Chatchenko - Active on Issues, NamingConventions and UnusedImport fixes
- Sean Flanigan - Config from classpath resource
- Sebastian Schuberth - Active on Issues, Windows support
- Olivier Lemasle - NP-Bugfix
- Marvin Ramin - Rules: MaxLineLength + LateinitUsage rule, Active on Issues, sdept and test cleanups
- Marc Prengemann - Support for custom output formats, prototyped Rule-Context-Issue separation
- Sebastiano Poggi - Enhanced milestone report script
- Ilya Tretyakov - Sonar runs should not auto correct formatting.
- Andrey T - Readme fix
- Niklas Baudy - Active on Issues, Bug fix, documentation fixes, detekt findings
- Ivan Balaksha - Rules: UnsafeCast, SpreadOperator, UnsafeCallOnNullableType, LabeledExpression
- Stanley Shyiko -
detekt
migrated the formatting rules from ktlint - Kotlin - Being an awesome language
- JetBrains - Creating awesome Intellij + Kotlin