diff --git a/CHANGELOG.md b/CHANGELOG.md index 187ef39..8c01ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ Change Log ========== +[Version 0.6](https://github.com/novoda/gradle-static-analysis-plugin/releases/tag/v0.6) +-------------------------- + +- Fix release to plugin portal ([PR#82](https://github.com/novoda/gradle-static-analysis-plugin/pull/82),[PR#83](https://github.com/novoda/gradle-static-analysis-plugin/pull/83)) +- Improve plugin documentation and samples ([PR#85](https://github.com/novoda/gradle-static-analysis-plugin/pull/85), +[PR#97](https://github.com/novoda/gradle-static-analysis-plugin/pull/97), +[PR#99](https://github.com/novoda/gradle-static-analysis-plugin/pull/99), +[PR#100](https://github.com/novoda/gradle-static-analysis-plugin/pull/100), +[PR#101](https://github.com/novoda/gradle-static-analysis-plugin/pull/101), +[PR#113](https://github.com/novoda/gradle-static-analysis-plugin/pull/113), +[PR#123](https://github.com/novoda/gradle-static-analysis-plugin/pull/123), +[PR#124](https://github.com/novoda/gradle-static-analysis-plugin/pull/124)) +- Improve support for Android Lint ([PR#105](https://github.com/novoda/gradle-static-analysis-plugin/pull/105)) +- Improve support for Detekt ([PR#90](https://github.com/novoda/gradle-static-analysis-plugin/pull/90), [PR#121](https://github.com/novoda/gradle-static-analysis-plugin/pull/121)) +- Rename built-in `failOnWarnings` penalty to `failFast` ([PR#92](https://github.com/novoda/gradle-static-analysis-plugin/pull/92)) +- Support multiple configurations for `Pmd`, `Findbugs`, `Checkstyle` ([PR#93](https://github.com/novoda/gradle-static-analysis-plugin/pull/93)) +- Support automatic snapshot builds from `develop` ([PR#106](https://github.com/novoda/gradle-static-analysis-plugin/pull/106),[PR#107](https://github.com/novoda/gradle-static-analysis-plugin/pull/107)) +- Automatically exclude Kotlin files from Java code quality tools ([PR#109](https://github.com/novoda/gradle-static-analysis-plugin/pull/109)) +- Integrate [KtLint](https://github.com/shyiko/ktlint) ([PR#110](https://github.com/novoda/gradle-static-analysis-plugin/pull/110)) + [Version 0.5.2](https://github.com/novoda/gradle-static-analysis-plugin/releases/tag/v0.5.2) -------------------------- diff --git a/README.md b/README.md index f63c2fe..5c29744 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,6 @@ The plugin supports various static analysis tools for Java, Kotlin and Android p * [`FindBugs`](http://findbugs.sourceforge.net/) * [`Detekt`](https://github.com/arturbosch/detekt) * [`Android Lint`](https://developer.android.com/studio/write/lint.html) - -Support for additional tools is planned but not available yet: - * [`KtLint`](https://github.com/shyiko/ktlint) Please note that the tools availability depends on the project the plugin is applied to. For more details please refer to the @@ -44,7 +41,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.novoda:gradle-static-analysis-plugin:0.5.2' + classpath 'com.novoda:gradle-static-analysis-plugin:0.6' } } @@ -55,7 +52,7 @@ or from the [Gradle Plugins Repository](https://plugins.gradle.org/): ```gradle plugins { - id 'com.novoda.static-analysis' version '0.5.2' + id 'com.novoda.static-analysis' version '0.6' } ``` @@ -83,6 +80,21 @@ This will enable all the tools with their default settings. For more advanced co ## Sample app There are two sample Android projects available, one consisting of a regular app - available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample) - and the other comprising a multi-module setup available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample-multi-module). Both sample projects showcase a setup featuring Checkstyle, FindBugs, PMD, Lint and Detekt. +## Snapshots +[![CI status](https://ci.novoda.com/buildStatus/icon?job=gradle-static-analysis-plugin-snapshot)](https://ci.novoda.com/job/gradle-static-analysis-plugin-snapshot/lastBuild/console) [![Download from Bintray](https://api.bintray.com/packages/novoda/snapshots/gradle-static-analysis-plugin/images/download.svg)](https://bintray.com/novoda/snapshots/gradle-static-analysis-plugin/_latestVersion) + +Snapshot builds from [`develop`](https://github.com/novoda/gradle-static-analysis-plugin/compare/master...develop) are automatically deployed to a [repository](https://bintray.com/novoda/snapshots/gradle-static-analysis-plugin/_latestVersion) that is not synced with JCenter. +To consume a snapshot build add an additional maven repo as follows: +``` +repositories { + maven { + url 'https://novoda.bintray.com/snapshots' + } +} +``` + +You can find the latest snapshot version following this [link](https://bintray.com/novoda/snapshots/gradle-static-analysis-plugin/_latestVersion). + ## Roadmap The plugin is under active development and to be considered in **beta stage**. It is routinely used by many Novoda projects and by other external projects with no known critical issues. The API is supposed to be relatively stable, but there still may be diff --git a/buildSrc/src/main/groovy/GradlePlugins.groovy b/buildSrc/src/main/groovy/GradlePlugins.groovy index d28ee27..1849230 100644 --- a/buildSrc/src/main/groovy/GradlePlugins.groovy +++ b/buildSrc/src/main/groovy/GradlePlugins.groovy @@ -1,6 +1,6 @@ class GradlePlugins { final bintrayRelease = 'com.novoda:bintray-release:0.4.0' - final buildProperties = 'com.novoda:gradle-build-properties-plugin:0.2' + final buildProperties = 'com.novoda:gradle-build-properties-plugin:0.4.1' final gradleGit = 'org.ajoberstar:gradle-git:1.6.0' final gradlePublish = 'com.gradle.publish:plugin-publish-plugin:0.9.9' } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 59edbae..c17687b 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -8,6 +8,7 @@ the build will not fail, which is very useful for legacy projects. * [Improve the report with a base URL](#improve-the-report-with-a-base-URL) * [Add exclusions with `exclude` filters](#add-exclusions-with-exclude-filters) * [Add exclusions with Android build variants](#add-exclusions-with-android-build-variants) + * [Consume rules from an artifact](#consume-rules-from-an-artifact) * [Custom violations evaluator (**incubating**)](incubating/custom-evaluator.md#custom-violations-evaluator-incubating) --- @@ -50,10 +51,10 @@ Besides manually specifying thresholds, the plugin includes a few built-in `pena ``` This will break the build if any error is found. Warnings instead are only logged and will not break the build. -* `failOnWarnings` +* `failFast` ```gradle staticAnalysis { - penalty failOnWarnings + penalty failFast } ``` This policy will fail the build if any warning or error is found. It is a zero-tolerance policy, useful to keep @@ -131,3 +132,70 @@ staticAnalysis { Please note that this is not yet supported for Detekt. [penaltyextensioncode]: https://github.com/novoda/gradle-static-analysis-plugin/blob/master/plugin/src/main/groovy/com/novoda/staticanalysis/PenaltyExtension.groovy + + +## Consume rules from an artifact +In order to reuse your rules among multiple projects or to easily use an open source rule set, we added support for consuming the +rules for all supported tools from a Maven artifact. + +### Rules artifact +A rule artifact is just a bundle of files that is published on a Maven repository as artifact. An example of how to do that can be be found [here](https://github.com/novoda/novoda/blob/master/scaffolding/build.gradle). +In this case we bundle the files as jar using the using the [java plugin](https://docs.gradle.org/current/userguide/java_plugin.html) and publish it using our [bintray-release plugin](https://github.com/novoda/bintray-release). + +### How to define a dependency from a rules artifact +In order to access the files inside a rule artifact you have to first define an entry in the `rules {}` extension of the plugin. An entry is defined by a name and a Maven coordinate, as follows: +``` +staticAnalysis { + rules { + novoda { + maven 'com.novoda:static-analysis-rules:0.2' + } + } +} +``` + +### Access rules from artifact +Once you have defined a rule artifact you can access the files inside it specifying the path of the file inside the bundle, e.g.: +``` +def modules = rules.novoda['checkstyle-modules.xml'] +``` +Note that `modules` is defined as [`TextResource`](https://docs.gradle.org/current/dsl/org.gradle.api.resources.TextResource.html), that is a read-only body of text backed by a string, file, archive entry, or other source. Some of the tools already accept `TextResource` as value for some of their configuration, while in other cases you have to transform a `TextResource` into a `File` or a path. Some examples of using a rule artifact in the supported tools is provided below: + +#### Checkstyle +```gradle +checkstyle { + toolVersion '8.8' + config rules.novoda['checkstyle-modules.xml'] +} +``` + +#### PMD +```gradle +pmd { + toolVersion '6.0.1' + ruleSetFiles = project.files(rules.novoda['pmd-rules.xml'].asFile().path) +} +``` + +#### FindBugs +```gradle +findbugs { + excludeFilter rules.novoda['findbugs-excludes.xml'].asFile() +} +``` + +#### Detekt +```gradle +detekt { + profile('main') { + config = rules.novoda['detekt.yml'].asFile().path + } +} +``` + +#### Android Lint +```gradle +lintOptions { + lintConfig = rules.novoda['lint-config.xml'].asFile() +} +``` diff --git a/docs/supported-tools.md b/docs/supported-tools.md index ae74f43..cba163b 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -10,9 +10,7 @@ Tool | Java | Android
(Java) | Kotlin | Android
(Kotlin) [`FindBugs`](http://findbugs.sourceforge.net/) | :white_check_mark: | :white_check_mark: | — | — [`Detekt`](https://github.com/arturbosch/detekt) | — | — | :white_check_mark: | :white_check_mark: [`Android Lint`](https://developer.android.com/studio/write/lint.html) | — | :white_check_mark:️ | — | :white_check_mark:️ -[`KtLint`\*](https://github.com/shyiko/ktlint) | — | — | ✖️ | ✖️ - -_\* Not supported [yet](https://github.com/novoda/gradle-static-analysis-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+tool%22)_ +[`KtLint`](https://github.com/shyiko/ktlint) | — | — | :white_check_mark:️ | :white_check_mark:️ For additional informations and tips on how to obtain advanced behaviours with the plugin and its tools, please refer to the [advanced usage](advanced-usage.md) page. @@ -25,7 +23,7 @@ For additional informations and tips on how to obtain advanced behaviours with t * [PMD](tools/pmd.md) * [Findbugs](tools/findbugs.md) * [Android Lint](tools/android_lint.md) - * KtLint — _COMING SOON_ + * [KtLint](tools/ktlint.md) * [Example configurations](#example-configurations) --- @@ -42,7 +40,9 @@ staticAnalysis { checkstyle {} pmd {} findbugs {} + lintOptions {} detekt {} + ktlint {} } ``` diff --git a/docs/tools/findbugs.md b/docs/tools/findbugs.md index 930043f..2dedd64 100644 --- a/docs/tools/findbugs.md +++ b/docs/tools/findbugs.md @@ -28,22 +28,4 @@ You can have multiple `exclude` statements. For more informations about Findbugs rules, refer to the [official website](http://findbugs.sourceforge.net/bugDescriptions.html). ## Findbugs in mixed-language projects -If your project mixes Java and Kotlin code, you most likely want to have an exclusion in place for all `*.kt` files. You can use the `exclude` -in the configuration closure, or you can do so by adding a suppression to the filter file: - -`findbugs-excludes.xml` -```xml - - - ... - - - - - - ... - - -``` - - +If your project mixes Java and Kotlin code, your Kotlin source files will automatically be ignored. diff --git a/docs/tools/ktlint.md b/docs/tools/ktlint.md new file mode 100644 index 0000000..f804859 --- /dev/null +++ b/docs/tools/ktlint.md @@ -0,0 +1,58 @@ +# ktlint +[Ktlint](https://github.com/shyiko/ktlint) is a linter for Kotlin with a built-in formatter. It does not support Java. Adding +this tool only makes sense when you have Kotlin sources in your project. In fact, it will fail to run if you have 0 Kotlin +files. + +## Table of contents + * [IMPORTANT: setup Ktlint](#important-setup-ktlint) + * [Configure Ktlint](#configure-ktlint) +--- + +## IMPORTANT: setup Ktlint + +Unlike the other tools, the plugin **won't automatically add Ktlint** to your project. If you forget to do it, the plugin will +fail the build with an error. + +In order to integrate Ktlint easily we choose to use the [Ktlint Gradle plugin](https://github.com/JLLeitschuh/ktlint-gradle/). +This plugin has a very good understanding of Android source sets and build flavors. You can refer to the +[official documentation](https://github.com/JLLeitschuh/ktlint-gradle/#how-to-use) for further details. + +Note that you should _not_ add the `ktlint` closure to your `build.gradle`s, unlike what the official documentation says. The +`ktlint` closure in the `staticAnalysis` configuration gets applied to all Kotlin modules automatically. + +In most common cases, adding Ktlint to a project boils down to these simple steps: + + 1. Add this statement to your root `build.gradle` project (change the version according to your needs): + ```gradle + plugins { + id 'org.jlleitschuh.gradle.ktlint' version '5.0.0' + // ... + } + ``` + 2. Add this statement to each Kotlin project's `build.gradle`s: + ```gradle + plugins { + id 'org.jlleitschuh.gradle.ktlint' + // ... + } + ``` + +## Configure Ktlint + +Unlike other tools, Ktlint does not offer much configuration. By default, it applies +[Kotlin style guide](https://kotlinlang.org/docs/reference/coding-conventions.html) or +[Android Kotlin style guide](https://android.github.io/kotlin-guides/style.html). + +To use Android style guide: + +```gradle +ktlint { + android true +} +``` + +For other configuration options and adding custom rules, refer to the +[official guide](https://github.com/JLLeitschuh/ktlint-gradle/#configuration). + +**Note:** Failures and threshold detection is handled by Static Analysis plugin. That is why `ignoreFailures = true` is set by +the plugin. Please do not manually override `ignoreFailures` property. diff --git a/gradle/publish.gradle b/gradle/publish.gradle index 79e6abf..edec54e 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -2,31 +2,65 @@ ext { websiteUrl = 'https://github.com/novoda/gradle-static-analysis-plugin' } -version = '0.5.2' +version = '0.6' String tag = "v$project.version" groovydoc.docTitle = 'Static Analysis Plugin' -apply plugin: 'com.gradle.plugin-publish' -apply plugin: 'com.novoda.bintray-release' -publish { - userOrg = 'novoda' - groupId = 'com.novoda' - artifactId = 'gradle-static-analysis-plugin' - publishVersion = project.version - website = websiteUrl -} - apply plugin: 'com.novoda.build-properties' buildProperties { + secrets { - file(rootProject.file('secrets.properties'), ''' -This file should contain: + using rootProject.file('secrets.properties') + description = ''' + This file should contain: - git.username: the username used to push to the repo - git.password: the password used to push to the repo - gradle.publish.key: the key to publish the plugin to the Gradle Plugins Repository - gradle.publish.secret: the secret to publish the plugin to the Gradle Plugins Repository - ''') + ''' } + + cli { + using(project) + } + + bintray { + def bintrayCredentials = { + return isDryRun() ? + ['bintrayRepo': 'n/a', 'bintrayUser': 'n/a', 'bintrayKey': 'n/a'] : + new File("${System.getenv('BINTRAY_PROPERTIES')}") + } + using(bintrayCredentials()).or(cli) + description = '''This should contain the following properties: + | - bintrayRepo: name of the repo of the organisation to deploy the artifacts to + | - bintrayUser: name of the account used to deploy the artifacts + | - bintrayKey: API key of the account used to deploy the artifacts + '''.stripMargin() + } + + publish { + def generateVersion = { + return isSnapshot() ? "SNAPSHOT-${System.getenv('BUILD_NUMBER') ?: 'LOCAL'}" : project.version + } + using(['version': "${generateVersion()}"]).or(buildProperties.bintray) + } + +} + +apply plugin: 'com.gradle.plugin-publish' +apply plugin: 'com.novoda.bintray-release' + +publish { + userOrg = 'novoda' + repoName = buildProperties.publish['bintrayRepo'].string + groupId = 'com.novoda' + artifactId = 'gradle-static-analysis-plugin' + + version = project.buildProperties.publish['version'].string + publishVersion = project.buildProperties.publish['version'].string + bintrayUser = project.buildProperties.publish['bintrayUser'].string + bintrayKey = project.buildProperties.publish['bintrayKey'].string + website = websiteUrl } apply plugin: 'org.ajoberstar.grgit' @@ -129,14 +163,25 @@ task publishGroovydoc { task publishRelease { description = "Publish release for plugin version: $tag" group = 'release' - if (project.hasProperty('dryRun') && project['dryRun'] == 'false') { - dependsOn prepareRelease, publishArtifact, publishGroovydoc, publishPlugins - doLast { - grgit.push { - tags = true - } - } + if (isDryRun()) { + dependsOn publishArtifact } else { dependsOn publishArtifact + if (!isSnapshot()) { + dependsOn prepareRelease, publishGroovydoc, publishPlugins + doLast { + grgit.push { + tags = true + } + } + } } } + +boolean isDryRun() { + buildProperties.cli['dryRun'].or(true).boolean +} + +boolean isSnapshot() { + buildProperties.cli['bintraySnapshot'].or(false).boolean +} diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisExtension.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisExtension.groovy index 8dc233a..e012af8 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisExtension.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisExtension.groovy @@ -18,11 +18,17 @@ class StaticAnalysisExtension { it.maxErrors = 0 } - final Action failOnWarnings = { + final Action failFast = { it.maxWarnings = 0 it.maxErrors = 0 } + @Deprecated + def getFailOnWarnings() { + logger.warn 'com.novoda.static-analysis: failOnWarnings is deprecated. Please use `penalty failFast` for clarity.' + failFast + } + private final Project project private final LogsExtension logs private final NamedDomainObjectContainer allViolations diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisPlugin.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisPlugin.groovy index 35d41eb..da9042f 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisPlugin.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/StaticAnalysisPlugin.groovy @@ -4,6 +4,7 @@ import com.novoda.staticanalysis.internal.CodeQualityConfigurator import com.novoda.staticanalysis.internal.checkstyle.CheckstyleConfigurator import com.novoda.staticanalysis.internal.detekt.DetektConfigurator import com.novoda.staticanalysis.internal.findbugs.FindbugsConfigurator +import com.novoda.staticanalysis.internal.ktlint.KtlintConfigurator import com.novoda.staticanalysis.internal.lint.LintConfigurator import com.novoda.staticanalysis.internal.pmd.PmdConfigurator import org.gradle.api.NamedDomainObjectContainer @@ -40,6 +41,7 @@ class StaticAnalysisPlugin implements Plugin { PmdConfigurator.create(project, violationsContainer, evaluateViolations), FindbugsConfigurator.create(project, violationsContainer, evaluateViolations), DetektConfigurator.create(project, violationsContainer, evaluateViolations), + KtlintConfigurator.create(project, violationsContainer, evaluateViolations), LintConfigurator.create(project, violationsContainer, evaluateViolations) ] } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy index 1c19473..94b7d86 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy @@ -83,7 +83,10 @@ abstract class CodeQualityConfigurator sourceFilter.applyTo(task) } + project.tasks.withType(taskClass) { task -> + sourceFilter.applyTo(task) + task.exclude '**/*.kt' + } } protected abstract Class getTaskClass() diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CollectViolationsTask.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CollectViolationsTask.groovy index 6d1cd91..776bc0c 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CollectViolationsTask.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CollectViolationsTask.groovy @@ -2,10 +2,12 @@ package com.novoda.staticanalysis.internal import com.novoda.staticanalysis.Violations import org.gradle.api.DefaultTask +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.TaskAction abstract class CollectViolationsTask extends DefaultTask { + @InputFile private File xmlReportFile private File htmlReportFile private Violations violations diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy index 9348104..534e0ba 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy @@ -59,6 +59,7 @@ class CheckstyleConfigurator extends CodeQualityConfigurator - collectViolations.xmlReportFile = checkstyle.reports.xml.destination - collectViolations.violations = violations - } + def task = project.tasks.maybeCreate("collect${checkstyle.name.capitalize()}Violations", CollectCheckstyleViolationsTask) + task.xmlReportFile = checkstyle.reports.xml.destination + task.violations = violations + task } } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/detekt/DetektConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/detekt/DetektConfigurator.groovy index 1ff7116..7e239c8 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/detekt/DetektConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/detekt/DetektConfigurator.groovy @@ -11,7 +11,10 @@ import org.gradle.api.Task class DetektConfigurator implements Configurator { private static final String DETEKT_PLUGIN = 'io.gitlab.arturbosch.detekt' + private static final String LAST_COMPATIBLE_DETEKT_VERSION = '1.0.0.RC8' private static final String DETEKT_NOT_APPLIED = 'The Detekt plugin is configured but not applied. Please apply the plugin in your build script.\nFor more information see https://github.com/arturbosch/detekt.' + private static final String OUTPUT_NOT_DEFINED = 'Output not defined! To analyze the results, `output` needs to be defined in Detekt profile.' + private static final String DETEKT_CONFIGURATION_ERROR = "A problem occurred while configuring Detekt. Please make sure to use a compatible version (All versions up to $LAST_COMPATIBLE_DETEKT_VERSION)" private final Project project private final Violations violations @@ -32,7 +35,7 @@ class DetektConfigurator implements Configurator { @Override void execute() { - project.extensions.findByType(StaticAnalysisExtension).ext.'detekt' = { Closure config -> + project.extensions.findByType(StaticAnalysisExtension).ext.detekt = { Closure config -> if (!isKotlinProject(project)) { return } @@ -41,34 +44,42 @@ class DetektConfigurator implements Configurator { throw new GradleException(DETEKT_NOT_APPLIED) } - project.extensions.findByName('detekt').with { - // apply configuration closure to detekt extension - config.delegate = it - config() - } - - configureToolTask() + def detekt = project.extensions.findByName('detekt') + config.delegate = detekt + config() + configureToolTask(detekt) } } - private void configureToolTask() { + private void configureToolTask(detekt) { def detektTask = project.tasks['detektCheck'] - // run detekt as part of check - project.tasks['check'].dependsOn(detektTask) + detektTask.group = 'verification' // evaluate violations after detekt - detektTask.group = 'verification' - def collectViolations = createCollectViolationsTask(violations) + def output = resolveOutput(detekt) + if (!output) { + throw new IllegalArgumentException(OUTPUT_NOT_DEFINED) + } + def collectViolations = createCollectViolationsTask(violations, project.file(output)) evaluateViolations.dependsOn collectViolations collectViolations.dependsOn detektTask } - private CollectDetektViolationsTask createCollectViolationsTask(Violations violations) { - def outputFolder = project.file(project.extensions.findByName('detekt').systemOrDefaultProfile().output) - project.tasks.create('collectDetektViolations', CollectDetektViolationsTask) { collectViolations -> - collectViolations.xmlReportFile = new File(outputFolder, 'detekt-checkstyle.xml') - collectViolations.htmlReportFile = new File(outputFolder, 'detekt-report.html') - collectViolations.violations = violations + private static resolveOutput(detekt) { + if (detekt.hasProperty('profileStorage')) { + detekt.profileStorage.systemOrDefault.output + } else if (detekt.respondsTo('systemOrDefaultProfile')) { + detekt.systemOrDefaultProfile().output + } else { + throw new IllegalStateException(DETEKT_CONFIGURATION_ERROR) + } + } + + private CollectDetektViolationsTask createCollectViolationsTask(Violations violations, File outputFolder) { + project.tasks.create('collectDetektViolations', CollectDetektViolationsTask) { task -> + task.xmlReportFile = new File(outputFolder, 'detekt-checkstyle.xml') + task.htmlReportFile = new File(outputFolder, 'detekt-report.html') + task.violations = violations } } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsConfigurator.groovy index 9aed9d9..38c51c8 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsConfigurator.groovy @@ -58,18 +58,19 @@ class FindbugsConfigurator extends CodeQualityConfigurator androidSourceDirs = variant.sourceSets.collect { it.javaDirectories }.flatten() task.with { description = "Run FindBugs analysis for ${variant.name} classes" source = androidSourceDirs classpath = variant.javaCompile.classpath + exclude '**/*.kt' } sourceFilter.applyTo(task) - task.conventionMapping.map("classes", { + task.conventionMapping.map("classes") { List includes = createIncludePatterns(task.source, androidSourceDirs) getAndroidClasses(variant, includes) - }) + } task.dependsOn variant.javaCompile } @@ -90,6 +91,7 @@ class FindbugsConfigurator extends CodeQualityConfigurator includes = createIncludePatterns(task.source, sourceDirs) getJavaClasses(sourceSet, includes) }) + task.exclude '**/*.kt' } } } @@ -140,19 +142,19 @@ class FindbugsConfigurator extends CodeQualityConfigurator - collectViolations.xmlReportFile = findBugs.reports.xml.destination - collectViolations.violations = violations - } + def task = project.tasks.maybeCreate("collect${findBugs.name.capitalize()}Violations", CollectFindbugsViolationsTask) + task.xmlReportFile = findBugs.reports.xml.destination + task.violations = violations + task } private GenerateFindBugsHtmlReport createHtmlReportTask(FindBugs findBugs, File xmlReportFile, File htmlReportFile) { - project.tasks.create("generate${findBugs.name.capitalize()}HtmlReport", GenerateFindBugsHtmlReport) { generateHtmlReport -> - generateHtmlReport.xmlReportFile = xmlReportFile - generateHtmlReport.htmlReportFile = htmlReportFile - generateHtmlReport.classpath = findBugs.findbugsClasspath - generateHtmlReport.onlyIf { xmlReportFile?.exists() } - } + def task = project.tasks.maybeCreate("generate${findBugs.name.capitalize()}HtmlReport", GenerateFindBugsHtmlReport) + task.xmlReportFile = xmlReportFile + task.htmlReportFile = htmlReportFile + task.classpath = findBugs.findbugsClasspath + task.onlyIf { xmlReportFile?.exists() } + task } } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintConfigurator.groovy new file mode 100644 index 0000000..cee82a4 --- /dev/null +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintConfigurator.groovy @@ -0,0 +1,97 @@ +package com.novoda.staticanalysis.internal.ktlint + +import com.novoda.staticanalysis.StaticAnalysisExtension +import com.novoda.staticanalysis.Violations +import com.novoda.staticanalysis.internal.Configurator +import com.novoda.staticanalysis.internal.VariantFilter +import com.novoda.staticanalysis.internal.checkstyle.CollectCheckstyleViolationsTask +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.Task + +class KtlintConfigurator implements Configurator { + + private static final String KTLINT_PLUGIN = 'org.jlleitschuh.gradle.ktlint' + private static final String KTLINT_NOT_APPLIED = 'The Ktlint plugin is configured but not applied. Please apply the plugin in your build script.\nFor more information see https://github.com/jeremymailen/kotlinter-gradle' + + private final Project project + private final Violations violations + private final Task evaluateViolations + private final VariantFilter variantFilter + + static KtlintConfigurator create(Project project, + NamedDomainObjectContainer violationsContainer, + Task evaluateViolations) { + Violations violations = violationsContainer.maybeCreate('ktlint') + return new KtlintConfigurator(project, violations, evaluateViolations) + } + + KtlintConfigurator(Project project, Violations violations, Task evaluateViolations) { + this.project = project + this.violations = violations + this.evaluateViolations = evaluateViolations + this.variantFilter = new VariantFilter(project) + } + + @Override + void execute() { + project.extensions.findByType(StaticAnalysisExtension).ext.ktlint = { Closure config -> + if (!project.plugins.hasPlugin(KTLINT_PLUGIN)) { + throw new GradleException(KTLINT_NOT_APPLIED) + } + + def ktlint = project.ktlint + ktlint.ignoreFailures = true + ktlint.reporters = ['CHECKSTYLE', 'PLAIN'] + ktlint.ext.includeVariants = { Closure filter -> + variantFilter.includeVariantsFilter = filter + } + config.delegate = ktlint + config() + + project.afterEvaluate { + + project.plugins.withId("kotlin") { + configureKotlinProject() + } + project.plugins.withId("kotlin2js") { + configureKotlinProject() + } + project.plugins.withId("kotlin-platform-common") { + configureKotlinProject() + } + project.plugins.withId('com.android.application') { + configureAndroidWithVariants(variantFilter.filteredApplicationVariants) + } + project.plugins.withId('com.android.library') { + configureAndroidWithVariants(variantFilter.filteredLibraryVariants) + } + } + } + } + + private void configureKotlinProject() { + project.sourceSets.each { configureKtlint(it) } + } + + private void configureAndroidWithVariants(def mainVariants) { + mainVariants.all { configureKtlint(it) } + variantFilter.filteredTestVariants.all { configureKtlint(it) } + variantFilter.filteredUnitTestVariants.all { configureKtlint(it) } + } + + private void configureKtlint(def sourceSet) { + def collectViolations = createCollectViolationsTask(violations, sourceSet.name) + evaluateViolations.dependsOn collectViolations + } + + private def createCollectViolationsTask(Violations violations, def sourceSetName) { + project.tasks.create("collectKtlint${sourceSetName.capitalize()}Violations", CollectCheckstyleViolationsTask) { task -> + task.xmlReportFile = new File(project.buildDir, "reports/ktlint/ktlint-${sourceSetName}.xml") + task.htmlReportFile = new File(project.buildDir, "reports/ktlint/ktlint-${sourceSetName}.txt") + task.violations = violations + task.dependsOn project.tasks["ktlint${sourceSetName.capitalize()}Check"] + } + } +} diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTask.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTask.groovy index ed128e9..aeb0ecb 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTask.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTask.groovy @@ -9,7 +9,7 @@ class CollectLintViolationsTask extends CollectViolationsTask { @Override void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) { GPathResult xml = new XmlSlurper().parse(xmlReportFile) - int errors = xml.'**'.findAll { node -> node.name() == 'issue' && node.@severity == 'Error' }.size() + int errors = xml.'**'.findAll { node -> node.name() == 'issue' && (node.@severity == 'Error' || node.@severity == 'Fatal') }.size() int warnings = xml.'**'.findAll { node -> node.name() == 'issue' && node.@severity == 'Warning' }.size() violations.addViolations(errors, warnings, htmlReportFile ?: xmlReportFile) } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/pmd/PmdConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/pmd/PmdConfigurator.groovy index 612bd00..d47a7eb 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/pmd/PmdConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/pmd/PmdConfigurator.groovy @@ -62,6 +62,7 @@ class PmdConfigurator extends CodeQualityConfigurator { task.with { description = "Run PMD analysis for ${sourceSet.name} classes" source = sourceSet.java.srcDirs + exclude '**/*.kt' } } sourceFilter.applyTo(task) @@ -82,10 +83,10 @@ class PmdConfigurator extends CodeQualityConfigurator { } private CollectPmdViolationsTask createViolationsCollectionTask(Pmd pmd, Violations violations) { - project.tasks.create("collect${pmd.name.capitalize()}Violations", CollectPmdViolationsTask) { collectViolations -> - collectViolations.xmlReportFile = pmd.reports.xml.destination - collectViolations.violations = violations - } + def task = project.tasks.maybeCreate("collect${pmd.name.capitalize()}Violations", CollectPmdViolationsTask) + task.xmlReportFile = pmd.reports.xml.destination + task.violations = violations + task } } diff --git a/plugin/src/test/fixtures/reports/lint/lint-results.xml b/plugin/src/test/fixtures/reports/lint/lint-results.xml index f6e1641..ad46cb3 100644 --- a/plugin/src/test/fixtures/reports/lint/lint-results.xml +++ b/plugin/src/test/fixtures/reports/lint/lint-results.xml @@ -1,6 +1,9 @@ + + rules() { - return [TestProjectRule.forKotlinProject(), TestProjectRule.forAndroidKotlinProject()] + private static final String DETEKT_NOT_APPLIED = 'The Detekt plugin is configured but not applied. Please apply the plugin in your build script.' + private static final String OUTPUT_NOT_DEFINED = 'Output not defined! To analyze the results, `output` needs to be defined in Detekt profile.' + + @Parameterized.Parameters(name = "{0} with Detekt: {1}") + static Iterable rules() { + return [ + [TestProjectRule.forKotlinProject(), "1.0.0.RC6-2"], + [TestProjectRule.forAndroidKotlinProject(), "1.0.0.RC6-2"], + [TestProjectRule.forKotlinProject(), "1.0.0.RC8"], + [TestProjectRule.forAndroidKotlinProject(), "1.0.0.RC8"], + ]*.toArray() } @Rule public final TestProjectRule projectRule + private final String detektVersion - DetektIntegrationTest(TestProjectRule projectRule) { + DetektIntegrationTest(TestProjectRule projectRule, String detektVersion) { this.projectRule = projectRule + this.detektVersion = detektVersion + } + + @Test + void shouldFailBuildOnConfigurationWhenNoOutputNotDefined() { + def emptyConfiguration = detektWith("") + + def result = createProjectWithZeroThreshold(Fixtures.Detekt.SOURCES_WITH_WARNINGS) + .withToolsConfig(emptyConfiguration) + .buildAndFail('check') + + assertThat(result.logs).contains(OUTPUT_NOT_DEFINED) + } + + @Test + void shouldFailBuildOnConfigurationWhenDetektConfiguredButNotApplied() { + def result = projectRule.newProject() + .withToolsConfig(detektConfiguration(Fixtures.Detekt.SOURCES_WITH_ERRORS)) + .buildAndFail('check') + + assertThat(result.logs).contains(DETEKT_NOT_APPLIED) } @Test @@ -74,12 +104,12 @@ class DetektIntegrationTest { @Test void shouldNotFailBuildWhenNoDetektWarningsOrErrorsEncounteredAndNoThresholdTrespassed() { def testProject = projectRule.newProject() - .withPlugin("io.gitlab.arturbosch.detekt", "1.0.0.RC6-2") + .withPlugin("io.gitlab.arturbosch.detekt", detektVersion) .withPenalty('''{ maxWarnings = 0 maxErrors = 0 }''') - testProject = testProject.withToolsConfig(detektConfigurationWithoutInput(testProject)) + .withToolsConfig(detektConfigurationWithoutInput()) TestProject.Result result = testProject .build('check') @@ -88,41 +118,24 @@ class DetektIntegrationTest { assertThat(result.logs).doesNotContainDetektViolations() } - @Test - void shouldFailBuildWhenDetektConfiguredButNotApplied() { - def testProject = projectRule.newProject() - .withPenalty('''{ - maxWarnings = 0 - maxErrors = 0 - }''') - - testProject = testProject.withToolsConfig(detektConfiguration(testProject, Fixtures.Detekt.SOURCES_WITH_ERRORS)) - - TestProject.Result result = testProject - .buildAndFail('check') - - assertThat(result.logs).containsDetektNotApplied() - } - private TestProject createProjectWithZeroThreshold(File sources) { createProjectWith(sources) } private TestProject createProjectWith(File sources, int maxWarnings = 0, int maxErrors = 0) { - def testProject = projectRule.newProject() - .withPlugin("io.gitlab.arturbosch.detekt", "1.0.0.RC6-2") + projectRule.newProject() + .withPlugin("io.gitlab.arturbosch.detekt", detektVersion) .withSourceSet('main', sources) .withPenalty("""{ maxWarnings = ${maxWarnings} maxErrors = ${maxErrors} }""") - - testProject.withToolsConfig(detektConfiguration(testProject, sources)) + .withToolsConfig(detektConfiguration(sources)) } private TestProject createProjectWithoutDetekt() { projectRule.newProject() - .withPlugin("io.gitlab.arturbosch.detekt", "1.0.0.RC6-2") + .withPlugin("io.gitlab.arturbosch.detekt", detektVersion) .withSourceSet('main', Fixtures.Detekt.SOURCES_WITH_WARNINGS) .withPenalty('''{ maxWarnings = 0 @@ -130,27 +143,29 @@ class DetektIntegrationTest { }''') } - private static GString detektConfiguration(TestProject testProject, File input) { + private static String detektConfiguration(File input) { + detektWith """ + config = '${Fixtures.Detekt.RULES}' + output = "\$buildDir/reports" + // The input just needs to be configured for the tests. + // Probably detekt doesn't pick up the changed source sets. + // In a example project it was not needed. + input = "${input}" """ - detekt { - profile('main') { - config = "${Fixtures.Detekt.RULES}" - output = "${testProject.projectDir()}/build/reports" - // The input just needs to be configured for the tests. - // Probably detekt doesn't pick up the changed source sets. - // In a example project it was not needed. - input = "${input}" - } - } + } + + private static String detektConfigurationWithoutInput() { + detektWith """ + config = '${Fixtures.Detekt.RULES}' + output = "\$buildDir/reports" """ } - private static GString detektConfigurationWithoutInput(TestProject testProject) { + private static String detektWith(String mainProfile) { """ detekt { profile('main') { - config = "${Fixtures.Detekt.RULES}" - output = "${testProject.projectDir()}/build/reports" + ${mainProfile.stripIndent()} } } """ diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy index ae6dba1..19d96a1 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy @@ -345,6 +345,20 @@ class FindbugsIntegrationTest { Truth.assertThat(result.outcome(':checkFindbugsClasses')).isEqualTo(TaskOutcome.SUCCESS) } + @Test + void shouldNotFailBuildWhenFindbugsIsConfiguredMultipleTimes() { + projectRule.newProject() + .withSourceSet('main', SOURCES_WITH_LOW_VIOLATION) + .withPenalty('none') + .withToolsConfig(""" + findbugs { } + findbugs { + ignoreFailures = false + } + """) + .build('check') + } + /** * The custom task created in the snippet below will check whether {@code Findbugs} tasks with * empty {@code source} will have empty {@code classes} too.

diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintIntegrationTest.groovy new file mode 100644 index 0000000..e259bfe --- /dev/null +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/ktlint/KtlintIntegrationTest.groovy @@ -0,0 +1,98 @@ +package com.novoda.staticanalysis.internal.ktlint + +import com.novoda.test.Fixtures +import com.novoda.test.TestProject +import com.novoda.test.TestProjectRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +import static com.novoda.test.LogsSubject.assertThat + +@RunWith(Parameterized.class) +class KtlintIntegrationTest { + + private static final String KTLINT_NOT_APPLIED = 'The Ktlint plugin is configured but not applied. Please apply the plugin in your build script.' + public static final String DEFAULT_CONFIG = ''' + ktlint { + includeVariants { it.name == "debug" } + } + ''' + public static final String EMPTY_CONFIG = 'ktlint {}' + + @Parameterized.Parameters(name = '{0}') + static def rules() { + return [ + [TestProjectRule.forKotlinProject(), 'main'].toArray(), + [TestProjectRule.forAndroidKotlinProject(), 'debug'].toArray() + ] + } + + @Rule + public final TestProjectRule projectRule + private final String sourceSetName; + + KtlintIntegrationTest(TestProjectRule projectRule, String sourceSetName) { + this.projectRule = projectRule + this.sourceSetName = sourceSetName + } + + @Test + void shouldNotFailWhenKtlintIsNotConfigured() { + def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR) + .build('evaluateViolations') + + assertThat(result.logs).doesNotContainKtlintViolations() + } + + @Test + void shouldFailBuildOnConfigurationWhenKtlintConfiguredButNotApplied() { + def result = projectRule.newProject() + .withToolsConfig(EMPTY_CONFIG) + .buildAndFail('evaluateViolations') + + assertThat(result.logs).contains(KTLINT_NOT_APPLIED) + } + + @Test + void shouldFailBuildWhenKtlintErrorsOverTheThreshold() { + def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR) + .withToolsConfig(DEFAULT_CONFIG) + .buildAndFail('evaluateViolations') + + assertThat(result.logs).containsLimitExceeded(1, 0) + assertThat(result.logs).containsKtlintViolations(1, + result.buildFileUrl("reports/ktlint/ktlint-${sourceSetName}.txt")) + } + + @Test + void shouldNotFailWhenErrorsAreWithinThreshold() { + def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR, 1) + .withToolsConfig(DEFAULT_CONFIG) + .build('evaluateViolations') + + assertThat(result.logs).containsKtlintViolations(1, + result.buildFileUrl("reports/ktlint/ktlint-${sourceSetName}.txt")) + } + + @Test + void shouldNotFailBuildWhenNoErrorsEncounteredAndNoThresholdTrespassed() { + def result = createProjectWith(Fixtures.Ktlint.SOURCES_NO_ERROR, 0) + .withToolsConfig(EMPTY_CONFIG) + .build('evaluateViolations') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).doesNotContainKtlintViolations() + } + + private TestProject createProjectWith(File sources, int maxErrors = 0) { + projectRule.newProject() + .withPlugin('org.jlleitschuh.gradle.ktlint', '4.1.0') + .withSourceSet('main', sources) + .withPenalty("""{ + maxWarnings = 0 + maxErrors = ${maxErrors} + }""") + } +} diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTaskTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTaskTest.groovy index 3e1f7a4..bd86437 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTaskTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/lint/CollectLintViolationsTaskTest.groovy @@ -18,7 +18,7 @@ class CollectLintViolationsTaskTest { Violations violations = new Violations('Android Lint') task.collectViolations(Fixtures.Lint.SAMPLE_REPORT, null, violations) - assertThat(violations.getErrors()).isEqualTo(1) + assertThat(violations.getErrors()).isEqualTo(2) assertThat(violations.getWarnings()).isEqualTo(1) } } diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy index 8ce3824..41771c5 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy @@ -213,6 +213,22 @@ public class PmdIntegrationTest { assertThat(result.logs).doesNotContainPmdViolations() } + @Test + void shouldNotFailBuildWhenPmdIsConfiguredMultipleTimes() { + projectRule.newProject() + .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) + .withPenalty('none') + .withToolsConfig(""" + pmd { + ruleSetFiles = $DEFAULT_RULES + } + pmd { + ignoreFailures = false + } + """) + .build('check') + } + private String pmd(String rules, String... configs) { """pmd { ruleSetFiles = $rules diff --git a/plugin/src/test/groovy/com/novoda/test/Fixtures.groovy b/plugin/src/test/groovy/com/novoda/test/Fixtures.groovy index a583ed6..9c17b14 100644 --- a/plugin/src/test/groovy/com/novoda/test/Fixtures.groovy +++ b/plugin/src/test/groovy/com/novoda/test/Fixtures.groovy @@ -50,6 +50,11 @@ public final class Fixtures { public static final File RULES = new File(RULES_DIR, 'detekt/detekt.yml') } + final static class Ktlint { + static final File SOURCES_WITH_ERROR = new File(SOURCES_DIR, 'ktlint/with-error') + static final File SOURCES_NO_ERROR = new File(SOURCES_DIR, 'ktlint/no-error') + } + final static class Lint { public static final File SOURCES_WITH_WARNINGS = new File(SOURCES_DIR, 'lint/warnings') public static final File SOURCES_WITH_ERRORS = new File(SOURCES_DIR, 'lint/errors') diff --git a/plugin/src/test/groovy/com/novoda/test/LogsSubject.groovy b/plugin/src/test/groovy/com/novoda/test/LogsSubject.groovy index 1abeb75..35fb1e9 100644 --- a/plugin/src/test/groovy/com/novoda/test/LogsSubject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/LogsSubject.groovy @@ -14,11 +14,11 @@ import static com.novoda.test.TestProject.Result.Logs; class LogsSubject extends Subject { private static final String VIOLATIONS_LIMIT_EXCEEDED = 'Violations limit exceeded' - private static final String DETEKT_NOT_APPLIED = 'The Detekt plugin is configured but not applied. Please apply the plugin in your build script.' private static final String CHECKSTYLE_VIOLATIONS_FOUND = 'Checkstyle violations found' private static final String PMD_VIOLATIONS_FOUND = 'PMD violations found' private static final String FINDBUGS_VIOLATIONS_FOUND = 'Findbugs violations found' private static final String DETEKT_VIOLATIONS_FOUND = 'Detekt violations found' + private static final String KTLINT_VIOLATIONS_FOUND = 'ktlint violations found' private static final String LINT_VIOLATIONS_FOUND = 'Lint violations found' private static final SubjectFactory FACTORY = new SubjectFactory() { @@ -44,8 +44,8 @@ class LogsSubject extends Subject { check().that(actual().output) } - public void containsDetektNotApplied() { - outputSubject.contains(DETEKT_NOT_APPLIED) + public void contains(log) { + outputSubject.contains(log) } public void doesNotContainLimitExceeded() { @@ -72,6 +72,10 @@ class LogsSubject extends Subject { outputSubject.doesNotContain(DETEKT_VIOLATIONS_FOUND) } + public void doesNotContainKtlintViolations() { + outputSubject.doesNotContain(KTLINT_VIOLATIONS_FOUND) + } + public void doesNotContainLintViolations() { outputSubject.doesNotContain(LINT_VIOLATIONS_FOUND) } @@ -92,6 +96,10 @@ class LogsSubject extends Subject { containsToolViolations(DETEKT_VIOLATIONS_FOUND, errors, warnings, reportUrls) } + public void containsKtlintViolations(int errors, String... reportUrls) { + containsToolViolations(KTLINT_VIOLATIONS_FOUND, errors, 0, reportUrls) + } + public void containsLintViolations(int errors, int warnings, String... reportUrls) { containsToolViolations(LINT_VIOLATIONS_FOUND, errors, warnings, reportUrls) } diff --git a/plugin/src/test/groovy/com/novoda/test/TestAndroidKotlinProject.groovy b/plugin/src/test/groovy/com/novoda/test/TestAndroidKotlinProject.groovy index eb9ffaf..3fceefc 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestAndroidKotlinProject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestAndroidKotlinProject.groovy @@ -14,7 +14,8 @@ buildscript { } } plugins { - ${formatPlugins(project)} + ${formatPlugins(project)} + id 'com.novoda.static-analysis' } repositories { google() @@ -58,7 +59,7 @@ ${formatExtension(project)} .collect { Map.Entry> entry -> """$entry.key { manifest.srcFile '${Fixtures.ANDROID_MANIFEST}' - kotlin { + java { ${entry.value.collect { "srcDir '$it'" }.join('\n\t\t\t\t')} } }""" diff --git a/plugin/src/test/groovy/com/novoda/test/TestAndroidProject.groovy b/plugin/src/test/groovy/com/novoda/test/TestAndroidProject.groovy index 2c90a82..f8d9eda 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestAndroidProject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestAndroidProject.groovy @@ -14,6 +14,7 @@ buildscript { } plugins { ${formatPlugins(project)} + id 'com.novoda.static-analysis' } repositories { google() diff --git a/plugin/src/test/groovy/com/novoda/test/TestJavaProject.groovy b/plugin/src/test/groovy/com/novoda/test/TestJavaProject.groovy index e014658..ac01e0a 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestJavaProject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestJavaProject.groovy @@ -5,7 +5,8 @@ final class TestJavaProject extends TestProject { private static final Closure TEMPLATE = { TestProject project -> """ plugins { - ${formatPlugins(project)} + ${formatPlugins(project)} + id 'com.novoda.static-analysis' } repositories { jcenter() diff --git a/plugin/src/test/groovy/com/novoda/test/TestKotlinProject.groovy b/plugin/src/test/groovy/com/novoda/test/TestKotlinProject.groovy index 89f80c0..2debb58 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestKotlinProject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestKotlinProject.groovy @@ -15,9 +15,15 @@ buildscript { plugins { ${formatPlugins(project)} + id 'com.novoda.static-analysis' } apply plugin: 'kotlin' + +repositories { + jcenter() +} + sourceSets { ${formatSourceSets(project)} } diff --git a/plugin/src/test/groovy/com/novoda/test/TestProject.groovy b/plugin/src/test/groovy/com/novoda/test/TestProject.groovy index 0b53755..c8e7efd 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestProject.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestProject.groovy @@ -34,7 +34,6 @@ ${project.additionalConfiguration} .withPluginClasspath() .forwardStdOutput(new OutputStreamWriter(System.out)) .forwardStdError(new OutputStreamWriter(System.out)) - withPlugin('com.novoda.static-analysis') } private static File createProjectDir(String path) { @@ -126,7 +125,7 @@ ${project.additionalConfiguration} } protected static String formatPlugins(TestProject project) { - "${project.plugins.join('\n')}" + project.plugins.join('\n') } public static class Result { diff --git a/sample-multi-module/build.gradle b/sample-multi-module/build.gradle index 5493a8a..f2a0dee 100644 --- a/sample-multi-module/build.gradle +++ b/sample-multi-module/build.gradle @@ -1,21 +1,22 @@ buildscript { - ext.kotlin_version = '1.2.30' + ext.kotlin_version = '1.2.60' repositories { google() - maven { url "https://plugins.gradle.org/m2/" } + gradlePluginPortal() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0.RC6-3" + classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0.RC8' + classpath 'gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:5.0.0' } } -apply from: "$rootDir/team-props/tasks.gradle" +apply from: rootProject.file('team-props/tasks.gradle') -subprojects { subproject -> +subprojects { buildscript { repositories { @@ -26,5 +27,6 @@ subprojects { subproject -> repositories { google() mavenCentral() + jcenter() } } diff --git a/sample-multi-module/gradle/wrapper/gradle-wrapper.jar b/sample-multi-module/gradle/wrapper/gradle-wrapper.jar index 7a3265e..f6b961f 100644 Binary files a/sample-multi-module/gradle/wrapper/gradle-wrapper.jar and b/sample-multi-module/gradle/wrapper/gradle-wrapper.jar differ diff --git a/sample-multi-module/gradle/wrapper/gradle-wrapper.properties b/sample-multi-module/gradle/wrapper/gradle-wrapper.properties index f5737a2..7dc503f 100644 --- a/sample-multi-module/gradle/wrapper/gradle-wrapper.properties +++ b/sample-multi-module/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Mar 12 18:00:46 GMT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/sample-multi-module/team-props/detekt-config.yml b/sample-multi-module/team-props/detekt-config.yml index cf14ee4..9059243 100644 --- a/sample-multi-module/team-props/detekt-config.yml +++ b/sample-multi-module/team-props/detekt-config.yml @@ -1,5 +1,4 @@ autoCorrect: true -failFast: false test-pattern: active: true @@ -19,16 +18,6 @@ test-pattern: - 'SpreadOperator' - 'TooManyFunctions' -build: - warningThreshold: 5 - failThreshold: 500 - maxIssues: 500 - weights: - complexity: 2 - LongParameterList: 1 - style: 1 - comments: 1 - processors: active: true diff --git a/sample-multi-module/team-props/findbugs-excludes.xml b/sample-multi-module/team-props/findbugs-excludes.xml index b0bf1a4..a04c37f 100644 --- a/sample-multi-module/team-props/findbugs-excludes.xml +++ b/sample-multi-module/team-props/findbugs-excludes.xml @@ -9,8 +9,4 @@ - - - - diff --git a/sample-multi-module/team-props/pmd-rules.xml b/sample-multi-module/team-props/pmd-rules.xml index a74ad7e..0e93fbf 100644 --- a/sample-multi-module/team-props/pmd-rules.xml +++ b/sample-multi-module/team-props/pmd-rules.xml @@ -5,7 +5,6 @@ Mandatory PMD rules description. - .*\.kt .*\.R\$.* diff --git a/sample-multi-module/team-props/static-analysis.gradle b/sample-multi-module/team-props/static-analysis.gradle index ec8f1cd..05279c1 100644 --- a/sample-multi-module/team-props/static-analysis.gradle +++ b/sample-multi-module/team-props/static-analysis.gradle @@ -1,5 +1,6 @@ apply plugin: com.novoda.staticanalysis.StaticAnalysisPlugin apply plugin: 'io.gitlab.arturbosch.detekt' +apply plugin: "org.jlleitschuh.gradle.ktlint" staticAnalysis { @@ -8,7 +9,6 @@ staticAnalysis { checkstyle { toolVersion '8.8' exclude project.fileTree('src/test/java') - exclude '**/*.kt' configFile rootProject.file('team-props/checkstyle-modules.xml') includeVariants { variant -> variant.name.contains('debug') } } @@ -41,4 +41,8 @@ staticAnalysis { output = project.file("build/reports/detekt") } } + + ktlint { + includeVariants { variant -> variant.name.contains('debug') } + } } diff --git a/sample/app/build.gradle b/sample/app/build.gradle index 48794e8..53f2243 100755 --- a/sample/app/build.gradle +++ b/sample/app/build.gradle @@ -1,5 +1,6 @@ plugins { - id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC6-3' + id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC8' + id "org.jlleitschuh.gradle.ktlint" version "5.0.0" } apply plugin: 'com.android.application' @@ -9,7 +10,6 @@ apply plugin: com.novoda.staticanalysis.StaticAnalysisPlugin android { compileSdkVersion 27 - buildToolsVersion '27.0.3' defaultConfig { applicationId 'com.novoda.staticanalysis.sample' @@ -30,7 +30,7 @@ android { } dependencies { - implementation 'com.android.support:appcompat-v7:27.1.0' + implementation 'com.android.support:appcompat-v7:27.1.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' } @@ -42,7 +42,6 @@ staticAnalysis { checkstyle { toolVersion '8.8' exclude project.fileTree('src/test/java') - exclude '**/*.kt' configFile rootProject.file('team-props/checkstyle-modules.xml') includeVariants { variant -> variant.name.contains('debug') } } @@ -76,4 +75,8 @@ staticAnalysis { } } + ktlint { + android true + includeVariants { variant -> variant.name.contains('debug') } + } } diff --git a/sample/build.gradle b/sample/build.gradle index 0500e1e..c63946c 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,13 +1,13 @@ buildscript { - ext.kotlin_version = '1.2.30' + ext.kotlin_version = '1.2.60' repositories { google() - maven { url "https://plugins.gradle.org/m2/" } + gradlePluginPortal() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -16,5 +16,6 @@ subprojects { repositories { google() mavenCentral() + jcenter() } } diff --git a/sample/gradle/wrapper/gradle-wrapper.jar b/sample/gradle/wrapper/gradle-wrapper.jar index 7a3265e..a5fe1cb 100755 Binary files a/sample/gradle/wrapper/gradle-wrapper.jar and b/sample/gradle/wrapper/gradle-wrapper.jar differ diff --git a/sample/gradle/wrapper/gradle-wrapper.properties b/sample/gradle/wrapper/gradle-wrapper.properties index 4bfa923..bd24854 100755 --- a/sample/gradle/wrapper/gradle-wrapper.properties +++ b/sample/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Mar 12 18:00:46 GMT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/sample/team-props/detekt-config.yml b/sample/team-props/detekt-config.yml index 0f8d6ff..9059243 100755 --- a/sample/team-props/detekt-config.yml +++ b/sample/team-props/detekt-config.yml @@ -1,5 +1,4 @@ autoCorrect: true -failFast: false test-pattern: active: true @@ -19,16 +18,6 @@ test-pattern: - 'SpreadOperator' - 'TooManyFunctions' -build: - warningThreshold: 5 - failThreshold: 10 - maxIssues: 10 - weights: - complexity: 2 - LongParameterList: 1 - style: 1 - comments: 1 - processors: active: true diff --git a/sample/team-props/findbugs-excludes.xml b/sample/team-props/findbugs-excludes.xml index b0bf1a4..a04c37f 100755 --- a/sample/team-props/findbugs-excludes.xml +++ b/sample/team-props/findbugs-excludes.xml @@ -9,8 +9,4 @@ - - - - diff --git a/sample/team-props/pmd-rules.xml b/sample/team-props/pmd-rules.xml index a74ad7e..0e93fbf 100755 --- a/sample/team-props/pmd-rules.xml +++ b/sample/team-props/pmd-rules.xml @@ -5,7 +5,6 @@ Mandatory PMD rules description. - .*\.kt .*\.R\$.*