├── .github └── workflows │ ├── Release.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── build.gradle ├── buildSrc ├── build.gradle └── src │ └── main │ └── groovy │ ├── DependenciesPlugin.groovy │ ├── GradlePlugins.groovy │ └── Libraries.groovy ├── docs ├── advanced-usage.md ├── incubating │ └── custom-evaluator.md ├── supported-tools.md └── tools │ ├── android_lint.md │ ├── checkstyle.md │ ├── detekt.md │ ├── ktlint.md │ ├── pmd.md │ └── spotbugs.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── groovy │ │ └── com │ │ └── gradleup │ │ └── staticanalysis │ │ ├── DefaultViolationsEvaluator.groovy │ │ ├── EvaluateViolationsTask.groovy │ │ ├── LogsExtension.groovy │ │ ├── PenaltyExtension.groovy │ │ ├── ReportUrlRenderer.groovy │ │ ├── RulesExtension.groovy │ │ ├── StaticAnalysisExtension.groovy │ │ ├── StaticAnalysisPlugin.groovy │ │ ├── Violations.groovy │ │ ├── ViolationsEvaluator.groovy │ │ └── internal │ │ ├── CodeQualityConfigurator.groovy │ │ ├── CollectViolationsTask.groovy │ │ ├── Configurator.groovy │ │ ├── Exceptions.groovy │ │ ├── QuietLogger.groovy │ │ ├── SourceFilter.groovy │ │ ├── VariantFilter.groovy │ │ ├── checkstyle │ │ ├── CheckstyleConfigurator.groovy │ │ └── CollectCheckstyleViolationsTask.groovy │ │ ├── detekt │ │ └── DetektConfigurator.groovy │ │ ├── ktlint │ │ └── KtlintConfigurator.groovy │ │ ├── lint │ │ ├── CollectLintViolationsTask.groovy │ │ └── LintConfigurator.groovy │ │ ├── pmd │ │ ├── CollectPmdViolationsTask.groovy │ │ ├── PmdConfigurator.groovy │ │ └── PmdViolationsEvaluator.groovy │ │ └── spotbugs │ │ ├── CollectSpotBugsViolationsTask.groovy │ │ ├── GenerateSpotBugsHtmlReport.groovy │ │ ├── SpotBugsConfigurator.groovy │ │ └── SpotBugsViolationsEvaluator.groovy │ └── test │ ├── fixtures │ ├── AndroidManifest.xml │ ├── reports │ │ ├── findbugs │ │ │ └── reports │ │ │ │ └── sample.xml │ │ ├── lint │ │ │ └── lint-results.xml │ │ └── pmd │ │ │ └── reports │ │ │ └── sample.xml │ ├── rules │ │ ├── checkstyle │ │ │ └── config │ │ │ │ └── modules.xml │ │ ├── detekt │ │ │ └── detekt.yml │ │ ├── lint │ │ │ └── lint.xml │ │ └── pmd │ │ │ └── config │ │ │ └── rules.xml │ └── sources │ │ ├── checkstyle │ │ ├── errors │ │ │ └── Greeter.java │ │ └── warnings │ │ │ └── Dice.java │ │ ├── detekt │ │ ├── errors │ │ │ └── Errors.kt │ │ └── warnings │ │ │ └── Warnings.kt │ │ ├── findbugs │ │ ├── high │ │ │ └── com │ │ │ │ └── gradleup │ │ │ │ └── test │ │ │ │ └── HighPriorityViolator.java │ │ ├── low │ │ │ └── LowPriorityViolator.java │ │ └── medium │ │ │ └── MediumPriorityViolator.java │ │ ├── ktlint │ │ ├── no-error │ │ │ └── ValidClass.kt │ │ └── with-error │ │ │ └── KtLintViolator.kt │ │ ├── lint │ │ ├── errors │ │ │ └── MyView.java │ │ └── warnings │ │ │ └── Warning.java │ │ └── pmd │ │ ├── priority1 │ │ └── Priority1Violator.java │ │ ├── priority2 │ │ └── Priority2Violator.java │ │ ├── priority3 │ │ └── Priority3Violator.java │ │ └── priority4 │ │ └── Priority4Violator.java │ └── groovy │ └── com │ └── gradleup │ ├── staticanalysis │ ├── DefaultViolationsEvaluatorTest.groovy │ ├── RulesIntegrationTest.groovy │ ├── StaticAnalysisExtensionTest.groovy │ ├── StaticAnalysisPluginTest.groovy │ └── internal │ │ ├── LogsConfigurationIntegrationTest.groovy │ │ ├── SourceFilterTest.groovy │ │ ├── checkstyle │ │ ├── CheckstyleAndroidVariantIntegrationTest.groovy │ │ ├── CheckstyleConfigurationTest.groovy │ │ └── CheckstyleIntegrationTest.groovy │ │ ├── detekt │ │ └── DetektIntegrationTest.groovy │ │ ├── ktlint │ │ └── KtlintIntegrationTest.groovy │ │ ├── lint │ │ ├── CollectLintViolationsTaskTest.groovy │ │ ├── LintAndroidVariantIntegrationTest.groovy │ │ └── LintIntegrationTest.groovy │ │ ├── pmd │ │ ├── PmdAndroidVariantIntegrationTest.groovy │ │ ├── PmdConfigurationTest.groovy │ │ ├── PmdIntegrationTest.groovy │ │ └── PmdViolationsEvaluatorTest.groovy │ │ └── spotbugs │ │ ├── SpotBugsConfigurationTest.groovy │ │ └── SpotBugsIntegrationTest.groovy │ └── test │ ├── DeployRulesTestRule.groovy │ ├── Fixtures.groovy │ ├── LogsSubject.groovy │ ├── PenaltyExtensionSubject.groovy │ ├── TestAndroidKotlinProject.groovy │ ├── TestAndroidProject.groovy │ ├── TestJavaProject.groovy │ ├── TestKotlinProject.groovy │ ├── TestProject.groovy │ ├── TestProjectRule.groovy │ └── TestProjectSubject.groovy ├── sample-multi-module ├── app │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── gradleup │ │ │ │ └── staticanalysisplugin │ │ │ │ └── sample │ │ │ │ ├── MyActivity.kt │ │ │ │ ├── MyClass.java │ │ │ │ └── SomeOtherActivity.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_my.xml │ │ │ └── activity_some_other.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── gradleup │ │ └── staticanalysisplugin │ │ └── sample │ │ └── MyClassTest.java ├── build.gradle ├── core │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── gradleup │ │ └── staticanalysisplugin │ │ └── sample │ │ ├── AnotherCoreClass.kt │ │ └── SomeJavaClass.java ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── team-props │ ├── checkstyle-modules.xml │ ├── detekt-config.yml │ ├── findbugs-excludes.xml │ ├── lint-config.xml │ ├── pmd-rules.xml │ ├── static-analysis.gradle │ └── tasks.gradle ├── sample ├── app │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── gradleup │ │ │ │ └── staticanalysisplugin │ │ │ │ └── sample │ │ │ │ ├── MyActivity.kt │ │ │ │ ├── MyClass.java │ │ │ │ └── SomeOtherActivity.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_my.xml │ │ │ └── activity_some_other.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── gradleup │ │ └── staticanalysisplugin │ │ └── sample │ │ └── MyClassTest.java ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── team-props │ ├── checkstyle-modules.xml │ ├── detekt-config.yml │ ├── findbugs-excludes.xml │ ├── lint-config.xml │ └── pmd-rules.xml └── settings.gradle /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Publish a release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | plugin-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout the repo 14 | uses: actions/checkout@v2 15 | - name: Publish the plugin artifacts 16 | run: ./gradlew publish publishPlugins -Pgradle.publish.key="$GRADLE_KEY" -Pgradle.publish.secret="$GRADLE_SECRET" 17 | env: 18 | GRADLE_KEY: ${{ secrets.GRADLE_KEY }} 19 | GRADLE_SECRET: ${{ secrets.GRADLE_SECRET }} 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | paths-ignore: 8 | - 'docs/**' 9 | - '*.md' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: gradle/wrapper-validation-action@v1 18 | - name: Cache gradle 19 | uses: actions/cache@v1 20 | with: 21 | path: ~/.gradle/caches 22 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 23 | restore-keys: | 24 | ${{ runner.os }}-gradle- 25 | 26 | - name: Run tests 27 | run: ./gradlew build --stacktrace 28 | 29 | - name: Run plugin on samples 30 | run: | 31 | ./gradlew -p sample evaluateViolations 32 | ./gradlew -p sample-multi-module evaluateViolations 33 | 34 | - name: Bundle the build report 35 | if: failure() 36 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 37 | - name: Upload the build report 38 | if: failure() 39 | uses: actions/upload-artifact@master 40 | with: 41 | name: error-report 42 | path: build-reports.zip 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ/Android Studio 2 | .idea 3 | *.iml 4 | 5 | # MacOS 6 | .DS_Store 7 | 8 | # Build output 9 | build 10 | out 11 | 12 | # Gradle files 13 | .gradle/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Log Files 19 | *.log 20 | 21 | # Generated files 22 | gen/ 23 | 24 | # Properties files 25 | secrets.properties 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 1.4 5 | ----------- 6 | 7 | - Support for Gradle 7.x added (Gradle 5.x and 6.x should still be supported) 8 | - Support for Spotbugs Gradle plugin 4.x added. Since Spotbugs is not backward compatible, support for 2.x/3.x are removed. 9 | - Minor behavior changes: 10 | - Hard-coded pmd version removed. It will now automatically use the associated version with Gradle. 11 | - Migrated to official Spotbugs Android support. There might be minor differences. 12 | 13 | Version 1.3 14 | ----------- 15 | 16 | - First release under new group name. The plugin is now available under 17 | `com.gradleup.static-analysis` plugin id and published in Gradle Plugin Portal. 18 | - Package names are changed according to the new repository 19 | - Findbugs support is completely removed in order to support Gradle 6 20 | 21 | ### Migration 22 | 23 | For more modern plugins approach 24 | 25 | ```groovy 26 | plugins { 27 | 28 | // Replace 29 | id 'com.novoda.static-analysis' version '1.2' 30 | 31 | // With 32 | id 'com.gradleup.static-analysis' version '1.3' 33 | } 34 | ``` 35 | 36 | For classpath dependency approach: 37 | 38 | ```groovy 39 | // Replace 40 | buildscript { 41 | // ... 42 | dependencies { 43 | classpath 'com.novoda:gradle-static-analysis-plugin:1.2' 44 | } 45 | } 46 | 47 | apply plugin: 'com.novoda.static-analysis' 48 | 49 | // With: 50 | buildscript { 51 | // ... 52 | dependencies { 53 | classpath("com.gradleup:static-analysis-plugin:1.3") 54 | } 55 | } 56 | 57 | apply plugin: 'com.gradleup.static-analysis' 58 | ``` 59 | 60 | Older Versions 61 | ------------- 62 | 63 | Please refer to Novoda's repository for versions older than 1.2: https://github.com/novoda/gradle-static-analysis-plugin/blob/master/CHANGELOG.md 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle static analysis plugin 2 | ![Test](https://github.com/GradleUp/static-analysis-plugin/workflows/Test/badge.svg) 3 | [![](https://img.shields.io/badge/License-Apache%202.0-lightgrey.svg)](LICENSE.txt) 4 | 5 | A Gradle plugin to easily apply the same setup of static analysis tools across different Android, Java or Kotlin projects. 6 | 7 | Supports [Task Configuration Avoidance](https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) so that you have zero overhead in build speeds when you use this plugin! 8 | 9 | ## Description 10 | Gradle supports many popular static analysis (Checkstyle, PMD, SpotBugs, etc) via a set of built-in plugins. 11 | Using these plugins in an Android module will require an additional setup to compensate for the differences between 12 | the model adopted by the Android plugin compared to the Java one. 13 | 14 | The `static-analysis-plugin` aims to provide: 15 | - flexible, configurable penalty strategy for builds 16 | - easy, Android-friendly integration for all static analysis 17 | - convenient way of sharing same setup across different projects 18 | - healthy, versionable and configurable defaults 19 | 20 | ### Supported tools 21 | The plugin supports various static analysis tools for Java, Kotlin and Android projects: 22 | 23 | * [`Checkstyle`](docs/tools/checkstyle.md) 24 | * [`PMD`](docs/tools/pmd.md) 25 | * [`SpotBugs`](docs/tools/spotbugs.md) 26 | * [`Detekt`](docs/tools/detekt.md) 27 | * [`Android Lint`](docs/tools/android_lint.md) 28 | * [`KtLint`](docs/tools/ktlint.md) 29 | 30 | Please note that the tools availability depends on the project the plugin is applied to. For more details please refer to the 31 | [supported tools](docs/supported-tools.md) page. 32 | 33 | ### Tools in-consideration 34 | 35 | * `CPD (Duplicate Code Detection) ` 36 | * `error-prone` 37 | * `Jetbrains IDEA Inspections` 38 | 39 | For all tools in consideration, please refer to [issues](https://github.com/GradleUp/static-analysis-plugin/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+tool%22). 40 | 41 | ### Out-of-the-box support for Android projects 42 | Android projects use a Gradle model that is not compatible with the Java one, supported by the built-in static analysis tools plugins. 43 | Applying `com.gradleup.static-analysis` Plugin to your Android project will make sure all the necessary tasks are created and correctly configured 44 | without any additional hassle. 45 | 46 | ## Add the plugin to your project 47 | 48 | [ ![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/gradleup/static-analysis/com.gradleup.static-analysis.gradle.plugin/maven-metadata.xml.svg?label=Gradle%20Plugins%20Portal) ](https://plugins.gradle.org/plugin/com.gradleup.static-analysis) 49 | 50 | Add the plugin in `build.gradle(.kts)` file 51 | 52 | ```kotlin 53 | plugins { 54 | id("com.gradleup.static-analysis") version "" 55 | } 56 | ``` 57 | 58 | Or apply the plugin from Gradle plugin portal as a classpath dependency 59 | 60 | ```groovy 61 | buildscript { 62 | repositories { 63 | gradlePluginPortal() 64 | } 65 | dependencies { 66 | classpath("com.gradleup:static-analysis-plugin:") 67 | } 68 | } 69 | 70 | apply plugin: 'com.gradleup.static-analysis' 71 | ``` 72 | 73 | ## Simple usage 74 | A typical configuration for the plugin will look like: 75 | 76 | ```gradle 77 | staticAnalysis { 78 | penalty { 79 | maxErrors = 0 80 | maxWarnings = 0 81 | } 82 | checkstyle { } 83 | pmd { } 84 | spotbugs { } 85 | detekt { } 86 | lintOptions { } 87 | } 88 | ``` 89 | 90 | This will enable all the tools with their default settings and create `evaluateViolations` task. Running `./gradlew evaluateViolations` task will run all configured tools and print the reports to console. For more advanced configurations, please refer to the 91 | [advanced usage](docs/advanced-usage.md) and to the [supported tools](docs/supported-tools.md) pages. 92 | 93 | ## Sample app 94 | There are two sample Android projects available, one consisting of a regular app - available [here](https://github.com/GradleUp/static-analysis-plugin/tree/master/sample) - and the other comprising a multi-module setup available [here](https://github.com/GradleUp/static-analysis-plugin/tree/master/sample-multi-module). Both sample projects showcase a setup featuring Checkstyle, SpotBugs, PMD, Lint, Ktlint and Detekt. 95 | 96 | ## License 97 | 98 | > This project is forked from its original location by the original authors https://github.com/novoda/gradle-static-analysis-plugin 99 | 100 | Copyright 2020 The GradleUp Authors 101 | 102 | Licensed under the Apache License, Version 2.0 (the "License"); 103 | you may not use this file except in compliance with the License. 104 | You may obtain a copy of the License at 105 | 106 | http://www.apache.org/licenses/LICENSE-2.0 107 | 108 | Unless required by applicable law or agreed to in writing, software 109 | distributed under the License is distributed on an "AS IS" BASIS, 110 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | See the License for the specific language governing permissions and 112 | limitations under the License. 113 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. Bump version code in `gradle.properties`. 4 | 1. Create an entry in `CHANGELOG.md` containing the changes in that release. 5 | 1. Create a tag with the same version and push it to origin. 6 | 1. After the release is successful do a manual [github release](https://github.com/novoda/gradle-static-analysis-plugin/releases) with the newly created tag. 7 | 8 | This releases the plugin to the [Gradle Plugins Repository](https://plugins.gradle.org/plugin/com.gradleup.static-analysis). 9 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: DependenciesPlugin 2 | 3 | subprojects { 4 | buildscript { 5 | repositories { 6 | gradlePluginPortal() 7 | } 8 | dependencies { 9 | classpath gradlePlugins.gradleGit 10 | } 11 | } 12 | 13 | group = property("GROUP") 14 | version = property("VERSION_NAME") 15 | 16 | afterEvaluate { project -> 17 | configurePomDetails(project) 18 | } 19 | 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | project.plugins.withType(JavaBasePlugin) { 25 | project.with { 26 | sourceCompatibility = JavaVersion.VERSION_1_7 27 | targetCompatibility = JavaVersion.VERSION_1_7 28 | } 29 | } 30 | } 31 | 32 | void configurePomDetails(Project project) { 33 | project.publishing { 34 | publications.withType(MavenPublication) { 35 | pom { 36 | url = findProperty("POM_URL") 37 | 38 | scm { 39 | url = findProperty("POM_SCM_URL") 40 | connection = findProperty("POM_SCM_CONNECTION") 41 | } 42 | 43 | licenses { 44 | license { 45 | name = findProperty("POM_LICENCE_NAME") 46 | url = findProperty("POM_LICENCE_URL") 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | wrapper { 55 | gradleVersion = '5.6.3' 56 | distributionType = Wrapper.DistributionType.ALL 57 | } 58 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | jcenter() 3 | } 4 | 5 | apply plugin: 'groovy' 6 | 7 | dependencies { 8 | implementation gradleApi() 9 | } 10 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/DependenciesPlugin.groovy: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | 4 | class DependenciesPlugin implements Plugin { 5 | 6 | @Override 7 | void apply(Project project) { 8 | project.extensions.create('gradlePlugins', GradlePlugins) 9 | project.extensions.create('libraries', Libraries, project) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/GradlePlugins.groovy: -------------------------------------------------------------------------------- 1 | class GradlePlugins { 2 | final gradleGit = 'org.ajoberstar:gradle-git:1.6.0' 3 | } 4 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/Libraries.groovy: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | 3 | class Libraries { 4 | final junit = 'junit:junit:4.12' 5 | final truth = 'com.google.truth:truth:0.30' 6 | final guava = 'com.google.guava:guava:19.0' 7 | final mockito = 'org.mockito:mockito-core:2.13.0' 8 | final findbugs = new Findbugs() 9 | 10 | private final Project project 11 | 12 | Libraries(Project project) { 13 | this.project = project 14 | } 15 | 16 | def getGradleApi() { 17 | project.dependencies.gradleApi() 18 | } 19 | 20 | def getGradleTestKit() { 21 | project.dependencies.gradleTestKit() 22 | } 23 | 24 | private static class Findbugs { 25 | final annotations = 'com.google.code.findbugs:jsr305:3.0.0' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/incubating/custom-evaluator.md: -------------------------------------------------------------------------------- 1 | # Custom violations evaluator (incubating) 2 | 3 | The plugin uses a [`ViolationsEvaluator`][violationsevaluatorcode] to determine what to do with the results collected from all the active 4 | tools (if any). The built-in behaviour is provided by the [`DefaultViolationsEvaluator`][defaultviolationsevaluatorcode], which you can 5 | read more about [below](#the-defaultviolationsevaluator). The plugin's violations evaluation behaviour is not fixed, and it can be 6 | customised by providing an implementation of the [`ViolationsEvaluator`][violationsevaluatorcode] interface. 7 | 8 | ## Table of contents 9 | * [The `DefaultViolationsEvaluator`](#the-defaultviolationsevaluator) 10 | * [Creating a custom violations evaluator](#creating-a-custom-violations-evaluator) 11 | 12 | --- 13 | 14 | ## The `DefaultViolationsEvaluator` 15 | The plugin has a default mechanism to decide whether to consider a build as passing or failed. The mechanism is manifesting itself 16 | as the `penalty` closure: 17 | 18 | ```gradle 19 | staticAnalysis { 20 | penalty { 21 | maxErrors = 0 22 | maxWarnings = 10 23 | } 24 | //... 25 | } 26 | ``` 27 | 28 | This closure instructs the plugin to use a [`DefaultViolationsEvaluator`][defaultviolationsevaluatorcode] that will count the number of 29 | errors and warnings and compare them against the set thresholds. For more details, see the 30 | [Configurable failure thresholds](../advanced-usage.md#configurable-failure-thresholds) documentation. 31 | 32 | ## Creating a custom violations evaluator 33 | In order to provide a custom evaluator, you can implement the [`ViolationsEvaluator`][violationsevaluatorcode] interface and provide 34 | that implementation to the `evaluator` property of the `staticAnalysis` closure. The [`ViolationsEvaluator`][violationsevaluatorcode] 35 | can be provided as a closure as well: 36 | 37 | ```gradle 38 | staticAnalysis { 39 | evaluator { Set allViolations -> 40 | // add your evaluation logic here 41 | } 42 | //... 43 | } 44 | ``` 45 | 46 | The `evaluator` is invoked after all the `collectViolations` tasks have been completed, and is the last step in executing the plugin's 47 | main task, `evaluateViolations`. 48 | 49 | The evaluation logic can be any arbitrary function that respects this contract: 50 | * The evaluator receives a set containing all the [`Violations`][violationscode] that have been collected by the tools (one per tool) 51 | * If the build is to be considered successful, then the evaluator will run to completion without throwing exceptions 52 | * If the build is to be considered failed, then the evaluator will throw a `GradleException` 53 | 54 | Anything that respect such contract is valid. For example, a custom evaluator might: 55 | * Collect all the report files and upload them somewhere, or send them to Slack or an email address 56 | * Use the GitHub API to report the issues on the PR that the build is running on, à la [GNAG](https://github.com/btkelly/gnag) 57 | * Only break the build if there are errors or warnings in one specific report 58 | * Or anything else that you can think of 59 | 60 | For example, this custom evaluator fails the build if PMD errors are greater than five: 61 | 62 | ```gradle 63 | evaluator { Set allViolations -> 64 | allViolations.each { violation -> 65 | if (violation.name == "PMD" && violation.errors > 5) { 66 | throw new GradleException("PMD Violations exceeded") 67 | } 68 | } 69 | } 70 | ``` 71 | The properties you can read from a [`Violation`][violationscode] result are: 72 | 73 | * `name`: Possible values are: `"PMD"`, `"Checkstyle"`, `"Spotbugs"`, `"KTlint"`, `"Detekt"` and `"Lint"`. 74 | * `errors`: Represents the number of errors found during the analysis. 75 | * `warnings`: Represents the number of warnings found during the analysis. 76 | * `reports`: Contains a list of the generated report files. 77 | 78 | --- 79 | Please note that the presence of an `evaluator` property will make the plugin ignore the `penalty` closure and its thresholds. If you 80 | want to provide behaviour on top of the default [`DefaultViolationsEvaluator`][defaultviolationsevaluatorcode], you can have your own 81 | evaluator run its logic and then delegate the thresholds counting to an instance of `DefaultViolationsEvaluator` you create. 82 | 83 | [violationsevaluatorcode]: https://github.com/GradleUp/static-analysis-plugin/blob/master/plugin/src/main/groovy/com/gradleup/staticanalysis/ViolationsEvaluator.groovy 84 | [defaultviolationsevaluatorcode]: https://github.com/GradleUp/static-analysis-plugin/blob/master/plugin/src/main/groovy/com/gradleup/staticanalysis/DefaultViolationsEvaluator.groovy 85 | [violationscode]: https://github.com/GradleUp/static-analysis-plugin/blob/master/plugin/src/main/groovy/com/gradleup/staticanalysis/Violations.groovy 86 | -------------------------------------------------------------------------------- /docs/supported-tools.md: -------------------------------------------------------------------------------- 1 | # Supported tools 2 | 3 | The plugin supports several static analysis tools. The availability of each tool depends on the project the plugin is applied to. 4 | Some tools only support Java code, some only Kotlin code, and some only work on Android projects. To be precise: 5 | 6 | Tool | Java | Android
(Java) | Kotlin | Android
(Kotlin) 7 | ---- | -------- | -------- | ----- | ----- 8 | [`Checkstyle`](https://checkstyle.sourceforge.net) | :white_check_mark: | :white_check_mark: | — | — 9 | [`PMD`](https://pmd.github.io) | :white_check_mark: | :white_check_mark: | — | — 10 | [`SpotBugs`](https://spotbugs.github.io/) | :white_check_mark: | :white_check_mark: | — | — 11 | [`Detekt`](https://github.com/arturbosch/detekt) | — | — | :white_check_mark: | :white_check_mark: 12 | [`Android Lint`](https://developer.android.com/studio/write/lint.html) | — | :white_check_mark:️ | — | :white_check_mark:️ 13 | [`KtLint`](https://github.com/shyiko/ktlint) | — | — | :white_check_mark:️ | :white_check_mark:️ 14 | 15 | For additional informations and tips on how to obtain advanced behaviours with the plugin and its tools, please refer to the 16 | [advanced usage](advanced-usage.md) page. 17 | 18 | ## Table of contents 19 | * [Enable and disable tools](#enable-and-disable-tools) 20 | * Configure the tools 21 | * [Detekt](tools/detekt.md) 22 | * [Checkstyle](tools/checkstyle.md) 23 | * [PMD](tools/pmd.md) 24 | * [SpotBugs](tools/spotbugs.md) 25 | * [Android Lint](tools/android_lint.md) 26 | * [KtLint](tools/ktlint.md) 27 | * [Example configurations](#example-configurations) 28 | 29 | --- 30 | 31 | ## Enable and disable tools 32 | In order to enable a tool, you just need to add it to the `staticAnalysis` closure. To enable all supported tools with their default configurations: 33 | 34 | ```gradle 35 | staticAnalysis { 36 | penalty { 37 | // ... (optional) 38 | } 39 | 40 | checkstyle {} 41 | pmd {} 42 | spotbugs {} 43 | lintOptions {} 44 | detekt {} 45 | ktlint {} 46 | } 47 | ``` 48 | 49 | To disable a tool, simply omit its closure from `staticAnalysis`. This means that, for example, this will not run any tools: 50 | 51 | ```gradle 52 | staticAnalysis { 53 | penalty { 54 | // ... 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/tools/android_lint.md: -------------------------------------------------------------------------------- 1 | # Android Lint 2 | [Android Lint](https://developer.android.com/studio/write/lint.html) is a linter and static analysis tool for Android projects which can detect bugs 3 | and potential issues in code, resources and configuration files. 4 | 5 | Be aware that Lint just supports Kotlin since version 3.1.0 of the [Android Gradle Plugin](https://developer.android.com/studio/releases/gradle-plugin.html). 6 | 7 | ## Configure Android Lint 8 | Lint is configured through the `lintOptions` closure. It supports all [official](https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.LintOptions.html) 9 | properties except `abortOnError`, `htmlReport` and `xmlReport`. These are overridden so that Lint won't break the build on its own and always generates reports. 10 | -------------------------------------------------------------------------------- /docs/tools/checkstyle.md: -------------------------------------------------------------------------------- 1 | # Checkstyle 2 | [Checkstyle](http://checkstyle.sourceforge.net/) is a code style static analysis tool for Java. It is supported for both pure Java and Java Android projects, 3 | but it does not support Kotlin nor Kotlin Android projects. It then only makes sense to have Checkstyle enabled if you have Java code in your project. The 4 | plugin only runs Checkstyle on projects that contain the Java or the Android plugin. 5 | 6 | ## Table of contents 7 | * [Configure Checkstyle](#configure-checkstyle) 8 | * [Checkstyle in mixed-language projects](#checkstyle-in-mixed-language-projects) 9 | 10 | --- 11 | 12 | ## Configure Checkstyle 13 | To enable and configure Checkstyle for a project use the `checkstyle` closure: 14 | 15 | ```gradle 16 | checkstyle { 17 | toolVersion // A string, as per http://checkstyle.sourceforge.net/releasenotes.html, e.g., '8.8' 18 | exclude // A fileTree, such as project.fileTree('src/test/java') to exclude Java unit tests 19 | configFile // A file containing the Checkstyle config, e.g., teamPropsFile('static-analysis/checkstyle-modules.xml') 20 | includeVariants { variant -> ... } // A closure to determine which variants (for Android) to include 21 | } 22 | ``` 23 | 24 | You can have multiple `exclude` statements. 25 | 26 | For more informations about Checkstyle rules, refer to the [official website](http://checkstyle.sourceforge.net/checks.html). 27 | 28 | ## Checkstyle in mixed-language projects 29 | 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` 30 | in the configuration closure, or you can do so by adding a suppressions file: 31 | 32 | `checkstyle-suppressions.xml` 33 | ```xml 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ``` 47 | 48 | You then need to reference this file from the Checkstyle configuration file: 49 | 50 | `checkstyle-modules.xml` 51 | ```xml 52 | 55 | 56 | 57 | ... 58 | 59 | 60 | 61 | ... 62 | 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/tools/detekt.md: -------------------------------------------------------------------------------- 1 | # Detekt 2 | [Detekt](https://github.com/arturbosch/detekt) is a static analysis tool that looks for potential bugs and style violations in Kotlin code. It 3 | does not support Java. It can be used in both pure Kotlin, and Android Kotlin projects. It then only makes sense to have Detekt enabled if you 4 | have Kotlin code in your project. The plugin only runs Detekt on projects that contain the Kotlin or the Kotlin-Android plugin. 5 | 6 | > Supported Detekt Gradle Plugin version: **1.0.0.RC9.2 and above** 7 | > Last tested Detekt Gradle Plugin version: **1.3.1** 8 | 9 | ## Table of contents 10 | * [IMPORTANT: setup Detekt](#important-setup-detekt) 11 | * [Configure Detekt](#configure-detekt) 12 | * [Exclude files from Detekt analysis](#exclude-files-from-detekt-analysis) 13 | * [Detekt in mixed-language projects](#detekt-in-mixed-language-projects) 14 | 15 | --- 16 | 17 | ## IMPORTANT: setup Detekt 18 | Unlike the other tools, the plugin **won't automatically add Detekt** to your project. If you forget to do it, the plugin will fail the build 19 | with an error. 20 | 21 | In order to use Detekt, you need to manually add it to **all** your Kotlin projects. You can refer to the 22 | [official documentation](https://github.com/arturbosch/detekt/#gradlegroovy) for further details. Note that you should _not_ add the `detekt` 23 | closure to your `build.gradle`s, unlike what the official documentation says. The `detekt` closure in the `staticAnalysis` configuration gets 24 | applied to all Kotlin modules automatically. 25 | 26 | In most common cases, adding Detekt to a project boils down to three simple steps: 27 | 28 | 1. Add this statement to your root `build.gradle` project (change the version according to your needs): 29 | ```gradle 30 | plugins { 31 | id 'io.gitlab.arturbosch.detekt' version '1.0.0-RC14' 32 | // ... 33 | } 34 | ``` 35 | 2. Add this statement to each Kotlin project's `build.gradle`s: 36 | ```gradle 37 | plugins { 38 | id 'io.gitlab.arturbosch.detekt' 39 | // ... 40 | } 41 | ``` 42 | 43 | ## Configure Detekt 44 | To enable and configure Detekt for a project use the `detekt` closure. The closure behaves exactly like the 45 | [standard Detekt plugin](https://github.com/arturbosch/detekt#with-gradle) does in Gradle, which is to say, quite differently 46 | from how the other tools' configurations closures work. For example: 47 | 48 | ```gradle 49 | detekt { 50 | toolVersion = "[version]" // custom toolVersion defined. By default, it is the Gradle plugin version 51 | input = files("src/main/kotlin") // Optional: files representing project's Kotlin sources 52 | filters = ".*/resources/.*,.*/build/.*" // A comma-separated list of regex exclusions 53 | baseline = file("my-detekt-baseline.xml") // Optional: Just if you want to create a baseline file. 54 | } 55 | ``` 56 | 57 | You need to provide **at a minimum** the `config` and `output` values. It's important that you do _not_ specify a `warningThreshold` nor a `failThreshold` 58 | in the Detekt configuration file as it will interfere with the functioning of the Static Analysis plugin's threshold counting. For the same reason, make 59 | sure that `failFast` is set to `false` in the Detekt configuration. 60 | 61 | For more information about Detekt rules, refer to the [official website](https://arturbosch.github.io/detekt/#quick-start-with-gradle). 62 | 63 | ## Exclude files from Detekt analysis 64 | 65 | In order to exclude files from Detekt analysis, you have to use the facilities provided by the Detekt plugin in the `detekt` configuration closure. This means, 66 | you have to provide a value to the `filters` property that contains the exclusion pattern(s) you wish Detekt to ignore. 67 | 68 | The `filters` property expects a string consisting in a comma-separated list of regular expression patterns, e.g., `'.*test.*,.*/resources/.*,.*/tmp/.*'` 69 | (which would exclude any path containing the word `test`, or any path containing a directory called `resources` or `tmp`). 70 | 71 | ## Detekt in mixed-language projects 72 | If your project mixes Java and Kotlin code, you don't need to have an exclusion in place for all `*.java` files. Detekt itself only looks for 73 | `*.kt` files, so no further configuration is required. 74 | -------------------------------------------------------------------------------- /docs/tools/ktlint.md: -------------------------------------------------------------------------------- 1 | # ktlint 2 | [Ktlint](https://github.com/shyiko/ktlint) is a linter for Kotlin with a built-in formatter. It does not support Java. Adding 3 | this tool only makes sense when you have Kotlin sources in your project. 4 | 5 | > Supported Ktlint Gradle Plugin version: **6.2.1 and above** 6 | 7 | ## Table of contents 8 | * [IMPORTANT: setup Ktlint](#important-setup-ktlint) 9 | * [Configure Ktlint](#configure-ktlint) 10 | --- 11 | 12 | ## IMPORTANT: setup Ktlint 13 | 14 | Unlike the other tools, the plugin **won't automatically add Ktlint** to your project. If you forget to do it, the plugin will 15 | fail the build with an error. 16 | 17 | In order to integrate Ktlint easily we choose to use the [Ktlint Gradle plugin](https://github.com/JLLeitschuh/ktlint-gradle/). 18 | This plugin has a very good understanding of Android source sets and build flavors. You can refer to the 19 | [official documentation](https://github.com/JLLeitschuh/ktlint-gradle/#how-to-use) for further details. 20 | 21 | Note that you should _not_ add the `ktlint` closure to your `build.gradle`s, unlike what the official documentation says. The 22 | `ktlint` closure in the `staticAnalysis` configuration gets applied to all Kotlin modules automatically. 23 | 24 | In most common cases, adding Ktlint to a project boils down to these simple steps: 25 | 26 | 1. Add this statement to your root `build.gradle` project (change the version according to your needs): 27 | ```gradle 28 | plugins { 29 | id 'org.jlleitschuh.gradle.ktlint' version '7.3.0' 30 | // ... 31 | } 32 | ``` 33 | 2. Add this statement to each Kotlin project's `build.gradle`s: 34 | ```gradle 35 | plugins { 36 | id 'org.jlleitschuh.gradle.ktlint' 37 | // ... 38 | } 39 | ``` 40 | 41 | ## Configure Ktlint 42 | 43 | Unlike other tools, Ktlint does not offer much configuration. By default, it applies 44 | [Kotlin style guide](https://kotlinlang.org/docs/reference/coding-conventions.html) or 45 | [Android Kotlin style guide](https://android.github.io/kotlin-guides/style.html). 46 | 47 | To use Android style guide: 48 | 49 | ```gradle 50 | ktlint { 51 | android true 52 | } 53 | ``` 54 | 55 | For other configuration options and adding custom rules, refer to the 56 | [official guide](https://github.com/JLLeitschuh/ktlint-gradle/#configuration). 57 | 58 | **Note:** Failures and threshold detection is handled by Static Analysis plugin. That is why `ignoreFailures = true` is set by 59 | the plugin. Please do not manually override `ignoreFailures` property. 60 | -------------------------------------------------------------------------------- /docs/tools/pmd.md: -------------------------------------------------------------------------------- 1 | # PMD 2 | [PMD](https://pmd.github.io/) is an extensible cross-language static code analyser. It supports Java and [several other languages](https://pmd.github.io/#about), 3 | but not Kotlin. It can be used in both pure Java, and Android Java projects. It then only makes sense to have PMD enabled if you have Java code in your project. 4 | The plugin only runs PMD on projects that contain the Java or the Android plugin. 5 | 6 | ## Table of contents 7 | * [Configure PMD](#configure-pmd) 8 | * [PMD in mixed-language projects](#pmd-in-mixed-language-projects) 9 | 10 | --- 11 | 12 | ## Configure PMD 13 | To enable and configure PMD for a project use the `pmd` closure: 14 | 15 | ```gradle 16 | pmd { 17 | toolVersion // A string, as per https://pmd.github.io/pmd-6.0.1/pmd_release_notes.html, e.g., '6.0.1' 18 | incrementalAnalysis = true // Available as of Gradle 5.6 19 | exclude // A fileTree, such as project.fileTree('src/test/java') to exclude Java unit tests 20 | ruleSetFiles // A set of files containing PMD rulesets, e.g., rootProject.files('team-props/static-analysis/pmd-rules.xml') 21 | ruleSets = [] // Note: this is a workaround to make the s in pmd-rules.xml actually work 22 | includeVariants { variant -> ... } // A closure to determine which variants (for Android) to include 23 | } 24 | ``` 25 | 26 | You can have multiple `exclude` statements. 27 | 28 | For more information about PMD Java rules, refer to the [official website](https://pmd.github.io/pmd-6.0.1/pmd_rules_java.html). 29 | 30 | ## PMD in mixed-language projects 31 | 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` 32 | in the configuration closure, or you can do so by adding a suppressions file: 33 | 34 | `pmd-rules.xml` 35 | ```xml 36 | 37 | 41 | 42 | ... 43 | 44 | 45 | .*\.kt 46 | 47 | 48 | ... 49 | 50 | 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/tools/spotbugs.md: -------------------------------------------------------------------------------- 1 | # SpotBugs 2 | [SpotBugs](https://spotbugs.github.io/) is a static analysis tool that looks for potential bugs in Java code. It does not support Kotlin. 3 | It can be used in both pure Java, and Android Java projects. It then only makes sense to have SpotBugs enabled if you have Java code in your project. 4 | The plugin only runs SpotBugs on projects that contain the Java or the Android plugin. 5 | 6 | ## Table of contents 7 | * [Configure SpotBugs](#configure-spotbugs) 8 | * [SpotBugs in mixed-language projects](#spotbugs-in-mixed-language-projects) 9 | 10 | --- 11 | 12 | ## Configure SpotBugs 13 | Enabling and configuring SpotBugs for a project is done through the `spotbugs` closure: 14 | 15 | ```gradle 16 | spotbugs { 17 | toolVersion // Optional string, the latest SpotBugs release (currently 4.0.0-beta4) 18 | excludeFilter // A file containing the SpotBugs exclusions, e.g., teamPropsFile('static-analysis/spotbugs-excludes.xml') 19 | htmlReportEnabled true // Control whether html report generation should be enabled. `true` by default. 20 | includeVariants { variant -> ... } // A closure to determine which variants (only for Android) to include 21 | } 22 | ``` 23 | 24 | For more information about SpotBugs rules, refer to the [official website](https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html). 25 | 26 | ## SpotBugs in mixed-language projects 27 | If your project mixes Java and Kotlin code, you will need to exclude your Kotlin files by using `excludeFilter` 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m 2 | 3 | kotlin.code.style=official 4 | 5 | GROUP=com.gradleup 6 | VERSION_NAME=1.4 7 | 8 | POM_URL=https://github.com/GradleUp/static-analysis-plugin/ 9 | POM_SCM_URL=https://github.com/GradleUp/static-analysis-plugin.git 10 | POM_SCM_CONNECTION=scm:git:git://github.com/GradleUp/static-analysis-plugin.git 11 | POM_LICENCE_NAME=Apache License 2.0 12 | POM_LICENCE_URL=https://raw.githubusercontent.com/GradleUp/static-analysis-plugin/master/LICENSE 13 | POM_LICENCE_DIST=repo 14 | 15 | # https://github.com/gradle/gradle/issues/11412 16 | systemProp.org.gradle.internal.publish.checksums.insecure=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradleUp/static-analysis-plugin/ef49300f72dd9f731bc6469955be3fc08f401560/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-gradle-plugin") 3 | id("groovy") 4 | id("com.gradle.plugin-publish") version "0.11.0" 5 | `maven-publish` 6 | } 7 | 8 | gradlePlugin { 9 | plugins { 10 | create("staticAnalysis") { 11 | id = "com.gradleup.static-analysis" 12 | displayName = findProperty("POM_NAME")?.toString() 13 | description = findProperty("POM_DESCRIPTION")?.toString() 14 | implementationClass = "com.gradleup.staticanalysis.StaticAnalysisPlugin" 15 | } 16 | } 17 | } 18 | 19 | java { 20 | sourceCompatibility = JavaVersion.VERSION_1_8 21 | targetCompatibility = JavaVersion.VERSION_1_8 22 | } 23 | 24 | pluginBundle { 25 | website = "https://github.com/GradleUp/static-analysis-plugin/" 26 | vcsUrl = "https://github.com/GradleUp/static-analysis-plugin.git" 27 | tags = listOf("pmd", "checkstyle", "spotbugs", "code-quality", "detekt", "ktlint", "lint", "android-lint", "GradleUp") 28 | } 29 | 30 | dependencies { 31 | testImplementation("junit:junit:4.13") 32 | testImplementation("com.google.truth:truth:0.30") 33 | testImplementation("com.google.guava:guava:19.0") 34 | testImplementation("org.mockito:mockito-core:3.11.1") 35 | testImplementation("com.google.code.findbugs:jsr305:3.0.0") 36 | } 37 | 38 | publishing { 39 | publications.withType { 40 | artifactId = findProperty("POM_ARTIFACT_ID")?.toString() 41 | pom { 42 | name.set(findProperty("POM_NAME")?.toString()) 43 | description.set(findProperty("POM_DESCRIPTION")?.toString()) 44 | packaging = findProperty("POM_PACKAGING")?.toString() 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=static-analysis-plugin 2 | POM_NAME=Static Analysis Gradle Plugin 3 | POM_DESCRIPTION=Easy and consistent setup of static analysis tools for Android and Java projects 4 | POM_PACKAGING=jar 5 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/DefaultViolationsEvaluator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.GradleException 4 | import org.gradle.api.logging.Logger 5 | 6 | class DefaultViolationsEvaluator implements ViolationsEvaluator { 7 | 8 | private final ReportUrlRenderer reportUrlRenderer 9 | private final Logger logger 10 | private final PenaltyExtension penalty 11 | 12 | DefaultViolationsEvaluator(ReportUrlRenderer reportUrlRenderer, Logger logger, PenaltyExtension penalty) { 13 | this.reportUrlRenderer = reportUrlRenderer 14 | this.logger = logger 15 | this.penalty = penalty 16 | } 17 | 18 | @Override 19 | void evaluate(Set allViolations) { 20 | String fullMessage = '' 21 | allViolations.each { Violations violations -> 22 | if (!violations.isEmpty()) { 23 | fullMessage += "> ${getViolationsMessage(violations, reportUrlRenderer)}\n" 24 | } 25 | } 26 | int totalErrors = allViolations.collect { it.errors }.sum() as int 27 | int totalWarnings = allViolations.collect { it.warnings }.sum() as int 28 | 29 | int errorsDiff = Math.max(0, totalErrors - penalty.maxErrors) 30 | int warningsDiff = Math.max(0, totalWarnings - penalty.maxWarnings) 31 | if (errorsDiff > 0 || warningsDiff > 0) { 32 | throw new GradleException("Violations limit exceeded by $errorsDiff errors, $warningsDiff warnings.\n$fullMessage") 33 | } else if (!fullMessage.isEmpty()) { 34 | logger.warn "\nViolations found ($totalErrors errors, $totalWarnings warnings)\n$fullMessage" 35 | } 36 | } 37 | 38 | private static String getViolationsMessage(Violations violations, ReportUrlRenderer reportUrlRenderer) { 39 | "$violations.name violations found ($violations.errors errors, $violations.warnings warnings). See the reports at:\n" + 40 | "${violations.reports.collect { "${reportUrlRenderer.render(it)}" }.join('\n')}" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/EvaluateViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.Input 5 | import org.gradle.api.tasks.TaskAction 6 | 7 | class EvaluateViolationsTask extends DefaultTask { 8 | 9 | @Input 10 | Closure evaluator 11 | @Input 12 | Closure> allViolations 13 | 14 | EvaluateViolationsTask() { 15 | group = 'verification' 16 | description = 'Evaluate total violations against penaltyExtension thresholds.' 17 | } 18 | 19 | 20 | @TaskAction 21 | void run() { 22 | evaluator().evaluate(allViolations()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/LogsExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.Project 4 | 5 | /** 6 | * This extension can be used to change the rendering of the logs printed by the plugin. 7 | */ 8 | class LogsExtension { 9 | 10 | private ReportUrlRenderer reportUrlRenderer = ReportUrlRenderer.DEFAULT 11 | final Project project 12 | 13 | LogsExtension(Project project) { 14 | this.project = project 15 | } 16 | 17 | /** 18 | * This method will create a new {@code ReportUrlRenderer} using the transformation closure provided. 19 | * The closure will receive the report as {@code File} and should provide a {@code String} as output. 20 | * 21 | * Here an example of custom renderer that will render report urls as markdown links (?!): 22 | *
23 |      *     staticAnalysis {
24 |      *         ...
25 |      *         logs {
26 |      *             reportUrlRenderer { report -> "[$report.name]($report.path)" }
27 |      *         }
28 |      *     }
29 |      * 
30 | * 31 | * @param renderer a closure that will transform a given {@code File} into a {@code String} 32 | */ 33 | void reportUrlRenderer(Closure renderer) { 34 | reportUrlRenderer = new ReportUrlRenderer() { 35 | @Override 36 | String render(File report) { 37 | renderer.call(report) 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * This method provides a convenient way for creating a custom {@code ReportUrlRenderer} that will replace the base 44 | * url of each report in the logs with one of choice. The target for the replacement by default is set to the 45 | * reports dir of the project, but can be changed if needed. 46 | * 47 | * @param newBaseUrl the base url to be used 48 | * @param localBaseUrl optional, the local base url to be replaced with {@code newBaseUrl} 49 | */ 50 | void reportBaseUrl(String newBaseUrl, String localBaseUrl = "$project.buildDir/reports") { 51 | reportUrlRenderer { report -> newBaseUrl + (report.path - ~/$localBaseUrl/) } 52 | } 53 | 54 | ReportUrlRenderer getReportUrlRenderer() { 55 | reportUrlRenderer 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/PenaltyExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | class PenaltyExtension { 4 | 5 | private int maxWarnings = Integer.MAX_VALUE 6 | private int maxErrors = 0 7 | 8 | void setMaxErrors(int value) { 9 | maxErrors = Math.max(0, value) 10 | } 11 | 12 | void setMaxWarnings(int value) { 13 | maxWarnings = Math.max(0, value) 14 | } 15 | 16 | int getMaxErrors() { 17 | maxErrors 18 | } 19 | 20 | int getMaxWarnings() { 21 | maxWarnings 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/ReportUrlRenderer.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.internal.logging.ConsoleRenderer 4 | 5 | interface ReportUrlRenderer { 6 | 7 | String render(File report) 8 | 9 | ReportUrlRenderer DEFAULT = new Default() 10 | 11 | private static class Default implements ReportUrlRenderer { 12 | private final ConsoleRenderer consoleRenderer = new ConsoleRenderer() 13 | @Override 14 | String render(File report) { 15 | consoleRenderer.asClickableFileUrl(report) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/RulesExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.artifacts.Configuration 5 | import org.gradle.api.resources.TextResource 6 | 7 | class RulesExtension { 8 | final String name 9 | final Project project 10 | Configuration configuration 11 | 12 | RulesExtension(String name, Project project) { 13 | this.name = name 14 | this.project = project 15 | } 16 | 17 | void maven(String coordinates) { 18 | def configurationName = "staticAnalysis${name.capitalize()}" 19 | configuration = project.configurations.create(configurationName) 20 | project.dependencies.add(configurationName, coordinates) 21 | } 22 | 23 | TextResource getAt(String relativePath) { 24 | project.resources.text.fromArchiveEntry(configuration, relativePath) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/StaticAnalysisExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.NamedDomainObjectContainer 5 | import org.gradle.api.NamedDomainObjectFactory 6 | import org.gradle.api.Project 7 | import org.gradle.api.logging.Logger 8 | 9 | class StaticAnalysisExtension { 10 | 11 | final Action none = { 12 | it.maxWarnings = Integer.MAX_VALUE 13 | it.maxErrors = Integer.MAX_VALUE 14 | } 15 | 16 | final Action failOnErrors = { 17 | it.maxWarnings = Integer.MAX_VALUE 18 | it.maxErrors = 0 19 | } 20 | 21 | final Action failFast = { 22 | it.maxWarnings = 0 23 | it.maxErrors = 0 24 | } 25 | 26 | private final Project project 27 | private final LogsExtension logs 28 | private final NamedDomainObjectContainer allViolations 29 | private final NamedDomainObjectContainer rules 30 | private Closure createEvaluator 31 | 32 | StaticAnalysisExtension(Project project) { 33 | this.project = project 34 | this.logs = new LogsExtension(project) 35 | this.allViolations = project.container(Violations) 36 | this.rules = project.container(RulesExtension, new NamedDomainObjectFactory() { 37 | @Override 38 | RulesExtension create(String name) { 39 | new RulesExtension(name, project) 40 | } 41 | }) 42 | this.createEvaluator = { 43 | new DefaultViolationsEvaluator(reportUrlRenderer, logger, new PenaltyExtension()) 44 | } 45 | } 46 | 47 | void penalty(Action action) { 48 | this.createEvaluator = { 49 | PenaltyExtension penalty = new PenaltyExtension() 50 | action.execute(penalty) 51 | new DefaultViolationsEvaluator(reportUrlRenderer, logger, penalty) 52 | } 53 | } 54 | 55 | void logs(Action action) { 56 | action.execute(logs) 57 | } 58 | 59 | void rules(Action> action) { 60 | action.execute(rules) 61 | } 62 | 63 | ReportUrlRenderer getReportUrlRenderer() { 64 | logs.reportUrlRenderer 65 | } 66 | 67 | NamedDomainObjectContainer getRules() { 68 | rules 69 | } 70 | 71 | NamedDomainObjectContainer getAllViolations() { 72 | allViolations 73 | } 74 | 75 | ViolationsEvaluator getEvaluator() { 76 | createEvaluator() 77 | } 78 | 79 | Logger getLogger() { 80 | project.logger 81 | } 82 | 83 | void evaluator(Action> action) { 84 | createEvaluator = { 85 | new ViolationsEvaluator() { 86 | @Override 87 | void evaluate(Set allViolations) { 88 | action.execute(allViolations) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/StaticAnalysisPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import com.gradleup.staticanalysis.internal.Configurator 4 | import com.gradleup.staticanalysis.internal.checkstyle.CheckstyleConfigurator 5 | import com.gradleup.staticanalysis.internal.detekt.DetektConfigurator 6 | import com.gradleup.staticanalysis.internal.ktlint.KtlintConfigurator 7 | import com.gradleup.staticanalysis.internal.lint.LintConfigurator 8 | import com.gradleup.staticanalysis.internal.pmd.PmdConfigurator 9 | import com.gradleup.staticanalysis.internal.spotbugs.SpotBugsConfigurator 10 | import org.gradle.api.NamedDomainObjectContainer 11 | import org.gradle.api.Plugin 12 | import org.gradle.api.Project 13 | import org.gradle.api.Task 14 | 15 | class StaticAnalysisPlugin implements Plugin { 16 | 17 | @Override 18 | void apply(Project project) { 19 | def pluginExtension = project.extensions.create('staticAnalysis', StaticAnalysisExtension, project) 20 | def evaluateViolations = createEvaluateViolationsTask(project, pluginExtension) 21 | createConfigurators(project, pluginExtension, evaluateViolations).each { configurator -> configurator.execute() } 22 | project.tasks 23 | .matching { it.name == 'check' } 24 | .configureEach { task -> 25 | task.dependsOn evaluateViolations 26 | } 27 | } 28 | 29 | private static Task createEvaluateViolationsTask(Project project, 30 | StaticAnalysisExtension extension) { 31 | project.tasks.create('evaluateViolations', EvaluateViolationsTask) { task -> 32 | task.evaluator = { extension.evaluator } 33 | task.allViolations = { extension.allViolations } 34 | } 35 | } 36 | 37 | private static List createConfigurators(Project project, 38 | StaticAnalysisExtension pluginExtension, 39 | Task evaluateViolations) { 40 | NamedDomainObjectContainer violationsContainer = pluginExtension.allViolations 41 | [ 42 | CheckstyleConfigurator.create(project, violationsContainer, evaluateViolations), 43 | PmdConfigurator.create(project, violationsContainer, evaluateViolations), 44 | SpotBugsConfigurator.create(project, violationsContainer, evaluateViolations), 45 | DetektConfigurator.create(project, violationsContainer, evaluateViolations), 46 | KtlintConfigurator.create(project, violationsContainer, evaluateViolations), 47 | LintConfigurator.create(project, violationsContainer, evaluateViolations) 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/Violations.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | class Violations { 4 | private final String toolName 5 | private int errors = 0 6 | private int warnings = 0 7 | private List reports = [] 8 | 9 | Violations(String toolName) { 10 | this.toolName = toolName 11 | } 12 | 13 | String getName() { 14 | return toolName 15 | } 16 | 17 | int getErrors() { 18 | errors 19 | } 20 | 21 | int getWarnings() { 22 | warnings 23 | } 24 | 25 | List getReports() { 26 | return reports 27 | } 28 | 29 | void addViolations(int errors, int warnings, File report) { 30 | this.errors += errors 31 | this.warnings += warnings 32 | if (errors > 0 || warnings > 0) { 33 | reports += report 34 | } 35 | } 36 | 37 | boolean isEmpty() { 38 | errors == 0 && warnings == 0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/ViolationsEvaluator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | interface ViolationsEvaluator { 4 | 5 | void evaluate(Set allViolations) 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/CodeQualityConfigurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import com.gradleup.staticanalysis.StaticAnalysisExtension 4 | import com.gradleup.staticanalysis.Violations 5 | import org.gradle.api.Action 6 | import org.gradle.api.DomainObjectSet 7 | import org.gradle.api.Project 8 | import org.gradle.api.Task 9 | import org.gradle.api.plugins.quality.CodeQualityExtension 10 | import org.gradle.api.tasks.SourceTask 11 | import org.gradle.api.tasks.VerificationTask 12 | 13 | abstract class CodeQualityConfigurator implements Configurator { 14 | 15 | protected final Project project 16 | protected final Violations violations 17 | protected final Task evaluateViolations 18 | protected final SourceFilter sourceFilter 19 | protected final VariantFilter variantFilter 20 | protected boolean configured = false 21 | 22 | protected CodeQualityConfigurator(Project project, Violations violations, Task evaluateViolations) { 23 | this.project = project 24 | this.violations = violations 25 | this.evaluateViolations = evaluateViolations 26 | this.sourceFilter = new SourceFilter(project) 27 | this.variantFilter = new VariantFilter(project) 28 | } 29 | 30 | @Override 31 | void execute() { 32 | project.extensions.findByType(StaticAnalysisExtension).ext."$toolName" = { Closure config -> 33 | project.apply plugin: toolPlugin 34 | project.extensions.findByType(extensionClass).with { 35 | defaultConfiguration.execute(it) 36 | ext.exclude = { Object rule -> sourceFilter.exclude(rule) } 37 | ext.includeVariants = { Closure filter -> variantFilter.includeVariantsFilter = filter } 38 | config.delegate = it 39 | config.resolveStrategy = Closure.DELEGATE_FIRST 40 | config() 41 | } 42 | project.plugins.withId('com.android.application') { 43 | configureAndroidWithVariants(variantFilter.filteredApplicationVariants) 44 | } 45 | project.plugins.withId('com.android.library') { 46 | configureAndroidWithVariants(variantFilter.filteredLibraryVariants) 47 | } 48 | project.plugins.withId('java') { 49 | configureJavaProject() 50 | } 51 | project.tasks.withType(taskClass) 52 | .configureEach { configureToolTask(it) } 53 | } 54 | } 55 | 56 | protected void configureJavaProject() { 57 | if (configured) return 58 | 59 | project.sourceSets.all { sourceSet -> 60 | def collectViolations = createCollectViolations(getToolTaskNameFor(sourceSet), violations) 61 | evaluateViolations.dependsOn collectViolations 62 | } 63 | configured = true 64 | } 65 | 66 | protected void configureAndroidWithVariants(DomainObjectSet variants) { 67 | if (configured) return 68 | 69 | project.android.sourceSets.all { sourceSet -> 70 | createToolTaskForAndroid(sourceSet) 71 | createCollectViolations(getToolTaskNameFor(sourceSet), violations) 72 | } 73 | variants.all { configureVariant(it) } 74 | variantFilter.filteredTestVariants.all { configureVariant(it) } 75 | variantFilter.filteredUnitTestVariants.all { configureVariant(it) } 76 | configured = true 77 | } 78 | 79 | protected void configureVariant(variant) { 80 | def collectViolations = createVariantMetaTask(variant) 81 | evaluateViolations.dependsOn collectViolations 82 | } 83 | 84 | private def createVariantMetaTask(variant) { 85 | project.tasks.register("collect${getToolTaskNameFor(variant)}VariantViolations", Task) { task -> 86 | task.group = 'verification' 87 | task.description = "Runs $toolName analysis on all sources for android ${variant.name} variant" 88 | task.mustRunAfter javaCompile(variant) 89 | 90 | variant.sourceSets.forEach { sourceSet -> 91 | def toolTaskName = getToolTaskNameFor(sourceSet) 92 | task.dependsOn "collect${toolTaskName.capitalize()}Violations" 93 | } 94 | } 95 | } 96 | 97 | protected abstract String getToolName() 98 | 99 | protected def getToolPlugin() { 100 | toolName 101 | } 102 | 103 | protected abstract Class getTaskClass() 104 | 105 | protected abstract Class getExtensionClass() 106 | 107 | protected Action getDefaultConfiguration() { 108 | return { ignored -> 109 | // no op 110 | } 111 | } 112 | 113 | protected abstract void createToolTaskForAndroid(sourceSet) 114 | 115 | protected abstract def createCollectViolations(String taskName, Violations violations) 116 | 117 | protected void configureToolTask(T task) { 118 | sourceFilter.applyTo(task) 119 | task.group = 'verification' 120 | task.exclude '**/*.kt' 121 | task.ignoreFailures = true 122 | task.metaClass.getLogger = { QuietLogger.INSTANCE } 123 | } 124 | 125 | protected final String getToolTaskNameFor(named) { 126 | "$toolName${named.name.capitalize()}" 127 | } 128 | 129 | protected static def javaCompile(variant) { 130 | if (variant.hasProperty('javaCompileProvider')) { 131 | variant.javaCompileProvider.get() 132 | } else { 133 | variant.javaCompile 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/CollectViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.InputFile 6 | import org.gradle.api.tasks.Internal 7 | import org.gradle.api.tasks.PathSensitive 8 | import org.gradle.api.tasks.TaskAction 9 | 10 | import static org.gradle.api.tasks.PathSensitivity.RELATIVE 11 | 12 | abstract class CollectViolationsTask extends DefaultTask { 13 | 14 | @InputFile 15 | @PathSensitive(RELATIVE) 16 | private File xmlReportFile 17 | @Internal 18 | private File htmlReportFile 19 | private Violations violations 20 | 21 | CollectViolationsTask() { 22 | onlyIf { xmlReportFile?.exists() } 23 | } 24 | 25 | void setXmlReportFile(File xmlReportFile) { 26 | this.xmlReportFile = xmlReportFile 27 | } 28 | 29 | void setHtmlReportFile(File htmlReportFile) { 30 | this.htmlReportFile = htmlReportFile 31 | } 32 | 33 | void setViolations(Violations violations) { 34 | this.violations = violations 35 | } 36 | 37 | @TaskAction 38 | final void run() { 39 | collectViolations(getXmlReportFile(), getHtmlReportFile(), violations) 40 | } 41 | 42 | File getXmlReportFile() { 43 | return xmlReportFile 44 | } 45 | 46 | File getHtmlReportFile() { 47 | htmlReportFile ?: new File(xmlReportFile.absolutePath - '.xml' + '.html') 48 | } 49 | 50 | abstract void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) 51 | } 52 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/Configurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | interface Configurator { 4 | 5 | void execute() 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/Exceptions.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import org.gradle.api.GradleException 4 | 5 | class Exceptions { 6 | 7 | /** 8 | * In case the cause is a GradleException rethrow because it probably already has useful information. 9 | * If not, wrap it and put useful information about the state of the integrations and versions. 10 | */ 11 | static void handleException(String message, Exception cause) { 12 | if (cause instanceof GradleException) { 13 | throw cause 14 | } 15 | throw new GradleException(message, cause) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/SourceFilter.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.file.FileCollection 5 | import org.gradle.api.file.FileTree 6 | import org.gradle.api.tasks.SourceTask 7 | 8 | class SourceFilter { 9 | 10 | private final Project project 11 | private final List excludes = [] 12 | 13 | SourceFilter(Project project) { 14 | this.project = project 15 | } 16 | 17 | void exclude(Object exclude) { 18 | excludes.add(exclude) 19 | } 20 | 21 | void applyTo(SourceTask task) { 22 | excludes.each { exclude -> 23 | if (exclude instanceof File) { 24 | apply(task, project.files(exclude)) 25 | } else if (exclude instanceof FileCollection) { 26 | apply(task, exclude) 27 | } else if (exclude instanceof Iterable) { 28 | apply(task, exclude.inject(null, accumulateIntoTree()) as FileTree) 29 | } else { 30 | task.exclude(exclude as String) 31 | } 32 | } 33 | } 34 | 35 | private void apply(SourceTask task, FileCollection excludedFiles) { 36 | task.source = task.source.findAll { !excludedFiles.contains(it) } 37 | } 38 | 39 | private def accumulateIntoTree() { 40 | return { tree, file -> tree?.plus(project.fileTree(file)) ?: project.fileTree(file) } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/VariantFilter.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import org.gradle.api.DomainObjectSet 4 | import org.gradle.api.Project 5 | 6 | final class VariantFilter { 7 | 8 | private final Project project 9 | Closure includeVariantsFilter 10 | 11 | VariantFilter(Project project) { 12 | this.project = project 13 | } 14 | 15 | DomainObjectSet getFilteredApplicationVariants() { 16 | filterVariants(project.android.applicationVariants) 17 | } 18 | 19 | DomainObjectSet getFilteredLibraryVariants() { 20 | filterVariants(project.android.libraryVariants) 21 | } 22 | 23 | DomainObjectSet getFilteredTestVariants() { 24 | filterVariants(project.android.testVariants) 25 | } 26 | 27 | DomainObjectSet getFilteredUnitTestVariants() { 28 | filterVariants(project.android.unitTestVariants) 29 | } 30 | 31 | private DomainObjectSet filterVariants(DomainObjectSet variants) { 32 | includeVariantsFilter != null ? variants.matching { includeVariantsFilter(it) } : variants 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.checkstyle 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CodeQualityConfigurator 5 | import org.gradle.api.Action 6 | import org.gradle.api.NamedDomainObjectContainer 7 | import org.gradle.api.Project 8 | import org.gradle.api.Task 9 | import org.gradle.api.plugins.quality.Checkstyle 10 | import org.gradle.api.plugins.quality.CheckstyleExtension 11 | 12 | class CheckstyleConfigurator extends CodeQualityConfigurator { 13 | 14 | static CheckstyleConfigurator create(Project project, 15 | NamedDomainObjectContainer violationsContainer, 16 | Task evaluateViolations) { 17 | Violations violations = violationsContainer.maybeCreate('Checkstyle') 18 | return new CheckstyleConfigurator(project, violations, evaluateViolations) 19 | } 20 | 21 | private CheckstyleConfigurator(Project project, 22 | Violations violations, 23 | Task evaluateViolations) { 24 | super(project, violations, evaluateViolations) 25 | } 26 | 27 | @Override 28 | protected String getToolName() { 29 | 'checkstyle' 30 | } 31 | 32 | @Override 33 | protected Class getExtensionClass() { 34 | CheckstyleExtension 35 | } 36 | 37 | @Override 38 | protected Action getDefaultConfiguration() { 39 | return { extension -> 40 | extension.toolVersion = '7.1.2' 41 | } 42 | } 43 | 44 | @Override 45 | protected Class getTaskClass() { 46 | Checkstyle 47 | } 48 | 49 | @Override 50 | protected void createToolTaskForAndroid(sourceSet) { 51 | project.tasks.register(getToolTaskNameFor(sourceSet), Checkstyle) { task -> 52 | task.description = "Run Checkstyle analysis for ${sourceSet.name} classes" 53 | task.source = sourceSet.java.srcDirs 54 | task.classpath = project.files("${project.buildDir}/intermediates/classes/") 55 | } 56 | } 57 | 58 | @Override 59 | protected void configureToolTask(Checkstyle task) { 60 | super.configureToolTask(task) 61 | task.showViolations = false 62 | } 63 | 64 | @Override 65 | protected def createCollectViolations(String taskName, Violations violations) { 66 | project.tasks.register("collect${taskName.capitalize()}Violations", CollectCheckstyleViolationsTask) { task -> 67 | def checkstyle = project.tasks[taskName] as Checkstyle 68 | task.xmlReportFile = checkstyle.reports.xml.destination 69 | task.violations = violations 70 | task.dependsOn(checkstyle) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/checkstyle/CollectCheckstyleViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.checkstyle 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CollectViolationsTask 5 | import groovy.util.slurpersupport.GPathResult 6 | 7 | class CollectCheckstyleViolationsTask extends CollectViolationsTask { 8 | 9 | @Override 10 | void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) { 11 | GPathResult xml = new XmlSlurper().parse(xmlReportFile) 12 | int errors = xml.'**'.findAll { node -> node.name() == 'error' && node.@severity == 'error' }.size() 13 | int warnings = xml.'**'.findAll { node -> node.name() == 'error' && node.@severity == 'warning' }.size() 14 | violations.addViolations(errors, warnings, htmlReportFile ?: xmlReportFile) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/detekt/DetektConfigurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.detekt 2 | 3 | import com.gradleup.staticanalysis.StaticAnalysisExtension 4 | import com.gradleup.staticanalysis.Violations 5 | import com.gradleup.staticanalysis.internal.Configurator 6 | import com.gradleup.staticanalysis.internal.checkstyle.CollectCheckstyleViolationsTask 7 | import org.gradle.api.GradleException 8 | import org.gradle.api.NamedDomainObjectContainer 9 | import org.gradle.api.Project 10 | import org.gradle.api.Task 11 | 12 | import static com.gradleup.staticanalysis.internal.Exceptions.handleException 13 | 14 | class DetektConfigurator implements Configurator { 15 | 16 | private static final String DETEKT_PLUGIN = 'io.gitlab.arturbosch.detekt' 17 | 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.' 18 | private static final String XML_REPORT_NOT_ENABLED = 'XML report must be enabled. Please make sure to enable "reports.xml" in your Detekt configuration' 19 | 20 | private static final String LAST_COMPATIBLE_DETEKT_VERSION = '1.3.1' 21 | private static final String MIN_COMPATIBLE_DETEKT_VERSION = '1.0.0.RC9.2' // Do not forget to update detekt.md 22 | private static final String DETEKT_CONFIGURATION_ERROR = """\ 23 | A problem occurred while configuring Detekt. Please make sure to use a compatible version: 24 | Minimum compatible Detekt version: $MIN_COMPATIBLE_DETEKT_VERSION 25 | Last tested compatible version: $LAST_COMPATIBLE_DETEKT_VERSION 26 | """ 27 | 28 | private final Project project 29 | private final Violations violations 30 | private final Task evaluateViolations 31 | 32 | static DetektConfigurator create(Project project, 33 | NamedDomainObjectContainer violationsContainer, 34 | Task evaluateViolations) { 35 | Violations violations = violationsContainer.maybeCreate('Detekt') 36 | return new DetektConfigurator(project, violations, evaluateViolations) 37 | } 38 | 39 | private DetektConfigurator(Project project, Violations violations, Task evaluateViolations) { 40 | this.project = project 41 | this.violations = violations 42 | this.evaluateViolations = evaluateViolations 43 | } 44 | 45 | @Override 46 | void execute() { 47 | project.extensions.findByType(StaticAnalysisExtension).ext.detekt = { Closure config -> 48 | if (!isKotlinProject(project)) { 49 | return 50 | } 51 | 52 | if (!project.plugins.hasPlugin(DETEKT_PLUGIN)) { 53 | throw new GradleException(DETEKT_NOT_APPLIED) 54 | } 55 | 56 | def detekt = project.extensions.findByName('detekt') 57 | setDefaultXmlReport(detekt) 58 | config.delegate = detekt 59 | config.resolveStrategy = Closure.DELEGATE_FIRST 60 | config() 61 | disableFailFast(detekt) 62 | 63 | def collectViolations = createCollectViolationsTask(violations) 64 | evaluateViolations.dependsOn collectViolations 65 | } 66 | } 67 | 68 | private void setDefaultXmlReport(detekt) { 69 | try { 70 | detekt.reports { 71 | xml.enabled = true 72 | xml.destination = new File(project.buildDir, 'reports/detekt/detekt.xml') 73 | } 74 | } catch (Exception exception) { 75 | handleException(DETEKT_CONFIGURATION_ERROR, exception) 76 | } 77 | } 78 | 79 | private static void disableFailFast(detekt) { 80 | if (detekt.hasProperty('failFast')) { 81 | detekt.failFast = false 82 | } 83 | } 84 | 85 | private def createCollectViolationsTask(Violations violations) { 86 | project.tasks.register('collectDetektViolations', CollectCheckstyleViolationsTask) { task -> 87 | def detektTask = project.tasks.findByName('detekt') 88 | try { 89 | def reports = detektTask.reports 90 | checkXmlReportEnabled(reports) 91 | task.xmlReportFile = reports.xml.destination 92 | task.htmlReportFile = reports.html.destination 93 | } catch (Exception exception) { 94 | handleException(DETEKT_CONFIGURATION_ERROR, exception) 95 | } 96 | task.violations = violations 97 | task.dependsOn detektTask 98 | } 99 | } 100 | 101 | private void checkXmlReportEnabled(reports) { 102 | if (!reports.xml.enabled) { 103 | throw new GradleException(XML_REPORT_NOT_ENABLED) 104 | } 105 | } 106 | 107 | private static boolean isKotlinProject(final Project project) { 108 | final boolean isKotlin = project.plugins.hasPlugin('kotlin') 109 | final boolean isKotlinAndroid = project.plugins.hasPlugin('kotlin-android') 110 | final boolean isKotlinPlatformCommon = project.plugins.hasPlugin('kotlin-platform-common') 111 | final boolean isKotlinMultiplatform = project.plugins.hasPlugin('org.jetbrains.kotlin.multiplatform') 112 | final boolean isKotlinPlatformJvm = project.plugins.hasPlugin('kotlin-platform-jvm') 113 | final boolean isKotlinPlatformJs = project.plugins.hasPlugin('kotlin-platform-js') 114 | return isKotlin || isKotlinAndroid || isKotlinPlatformCommon || isKotlinMultiplatform || isKotlinPlatformJvm || isKotlinPlatformJs 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/lint/CollectLintViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.lint 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CollectViolationsTask 5 | import groovy.util.slurpersupport.GPathResult 6 | 7 | class CollectLintViolationsTask extends CollectViolationsTask { 8 | 9 | @Override 10 | void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) { 11 | GPathResult xml = new XmlSlurper().parse(xmlReportFile) 12 | int errors = xml.'**'.findAll { node -> node.name() == 'issue' && (node.@severity == 'Error' || node.@severity == 'Fatal') }.size() 13 | int warnings = xml.'**'.findAll { node -> node.name() == 'issue' && node.@severity == 'Warning' }.size() 14 | violations.addViolations(errors, warnings, htmlReportFile ?: xmlReportFile) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/lint/LintConfigurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.lint 2 | 3 | import com.gradleup.staticanalysis.StaticAnalysisExtension 4 | import com.gradleup.staticanalysis.Violations 5 | import com.gradleup.staticanalysis.internal.Configurator 6 | import com.gradleup.staticanalysis.internal.VariantFilter 7 | import org.gradle.api.DomainObjectSet 8 | import org.gradle.api.NamedDomainObjectContainer 9 | import org.gradle.api.Project 10 | import org.gradle.api.Task 11 | 12 | class LintConfigurator implements Configurator { 13 | 14 | private final Project project 15 | private final Violations violations 16 | private final Task evaluateViolations 17 | private final VariantFilter variantFilter 18 | private boolean configured = false 19 | 20 | static LintConfigurator create(Project project, 21 | NamedDomainObjectContainer violationsContainer, 22 | Task evaluateViolations) { 23 | Violations violations = violationsContainer.maybeCreate('Lint') 24 | return new LintConfigurator(project, violations, evaluateViolations) 25 | } 26 | 27 | private LintConfigurator(Project project, Violations violations, Task evaluateViolations) { 28 | this.project = project 29 | this.violations = violations 30 | this.evaluateViolations = evaluateViolations 31 | this.variantFilter = new VariantFilter(project) 32 | } 33 | 34 | @Override 35 | void execute() { 36 | project.extensions.findByType(StaticAnalysisExtension).ext.lintOptions = { Closure config -> 37 | project.plugins.withId('com.android.application') { 38 | configureLint(config) 39 | configureWithVariants(variantFilter.filteredApplicationVariants) 40 | } 41 | project.plugins.withId('com.android.library') { 42 | configureLint(config) 43 | configureWithVariants(variantFilter.filteredLibraryVariants) 44 | } 45 | } 46 | } 47 | 48 | private void configureLint(Closure config) { 49 | project.android.lintOptions.ext.includeVariants = { Closure filter -> 50 | variantFilter.includeVariantsFilter = filter 51 | } 52 | project.android.lintOptions(config) 53 | project.android.lintOptions { 54 | xmlReport = true 55 | htmlReport = true 56 | abortOnError false 57 | } 58 | } 59 | 60 | private void configureWithVariants(DomainObjectSet variants) { 61 | if (configured) return 62 | 63 | if (variantFilter.includeVariantsFilter != null) { 64 | variants.all { 65 | configureCollectViolationsTask(it.name, "lint-results-${it.name}") 66 | } 67 | } else { 68 | configureCollectViolationsTask('lint-results') 69 | } 70 | } 71 | 72 | private void configureCollectViolationsTask(String taskSuffix = '', String reportFileName) { 73 | def collectViolations = createCollectViolationsTask(taskSuffix, reportFileName, violations) 74 | evaluateViolations.dependsOn collectViolations 75 | configured = true 76 | } 77 | 78 | private def createCollectViolationsTask(String taskSuffix, String reportFileName, Violations violations) { 79 | project.tasks.register("collectLint${taskSuffix.capitalize()}Violations", CollectLintViolationsTask) { task -> 80 | task.xmlReportFile = xmlOutputFileFor(reportFileName) 81 | task.htmlReportFile = htmlOutputFileFor(reportFileName) 82 | task.violations = violations 83 | task.dependsOn project.tasks["lint${taskSuffix.capitalize()}"] 84 | } 85 | } 86 | 87 | private File xmlOutputFileFor(reportFileName) { 88 | project.android.lintOptions.xmlOutput ?: new File(defaultOutputFolder, "${reportFileName}.xml") 89 | } 90 | 91 | private File htmlOutputFileFor(reportFileName) { 92 | project.android.lintOptions.htmlOutput ?: new File(defaultOutputFolder, "${reportFileName}.html") 93 | } 94 | 95 | private File getDefaultOutputFolder() { 96 | new File(project.buildDir, 'reports') 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/pmd/CollectPmdViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.pmd 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CollectViolationsTask 5 | 6 | class CollectPmdViolationsTask extends CollectViolationsTask { 7 | 8 | @Override 9 | void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) { 10 | PmdViolationsEvaluator evaluator = new PmdViolationsEvaluator(xmlReportFile) 11 | int errors = 0 12 | int warnings = 0 13 | evaluator.collectViolations().each { PmdViolationsEvaluator.PmdViolation violation -> 14 | if (violation.isError()) { 15 | errors += 1 16 | } else { 17 | warnings += 1 18 | } 19 | } 20 | violations.addViolations(errors, warnings, htmlReportFile ?: xmlReportFile) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/pmd/PmdConfigurator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.pmd 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CodeQualityConfigurator 5 | import org.gradle.api.Action 6 | import org.gradle.api.NamedDomainObjectContainer 7 | import org.gradle.api.Project 8 | import org.gradle.api.Task 9 | import org.gradle.api.plugins.quality.Pmd 10 | import org.gradle.api.plugins.quality.PmdExtension 11 | 12 | class PmdConfigurator extends CodeQualityConfigurator { 13 | 14 | static PmdConfigurator create(Project project, 15 | NamedDomainObjectContainer violationsContainer, 16 | Task evaluateViolations) { 17 | Violations violations = violationsContainer.maybeCreate('PMD') 18 | return new PmdConfigurator(project, violations, evaluateViolations) 19 | } 20 | 21 | private PmdConfigurator(Project project, 22 | Violations violations, 23 | Task evaluateViolations) { 24 | super(project, violations, evaluateViolations) 25 | } 26 | 27 | @Override 28 | protected String getToolName() { 29 | 'pmd' 30 | } 31 | 32 | @Override 33 | protected Class getExtensionClass() { 34 | PmdExtension 35 | } 36 | 37 | @Override 38 | protected Class getTaskClass() { 39 | Pmd 40 | } 41 | 42 | @Override 43 | protected void createToolTaskForAndroid(sourceSet) { 44 | project.tasks.register(getToolTaskNameFor(sourceSet), Pmd) { Pmd task -> 45 | task.description = "Run PMD analysis for sourceSet ${sourceSet.name} classes" 46 | task.source = sourceSet.java.srcDirs 47 | } 48 | } 49 | 50 | @Override 51 | protected def createCollectViolations(String taskName, Violations violations) { 52 | project.tasks.register("collect${taskName.capitalize()}Violations", CollectPmdViolationsTask) { task -> 53 | def pmd = project.tasks[taskName] as Pmd 54 | task.xmlReportFile = pmd.reports.xml.destination 55 | task.violations = violations 56 | task.dependsOn(pmd) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/pmd/PmdViolationsEvaluator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.pmd 2 | 3 | import groovy.util.slurpersupport.GPathResult 4 | 5 | class PmdViolationsEvaluator { 6 | 7 | private final GPathResult xml 8 | 9 | PmdViolationsEvaluator(File report) { 10 | this(new XmlSlurper().parse(report)) 11 | } 12 | 13 | PmdViolationsEvaluator(GPathResult xml) { 14 | this.xml = xml 15 | } 16 | 17 | Set collectViolations() { 18 | Collection files = xml.'**'.findAll { node -> node.name() == 'file' } 19 | files.inject(new HashSet()) { HashSet violations, file -> 20 | violations += file.'**' 21 | .findAll { violation -> violation.name() == 'violation' } 22 | .collect { violation -> new PmdViolation(violation, file.@name as String) } 23 | violations 24 | } 25 | } 26 | 27 | static class PmdViolation { 28 | String beginLine 29 | String endLine 30 | String beginColumn 31 | String endColumn 32 | String rule 33 | String ruleSet 34 | String className 35 | String methodName 36 | String priority 37 | 38 | PmdViolation(def violation, String file) { 39 | this(violation.@beginline as String, 40 | violation.@endline as String, 41 | violation.@begincolumn as String, 42 | violation.@endcolumn as String, 43 | violation.@rule as String, 44 | violation.@ruleset as String, 45 | file, 46 | violation.@method as String, 47 | violation.@priority as String) 48 | } 49 | 50 | PmdViolation(String beginLine, 51 | String endLine, 52 | String beginColumn, 53 | String endColumn, 54 | String rule, 55 | String ruleSet, 56 | String className, 57 | String methodName, 58 | String priority) { 59 | this.beginLine = beginLine 60 | this.endLine = endLine 61 | this.beginColumn = beginColumn 62 | this.endColumn = endColumn 63 | this.rule = rule 64 | this.ruleSet = ruleSet 65 | this.className = className 66 | this.methodName = methodName 67 | this.priority = priority 68 | } 69 | 70 | boolean isError() { 71 | priority == '1' || priority == '2' 72 | } 73 | 74 | boolean equals(o) { 75 | if (this.is(o)) return true 76 | if (getClass() != o.class) return false 77 | 78 | PmdViolation that = (PmdViolation) o 79 | 80 | if (!beginColumn.equals(that.beginColumn)) return false 81 | if (!beginLine.equals(that.beginLine)) return false 82 | if (!className.equals(that.className)) return false 83 | if (!endColumn.equals(that.endColumn)) return false 84 | if (!endLine.equals(that.endLine)) return false 85 | if (methodName != that.methodName) return false 86 | if (methodName != null && that.methodName != null && !methodName.equals(that.methodName)) return false 87 | if (!priority.equals(that.priority)) return false 88 | if (!rule.equals(that.rule)) return false 89 | if (!ruleSet.equals(that.ruleSet)) return false 90 | 91 | return true 92 | } 93 | 94 | int hashCode() { 95 | int result 96 | result = beginLine.hashCode() 97 | result = 31 * result + endLine.hashCode() 98 | result = 31 * result + beginColumn.hashCode() 99 | result = 31 * result + endColumn.hashCode() 100 | result = 31 * result + rule.hashCode() 101 | result = 31 * result + ruleSet.hashCode() 102 | result = 31 * result + className.hashCode() 103 | result = 31 * result + (methodName != null ? methodName.hashCode() : 0) 104 | result = 31 * result + priority.hashCode() 105 | return result 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "PmdViolation{" + 111 | "beginLine='" + beginLine + '\'' + 112 | ", endLine='" + endLine + '\'' + 113 | ", beginColumn='" + beginColumn + '\'' + 114 | ", endColumn='" + endColumn + '\'' + 115 | ", rule='" + rule + '\'' + 116 | ", ruleSet='" + ruleSet + '\'' + 117 | ", className='" + className + '\'' + 118 | ", methodName='" + methodName + '\'' + 119 | ", priority='" + priority + '\'' + 120 | "} " + super.toString(); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/spotbugs/CollectSpotBugsViolationsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.spotbugs 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.staticanalysis.internal.CollectViolationsTask 5 | 6 | class CollectSpotBugsViolationsTask extends CollectViolationsTask { 7 | 8 | @Override 9 | void collectViolations(File xmlReportFile, File htmlReportFile, Violations violations) { 10 | def evaluator = new SpotBugsViolationsEvaluator(xmlReportFile) 11 | violations.addViolations(evaluator.errorsCount(), evaluator.warningsCount(), htmlReportFile) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/spotbugs/GenerateSpotBugsHtmlReport.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.spotbugs 2 | 3 | import org.gradle.api.tasks.CacheableTask 4 | import org.gradle.api.tasks.InputFile 5 | import org.gradle.api.tasks.JavaExec 6 | import org.gradle.api.tasks.OutputFile 7 | import org.gradle.api.tasks.PathSensitive 8 | 9 | import static org.gradle.api.tasks.PathSensitivity.RELATIVE 10 | 11 | @CacheableTask 12 | class GenerateSpotBugsHtmlReport extends JavaExec { 13 | 14 | @InputFile 15 | @PathSensitive(RELATIVE) 16 | File xmlReportFile 17 | @OutputFile 18 | File htmlReportFile 19 | 20 | GenerateSpotBugsHtmlReport() { 21 | onlyIf { xmlReportFile?.exists() } 22 | mainClass.set('edu.umd.cs.findbugs.PrintingBugReporter') 23 | } 24 | 25 | @Override 26 | void exec() { 27 | standardOutput = new FileOutputStream(htmlReportFile) 28 | args '-html', xmlReportFile 29 | super.exec() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/gradleup/staticanalysis/internal/spotbugs/SpotBugsViolationsEvaluator.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.spotbugs 2 | 3 | import groovy.util.slurpersupport.GPathResult 4 | 5 | class SpotBugsViolationsEvaluator { 6 | 7 | private final GPathResult xml 8 | 9 | SpotBugsViolationsEvaluator(File report) { 10 | this(new XmlSlurper().parse(report)) 11 | } 12 | 13 | SpotBugsViolationsEvaluator(GPathResult xml) { 14 | this.xml = xml 15 | } 16 | 17 | int errorsCount() { 18 | count('@priority_1') 19 | } 20 | 21 | private int count(String attr) { 22 | def count = xml.FindBugsSummary[attr] 23 | count == "" ? 0 : count.toInteger() 24 | } 25 | 26 | int warningsCount() { 27 | count("@priority_2") + count("@priority_3") 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/reports/lint/lint-results.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 17 | 21 | 22 | 23 | 35 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/reports/pmd/reports/sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | A class which only has private constructors should be final 8 | 9 | 10 | 11 | 13 | Method call on object which may be null 14 | 15 | 17 | Method call on object which may be null 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/rules/checkstyle/config/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/rules/lint/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/rules/pmd/config/rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 5 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/checkstyle/errors/Greeter.java: -------------------------------------------------------------------------------- 1 | public class Greeter { 2 | public String greet() { 3 | // this is a very very very very very very very very very very very very very very very very very very long comment 4 | return "Hallo, wie geht's?"; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/checkstyle/warnings/Dice.java: -------------------------------------------------------------------------------- 1 | import java.util.Random; 2 | 3 | public final class Dice { 4 | private final Random random; 5 | 6 | public Dice() { 7 | random = new Random(); 8 | } 9 | 10 | public int roll() { 11 | return random.nextInt(6) + 1; // magic number violation 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/detekt/errors/Errors.kt: -------------------------------------------------------------------------------- 1 | class Errors { 2 | 3 | override fun equals(other: Any?): Boolean { 4 | // this is not allowed 5 | return true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/detekt/warnings/Warnings.kt: -------------------------------------------------------------------------------- 1 | class Warnings { 2 | 3 | private fun foo() { 4 | for (i in 1..2) { 5 | break 6 | val number = 1 // unreachable 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/findbugs/high/com/gradleup/test/HighPriorityViolator.java: -------------------------------------------------------------------------------- 1 | package com.gradleup.test; 2 | 3 | public class HighPriorityViolator { 4 | 5 | public static class Internal { 6 | 7 | public void impossibleCast() { 8 | final Object doubleValue = Double.valueOf(1.0); 9 | final Long value = (Long) doubleValue; 10 | System.out.println(" - " + value); 11 | } 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/findbugs/low/LowPriorityViolator.java: -------------------------------------------------------------------------------- 1 | public class LowPriorityViolator { 2 | public void foo() { 3 | int a = 1; 4 | a++; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/findbugs/medium/MediumPriorityViolator.java: -------------------------------------------------------------------------------- 1 | public class MediumPriorityViolator { 2 | private String foo; 3 | 4 | public MediumPriorityViolator(String foo) { 5 | this.foo = foo; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/ktlint/no-error/ValidClass.kt: -------------------------------------------------------------------------------- 1 | data class ValidClass(val prop1: String, val prop2: Int) 2 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/ktlint/with-error/KtLintViolator.kt: -------------------------------------------------------------------------------- 1 | class KtLintViolator { 2 | 3 | override fun equals(other: Any?): Boolean { 4 | // this is not allowed 5 | return true; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/lint/errors/MyView.java: -------------------------------------------------------------------------------- 1 | import android.content.Context; 2 | import android.view.View; 3 | 4 | public class MyView extends View { 5 | 6 | public MyView(Context context) { 7 | super(context); 8 | } 9 | 10 | @Override 11 | protected void onDetachedFromWindow() { 12 | // missing super call 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/lint/warnings/Warning.java: -------------------------------------------------------------------------------- 1 | public class Warning { 2 | 3 | public Warning() { 4 | "Test".toUpperCase(); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/pmd/priority1/Priority1Violator.java: -------------------------------------------------------------------------------- 1 | public class Priority1Violator { //Should be final 2 | private Priority1Violator() { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/pmd/priority2/Priority2Violator.java: -------------------------------------------------------------------------------- 1 | public class Priority2Violator { 2 | public void foo(String string) { 3 | // should be && 4 | if (string != null || !string.equals("")) { 5 | System.out.println(string); 6 | } 7 | System.out.println("bar"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/pmd/priority3/Priority3Violator.java: -------------------------------------------------------------------------------- 1 | public class Priority3Violator { 2 | public void foo() { 3 | if (true) { // fixed conditional, not recommended 4 | System.out.println("bar"); 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugin/src/test/fixtures/sources/pmd/priority4/Priority4Violator.java: -------------------------------------------------------------------------------- 1 | public class Priority4Violator extends Object { // not required 2 | } 3 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/DefaultViolationsEvaluatorTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.GradleException 4 | import org.gradle.api.logging.Logger 5 | import org.gradle.internal.logging.ConsoleRenderer 6 | import org.junit.Before 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.rules.TemporaryFolder 10 | import org.mockito.ArgumentCaptor 11 | 12 | import static com.google.common.truth.Truth.assertThat 13 | import static org.junit.Assert.fail 14 | import static org.mockito.Mockito.mock 15 | import static org.mockito.Mockito.verify 16 | import static org.mockito.Mockito.verifyZeroInteractions 17 | 18 | class DefaultViolationsEvaluatorTest { 19 | 20 | private static final String TOOL_NAME = 'SomeTool' 21 | 22 | @Rule 23 | public TemporaryFolder temporaryFolder = new TemporaryFolder() 24 | 25 | private PenaltyExtension penalty 26 | private Violations violations 27 | private File reportFile 28 | private Logger logger = mock(Logger) 29 | 30 | private DefaultViolationsEvaluator evaluator 31 | 32 | @Before 33 | void setUp() { 34 | penalty = new PenaltyExtension() 35 | penalty.maxErrors = 1 36 | penalty.maxWarnings = 1 37 | violations = new Violations(TOOL_NAME) 38 | reportFile = temporaryFolder.newFile('report.xml') 39 | evaluator = new DefaultViolationsEvaluator(ReportUrlRenderer.DEFAULT, logger, penalty) 40 | } 41 | 42 | @Test 43 | void shouldLogViolationsNumberWhenBelowThreshold() { 44 | violations.addViolations(1, 0, reportFile) 45 | 46 | evaluator.evaluate(allViolations) 47 | 48 | def expected = """ 49 | Violations found (1 errors, 0 warnings) 50 | > $TOOL_NAME violations found (1 errors, 0 warnings). See the reports at: 51 | $consoleClickableFileUrl 52 | """ 53 | assertThat(warningLog).isEqualTo(expected.stripIndent()) 54 | } 55 | 56 | @Test 57 | void shouldNotLogViolationsNumberWhenNoViolations() { 58 | violations.addViolations(0, 0, reportFile) 59 | 60 | evaluator.evaluate(allViolations) 61 | 62 | verifyZeroInteractions(logger) 63 | } 64 | 65 | @Test 66 | void shouldThrowExceptionWhenViolationsNumberWhenAboveThreshold() { 67 | violations.addViolations(1, 2, reportFile) 68 | 69 | try { 70 | evaluator.evaluate(allViolations) 71 | fail('Exception expected but not thrown') 72 | } catch (GradleException e) { 73 | def expected = 74 | """|Violations limit exceeded by 0 errors, 1 warnings. 75 | |> $TOOL_NAME violations found (1 errors, 2 warnings). See the reports at: 76 | |$consoleClickableFileUrl 77 | |""" 78 | assertThat(e.message).isEqualTo(expected.stripMargin()) 79 | } 80 | } 81 | 82 | private Set getAllViolations() { 83 | [violations] as Set 84 | } 85 | 86 | private String getWarningLog() { 87 | ArgumentCaptor captor = ArgumentCaptor.forClass(String) 88 | verify(logger).warn(captor.capture()) 89 | captor.getValue() 90 | } 91 | 92 | private String getConsoleClickableFileUrl() { 93 | new ConsoleRenderer().asClickableFileUrl(reportFile) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/RulesIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import com.gradleup.test.DeployRulesTestRule 4 | import com.gradleup.test.Fixtures 5 | import com.gradleup.test.TestProject 6 | import com.gradleup.test.TestProjectRule 7 | import org.junit.ClassRule 8 | import org.junit.Rule 9 | import org.junit.Test 10 | import org.junit.runner.RunWith 11 | import org.junit.runners.Parameterized 12 | 13 | import static com.gradleup.test.LogsSubject.assertThat 14 | 15 | @RunWith(Parameterized.class) 16 | class RulesIntegrationTest { 17 | 18 | @ClassRule 19 | public static final DeployRulesTestRule DEPLOY_RULES = new DeployRulesTestRule( 20 | resourceDirs: [Fixtures.RULES_DIR], 21 | repoDir: new File(Fixtures.BUILD_DIR, 'rules')) 22 | 23 | @Parameterized.Parameters(name = "{0}") 24 | public static Iterable rules() { 25 | return [TestProjectRule.forJavaProject(), TestProjectRule.forAndroidProject()] 26 | } 27 | 28 | @Rule 29 | public final TestProjectRule projectRule 30 | 31 | RulesIntegrationTest(TestProjectRule projectRule) { 32 | this.projectRule = projectRule 33 | } 34 | 35 | @Test 36 | public void shouldUseConfigurationFromArtifactWhenProvided() { 37 | TestProject.Result result = projectRule.newProject() 38 | .withAdditionalConfiguration("repositories { maven { url '$DEPLOY_RULES.repoDir' } }") 39 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 40 | .withPenalty('''{ 41 | maxWarnings = 0 42 | maxErrors = 0 43 | }''') 44 | .withToolsConfig(""" 45 | rules { 46 | gradleup { maven '$DEPLOY_RULES.mavenCoordinates' } 47 | } 48 | checkstyle { 49 | config rules.gradleup['checkstyle/config/modules.xml'] 50 | } 51 | """) 52 | .buildAndFail('check') 53 | 54 | assertThat(result.logs).containsLimitExceeded(0, 1) 55 | assertThat(result.logs).containsCheckstyleViolations(0, 1, 56 | result.buildFileUrl('reports/checkstyle/main.html')) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/StaticAnalysisExtensionTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import org.gradle.api.GradleException 4 | import org.gradle.api.Project 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import org.junit.Before 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.rules.TemporaryFolder 10 | 11 | import static com.google.common.truth.Truth.assertThat 12 | import static org.junit.Assert.fail 13 | 14 | class StaticAnalysisExtensionTest { 15 | 16 | @Rule 17 | public TemporaryFolder temporaryFolder = new TemporaryFolder() 18 | 19 | private Project project 20 | private StaticAnalysisExtension extension 21 | 22 | @Before 23 | void setUp() { 24 | project = ProjectBuilder.builder() 25 | .withProjectDir(temporaryFolder.newFolder()) 26 | .build() 27 | 28 | extension = new StaticAnalysisExtension(project) 29 | extension.allViolations.create('SomeTool') { 30 | it.addViolations(2, 2, temporaryFolder.newFile()) 31 | } 32 | } 33 | 34 | @Test 35 | void shouldOverrideEvaluatorWhenEvaluationLogicProvided() { 36 | def e = new GradleException() 37 | extension.evaluator { throw e } 38 | 39 | try { 40 | extension.evaluator.evaluate(extension.allViolations) 41 | 42 | fail('Exception expected but not thrown') 43 | } catch (Exception thrown) { 44 | assertThat(thrown).isEqualTo(e) 45 | } 46 | } 47 | 48 | @Test 49 | void shouldProvideViolationsFromExtensionToDefineCustomEvaluator() { 50 | Set capturedViolations = null 51 | extension.evaluator { allViolations -> 52 | capturedViolations = allViolations 53 | } 54 | 55 | extension.evaluator.evaluate(extension.allViolations) 56 | 57 | assertThat(capturedViolations).containsAllIn(extension.allViolations) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/StaticAnalysisPluginTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis 2 | 3 | import com.gradleup.test.TestProject 4 | import com.gradleup.test.TestProjectRule 5 | import org.junit.Rule 6 | import org.junit.Test 7 | 8 | class StaticAnalysisPluginTest { 9 | 10 | @Rule 11 | public final TestProjectRule rule = new TestProjectRule({ new EmptyProject() }, { "" }, 'Empty project') 12 | 13 | @Test 14 | void shouldNotFailWhenNoJavaOrAndroidPluginsAreApplied() { 15 | rule.newProject() 16 | .build("help") 17 | } 18 | 19 | private static class EmptyProject extends TestProject { 20 | private static final Closure TEMPLATE = { TestProject project -> 21 | """ 22 | plugins { 23 | id("com.gradleup.static-analysis") 24 | } 25 | """ 26 | } 27 | 28 | EmptyProject() { 29 | super(TEMPLATE) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/LogsConfigurationIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import com.gradleup.test.Fixtures 4 | import com.gradleup.test.TestProject 5 | import com.gradleup.test.TestProjectRule 6 | import org.junit.Rule 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | import org.junit.runners.Parameterized 10 | 11 | import static com.gradleup.test.LogsSubject.assertThat 12 | 13 | @RunWith(Parameterized.class) 14 | class LogsConfigurationIntegrationTest { 15 | 16 | private static 17 | final String DEFAULT_CHECKSTYLE_CONFIG = "checkstyle { configFile new File('${Fixtures.Checkstyle.MODULES.path}') }" 18 | 19 | @Parameterized.Parameters(name = "{0}") 20 | public static Iterable rules() { 21 | return [TestProjectRule.forJavaProject(), TestProjectRule.forAndroidProject()] 22 | } 23 | 24 | @Rule 25 | public final TestProjectRule projectRule 26 | 27 | public LogsConfigurationIntegrationTest(TestProjectRule projectRule) { 28 | this.projectRule = projectRule 29 | } 30 | 31 | @Test 32 | public void shouldUseCustomReportUrlRendererWhenProvided() { 33 | TestProject.Result result = projectRule.newProject() 34 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 35 | .withPenalty('''{ 36 | maxErrors = 0 37 | maxWarnings = 1 38 | }''') 39 | .withToolsConfig(""" 40 | $DEFAULT_CHECKSTYLE_CONFIG 41 | logs { 42 | reportUrlRenderer { report -> "**\${report.path}**" } 43 | } 44 | """) 45 | .build('check') 46 | 47 | assertThat(result.logs).containsCheckstyleViolations(0, 1, 48 | "**${result.buildFileUrl('reports/checkstyle/main.html')}**") 49 | } 50 | 51 | @Test 52 | public void shouldUseDifferentReportBaseUrlWhenProvided() { 53 | TestProject.Result result = projectRule.newProject() 54 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 55 | .withPenalty('''{ 56 | maxErrors = 0 57 | maxWarnings = 1 58 | }''') 59 | .withToolsConfig(""" 60 | $DEFAULT_CHECKSTYLE_CONFIG 61 | logs { 62 | reportBaseUrl "what://foo/bar/reports" 63 | } 64 | """) 65 | .build('check') 66 | 67 | assertThat(result.logs).containsCheckstyleViolations(0, 1, 'what://foo/bar/reports/checkstyle/main.html') 68 | } 69 | 70 | @Test 71 | public void shouldMatchDifferentReportBaseUrlWhenProvided() { 72 | TestProject.Result result = projectRule.newProject() 73 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 74 | .withPenalty('''{ 75 | maxErrors = 0 76 | maxWarnings = 1 77 | }''') 78 | .withToolsConfig(""" 79 | $DEFAULT_CHECKSTYLE_CONFIG 80 | logs { 81 | reportBaseUrl "what://foo/bar", "\${project.buildDir}/reports" 82 | } 83 | """) 84 | .build('check') 85 | 86 | assertThat(result.logs).containsCheckstyleViolations(0, 1, 'what://foo/bar/checkstyle/main.html') 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/SourceFilterTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.tasks.SourceTask 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import org.junit.Before 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.rules.TemporaryFolder 10 | 11 | import static com.google.common.truth.Truth.assertThat 12 | import static com.gradleup.test.Fixtures.Checkstyle.SOURCES_WITH_ERRORS 13 | import static com.gradleup.test.Fixtures.Checkstyle.SOURCES_WITH_WARNINGS 14 | 15 | class SourceFilterTest { 16 | 17 | @Rule 18 | public TemporaryFolder temporaryFolder = new TemporaryFolder() 19 | 20 | private Project project 21 | private SourceFilter filter 22 | 23 | @Before 24 | public void setUp() { 25 | project = ProjectBuilder.builder() 26 | .withProjectDir(temporaryFolder.newFolder()) 27 | .build() 28 | filter = new SourceFilter(project) 29 | } 30 | 31 | @Test 32 | public void shouldKeepAllSourcesWhenNoExcludeFilterProvided() { 33 | SourceTask task = givenTaskWith(errorsSources + warningsSources) 34 | 35 | filter.applyTo(task) 36 | 37 | assertThat(task.source).containsExactlyElementsIn(errorsSources + warningsSources) 38 | } 39 | 40 | @Test 41 | public void shouldRemoveFilesMatchingThePatternWhenExcludePatternProvided() { 42 | SourceTask task = givenTaskWith(errorsSources + warningsSources) 43 | filter.exclude('**/*.java') 44 | 45 | filter.applyTo(task) 46 | 47 | assertThat(task.source).isEmpty() 48 | } 49 | 50 | @Test 51 | public void shouldRemoveFilesInSpecifiedFileCollectionWhenExcludeFileCollectionProvided() { 52 | SourceTask task = givenTaskWith(errorsSources + warningsSources) 53 | filter.exclude(project.fileTree(SOURCES_WITH_ERRORS)) 54 | 55 | filter.applyTo(task) 56 | 57 | assertThat(task.source).containsExactlyElementsIn(warningsSources) 58 | } 59 | 60 | @Test 61 | public void shouldRemoveFilesInSpecifiedFileCollectionWhenExcludeSourceSetProvided() { 62 | project.apply plugin: 'java' 63 | project.sourceSets.main { 64 | java { 65 | srcDir project.file(SOURCES_WITH_ERRORS) 66 | } 67 | } 68 | SourceTask task = givenTaskWith(errorsSources) 69 | filter.exclude(project.sourceSets.main.java.srcDirs) 70 | 71 | filter.applyTo(task) 72 | 73 | assertThat(task.source).isEmpty() 74 | } 75 | 76 | @Test 77 | public void shouldRemoveFilesInSpecifiedFileCollectionWhenSourceFileExcluded() { 78 | project.apply plugin: 'java' 79 | project.sourceSets.main { 80 | java { 81 | srcDir project.file(SOURCES_WITH_ERRORS) 82 | } 83 | } 84 | SourceTask task = givenTaskWith(errorsSources) 85 | filter.exclude(new File(SOURCES_WITH_ERRORS, 'Greeter.java')) 86 | 87 | filter.applyTo(task) 88 | 89 | assertThat(task.source).isEmpty() 90 | } 91 | 92 | private SourceTask givenTaskWith(Iterable files) { 93 | project.tasks.create('someSourceTask', SourceTask) { 94 | source = project.files(files) 95 | } 96 | } 97 | 98 | private Set getErrorsSources() { 99 | project.fileTree(SOURCES_WITH_ERRORS).files 100 | } 101 | 102 | private Set getWarningsSources() { 103 | project.fileTree(SOURCES_WITH_WARNINGS).files 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/checkstyle/CheckstyleConfigurationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.checkstyle 2 | 3 | import com.gradleup.test.Fixtures 4 | import com.gradleup.test.TestProjectRule 5 | import org.junit.Rule 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.junit.runners.Parameterized 9 | 10 | @RunWith(Parameterized.class) 11 | class CheckstyleConfigurationTest { 12 | 13 | @Parameterized.Parameters(name = "{0}") 14 | static Iterable rules() { 15 | return [ 16 | TestProjectRule.forJavaProject(), 17 | TestProjectRule.forKotlinProject(), 18 | TestProjectRule.forAndroidProject(), 19 | TestProjectRule.forAndroidKotlinProject(), 20 | ] 21 | } 22 | 23 | @Rule 24 | public final TestProjectRule projectRule 25 | 26 | CheckstyleConfigurationTest(TestProjectRule projectRule) { 27 | this.projectRule = projectRule 28 | } 29 | 30 | @Test 31 | void shouldConfigureSuccessFully() { 32 | projectRule.newProject() 33 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 34 | .withToolsConfig("checkstyle { }") 35 | .build('check', '--dry-run') 36 | } 37 | 38 | @Test 39 | void shouldNotFailBuildWhenCheckstyleIsConfiguredMultipleTimes() { 40 | projectRule.newProject() 41 | .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) 42 | .withToolsConfig(""" 43 | checkstyle { 44 | configFile new File('${Fixtures.Checkstyle.MODULES.path}') 45 | } 46 | checkstyle { 47 | ignoreFailures = false 48 | } 49 | """) 50 | .build('check', '--dry-run') 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/ktlint/KtlintIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.ktlint 2 | 3 | import com.gradleup.test.Fixtures 4 | import com.gradleup.test.TestProject 5 | import com.gradleup.test.TestProjectRule 6 | import org.junit.Rule 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | import org.junit.runners.Parameterized 10 | 11 | import static com.gradleup.test.LogsSubject.assertThat 12 | 13 | @RunWith(Parameterized.class) 14 | class KtlintIntegrationTest { 15 | 16 | private static final String KTLINT_NOT_APPLIED = 'The Ktlint plugin is configured but not applied. Please apply the plugin in your build script.' 17 | private static final String XML_REPORT_NOT_ENABLED = 'XML report must be enabled. Please make sure to add "CHECKSTYLE" into reports in your Ktlint configuration' 18 | 19 | public static final String DEFAULT_CONFIG = ''' 20 | ktlint { 21 | reporters { 22 | reporter "plain" 23 | reporter "checkstyle" 24 | } 25 | 26 | includeVariants { it.name == "debug" } 27 | } 28 | ''' 29 | 30 | @Parameterized.Parameters(name = '{0} with ktlint {1}') 31 | static def rules() { 32 | return [ 33 | [TestProjectRule.forKotlinProject(), '9.0.0', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 34 | [TestProjectRule.forAndroidKotlinProject(), '9.0.0', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 35 | [TestProjectRule.forKotlinProject(), '9.1.1', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 36 | [TestProjectRule.forAndroidKotlinProject(), '9.1.1', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 37 | [TestProjectRule.forKotlinProject(), '9.2.1', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 38 | [TestProjectRule.forAndroidKotlinProject(), '9.2.1', 'ktlintMainSourceSetCheck.txt', DEFAULT_CONFIG], 39 | ]*.toArray() 40 | } 41 | 42 | @Rule 43 | public final TestProjectRule projectRule 44 | private final String ktlintVersion 45 | private final String expectedOutputFileName 46 | private final String defaultConfig 47 | 48 | KtlintIntegrationTest(TestProjectRule projectRule, String ktlintVersion, String expectedOutputFileName, String defaultConfig) { 49 | this.projectRule = projectRule 50 | this.ktlintVersion = ktlintVersion 51 | this.expectedOutputFileName = expectedOutputFileName 52 | this.defaultConfig = defaultConfig 53 | } 54 | 55 | @Test 56 | void shouldNotFailWhenKtlintIsNotConfigured() { 57 | def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR) 58 | .build('evaluateViolations') 59 | 60 | assertThat(result.logs).doesNotContainKtlintViolations() 61 | } 62 | 63 | @Test 64 | void shouldFailBuildOnConfigurationWhenDetektConfiguredWithoutXmlReport() { 65 | def result = projectRule.newProject() 66 | .withPlugin('org.jlleitschuh.gradle.ktlint', ktlintVersion) 67 | .withToolsConfig('ktlint { }') 68 | .buildAndFail('evaluateViolations') 69 | 70 | assertThat(result.logs).contains(XML_REPORT_NOT_ENABLED) 71 | } 72 | 73 | @Test 74 | void shouldFailBuildOnConfigurationWhenKtlintConfiguredButNotApplied() { 75 | def result = projectRule.newProject() 76 | .withToolsConfig(defaultConfig) 77 | .buildAndFail('evaluateViolations') 78 | 79 | assertThat(result.logs).contains(KTLINT_NOT_APPLIED) 80 | } 81 | 82 | @Test 83 | void shouldFailBuildWhenKtlintErrorsOverTheThreshold() { 84 | def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR) 85 | .withToolsConfig(defaultConfig) 86 | .buildAndFail('evaluateViolations') 87 | 88 | assertThat(result.logs).containsLimitExceeded(1, 0) 89 | assertThat(result.logs).containsKtlintViolations(1, 90 | result.buildFileUrl("reports/ktlint/$expectedOutputFileName")) 91 | } 92 | 93 | @Test 94 | void shouldNotFailWhenErrorsAreWithinThreshold() { 95 | def result = createProjectWith(Fixtures.Ktlint.SOURCES_WITH_ERROR, 1) 96 | .withToolsConfig(defaultConfig) 97 | .build('evaluateViolations') 98 | 99 | assertThat(result.logs).containsKtlintViolations(1, 100 | result.buildFileUrl("reports/ktlint/$expectedOutputFileName")) 101 | } 102 | 103 | @Test 104 | void shouldNotFailBuildWhenNoErrorsEncounteredAndNoThresholdTrespassed() { 105 | def result = createProjectWith(Fixtures.Ktlint.SOURCES_NO_ERROR, 0) 106 | .withToolsConfig(defaultConfig) 107 | .build('evaluateViolations') 108 | 109 | assertThat(result.logs).doesNotContainLimitExceeded() 110 | assertThat(result.logs).doesNotContainKtlintViolations() 111 | } 112 | 113 | private TestProject createProjectWith(File sources, int maxErrors = 0) { 114 | projectRule.newProject() 115 | .withPlugin('org.jlleitschuh.gradle.ktlint', ktlintVersion) 116 | .copyIntoSourceSet('main', sources) 117 | .withPenalty("""{ 118 | maxWarnings = 0 119 | maxErrors = ${maxErrors} 120 | }""") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/lint/CollectLintViolationsTaskTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.lint 2 | 3 | import com.gradleup.staticanalysis.Violations 4 | import com.gradleup.test.Fixtures 5 | import org.gradle.api.Project 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import org.junit.Test 8 | 9 | import static com.google.common.truth.Truth.assertThat 10 | 11 | class CollectLintViolationsTaskTest { 12 | 13 | @Test 14 | void shouldAddResultsToViolations() { 15 | Project project = ProjectBuilder.builder().build() 16 | def task = project.task('collectLintViolationsTask', type: CollectLintViolationsTask) 17 | 18 | Violations violations = new Violations('Android Lint') 19 | task.collectViolations(Fixtures.Lint.SAMPLE_REPORT, null, violations) 20 | 21 | assertThat(violations.getErrors()).isEqualTo(2) 22 | assertThat(violations.getWarnings()).isEqualTo(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/lint/LintAndroidVariantIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.lint 2 | 3 | import com.google.common.truth.Truth 4 | import com.gradleup.test.Fixtures 5 | import com.gradleup.test.TestAndroidProject 6 | import com.gradleup.test.TestProject 7 | import com.gradleup.test.TestProjectRule 8 | import org.junit.Rule 9 | import org.junit.Test 10 | 11 | import static com.gradleup.test.LogsSubject.assertThat 12 | 13 | class LintAndroidVariantIntegrationTest { 14 | 15 | @Rule 16 | public final TestProjectRule projectRule = TestProjectRule.forAndroidLintProject() 17 | 18 | @Test 19 | void shouldFailBuildWhenLintViolationsOverThreshold() { 20 | TestProject.Result result = starterProject() 21 | .withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) 22 | .withToolsConfig(DEFAULT_CONFIG) 23 | .buildAndFail('evaluateViolations') 24 | 25 | assertThat(result.logs).containsLimitExceeded(1, 1) 26 | assertThat(result.logs).containsLintViolations(1, 1, 27 | 'reports/lint-results.html') 28 | } 29 | 30 | @Test 31 | void givenVariantsFilteredShouldFailBuildWithDuplicatedNumbers() { 32 | TestProject.Result result = starterProject() 33 | .withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) 34 | .withToolsConfig(configWithVariants('demoDebug', 'fullRelease')) 35 | .buildAndFail('evaluateViolations') 36 | 37 | assertThat(result.logs).containsLimitExceeded(1, 2) 38 | assertThat(result.logs).containsLintViolations(1, 2, 39 | 'reports/lint-results-demoDebug.html', 40 | 'reports/lint-results-fullRelease.html' 41 | ) 42 | } 43 | 44 | @Test 45 | void shouldIgnoreErrorsFromInactiveVariant() { 46 | TestProject.Result result = starterProject(maxWarnings: 1) 47 | .withSourceSet('demo', Fixtures.Lint.SOURCES_WITH_ERRORS) 48 | .withToolsConfig(configWithVariants('fullRelease')) 49 | .build('evaluateViolations') 50 | 51 | assertThat(result.logs).containsLintViolations(0, 1) 52 | } 53 | 54 | @Test 55 | void shouldContainCollectLintTasks() { 56 | TestProject.Result result = starterProject(maxWarnings: 1) 57 | .withToolsConfig(DEFAULT_CONFIG) 58 | .build('evaluateViolations') 59 | 60 | Truth.assertThat(result.tasksPaths).containsAllOf( 61 | ':lint', 62 | ':collectLintViolations', 63 | ) 64 | } 65 | 66 | @Test 67 | void givenVariantsFilteredShouldContainTasksForIncludedVariantsOnly() { 68 | TestProject.Result result = starterProject(maxWarnings: 1) 69 | .withToolsConfig(configWithVariants('demoDebug')) 70 | .build('evaluateViolations') 71 | 72 | Truth.assertThat(result.tasksPaths).containsAllOf( 73 | ':lintDemoDebug', 74 | ':collectLintDemoDebugViolations') 75 | Truth.assertThat(result.tasksPaths).containsNoneOf( 76 | ':lint', 77 | ':lintDemoRelease', 78 | ':collectLintDemoReleaseViolations', 79 | ':lintFullDebug', 80 | ':collectLintFullDebugViolations', 81 | ':lintFullRelease', 82 | ':collectLintFullReleaseViolations') 83 | } 84 | 85 | private static final String DEFAULT_CONFIG = 86 | """ 87 | lintOptions { 88 | lintConfig = file("${Fixtures.Lint.RULES}") 89 | } 90 | """ 91 | 92 | private static configWithVariants(String... variantNames) { 93 | def commaSeparatedVariants = variantNames.collect { "'$it'" }.join(', ') 94 | """ 95 | lintOptions { 96 | checkReleaseBuilds false 97 | lintConfig = file("${Fixtures.Lint.RULES}") 98 | includeVariants { it.name in [$commaSeparatedVariants] } 99 | } 100 | """ 101 | } 102 | 103 | private TestAndroidProject starterProject(def args = [:]) { 104 | projectRule.newProject() 105 | .withSourceSet('main', Fixtures.Lint.SOURCES_WITH_WARNINGS) 106 | .withPenalty("""{ 107 | maxErrors = ${args.maxErrors ?: 0} 108 | maxWarnings = ${args.maxWarnings ?: 0} 109 | }""") 110 | .withAdditionalAndroidConfig(''' 111 | flavorDimensions 'tier' 112 | 113 | productFlavors { 114 | demo { dimension 'tier' } 115 | full { dimension 'tier' } 116 | } 117 | ''') 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/lint/LintIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.lint 2 | 3 | import com.gradleup.test.Fixtures 4 | import com.gradleup.test.TestAndroidProject 5 | import com.gradleup.test.TestProject 6 | import org.junit.Test 7 | 8 | import static com.gradleup.test.LogsSubject.assertThat 9 | 10 | class LintIntegrationTest { 11 | 12 | private static final String LINT_CONFIGURATION = 13 | """ 14 | lintOptions { 15 | lintConfig = file("${Fixtures.Lint.RULES}") 16 | } 17 | """ 18 | 19 | @Test 20 | void shouldFailBuildWhenLintErrorsOverTheThresholds() { 21 | def result = createAndroidProjectWith(Fixtures.Lint.SOURCES_WITH_ERRORS, 0, 0) 22 | .buildAndFail('check') 23 | 24 | assertThat(result.logs).containsLintViolations(1, 0, 'reports/lint-results.html') 25 | } 26 | 27 | @Test 28 | void shouldNotFailBuildWhenLintErrorsWithinTheThresholds() { 29 | def result = createAndroidProjectWith(Fixtures.Lint.SOURCES_WITH_ERRORS, 0, 1) 30 | .build('check') 31 | 32 | assertThat(result.logs).doesNotContainLimitExceeded() 33 | } 34 | 35 | @Test 36 | void shouldFailBuildWhenLintWarningsOverTheThresholds() { 37 | def result = createAndroidProjectWith(Fixtures.Lint.SOURCES_WITH_WARNINGS, 0, 0) 38 | .buildAndFail('check') 39 | 40 | assertThat(result.logs).containsLintViolations(0, 1, 'reports/lint-results.html') 41 | } 42 | 43 | @Test 44 | void shouldNotFailBuildWhenLintWarningsWithinTheThresholds() { 45 | def result = createAndroidProjectWith(Fixtures.Lint.SOURCES_WITH_WARNINGS, 1, 0) 46 | .build('check') 47 | 48 | assertThat(result.logs).doesNotContainLimitExceeded() 49 | } 50 | 51 | @Test 52 | void shouldNotFailBuildWhenLintIsConfiguredMultipleTimes() { 53 | createAndroidProjectWith(Fixtures.Lint.SOURCES_WITH_WARNINGS, 1, 0) 54 | .withToolsConfig(""" 55 | lintOptions { 56 | lintConfig = file("${Fixtures.Lint.RULES}") 57 | } 58 | lintOptions { 59 | checkReleaseBuilds false 60 | } 61 | """) 62 | .build('check', '--dry-run') 63 | } 64 | 65 | private static TestProject createAndroidProjectWith(File sources, int maxWarnings = 0, int maxErrors = 0) { 66 | return new TestAndroidProject(false) 67 | .withSourceSet('main', sources) 68 | .withPenalty("""{ 69 | maxWarnings = ${maxWarnings} 70 | maxErrors = ${maxErrors} 71 | }""") 72 | .withToolsConfig(LINT_CONFIGURATION) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/pmd/PmdConfigurationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.pmd 2 | 3 | import com.gradleup.test.Fixtures 4 | import com.gradleup.test.TestProjectRule 5 | import org.junit.Rule 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.junit.runners.Parameterized 9 | 10 | @RunWith(Parameterized.class) 11 | class PmdConfigurationTest { 12 | 13 | @Parameterized.Parameters(name = "{0}") 14 | static Iterable rules() { 15 | return [ 16 | TestProjectRule.forJavaProject(), 17 | TestProjectRule.forKotlinProject(), 18 | TestProjectRule.forAndroidProject(), 19 | TestProjectRule.forAndroidKotlinProject(), 20 | ] 21 | } 22 | 23 | @Rule 24 | public final TestProjectRule projectRule 25 | 26 | PmdConfigurationTest(TestProjectRule projectRule) { 27 | this.projectRule = projectRule 28 | } 29 | 30 | @Test 31 | void shouldConfigureSuccessFully() { 32 | projectRule.newProject() 33 | .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) 34 | .withToolsConfig("pmd { }") 35 | .build('check', '--dry-run') 36 | } 37 | 38 | @Test 39 | void shouldNotFailBuildWhenPmdIsConfiguredMultipleTimes() { 40 | projectRule.newProject() 41 | .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) 42 | .withToolsConfig(""" 43 | pmd { 44 | } 45 | pmd { 46 | ignoreFailures = false 47 | } 48 | """) 49 | .build('check', '--dry-run') 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/pmd/PmdViolationsEvaluatorTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.pmd 2 | 3 | import com.gradleup.test.Fixtures 4 | import org.junit.Before 5 | import org.junit.Test 6 | 7 | import static com.google.common.truth.Truth.assertThat 8 | import static com.gradleup.staticanalysis.internal.pmd.PmdViolationsEvaluator.PmdViolation 9 | 10 | public class PmdViolationsEvaluatorTest { 11 | 12 | private PmdViolationsEvaluator evaluator 13 | 14 | @Before 15 | public void setUp() { 16 | evaluator = new PmdViolationsEvaluator(new XmlSlurper().parse(Fixtures.Pmd.SAMPLE_REPORT)) 17 | } 18 | 19 | @Test 20 | public void shouldCollectDistinctViolationFromReport() { 21 | def expected = [new PmdViolation('1', '4', '8', '1', 'ClassWithOnlyPrivateConstructorsShouldBeFinal', 'Design', 'static-analysis-plugin/plugin/fixtures/pmd/priority1/Priority1Violator.java', '', '1'), 22 | new PmdViolation('4', '6', '9', '9', 'BrokenNullCheck', 'Basic', 'static-analysis-plugin/plugin/fixtures/pmd/priority2/Priority2Violator.java', 'foo', '2')] 23 | 24 | Set violations = evaluator.collectViolations() 25 | 26 | assertThat(violations).containsExactlyElementsIn(expected) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/staticanalysis/internal/spotbugs/SpotBugsConfigurationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysis.internal.spotbugs 2 | 3 | import com.gradleup.test.TestProjectRule 4 | import org.junit.Rule 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | import org.junit.runners.Parameterized 8 | 9 | import static com.gradleup.test.Fixtures.Findbugs.SOURCES_WITH_LOW_VIOLATION 10 | 11 | @RunWith(Parameterized.class) 12 | class SpotBugsConfigurationTest { 13 | 14 | @Parameterized.Parameters(name = "{0}") 15 | static Iterable rules() { 16 | return [ 17 | TestProjectRule.forJavaProject(), 18 | TestProjectRule.forKotlinProject(), 19 | // TODO Android tests are currently disabled because of ClassNotFound 20 | // TestProjectRule.forAndroidProject(), 21 | // TestProjectRule.forAndroidKotlinProject(), 22 | ] 23 | } 24 | 25 | @Rule 26 | public final TestProjectRule projectRule 27 | 28 | SpotBugsConfigurationTest(TestProjectRule projectRule) { 29 | this.projectRule = projectRule 30 | } 31 | 32 | @Test 33 | void shouldConfigureSuccessFully() { 34 | projectRule.newProject() 35 | .withPlugin('com.github.spotbugs', "4.7.1") 36 | .withSourceSet('main', SOURCES_WITH_LOW_VIOLATION) 37 | .withToolsConfig("spotbugs { }") 38 | .build('check', '--dry-run') 39 | } 40 | 41 | @Test 42 | void shouldNotFailBuildWhenSpotBugsIsConfiguredMultipleTimes() { 43 | projectRule.newProject() 44 | .withPlugin('com.github.spotbugs', "4.7.1") 45 | .withSourceSet('main', SOURCES_WITH_LOW_VIOLATION) 46 | .withPenalty('none') 47 | .withToolsConfig(""" 48 | spotbugs { } 49 | spotbugs { 50 | effort = "max" 51 | } 52 | """) 53 | .build('check', '--dry-run') 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/DeployRulesTestRule.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import org.gradle.testkit.runner.GradleRunner 4 | import org.junit.rules.TestRule 5 | import org.junit.runner.Description 6 | import org.junit.runners.model.Statement 7 | 8 | class DeployRulesTestRule implements TestRule { 9 | 10 | List resourceDirs 11 | File repoDir 12 | String groupId = 'test' 13 | String artifactId = 'rules' 14 | String version = 'unreleased' 15 | 16 | String getMavenCoordinates() { 17 | "$groupId:$artifactId:$version" 18 | } 19 | 20 | @Override 21 | Statement apply(Statement base, Description description) { 22 | new Statement() { 23 | @Override 24 | void evaluate() throws Throwable { 25 | cleanRepo() 26 | File projectDir = createProjectDir("${System.currentTimeMillis()}") 27 | createBuildScripts(projectDir) 28 | GradleRunner.create() 29 | .withProjectDir(projectDir) 30 | .withDebug(true) 31 | .withArguments('clean', 'publish') 32 | .withPluginClasspath() 33 | .forwardOutput() 34 | .build() 35 | base.evaluate() 36 | projectDir.deleteDir() 37 | } 38 | 39 | } 40 | } 41 | 42 | private void cleanRepo() { 43 | def artifactDir = new File(repoDir, "${groupId.replace('.', '/')}/$artifactId/$version") 44 | if (artifactDir.exists()) { 45 | artifactDir.deleteDir() 46 | } 47 | } 48 | 49 | private void createBuildScripts(File projectDir) { 50 | new File(projectDir, 'build.gradle').text = """ 51 | buildscript { 52 | repositories { 53 | mavenCentral() 54 | } 55 | } 56 | 57 | version='$version' 58 | 59 | apply plugin: 'java' 60 | 61 | sourceSets { 62 | main { 63 | resources { 64 | srcDirs = ${resourceDirs.collect { "'$it.path'" }} 65 | } 66 | } 67 | } 68 | 69 | apply plugin: 'maven-publish' 70 | 71 | publishing { 72 | repositories { 73 | maven { url '${repoDir}' } 74 | } 75 | publications { 76 | mavenJava(MavenPublication) { 77 | groupId '$groupId' 78 | artifactId '$artifactId' 79 | from components.java 80 | } 81 | } 82 | }""".stripIndent() 83 | new File(projectDir, 'settings.gradle').text = '''''' 84 | } 85 | 86 | private static File createProjectDir(String path) { 87 | File dir = new File(Fixtures.BUILD_DIR, "test-projects/$path") 88 | dir.deleteDir() 89 | dir.mkdirs() 90 | return dir 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/Fixtures.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import com.google.common.io.Resources 4 | 5 | public final class Fixtures { 6 | private static final Exception NO_INSTANCE_ALLOWED = new UnsupportedOperationException("No instance allowed"); 7 | public static final File ROOT_DIR = new File(Resources.getResource('.').file).parentFile.parentFile.parentFile.parentFile 8 | private static final File FIXTURES_DIR = new File(ROOT_DIR, 'src/test/fixtures') 9 | private static final File SOURCES_DIR = new File(FIXTURES_DIR, 'sources') 10 | private static final File REPORTS_DIR = new File(FIXTURES_DIR, 'reports') 11 | public static final File RULES_DIR = new File(FIXTURES_DIR, 'rules') 12 | public static final File BUILD_DIR = new File(ROOT_DIR, 'build') 13 | public static final File LOCAL_PROPERTIES = new File(ROOT_DIR.parentFile, 'local.properties') 14 | public static final File ANDROID_MANIFEST = new File(FIXTURES_DIR, 'AndroidManifest.xml') 15 | 16 | private Fixtures() { 17 | throw NO_INSTANCE_ALLOWED 18 | } 19 | 20 | public final static class Checkstyle { 21 | public static final File MODULES = new File(RULES_DIR, 'checkstyle/config/modules.xml') 22 | public static final File SOURCES_WITH_ERRORS = new File(SOURCES_DIR, 'checkstyle/errors') 23 | public static final File SOURCES_WITH_WARNINGS = new File(SOURCES_DIR, 'checkstyle/warnings') 24 | 25 | private Checkstyle() { 26 | throw NO_INSTANCE_ALLOWED 27 | } 28 | } 29 | 30 | public final static class Pmd { 31 | public static final File RULES = new File(RULES_DIR, 'pmd/config/rules.xml') 32 | public static final File SOURCES_WITH_PRIORITY_1_VIOLATION = new File(SOURCES_DIR, 'pmd/priority1') 33 | public static final File SOURCES_WITH_PRIORITY_2_VIOLATION = new File(SOURCES_DIR, 'pmd/priority2') 34 | public static final File SOURCES_WITH_PRIORITY_3_VIOLATION = new File(SOURCES_DIR, 'pmd/priority3') 35 | public static final File SOURCES_WITH_PRIORITY_4_VIOLATION = new File(SOURCES_DIR, 'pmd/priority4') 36 | public static final File SAMPLE_REPORT = new File(REPORTS_DIR, 'pmd/reports/sample.xml') 37 | } 38 | 39 | public final static class Findbugs { 40 | public static final File SOURCES_WITH_HIGH_VIOLATION = new File(SOURCES_DIR, 'findbugs/high') 41 | public static final File SOURCES_WITH_MEDIUM_VIOLATION = new File(SOURCES_DIR, 'findbugs/medium') 42 | public static final File SOURCES_WITH_LOW_VIOLATION = new File(SOURCES_DIR, 'findbugs/low') 43 | public static final File SAMPLE_REPORT = new File(REPORTS_DIR, 'findbugs/reports/sample.xml') 44 | } 45 | 46 | final static class Detekt { 47 | public static final File SOURCES_WITH_WARNINGS = new File(SOURCES_DIR, 'detekt/warnings') 48 | public static final File SOURCES_WITH_ERRORS = new File(SOURCES_DIR, 'detekt/errors') 49 | public static final File RULES = new File(RULES_DIR, 'detekt/detekt.yml') 50 | } 51 | 52 | final static class Ktlint { 53 | static final File SOURCES_WITH_ERROR = new File(SOURCES_DIR, 'ktlint/with-error') 54 | static final File SOURCES_NO_ERROR = new File(SOURCES_DIR, 'ktlint/no-error') 55 | } 56 | 57 | final static class Lint { 58 | public static final File SOURCES_WITH_WARNINGS = new File(SOURCES_DIR, 'lint/warnings') 59 | public static final File SOURCES_WITH_ERRORS = new File(SOURCES_DIR, 'lint/errors') 60 | public static final File SAMPLE_REPORT = new File(REPORTS_DIR, 'lint/lint-results.xml') 61 | public static final File RULES = new File(RULES_DIR, 'lint/lint.xml') 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/LogsSubject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import com.google.common.truth.FailureStrategy 4 | import com.google.common.truth.StringSubject 5 | import com.google.common.truth.Subject 6 | import com.google.common.truth.SubjectFactory 7 | import com.google.common.truth.Truth 8 | import com.google.common.truth.TruthJUnit 9 | 10 | import javax.annotation.Nullable 11 | 12 | import static com.gradleup.test.TestProject.Result.Logs 13 | 14 | class LogsSubject extends Subject { 15 | 16 | private static final String VIOLATIONS_LIMIT_EXCEEDED = 'Violations limit exceeded' 17 | private static final String CHECKSTYLE_VIOLATIONS_FOUND = 'Checkstyle violations found' 18 | private static final String PMD_VIOLATIONS_FOUND = 'PMD violations found' 19 | private static final String SPOTBUGS_VIOLATIONS_FOUND = 'SpotBugs violations found' 20 | private static final String DETEKT_VIOLATIONS_FOUND = 'Detekt violations found' 21 | private static final String KTLINT_VIOLATIONS_FOUND = 'ktlint violations found' 22 | private static final String LINT_VIOLATIONS_FOUND = 'Lint violations found' 23 | 24 | private static final SubjectFactory FACTORY = new SubjectFactory() { 25 | @Override 26 | LogsSubject getSubject(FailureStrategy failureStrategy, Logs logs) { 27 | new LogsSubject(failureStrategy, logs) 28 | } 29 | } 30 | 31 | public static LogsSubject assertThat(Logs logs) { 32 | Truth.assertAbout(FACTORY).that(logs) 33 | } 34 | 35 | public static LogsSubject assumeThat(Logs logs) { 36 | TruthJUnit.assume().about(FACTORY).that(logs) 37 | } 38 | 39 | LogsSubject(FailureStrategy failureStrategy, @Nullable Logs actual) { 40 | super(failureStrategy, actual) 41 | } 42 | 43 | private StringSubject getOutputSubject() { 44 | check().that(actual().output) 45 | } 46 | 47 | public void contains(log) { 48 | outputSubject.contains(log) 49 | } 50 | 51 | public void doesNotContainLimitExceeded() { 52 | outputSubject.doesNotContain(VIOLATIONS_LIMIT_EXCEEDED) 53 | } 54 | 55 | public void containsLimitExceeded(int errors, int warnings) { 56 | outputSubject.contains("$VIOLATIONS_LIMIT_EXCEEDED by $errors errors, $warnings warnings.") 57 | } 58 | 59 | public void doesNotContainCheckstyleViolations() { 60 | outputSubject.doesNotContain(CHECKSTYLE_VIOLATIONS_FOUND) 61 | } 62 | 63 | public void doesNotContainPmdViolations() { 64 | outputSubject.doesNotContain(PMD_VIOLATIONS_FOUND) 65 | } 66 | 67 | public void doesNotContainSpotBugsViolations() { 68 | outputSubject.doesNotContain(SPOTBUGS_VIOLATIONS_FOUND) 69 | } 70 | 71 | public void doesNotContainDetektViolations() { 72 | outputSubject.doesNotContain(DETEKT_VIOLATIONS_FOUND) 73 | } 74 | 75 | public void doesNotContainKtlintViolations() { 76 | outputSubject.doesNotContain(KTLINT_VIOLATIONS_FOUND) 77 | } 78 | 79 | public void doesNotContainLintViolations() { 80 | outputSubject.doesNotContain(LINT_VIOLATIONS_FOUND) 81 | } 82 | 83 | public void containsCheckstyleViolations(int errors, int warnings, String... reportUrls) { 84 | containsToolViolations(CHECKSTYLE_VIOLATIONS_FOUND, errors, warnings, reportUrls) 85 | } 86 | 87 | public void containsPmdViolations(int errors, int warnings, String... reportUrls) { 88 | containsToolViolations(PMD_VIOLATIONS_FOUND, errors, warnings, reportUrls) 89 | } 90 | 91 | public void containsSpotBugsViolations(int errors, int warnings, String... reportUrls) { 92 | containsToolViolations(SPOTBUGS_VIOLATIONS_FOUND, errors, warnings, reportUrls) 93 | } 94 | 95 | public void containsDetektViolations(int errors, int warnings, String... reportUrls) { 96 | containsToolViolations(DETEKT_VIOLATIONS_FOUND, errors, warnings, reportUrls) 97 | } 98 | 99 | public void containsKtlintViolations(int errors, String... reportUrls) { 100 | containsToolViolations(KTLINT_VIOLATIONS_FOUND, errors, 0, reportUrls) 101 | } 102 | 103 | public void containsLintViolations(int errors, int warnings, String... reportUrls) { 104 | containsToolViolations(LINT_VIOLATIONS_FOUND, errors, warnings, reportUrls) 105 | } 106 | 107 | private void containsToolViolations(String template, int errors, int warnings, String... reportUrls) { 108 | outputSubject.contains("$template ($errors errors, $warnings warnings). See the reports at:\n") 109 | for (String reportUrl : reportUrls) { 110 | outputSubject.contains(reportUrl) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/PenaltyExtensionSubject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import com.google.common.truth.FailureStrategy 4 | import com.google.common.truth.Subject 5 | import com.google.common.truth.SubjectFactory 6 | import com.gradleup.staticanalysis.PenaltyExtension 7 | 8 | import javax.annotation.Nullable 9 | 10 | import static com.google.common.truth.Truth.assertAbout 11 | import static com.google.common.truth.TruthJUnit.assume 12 | 13 | class PenaltyExtensionSubject extends Subject { 14 | 15 | private static final SubjectFactory FACTORY = newFactory() 16 | private static SubjectFactory newFactory() { 17 | new SubjectFactory() { 18 | @Override 19 | PenaltyExtensionSubject getSubject(FailureStrategy failureStrategy, PenaltyExtension extension) { 20 | new PenaltyExtensionSubject(failureStrategy, extension) 21 | } 22 | } 23 | } 24 | 25 | public static PenaltyExtensionSubject assertThat(PenaltyExtension extension) { 26 | assertAbout(FACTORY).that(extension) 27 | } 28 | 29 | 30 | public static PenaltyExtensionSubject assumeThat(PenaltyExtension extension) { 31 | assume().about(FACTORY).that(extension) 32 | } 33 | 34 | private PenaltyExtensionSubject(FailureStrategy failureStrategy, @Nullable PenaltyExtension actual) { 35 | super(failureStrategy, actual) 36 | } 37 | 38 | public void hasMaxWarnings(int maxWarnings) { 39 | check().that(actual().maxWarnings).isEqualTo(maxWarnings) 40 | } 41 | 42 | public void hasMaxErrors(int maxErrors) { 43 | check().that(actual().maxErrors).isEqualTo(maxErrors) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestAndroidKotlinProject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | class TestAndroidKotlinProject extends TestProject { 4 | private static final Closure TEMPLATE = { TestAndroidKotlinProject project -> 5 | """ 6 | buildscript { 7 | repositories { 8 | google() 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.2.1' 13 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21' 14 | } 15 | } 16 | plugins { 17 | ${formatPlugins(project)} 18 | id 'com.gradleup.static-analysis' 19 | } 20 | repositories { 21 | google() 22 | jcenter() 23 | } 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | apply plugin: 'kotlin-kapt' // adding kapt since we face compat issues before 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | defaultConfig { 32 | minSdkVersion 16 33 | targetSdkVersion 29 34 | versionCode 1 35 | versionName '1.0' 36 | } 37 | lintOptions { 38 | disable 'OldTargetApi' 39 | } 40 | sourceSets { 41 | ${formatSourceSets(project)} 42 | } 43 | ${project.additionalAndroidConfig} 44 | } 45 | 46 | dependencies { 47 | implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' 48 | } 49 | 50 | ${formatExtension(project)} 51 | """ 52 | } 53 | 54 | private String additionalAndroidConfig = '' 55 | 56 | TestAndroidKotlinProject() { 57 | super(TEMPLATE) 58 | File localProperties = Fixtures.LOCAL_PROPERTIES 59 | if (localProperties.exists()) { 60 | withFile(localProperties, 'local.properties') 61 | } 62 | } 63 | 64 | private static String formatSourceSets(TestProject project) { 65 | project.sourceSets 66 | .entrySet() 67 | .collect { Map.Entry> entry -> 68 | """$entry.key { 69 | manifest.srcFile '${Fixtures.ANDROID_MANIFEST}' 70 | java { 71 | ${entry.value.collect { "srcDir '$it'" }.join('\n\t\t\t\t')} 72 | } 73 | }""" 74 | } 75 | .join('\n\t\t') 76 | } 77 | 78 | @Override 79 | List defaultArguments() { 80 | ['-x', 'lint'] + super.defaultArguments() 81 | } 82 | 83 | TestAndroidKotlinProject withAdditionalAndroidConfig(String additionalAndroidConfig) { 84 | this.additionalAndroidConfig = additionalAndroidConfig 85 | return this 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestAndroidProject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | class TestAndroidProject extends TestProject { 4 | private static final Closure TEMPLATE = { TestAndroidProject project -> 5 | """ 6 | buildscript { 7 | repositories { 8 | google() 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.2.1' 13 | } 14 | } 15 | plugins { 16 | ${formatPlugins(project)} 17 | id 'com.gradleup.static-analysis' 18 | } 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | apply plugin: 'com.android.library' 24 | android { 25 | compileSdkVersion 29 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | targetSdkVersion 29 30 | versionCode 1 31 | versionName '1.0' 32 | } 33 | lintOptions { 34 | disable 'OldTargetApi' 35 | } 36 | sourceSets { 37 | ${formatSourceSets(project)} 38 | } 39 | ${project.additionalAndroidConfig} 40 | } 41 | ${formatExtension(project)} 42 | """ 43 | } 44 | 45 | private final boolean shouldDisableAndroidLint 46 | 47 | private String additionalAndroidConfig = '' 48 | 49 | TestAndroidProject(boolean shouldDisableAndroidLint) { 50 | super(TEMPLATE) 51 | File localProperties = Fixtures.LOCAL_PROPERTIES 52 | if (localProperties.exists()) { 53 | withFile(localProperties, 'local.properties') 54 | } 55 | this.shouldDisableAndroidLint = shouldDisableAndroidLint 56 | } 57 | 58 | private static String formatSourceSets(TestProject project) { 59 | project.sourceSets 60 | .entrySet() 61 | .collect { Map.Entry> entry -> 62 | """$entry.key { 63 | manifest.srcFile '${Fixtures.ANDROID_MANIFEST}' 64 | java { 65 | ${entry.value.collect { "srcDir '$it'" }.join('\n\t\t\t\t')} 66 | } 67 | }""" 68 | } 69 | .join('\n\t\t') 70 | } 71 | 72 | @Override 73 | List defaultArguments() { 74 | defaultAndroidArguments() + super.defaultArguments() 75 | } 76 | 77 | private List defaultAndroidArguments() { 78 | shouldDisableAndroidLint ? ['-x', 'lint'] : [] 79 | } 80 | 81 | TestAndroidProject withAdditionalAndroidConfig(String additionalAndroidConfig) { 82 | this.additionalAndroidConfig = additionalAndroidConfig 83 | return this 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestJavaProject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | final class TestJavaProject extends TestProject { 4 | 5 | private static final Closure TEMPLATE = { TestProject project -> 6 | """ 7 | plugins { 8 | ${formatPlugins(project)} 9 | id 'com.gradleup.static-analysis' 10 | } 11 | repositories { 12 | jcenter() 13 | } 14 | apply plugin: 'java' 15 | 16 | sourceSets { 17 | ${formatSourceSets(project)} 18 | } 19 | ${formatExtension(project)} 20 | """ 21 | } 22 | 23 | TestJavaProject() { 24 | super(TEMPLATE) 25 | } 26 | 27 | private static String formatSourceSets(TestProject project) { 28 | project.sourceSets 29 | .entrySet() 30 | .collect { Map.Entry> entry -> 31 | """$entry.key { 32 | java { 33 | ${entry.value.collect { "srcDir '$it'" }.join('\n\t\t\t\t')} 34 | } 35 | }""" 36 | } 37 | .join('\n\t') 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestKotlinProject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | final class TestKotlinProject extends TestProject { 4 | 5 | private static final Closure TEMPLATE = { TestProject project -> 6 | """ 7 | buildscript { 8 | repositories { 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21' 13 | } 14 | } 15 | 16 | plugins { 17 | ${formatPlugins(project)} 18 | id 'com.gradleup.static-analysis' 19 | } 20 | 21 | apply plugin: 'kotlin' 22 | apply plugin: 'kotlin-kapt' // adding kapt since we face compat issues before 23 | 24 | repositories { 25 | jcenter() 26 | } 27 | 28 | dependencies { 29 | implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' 30 | } 31 | 32 | sourceSets { 33 | ${formatSourceSets(project)} 34 | } 35 | ${formatExtension(project)} 36 | """ 37 | } 38 | 39 | TestKotlinProject() { 40 | super(TEMPLATE) 41 | } 42 | 43 | private static String formatSourceSets(TestProject project) { 44 | project.sourceSets 45 | .entrySet() 46 | .collect { Map.Entry> entry -> 47 | """$entry.key { 48 | kotlin { 49 | ${entry.value.collect { "srcDir '$it'" }.join('\n\t\t\t\t')} 50 | } 51 | }""" 52 | } 53 | .join('\n\t') 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestProjectRule.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import org.junit.rules.TestRule 4 | import org.junit.runner.Description 5 | import org.junit.runners.model.Statement 6 | 7 | final class TestProjectRule implements TestRule { 8 | 9 | private final Closure projectFactory 10 | private final Closure sourceSetNameFactory 11 | private final String label 12 | private T project 13 | 14 | static TestProjectRule forJavaProject() { 15 | new TestProjectRule({ new TestJavaProject() }, { String name -> "project.sourceSets.$name" }, 'Java project') 16 | } 17 | 18 | static TestProjectRule forAndroidProject() { 19 | new TestProjectRule({ new TestAndroidProject(true) }, { String name -> "project.android.sourceSets.$name" }, 'Android project') 20 | } 21 | 22 | static TestProjectRule forAndroidLintProject() { 23 | new TestProjectRule( 24 | { new TestAndroidProject(false) }, 25 | { String name -> "project.android.sourceSets.$name" }, 26 | 'Android Lint project' 27 | ) 28 | } 29 | 30 | static TestProjectRule forAndroidKotlinProject() { 31 | new TestProjectRule({ new TestAndroidKotlinProject() }, { String name -> "project.android.sourceSets.$name" }, 'Android kotlin project') 32 | } 33 | 34 | static TestProjectRule forKotlinProject() { 35 | new TestProjectRule({ new TestKotlinProject() }, { String name -> "project.sourceSets.$name" }, 'Kotlin project') 36 | } 37 | 38 | TestProjectRule(Closure projectFactory, Closure sourceSetNameFactory, String label) { 39 | this.projectFactory = projectFactory 40 | this.sourceSetNameFactory = sourceSetNameFactory 41 | this.label = label 42 | } 43 | 44 | public T newProject() { 45 | project = projectFactory.call() 46 | return project 47 | } 48 | 49 | public String printSourceSet(String name) { 50 | sourceSetNameFactory.call(name) 51 | } 52 | 53 | @Override 54 | Statement apply(Statement base, Description description) { 55 | return new Statement() { 56 | @Override 57 | void evaluate() throws Throwable { 58 | base.evaluate() 59 | project?.deleteDir() 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | String toString() { 66 | label 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/com/gradleup/test/TestProjectSubject.groovy: -------------------------------------------------------------------------------- 1 | package com.gradleup.test 2 | 3 | import com.google.common.truth.* 4 | 5 | import javax.annotation.Nullable 6 | 7 | class TestProjectSubject extends Subject { 8 | 9 | private static final SubjectFactory FACTORY = newFactory() 10 | private static SubjectFactory newFactory() { 11 | new SubjectFactory() { 12 | @Override 13 | TestProjectSubject getSubject(FailureStrategy failureStrategy, TestProject testProject) { 14 | return new TestProjectSubject(failureStrategy, testProject) 15 | } 16 | } 17 | } 18 | 19 | private TestProjectSubject(FailureStrategy failureStrategy, @Nullable TestProject testProject) { 20 | super(failureStrategy, testProject) 21 | } 22 | 23 | public static TestProjectSubject assumeThat(TestProject testProject) { 24 | TruthJUnit.assume().about(FACTORY).that(testProject) 25 | } 26 | 27 | public void isAndroidProject() { 28 | check().that(actual()).isInstanceOf(TestAndroidProject) 29 | } 30 | 31 | public void isJavaProject() { 32 | check().that(actual()).isInstanceOf(TestJavaProject) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample-multi-module/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | applicationId 'com.gradleup.staticanalysis.sample' 10 | minSdkVersion 16 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName '1.0' 14 | } 15 | 16 | buildTypes { 17 | debug { 18 | } 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt') 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation project(path: ':core') 28 | implementation 'com.android.support:appcompat-v7:27.1.0' 29 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 30 | testImplementation 'junit:junit:4.12' 31 | } 32 | 33 | apply from: rootProject.file('team-props/static-analysis.gradle') 34 | -------------------------------------------------------------------------------- /sample-multi-module/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample-multi-module/app/src/main/java/com/gradleup/staticanalysisplugin/sample/MyActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysisplugin.sample 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.widget.Toast 7 | import kotlinx.android.synthetic.main.activity_my.* 8 | 9 | class MyActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_my) 14 | 15 | val another: AnotherCoreClass 16 | 17 | button.setOnClickListener({ 18 | LOOK_ANOTHER_CRAZY_LONG_LONG_LONG_METHOD_NAME_FOR_NO_APPARENT_REASON_WHY_IS_THIS_METHOD_NAMED_LIKE_THIS_WHY_IT_LITERALLY_MAKES_NO_SENSE_WHATSOEVER(1) 19 | startActivity(Intent(this, SomeOtherActivity::class.java)) 20 | }) 21 | } 22 | 23 | private fun LOOK_ANOTHER_CRAZY_LONG_LONG_LONG_METHOD_NAME_FOR_NO_APPARENT_REASON_WHY_IS_THIS_METHOD_NAMED_LIKE_THIS_WHY_IT_LITERALLY_MAKES_NO_SENSE_WHATSOEVER(duration: Int) = Toast.makeText(this, "some useless message", duration).show() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /sample-multi-module/app/src/main/java/com/gradleup/staticanalysisplugin/sample/MyClass.java: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysisplugin.sample; 2 | 3 | public class MyClass { 4 | } 5 | -------------------------------------------------------------------------------- /sample-multi-module/app/src/main/java/com/gradleup/staticanalysisplugin/sample/SomeOtherActivity.java: -------------------------------------------------------------------------------- 1 | package com.gradleup.staticanalysisplugin.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.widget.Toast; 8 | 9 | public class SomeOtherActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(@Nullable Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_some_other); 15 | 16 | THIS_IS_A_VERY_VERY_VERY_LONG_NAME_FOR_A_METHOD_IT_IS_IN_FACT_VERY_LONG_INDEED_NO_NEED_TO_COUNT_THE_NUMBER_OF_CHARACTERS_YOU_CAN_CLEARLY_SEE_THIS_IS_WAY_LONGER_THAN_IT_SHOULD(0); 17 | 18 | String extra = getIntent().getStringExtra("nope"); 19 | if (extra != null) { 20 | Log.d(SomeOtherActivity.class.getSimpleName(), extra); 21 | } 22 | int boom = extra.length(); 23 | } 24 | 25 | private void THIS_IS_A_VERY_VERY_VERY_LONG_NAME_FOR_A_METHOD_IT_IS_IN_FACT_VERY_LONG_INDEED_NO_NEED_TO_COUNT_THE_NUMBER_OF_CHARACTERS_YOU_CAN_CLEARLY_SEE_THIS_IS_WAY_LONGER_THAN_IT_SHOULD(int duration) { 26 | Toast.makeText(this, "i have no idea what to write here", duration).show(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample-multi-module/app/src/main/res/layout/activity_my.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |