├── .editorconfig ├── .gitattributes ├── .github ├── actions │ └── base-release │ │ └── action.yml ├── commands │ └── prepare_release.yml ├── dependabot.yml └── workflows │ ├── build-and-check.yml │ ├── changelog.yml │ ├── codeql.yml │ ├── gradle-wrapper-validation.yml │ ├── new-release.yml │ ├── scorecard.yml │ ├── submit-dependency-graph.yml │ └── trigger-release.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── .editorconfig ├── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── adapter │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ ├── reporter │ │ ├── CustomReporter.kt │ │ ├── GenericReporter.kt │ │ ├── GenericReporterProvider.kt │ │ ├── LoadedReporter.kt │ │ ├── ReporterProviderWrapper.kt │ │ ├── ReporterType.kt │ │ └── ReportersLoaderAdapter.kt │ │ └── worker │ │ ├── BaselineLoader.kt │ │ ├── KtLintInvocation.kt │ │ ├── KtLintInvocationFactory.kt │ │ ├── LintErrorResult.kt │ │ └── SerializableLintError.kt │ ├── adapter100 │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ ├── reporter │ │ ├── ReporterProviderV2ReporterProvider.kt │ │ ├── ReporterV2Reporter.kt │ │ └── ReportersProviderV2Loader.kt │ │ └── worker │ │ ├── BaselineLoader49.kt │ │ ├── CliKtLintErrorExt.kt │ │ └── KtLintInvocation100.kt │ ├── main │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ ├── Configurations.kt │ │ ├── GitHook.kt │ │ ├── KtLintCompatibility.kt │ │ ├── KtlintBasePlugin.kt │ │ ├── KtlintExtension.kt │ │ ├── KtlintPlugin.kt │ │ ├── PluginUtil.kt │ │ ├── TaskCreation.kt │ │ ├── android │ │ └── AndroidPluginsApplier.kt │ │ ├── tasks │ │ ├── BaseKtLintCheckTask.kt │ │ ├── GenerateBaselineTask.kt │ │ ├── GenerateReportsTask.kt │ │ ├── KtLintCheckTask.kt │ │ ├── KtLintFormatTask.kt │ │ └── LoadReportersTask.kt │ │ └── worker │ │ ├── ConsoleReportWorkAction.kt │ │ ├── GenerateBaselineWorkAction.kt │ │ ├── GenerateReportsWorkAction.kt │ │ ├── KtLintClassesSerializer.kt │ │ ├── KtLintWorkAction.kt │ │ └── LoadReportersWorkAction.kt │ └── test │ ├── java │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── testdsl │ │ └── CommonTest.java │ └── kotlin │ └── org │ ├── assertj │ └── core │ │ └── api │ │ └── Assertions.kt │ └── jlleitschuh │ └── gradle │ └── ktlint │ ├── AbstractPluginTest.kt │ ├── BuildCacheTest.kt │ ├── ConfigurationCacheTest.kt │ ├── DisabledRulesTest.kt │ ├── EditorConfigTests.kt │ ├── GitHookTasksTest.kt │ ├── KotlinJsPluginTests.kt │ ├── KotlinMultiplatformPluginTests.kt │ ├── KtLintClassesUsageScopeTest.kt │ ├── KtLintSupportedVersionsTest.kt │ ├── KtlintBaselineSupportTest.kt │ ├── KtlintPluginTest.kt │ ├── KtlintPluginVersionTest.kt │ ├── PluginUtilTest.kt │ ├── ReportersTest.kt │ ├── TaskConfigurationAvoidanceTest.kt │ ├── TestsCommon.kt │ ├── UnsupportedGradleTest.kt │ ├── android │ └── KtlintPluginAndroidTest.kt │ ├── testdsl │ ├── AndroidSetup.kt │ ├── JavaUtil.kt │ ├── TestAnnotations.kt │ └── TestDsl.kt │ └── worker │ └── KtLintClassesSerializerKtTest.kt ├── samples ├── android-app │ ├── build.gradle.kts │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── android │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── org │ │ │ │ └── jlleitschuh │ │ │ │ └── gradle │ │ │ │ └── ktlint │ │ │ │ └── android │ │ │ │ ├── ItemDetailActivity.kt │ │ │ │ ├── ItemDetailFragment.kt │ │ │ │ ├── ItemListActivity.kt │ │ │ │ └── dummy │ │ │ │ └── DummyContent.kt │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── jlleitschuh │ │ │ │ └── gradle │ │ │ │ └── ktlint │ │ │ │ └── android │ │ │ │ └── AlternativeSample.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ ├── activity_item_detail.xml │ │ │ ├── activity_item_list.xml │ │ │ ├── item_detail.xml │ │ │ ├── item_list.xml │ │ │ └── item_list_content.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── android │ │ └── ExampleUnitTest.kt ├── kotlin-gradle │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── gradle │ │ └── Main.kt ├── kotlin-js │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── js │ │ └── Main.kt ├── kotlin-ks │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── kotlin │ │ ├── Main.kt │ │ └── style-violations.kt ├── kotlin-mpp-android │ ├── build.gradle.kts │ └── src │ │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ └── mppandroid │ │ │ └── AndroidLocationProvider.kt │ │ └── commonMain │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── mppandroid │ │ └── LocationProvider.kt ├── kotlin-mpp │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ └── mpp │ │ │ └── CommonInterface.kt │ │ ├── jsMain │ │ └── kotlin │ │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ └── mpp │ │ │ └── JsCommon.kt │ │ ├── jvmMain │ │ └── kotlin │ │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ └── mpp │ │ │ ├── JvmCommon.kt │ │ │ └── Main.kt │ │ └── linuxX64Main │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── mpp │ │ └── LinuxCommon.kt ├── kotlin-reporter-creating │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ ├── CsvReporter.kt │ │ │ └── CsvReporterProvider.kt │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.pinterest.ktlint.cli.reporter.core.api.ReporterProviderV2 ├── kotlin-reporter-using │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── jlleitschuh │ │ └── gradle │ │ └── ktlint │ │ └── sample │ │ └── kotlin │ │ └── Main.kt ├── kotlin-rulesets-creating │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── org │ │ │ └── jlleitschuh │ │ │ └── gradle │ │ │ └── ktlint │ │ │ └── sample │ │ │ └── kotlin │ │ │ ├── CustomRuleSetProvider.kt │ │ │ └── NoVarRule.kt │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 └── kotlin-rulesets-using │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── org │ └── jlleitschuh │ └── gradle │ └── ktlint │ └── sample │ └── kotlin │ └── Main.kt └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.{kt,kts}] 9 | indent_size = 4 10 | ij_kotlin_packages_to_use_import_on_demand = unset 11 | ij_kotlin_name_count_to_use_star_import = 999 12 | ij_kotlin_name_count_to_use_star_import_for_members = 999 13 | ktlint_code_style=intellij_idea 14 | ktlint_standard_function-expression-body = disabled 15 | ktlint_standard_function-signature = disabled 16 | ktlint_standard_class-signature = disabled 17 | 18 | [*.{yml,yaml}] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | # 4 | # The above will handle all files NOT found below 5 | # https://help.github.com/articles/dealing-with-line-endings/ 6 | # https://github.com/Danimoth/gitattributes 7 | 8 | # These are explicitly windows files and should use crlf 9 | *.bat text eol=crlf 10 | 11 | # These files are text and should be normalized (Convert crlf => lf) 12 | *.bash text eol=lf 13 | *.css text diff=css 14 | *.htm text diff=html 15 | *.html text diff=html 16 | *.java text diff=java 17 | *.sh text eol=lf 18 | 19 | 20 | # These files are binary and should be left untouched 21 | # (binary is a macro for -text -diff) 22 | *.a binary 23 | *.lib binary 24 | *.icns binary 25 | *.png binary 26 | *.jpg binary 27 | *.jpeg binary 28 | *.gif binary 29 | *.ico binary 30 | *.mov binary 31 | *.mp4 binary 32 | *.mp3 binary 33 | *.flv binary 34 | *.fla binary 35 | *.swf binary 36 | *.gz binary 37 | *.zip binary 38 | *.jar binary 39 | *.tar binary 40 | *.tar.gz binary 41 | *.7z binary 42 | *.ttf binary 43 | *.pyc binary 44 | *.gpg binary 45 | *.bin binary 46 | -------------------------------------------------------------------------------- /.github/actions/base-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Base Release Setup' 2 | description: 'Setup environment for Release' 3 | # This action is used to set up the environment for a release. 4 | # It is a reusable action because it allows us to test the release process in the 5 | # `build-and-check.yml` workflow. 6 | 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - name: Fetch tags 11 | shell: bash 12 | # Workaround for https://github.com/actions/checkout/issues/1471 13 | # See: https://github.com/actions/checkout/issues/1471#issuecomment-1755560284 14 | run: git fetch --prune --unshallow --tags --force 15 | - name: Set up JDK 8 16 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 17 | with: 18 | java-version: 8 19 | distribution: 'zulu' 20 | - name: Setup Gradle 21 | uses: gradle/actions/setup-gradle@v4 22 | with: 23 | # Disable cache so that release is more hermetic 24 | cache-disabled: true 25 | -------------------------------------------------------------------------------- /.github/commands/prepare_release.yml: -------------------------------------------------------------------------------- 1 | # Learn more about the syntax here: 2 | # https://docs.github.com/en/early-access/github/save-time-with-slash-commands/syntax-for-user-defined-slash-commands 3 | --- 4 | trigger: release_prepare # <-- 👋 pick a trigger 5 | title: Prepare for release # <-- 👋 pick a title 6 | description: Prepares the repository for a release 7 | 8 | # Each command has one or more steps. You can add up to 25 steps. 9 | steps: 10 | - type: repository_dispatch 11 | event_type: release_prepare 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: "/" # Location of package manifests 10 | labels: 11 | - "skip news" 12 | schedule: 13 | interval: "weekly" 14 | - package-ecosystem: gradle 15 | directory: "/plugin" # Plugin directory 16 | schedule: 17 | interval: "weekly" 18 | - package-ecosystem: gradle 19 | directory: "/" # Location of package manifests 20 | labels: 21 | - "skip news" # We can skip news for all updates to the samples 22 | schedule: 23 | interval: "weekly" 24 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: changelog 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, labeled, unlabeled, reopened] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | build: 12 | name: Changelog Entry Check 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Harden the runner (Audit all outbound calls) 18 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 19 | with: 20 | egress-policy: audit 21 | 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | 24 | - name: Grep CHANGES.md for PR number 25 | if: contains(github.event.pull_request.labels.*.name, 'skip news') != true 26 | run: | 27 | grep -Pz "\[(\n\s*)?#${{ github.event.pull_request.number }}(\n\s*)?\]\((\n\s*)?https://github\.com/JLLeitschuh/ktlint-gradle/pull/${{ github.event.pull_request.number }}(\n\s*)?\)" CHANGELOG.md || \ 28 | (echo "Please add '[#${{ github.event.pull_request.number }}](https://github.com/JLLeitschuh/ktlint-gradle/pull/${{ github.event.pull_request.number }})' change line to CHANGELOG.md" && \ 29 | exit 1) 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '36 4 * * 6' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | # required to fetch internal or private CodeQL packs 35 | packages: read 36 | 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | include: 41 | - language: actions 42 | build-mode: none 43 | - language: java-kotlin 44 | build-mode: manual 45 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 46 | # Use `c-cpp` to analyze code written in C, C++ or both 47 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 48 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 49 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 50 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 51 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 52 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 53 | steps: 54 | - name: Checkout repository 55 | uses: actions/checkout@v4 56 | 57 | # Add any setup steps before running the `github/codeql-action/init` action. 58 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 59 | # or others). This is typically only required for manual builds. 60 | # - name: Setup runtime (example) 61 | # uses: actions/setup-example@v1 62 | 63 | - name: Setup Gradle 64 | if: matrix.language == 'java-kotlin' 65 | uses: gradle/actions/setup-gradle@v4 66 | 67 | # Initializes the CodeQL tools for scanning. 68 | - name: Initialize CodeQL 69 | uses: github/codeql-action/init@v3 70 | with: 71 | languages: ${{ matrix.language }} 72 | build-mode: ${{ matrix.build-mode }} 73 | # If you wish to specify custom queries, you can do so here or in a config file. 74 | # By default, queries listed here will override any specified in a config file. 75 | # Prefix the list here with "+" to use these queries and those in the config file. 76 | 77 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 78 | # queries: security-extended,security-and-quality 79 | 80 | - name: Build 81 | # Now that an explicit java version has been set, use it 82 | if: matrix.language == 'java-kotlin' 83 | run: | 84 | ./gradlew -p plugin --no-daemon --no-build-cache --console=plain --no-configuration-cache -x test -x check 85 | 86 | - name: Perform CodeQL Analysis 87 | uses: github/codeql-action/analyze@v3 88 | with: 89 | category: "/language:${{matrix.language}}" 90 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: [push, pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | validation: 9 | name: "Validation" 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Harden the runner (Audit all outbound calls) 13 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 14 | with: 15 | egress-policy: audit 16 | 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | - uses: gradle/wrapper-validation-action@f9c9c575b8b21b6485636a91ffecd10e558c62f6 # v3.5.0 19 | with: 20 | min-wrapper-count: 2 21 | -------------------------------------------------------------------------------- /.github/workflows/new-release.yml: -------------------------------------------------------------------------------- 1 | name: "New plugin release" 2 | # This workflow is triggered by a tag push to the repository. 3 | # This workflow should be triggered by the `trigger-release.yaml` workflow 4 | on: 5 | push: 6 | tags: 7 | - v[0-9]+.[0-9]+.[0-9]+ 8 | - v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+ 9 | 10 | jobs: 11 | make-new-release: 12 | name: "Releasing plugin" 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Harden the runner (Audit all outbound calls) 16 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 17 | with: 18 | egress-policy: audit 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | - name: Base Release Environment Setup 21 | uses: ./.github/actions/base-release 22 | - name: Publish Plugin Candidate 23 | if: contains(github.ref, '-rc.') 24 | env: 25 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 26 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} 27 | GITHUB_KEY: ${{ secrets.GITHUB_TOKEN }} 28 | run: | 29 | ./plugin/gradlew \ 30 | -p ./plugin \ 31 | -Preleasing \ 32 | -Prelease.disableGitChecks=true \ 33 | -Prelease.useLastTag=true \ 34 | candidate \ 35 | publishPlugins \ 36 | githubRelease 37 | 38 | - name: Publish Plugin Release 39 | if: (!contains(github.ref, '-rc.')) 40 | env: 41 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 42 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} 43 | GITHUB_KEY: ${{ secrets.GITHUB_TOKEN }} 44 | run: | 45 | ./plugin/gradlew \ 46 | -p ./plugin \ 47 | -Preleasing \ 48 | -Prelease.disableGitChecks=true \ 49 | -Prelease.useLastTag=true \ 50 | final \ 51 | publishPlugins \ 52 | githubRelease 53 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '40 1 * * 0' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: Harden the runner (Audit all outbound calls) 35 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 36 | with: 37 | egress-policy: audit 38 | 39 | - name: "Checkout code" 40 | uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb # v2.7.0 41 | with: 42 | persist-credentials: false 43 | 44 | - name: "Run analysis" 45 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 46 | with: 47 | results_file: results.sarif 48 | results_format: sarif 49 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 50 | # - you want to enable the Branch-Protection check on a *public* repository, or 51 | # - you are installing Scorecard on a *private* repository 52 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 53 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 54 | 55 | # Public repositories: 56 | # - Publish results to OpenSSF REST API for easy access by consumers 57 | # - Allows the repository to include the Scorecard badge. 58 | # - See https://github.com/ossf/scorecard-action#publishing-results. 59 | # For private repositories: 60 | # - `publish_results` will always be set to `false`, regardless 61 | # of the value entered here. 62 | publish_results: true 63 | 64 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 65 | # format to the repository Actions tab. 66 | - name: "Upload artifact" 67 | uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 # v3.1.0 68 | with: 69 | name: SARIF file 70 | path: results.sarif 71 | retention-days: 5 72 | 73 | # Upload the results to GitHub's code scanning dashboard. 74 | - name: "Upload to code-scanning" 75 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 76 | with: 77 | sarif_file: results.sarif 78 | -------------------------------------------------------------------------------- /.github/workflows/submit-dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Download and submit dependency graph 2 | 3 | on: 4 | workflow_run: 5 | workflows: ['Build and check'] 6 | types: [completed] 7 | 8 | permissions: 9 | actions: read 10 | contents: write 11 | 12 | jobs: 13 | submit-dependency-graph: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Harden the runner (Audit all outbound calls) 17 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 18 | with: 19 | egress-policy: audit 20 | - name: Download and submit dependency graph 21 | uses: gradle/actions/dependency-submission@v4 22 | with: 23 | dependency-graph: download-and-submit # Download saved dependency-graph and submit 24 | -------------------------------------------------------------------------------- /.github/workflows/trigger-release.yaml: -------------------------------------------------------------------------------- 1 | name: "Trigger Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | new_version: 6 | description: "Release Version (no 'v'). End with `-rc.#` for release candidate" 7 | required: true 8 | type: string 9 | 10 | jobs: 11 | perform-release: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Harden the runner (Audit all outbound calls) 15 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 16 | with: 17 | egress-policy: audit 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | with: 22 | # Required to trigger follow-on workflow runs 23 | # https://github.com/stefanzweifel/git-auto-commit-action#commits-made-by-this-action-do-not-trigger-new-workflow-runs 24 | token: ${{ secrets.PAT }} 25 | 26 | - name: Update Changelog 27 | uses: thomaseizinger/keep-a-changelog-new-release@f62c3c390716df5af712ba5d94f4f4a8efc1306d # 3.1.0 28 | with: 29 | tag: v${{ github.event.inputs.new_version }} 30 | version: ${{ github.event.inputs.new_version }} 31 | 32 | - name: Update README 33 | run: | 34 | new_version=$(echo "${{ github.event.inputs.new_version }}") 35 | stripped_version=$($new_version | sed -e 's/\.//g') 36 | formatted_date=$(date "+%Y%m%d") 37 | sed -i 's|\(Latest plugin version: \[\).*\(\](/CHANGELOG.md#\).*---.*)|\1'"$new_version"'\2'"$stripped_version"'---'"$formatted_date"')|' README.md 38 | 39 | - name: Upload Changed Files as Artifacts 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: changed-files 43 | path: | 44 | CHANGELOG.md 45 | README.md 46 | 47 | - name: Cleanup Changed Files for RC 48 | if: contains(github.event.inputs.new_version, '-rc.') 49 | run: | 50 | # Reset the README.md and CHANGELOG.md files to their original state 51 | git checkout HEAD -- README.md CHANGELOG.md 52 | 53 | - name: Perform Tagging and Push 54 | # This will trigger the new release job that will take the tag and push the release to the plugin portal 55 | uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0 56 | with: 57 | commit_options: --allow-empty 58 | commit_message: Release version ${{ github.event.inputs.new_version }} 59 | tagging_message: 'v${{ github.event.inputs.new_version }}' 60 | skip_dirty_check: ${{contains(github.event.inputs.new_version, '-rc.')}} 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/eclipse,gradle,intellij,vim,java 3 | 4 | ### Eclipse ### 5 | 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # Eclipse Core 19 | .project 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # PyDev specific (Python IDE for Eclipse) 28 | *.pydevproject 29 | 30 | # CDT-specific (C/C++ Development Tooling) 31 | .cproject 32 | 33 | # JDT-specific (Eclipse Java Development Tools) 34 | .classpath 35 | 36 | # Java annotation processor (APT) 37 | .factorypath 38 | 39 | # PDT-specific (PHP Development Tools) 40 | .buildpath 41 | 42 | # sbteclipse plugin 43 | .target 44 | 45 | # Tern plugin 46 | .tern-project 47 | 48 | # TeXlipse plugin 49 | .texlipse 50 | 51 | # STS (Spring Tool Suite) 52 | .springBeans 53 | 54 | # Code Recommenders 55 | .recommenders/ 56 | 57 | ### Intellij ### 58 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 59 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 60 | 61 | # User-specific stuff: 62 | .idea/**/workspace.xml 63 | .idea/**/tasks.xml 64 | 65 | # Sensitive or high-churn files: 66 | .idea/**/dataSources/ 67 | .idea/**/dataSources.ids 68 | .idea/**/dataSources.xml 69 | .idea/**/dataSources.local.xml 70 | .idea/**/sqlDataSources.xml 71 | .idea/**/dynamic.xml 72 | .idea/**/uiDesigner.xml 73 | 74 | # Gradle: 75 | .idea/**/gradle.xml 76 | .idea/**/libraries 77 | 78 | # Mongo Explorer plugin: 79 | .idea/**/mongoSettings.xml 80 | 81 | ## File-based project format: 82 | *.iws 83 | 84 | ## Plugin-specific files: 85 | 86 | # IntelliJ 87 | out/ 88 | 89 | # mpeltonen/sbt-idea plugin 90 | .idea_modules/ 91 | 92 | # JIRA plugin 93 | atlassian-ide-plugin.xml 94 | 95 | # Crashlytics plugin (for Android Studio and IntelliJ) 96 | com_crashlytics_export_strings.xml 97 | crashlytics.properties 98 | crashlytics-build.properties 99 | fabric.properties 100 | 101 | ### Intellij Patch ### 102 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 103 | .idea/ 104 | *.iml 105 | 106 | ### Java ### 107 | # Compiled class file 108 | *.class 109 | 110 | # Log file 111 | *.log 112 | 113 | # BlueJ files 114 | *.ctxt 115 | 116 | .kotlin/ 117 | 118 | # Mobile Tools for Java (J2ME) 119 | .mtj.tmp/ 120 | 121 | # Package Files # 122 | *.jar 123 | *.war 124 | *.ear 125 | *.zip 126 | *.tar.gz 127 | *.rar 128 | 129 | # virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml 130 | hs_err_pid* 131 | 132 | ### Vim ### 133 | # swap 134 | [._]*.s[a-v][a-z] 135 | [._]*.sw[a-p] 136 | [._]s[a-v][a-z] 137 | [._]sw[a-p] 138 | # session 139 | Session.vim 140 | # temporary 141 | .netrwhist 142 | *~ 143 | # auto-generated tag files 144 | tags 145 | 146 | ### Gradle ### 147 | .gradle 148 | build/ 149 | 150 | # Ignore Gradle GUI config 151 | gradle-app.setting 152 | 153 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 154 | !gradle-wrapper.jar 155 | 156 | # Cache of project 157 | .gradletasknamecache 158 | 159 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 160 | # gradle/wrapper/gradle-wrapper.properties 161 | 162 | # End of https://www.gitignore.io/api/eclipse,gradle,intellij,vim,java 163 | 164 | .gradle-test-kit 165 | .obsidian 166 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Common Tasks 4 | 5 | ### Updating Gradle 6 | 7 | 1. Find the latest version and latest checksums here: https://gradle.org/release-checksums/ 8 | 2. Update the `gradleWrapper` and `gradleWrapperSha` version in the [version file](plugin/gradle/libs.versions.toml). 9 | 3. In this directory run the following command twice: 10 | ```bash 11 | ./gradlew wrapper 12 | ``` 13 | This will update the `gradle/gradle-wrapper.properties` and `gradle/gradle-wrapper.jar` files. 14 | 4. Now run the following command twice: 15 | ```bash 16 | ./plugin/gradlew wrapper 17 | ``` 18 | This will update the `plugin/gradle/gradle-wrapper.properties` and `plugin/gradle/gradle-wrapper.jar` files. 19 | 20 | The reason this is required is that the `plugin` itself is an included build of the parent project which is actually a gradle build intended to execute and test the `samples`. 21 | 22 | ### Releasing 23 | 24 | Note: This section is only relevant for maintainers of the project. 25 | 26 | How to perform a release of the plugin. 27 | 28 | 1. Trigger a new release with the 29 | [Trigger Release](https://github.com/JLLeitschuh/ktlint-gradle/actions/workflows/prepare-release.yaml) 30 | Workflow. For non-rc tagged versions, this will update the `CHANGELOG.md` file automatically. 31 | 32 | 2. Verify that the 33 | [CHANGELOG.md](https://github.com/JLLeitschuh/ktlint-gradle/blob/main/CHANGELOG.md) 34 | file looks good. 35 | 36 | **NOTE**: If publishing a release candidate (rc) version, the `CHANGELOG.md` file will not be updated automatically. 37 | You will need to inspect the `CHANGELOG.md` and the `README.md` files that are uploaded as workflow artifacts. 38 | 39 | **NOTE**: The `## [Unreleased]` header should still remain, but should now be empty. 40 | This is expected by the GitHub release note upload step. 41 | 42 | 3. Await the completion of the 43 | [New Release](https://github.com/JLLeitschuh/ktlint-gradle/actions/workflows/prepare-release.yaml) 44 | Workflow. This will create a new release on GitHub and upload the release notes to the release. 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jonathan Leitschuh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | plugins { 9 | kotlin("jvm") apply false 10 | id("com.android.application") apply false 11 | kotlin("js") apply false 12 | id("org.jlleitschuh.gradle.ktlint") 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | tasks.withType(Wrapper::class.java).configureEach { 23 | gradleVersion = pluginLibs.versions.gradleWrapper.get() 24 | distributionSha256Sum = pluginLibs.versions.gradleWrapperSha.get() 25 | distributionType = Wrapper.DistributionType.BIN 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.configureondemand=false 2 | org.gradle.vfs.watch=true 3 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m 4 | org.gradle.parallel=true 5 | 6 | kotlin.mpp.stability.nowarn=true 7 | 8 | android.useAndroidX=true 9 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidXFragment = "1.8.6" 3 | androidXRecyclerView = "1.1.0" 4 | androidMaterial = "1.12.0" 5 | androidXTestRunner = "1.6.2" 6 | androidXEspresso = "3.3.0" 7 | junit = "4.13.2" 8 | 9 | [libraries] 10 | android-material = { module = "com.google.android.material:material", version.ref = "androidMaterial" } 11 | androidx-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "androidXEspresso" } 12 | androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidXFragment" } 13 | androidx-recycler-view = { module = "androidx.recyclerview:recyclerview", version.ref = "androidXRecyclerView" } 14 | androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidXTestRunner" } 15 | junit = { module = "junit:junit", version.ref = "junit" } 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /plugin/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.{kt,kts}] 9 | indent_size = 4 10 | ij_kotlin_packages_to_use_import_on_demand = unset 11 | ij_kotlin_name_count_to_use_star_import = 999 12 | ij_kotlin_name_count_to_use_star_import_for_members = 999 13 | ij_kotlin_allow_trailing_comma = false 14 | ij_kotlin_allow_trailing_comma_on_call_site = false 15 | ktlint_code_style=intellij_idea 16 | ktlint_standard_function-expression-body = disabled 17 | ktlint_standard_function-signature = disabled 18 | ktlint_standard_class-signature = disabled 19 | 20 | [*.{yml,yaml}] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.parallel=true 2 | org.gradle.configureondemand=false 3 | org.gradle.vfs.watch=true 4 | # Shadow plugin does not fully support configuration cache: https://github.com/johnrengelman/shadow/issues/687 5 | #org.gradle.unsafe.configuration-cache=true 6 | 7 | org.gradle.workers.max = 6 8 | # Used by the nebula release plugin to locate the root git directory 9 | git.root=.. 10 | -------------------------------------------------------------------------------- /plugin/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.5.31" 3 | ktlint = "1.0.0" 4 | androidPlugin = "4.1.0" 5 | semver = "1.1.1" 6 | jgit = "5.13.3.202401111512-r" 7 | sl4fj = "1.7.30" 8 | gradleWrapper = "8.13" 9 | gradleWrapperSha = "20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78" 10 | junit5 = "5.5.2" 11 | assertJ = "3.11.1" 12 | commonsIo = "2.17.0" 13 | archUnit = "1.4.0" 14 | 15 | [libraries] 16 | android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidPlugin" } 17 | archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archUnit" } 18 | assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertJ" } 19 | commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" } 20 | jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgit" } 21 | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } 22 | kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 23 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 24 | kotlin-script-runtime = { module = "org.jetbrains.kotlin:kotlin-script-runtime", version.ref = "kotlin" } 25 | ktlint-rule-engine = { module = "com.pinterest.ktlint:ktlint-rule-engine", version.ref = "ktlint" } 26 | semver = { module = "net.swiftzer.semver:semver", version.ref = "semver" } 27 | slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "sl4fj" } 28 | -------------------------------------------------------------------------------- /plugin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/plugin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plugin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /plugin/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | plugins { 3 | // We just always pull the latest published version of the plugin 4 | // Downside is that the build is now not 100% reproducible 5 | // Upside is that we don't need to update the version in the settings.gradle.kts 6 | // every release 7 | id("org.jlleitschuh.gradle.ktlint") version "latest.release" 8 | id("org.jetbrains.kotlin.jvm") version "2.1.20" 9 | id("com.gradle.plugin-publish") version "0.15.0" 10 | `java-gradle-plugin` 11 | id("com.github.johnrengelman.shadow") version "7.0.0" 12 | id("com.github.breadmoirai.github-release") version "2.5.2" 13 | id("com.netflix.nebula.release") version "20.2.0" 14 | } 15 | } 16 | 17 | plugins { 18 | id("com.gradle.develocity") version "3.17" 19 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0" 20 | } 21 | 22 | develocity { 23 | buildScan { 24 | termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") 25 | termsOfUseAgree.set("yes") 26 | } 27 | } 28 | 29 | rootProject.name = "ktlint-gradle" 30 | rootProject.buildFileName = "build.gradle.kts" 31 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/CustomReporter.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * @param name required for Groovy interop, same as [reporterId] 7 | * @param reporterId an id that reporter exposes for ktlint `ServiceLocator` 8 | * @param fileExtension generated report file extension 9 | * @param dependency reporter [dependency notation](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html#N17198). 10 | * 11 | * For example, [dependency] could have following notation: 12 | * ``` 13 | * "some.group:reporter:0.1.0" 14 | * project(":custom:reporter") 15 | * ``` 16 | */ 17 | data class CustomReporter( 18 | val name: String, 19 | val reporterId: String = name, 20 | var fileExtension: String = reporterId, 21 | @Transient var dependency: Any? = null 22 | ) : Serializable { 23 | companion object { 24 | private const val serialVersionUID: Long = 2012775L 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/GenericReporter.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import org.jlleitschuh.gradle.ktlint.worker.SerializableLintError 4 | 5 | /** 6 | * Abstraction over Reporter and ReporterV2 7 | */ 8 | interface GenericReporter { 9 | fun beforeAll() 10 | 11 | /** 12 | * Called when file (matching the pattern) is found but before it's parsed. 13 | */ 14 | fun before(file: String) 15 | fun onLintError(file: String, err: SerializableLintError, corrected: Boolean) 16 | 17 | /** 18 | * Called after ktlint is done with the file. 19 | */ 20 | fun after(file: String) 21 | 22 | /** 23 | * Called once, after all the files (if any) have been processed. 24 | * It's guarantied to be called after all other Reporter's methods. 25 | */ 26 | fun afterAll() 27 | } 28 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/GenericReporterProvider.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import java.io.PrintStream 4 | 5 | /** 6 | * Abstraction over ReporterProvider and ReporterProviderV2 7 | */ 8 | interface GenericReporterProvider> { 9 | val id: String 10 | fun get( 11 | outputStream: PrintStream, 12 | opt: Map 13 | ): T 14 | } 15 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/LoadedReporter.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import java.io.Serializable 4 | 5 | data class LoadedReporter( 6 | val reporterId: String, 7 | val fileExtension: String, 8 | val reporterOptions: Map 9 | ) : Serializable { 10 | companion object { 11 | private const val serialVersionUID: Long = 201201233L 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReporterProviderWrapper.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | class ReporterProviderWrapper(val id: String, val reporterProvider: T) 4 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReporterType.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import net.swiftzer.semver.SemVer 4 | import java.io.Serializable 5 | 6 | enum class ReporterType( 7 | val reporterName: String, 8 | val availableSinceVersion: SemVer, 9 | val fileExtension: String, 10 | val options: List 11 | ) : Serializable { 12 | PLAIN("plain", SemVer(0, 9, 0), "txt", emptyList()), 13 | PLAIN_GROUP_BY_FILE("plain", SemVer(0, 9, 0), "txt", listOf("group_by_file")), 14 | CHECKSTYLE("checkstyle", SemVer(0, 9, 0), "xml", emptyList()), 15 | JSON("json", SemVer(0, 9, 0), "json", emptyList()), 16 | SARIF("sarif", SemVer(0, 42, 0), "sarif", emptyList()), 17 | HTML("html", SemVer(0, 36, 0), "html", emptyList()); 18 | 19 | companion object { 20 | private const val serialVersionUID: Long = 201202199L 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReportersLoaderAdapter.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import java.io.File 4 | import java.io.Serializable 5 | 6 | /** 7 | * T is a ReporterProvider / ReporterProviderV2 8 | * GR is a generic wrapper for the reporter 9 | * GRP is the generic wrapper for the provider 10 | */ 11 | interface ReportersLoaderAdapter< 12 | R, 13 | RP : Serializable, 14 | GR : GenericReporter, 15 | GRP : GenericReporterProvider 16 | > { 17 | fun loadAllReporterProviders(): List> 18 | fun filterEnabledBuiltInProviders( 19 | enabledReporters: Set, 20 | allProviders: List> 21 | ): List> 22 | 23 | fun filterCustomProviders( 24 | customReporters: Set, 25 | allProviders: List 26 | ): List> 27 | 28 | fun allEnabledProviders( 29 | enabledReporters: Set, 30 | customReporters: Set 31 | ): List> { 32 | val all = loadAllReporterProviders() 33 | return filterEnabledBuiltInProviders(enabledReporters, all) 34 | .plus(filterCustomProviders(customReporters, all.map { it.reporterProvider })) 35 | } 36 | 37 | fun loadReporterProviders(serializedReporterProviders: File): List 38 | 39 | fun loadAllGenericReporterProviders(): List 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/worker/BaselineLoader.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | interface BaselineLoader { 4 | fun loadBaselineRules(path: String): Map> 5 | } 6 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintInvocation.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import java.io.File 4 | 5 | /** 6 | * An abstraction for invoking ktlint across all breaking changes between versions 7 | */ 8 | interface KtLintInvocation { 9 | fun invokeLint(file: File): LintErrorResult 10 | fun invokeFormat(file: File): Pair 11 | 12 | fun trimMemory() 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintInvocationFactory.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | interface KtLintInvocationFactory 4 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/worker/LintErrorResult.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import java.io.File 4 | import java.io.Serializable 5 | 6 | /** 7 | * Represents result of file code style check. 8 | * 9 | * @param lintedFile file that was checked by KtLint 10 | * @param lintErrors list of found errors and flag indicating if this error was corrected 11 | */ 12 | data class LintErrorResult( 13 | val lintedFile: File, 14 | val lintErrors: List> 15 | ) : Serializable { 16 | companion object { 17 | private const val serialVersionUID: Long = 2012012585L 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plugin/src/adapter/kotlin/org/jlleitschuh/gradle/ktlint/worker/SerializableLintError.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * Our Representation of a lint error to have consistent serialization and compatibility with multiple ktlint versions 7 | */ 8 | data class SerializableLintError( 9 | var line: Int = 0, 10 | var col: Int = 0, 11 | var ruleId: String = "", 12 | var detail: String = "", 13 | var canBeAutoCorrected: Boolean = false 14 | ) : Serializable { 15 | companion object { 16 | private const val serialVersionUID: Long = 20120922L 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReporterProviderV2ReporterProvider.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import com.pinterest.ktlint.cli.reporter.core.api.ReporterProviderV2 4 | import java.io.PrintStream 5 | 6 | class ReporterProviderV2ReporterProvider( 7 | val reporterProvider: ReporterProviderV2<*> 8 | ) : GenericReporterProvider { 9 | override fun get(outputStream: PrintStream, opt: Map): ReporterV2Reporter { 10 | return ReporterV2Reporter(reporterProvider.get(outputStream, opt)) 11 | } 12 | 13 | override val id: String 14 | get() = reporterProvider.id 15 | } 16 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReporterV2Reporter.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import com.pinterest.ktlint.cli.reporter.core.api.ReporterV2 4 | import org.jlleitschuh.gradle.ktlint.worker.SerializableLintError 5 | import org.jlleitschuh.gradle.ktlint.worker.toCliError 6 | 7 | class ReporterV2Reporter(val reporter: ReporterV2) : GenericReporter { 8 | override fun beforeAll() { 9 | reporter.beforeAll() 10 | } 11 | 12 | override fun before(file: String) { 13 | reporter.before(file) 14 | } 15 | 16 | override fun onLintError(file: String, err: SerializableLintError, corrected: Boolean) { 17 | reporter.onLintError(file, err.toCliError()) 18 | } 19 | 20 | override fun after(file: String) { 21 | reporter.after(file) 22 | } 23 | 24 | override fun afterAll() { 25 | reporter.afterAll() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/reporter/ReportersProviderV2Loader.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.reporter 2 | 3 | import com.pinterest.ktlint.cli.reporter.core.api.ReporterProviderV2 4 | import com.pinterest.ktlint.cli.reporter.core.api.ReporterV2 5 | import java.io.File 6 | import java.io.ObjectInputStream 7 | import java.util.ServiceLoader 8 | 9 | class ReportersProviderV2Loader : 10 | ReportersLoaderAdapter, ReporterV2Reporter, ReporterProviderV2ReporterProvider> { 11 | override fun loadAllReporterProviders(): List>> = ServiceLoader 12 | .load(ReporterProviderV2::class.java) 13 | .toList().map { 14 | ReporterProviderWrapper(it.id, it) 15 | } 16 | 17 | override fun loadReporterProviders(serializedReporterProviders: File): List { 18 | return ObjectInputStream( 19 | serializedReporterProviders.inputStream().buffered() 20 | ).use { 21 | @Suppress("UNCHECKED_CAST") 22 | it.readObject() as List> 23 | }.map { ReporterProviderV2ReporterProvider(it) } 24 | } 25 | 26 | override fun loadAllGenericReporterProviders(): List = ServiceLoader 27 | .load(ReporterProviderV2::class.java) 28 | .toList().map { 29 | ReporterProviderV2ReporterProvider(it) 30 | } 31 | 32 | override fun filterCustomProviders( 33 | customReporters: Set, 34 | allProviders: List> 35 | ): List>> { 36 | val customProviders = allProviders 37 | .filter { reporterProvider -> 38 | customReporters.any { reporterProvider.id == it.reporterId } 39 | } 40 | 41 | return customReporters 42 | .map { customReporter -> 43 | val provider = customProviders.find { customReporter.reporterId == it.id } 44 | ?: throw RuntimeException( 45 | "KtLint plugin failed to load ${customReporter.reporterId} custom reporter." 46 | ) 47 | LoadedReporter(customReporter.reporterId, customReporter.fileExtension, emptyMap()) to provider 48 | } 49 | } 50 | 51 | override fun filterEnabledBuiltInProviders( 52 | enabledReporters: Set, 53 | allProviders: List>> 54 | ): List>> { 55 | val enabledProviders = allProviders 56 | .filter { reporterProvider -> 57 | enabledReporters.any { 58 | reporterProvider.id == it.reporterName 59 | } 60 | } 61 | return enabledReporters 62 | .map { reporterType -> 63 | val provider = enabledProviders.find { reporterType.reporterName == it.id } 64 | ?: throw RuntimeException( 65 | "KtLint plugin failed to load reporter ${reporterType.reporterName}." 66 | ) 67 | 68 | val options = if (reporterType == ReporterType.PLAIN_GROUP_BY_FILE) { 69 | reporterType.options.associateWith { "true" } 70 | } else { 71 | emptyMap() 72 | } 73 | 74 | LoadedReporter(provider.id, reporterType.fileExtension, options) to provider.reporterProvider 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/worker/BaselineLoader49.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import com.pinterest.ktlint.cli.reporter.baseline.loadBaseline 4 | 5 | class BaselineLoader49 : BaselineLoader { 6 | override fun loadBaselineRules(path: String): Map> { 7 | return loadBaseline(path).lintErrorsPerFile.mapValues { it.value.map { it.toSerializable() } } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/worker/CliKtLintErrorExt.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import com.pinterest.ktlint.cli.reporter.core.api.KtlintCliError 4 | 5 | fun KtlintCliError.toSerializable(): SerializableLintError { 6 | return SerializableLintError(line, col, ruleId, detail, status.toBoolean()) 7 | } 8 | 9 | fun KtlintCliError.Status.toBoolean(): Boolean { 10 | return when (this) { 11 | KtlintCliError.Status.LINT_CAN_NOT_BE_AUTOCORRECTED -> false 12 | KtlintCliError.Status.LINT_CAN_BE_AUTOCORRECTED -> true 13 | else -> false 14 | } 15 | } 16 | 17 | fun SerializableLintError.toCliError(): KtlintCliError { 18 | return KtlintCliError( 19 | line, 20 | col, 21 | ruleId, 22 | detail, 23 | if (canBeAutoCorrected) { 24 | KtlintCliError.Status.LINT_CAN_BE_AUTOCORRECTED 25 | } else { 26 | KtlintCliError.Status.LINT_CAN_NOT_BE_AUTOCORRECTED 27 | } 28 | ) 29 | } 30 | 31 | fun List.containsLintError(error: KtlintCliError): Boolean { 32 | return firstOrNull { lintError -> 33 | lintError.col == error.col && 34 | lintError.line == error.line && 35 | lintError.ruleId == error.ruleId 36 | } != null 37 | } 38 | -------------------------------------------------------------------------------- /plugin/src/adapter100/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintInvocation100.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 4 | import com.pinterest.ktlint.rule.engine.api.Code 5 | import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride 6 | import com.pinterest.ktlint.rule.engine.api.EditorConfigPropertyRegistry 7 | import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine 8 | import com.pinterest.ktlint.rule.engine.api.LintError 9 | import com.pinterest.ktlint.rule.engine.core.api.RuleProvider 10 | import java.io.File 11 | import java.util.ServiceLoader 12 | 13 | class KtLintInvocation100( 14 | private val engine: KtLintRuleEngine 15 | ) : KtLintInvocation { 16 | companion object Factory : KtLintInvocationFactory { 17 | fun initialize(editorConfigOverrides: Map): KtLintInvocation { 18 | val ruleProviders = loadRuleSetsFromClasspathWithRuleSetProviderV3() 19 | val editorConfigPropertyRegistry = EditorConfigPropertyRegistry(ruleProviders) 20 | val engine = if (editorConfigOverrides.isEmpty()) { 21 | KtLintRuleEngine(ruleProviders = ruleProviders) 22 | } else { 23 | KtLintRuleEngine( 24 | ruleProviders = ruleProviders, 25 | editorConfigOverride = EditorConfigOverride.from( 26 | *editorConfigOverrides 27 | .mapKeys { editorConfigPropertyRegistry.find(it.key) } 28 | .entries 29 | .map { it.key to it.value } 30 | .toTypedArray() 31 | ) 32 | ) 33 | } 34 | return KtLintInvocation100(engine) 35 | } 36 | 37 | private fun loadRuleSetsFromClasspathWithRuleSetProviderV3(): Set { 38 | return ServiceLoader 39 | .load(RuleSetProviderV3::class.java) 40 | .flatMap { it.getRuleProviders() } 41 | .toSet() 42 | } 43 | } 44 | 45 | override fun invokeLint(file: File): LintErrorResult { 46 | val errors = mutableListOf>() 47 | engine.lint(Code.fromFile(file)) { le: LintError -> 48 | errors.add(le.toSerializable() to false) 49 | } 50 | return LintErrorResult(file, errors) 51 | } 52 | 53 | override fun invokeFormat(file: File): Pair { 54 | val errors = mutableListOf>() 55 | val newCode = 56 | engine.format(Code.fromFile(file)) { le, boolean -> 57 | errors.add(le.toSerializable() to boolean) 58 | } 59 | return newCode to LintErrorResult(file, errors) 60 | } 61 | 62 | override fun trimMemory() { 63 | engine.trimMemory() 64 | } 65 | } 66 | 67 | internal fun LintError.toSerializable(): SerializableLintError { 68 | return SerializableLintError(line, col, ruleId.value, detail, canBeAutoCorrected) 69 | } 70 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtLintCompatibility.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import net.swiftzer.semver.SemVer 4 | import org.gradle.api.GradleException 5 | import org.jlleitschuh.gradle.ktlint.reporter.ReportersLoaderAdapter 6 | import org.jlleitschuh.gradle.ktlint.reporter.ReportersProviderV2Loader 7 | import org.jlleitschuh.gradle.ktlint.worker.KtLintInvocation100 8 | import org.jlleitschuh.gradle.ktlint.worker.KtLintInvocationFactory 9 | import java.io.Serializable 10 | 11 | internal fun selectInvocation(version: String): KtLintInvocationFactory { 12 | val semVer = SemVer.parse(version) 13 | return if (semVer.major == 0) { 14 | throw GradleException("ktlint $version is incompatible with ktlint-gradle. Please upgrade to 1+") 15 | } else { 16 | KtLintInvocation100 17 | } 18 | } 19 | 20 | internal fun selectReportersLoaderAdapter(version: String): ReportersLoaderAdapter<*, out Serializable, *, *> { 21 | val semVer = SemVer.parse(version) 22 | return if (semVer.major == 0) { 23 | throw GradleException("ktlint $version is incompatible with ktlint-gradle. Please upgrade to 1+") 24 | } else { 25 | ReportersProviderV2Loader() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintBasePlugin.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.gradle.api.Task 8 | import org.gradle.api.file.ConfigurableFileTree 9 | import org.gradle.api.tasks.util.PatternFilterable 10 | import org.gradle.util.GradleVersion 11 | import org.jlleitschuh.gradle.ktlint.reporter.CustomReporter 12 | import org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask 13 | import org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask 14 | import org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask 15 | 16 | internal typealias FilterApplier = (Action) -> Unit 17 | internal typealias KotlinScriptAdditionalPathApplier = (ConfigurableFileTree) -> Unit 18 | 19 | /** 20 | * The base Ktlint plugin that all other plugins are built on. 21 | */ 22 | open class KtlintBasePlugin : Plugin { 23 | internal lateinit var extension: KtlintExtension 24 | 25 | override fun apply(target: Project) { 26 | target.checkMinimalSupportedGradleVersion() 27 | val filterTargetApplier: FilterApplier = { 28 | target.tasks.withType(BaseKtLintCheckTask::class.java).configureEach(it) 29 | } 30 | 31 | val kotlinScriptAdditionalPathApplier: KotlinScriptAdditionalPathApplier = { additionalFileTree -> 32 | val configureAction = Action { 33 | with(this as BaseKtLintCheckTask) { 34 | source( 35 | additionalFileTree.also { 36 | it.include("*.kts") 37 | } 38 | ) 39 | } 40 | } 41 | 42 | target.tasks.named(KtLintCheckTask.KOTLIN_SCRIPT_TASK_NAME).configure(configureAction) 43 | target.tasks.named(KtLintFormatTask.KOTLIN_SCRIPT_TASK_NAME).configure(configureAction) 44 | } 45 | 46 | extension = target.extensions.create( 47 | "ktlint", 48 | KtlintExtension::class.java, 49 | target.objects, 50 | target.container(CustomReporter::class.java) { CustomReporter(it) }, 51 | filterTargetApplier, 52 | kotlinScriptAdditionalPathApplier 53 | ) 54 | } 55 | 56 | companion object { 57 | const val LOWEST_SUPPORTED_GRADLE_VERSION = "7.4.2" 58 | } 59 | 60 | /** 61 | * @deprecated Now that we declare gradle API metadata, this code should not be needed. 62 | * Ee need to check which version of gradle introduced gradle API metadata checking 63 | */ 64 | @Deprecated("Now that we declare gradle API metadata, this code should not be needed") 65 | private fun Project.checkMinimalSupportedGradleVersion() { 66 | if (GradleVersion.version(gradle.gradleVersion) < GradleVersion.version(LOWEST_SUPPORTED_GRADLE_VERSION)) { 67 | throw GradleException( 68 | "Current version of plugin supports minimal Gradle version: $LOWEST_SUPPORTED_GRADLE_VERSION" 69 | ) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import net.swiftzer.semver.SemVer 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.Project 6 | import org.gradle.api.Task 7 | import org.gradle.api.file.ProjectLayout 8 | import org.gradle.api.file.RegularFile 9 | import org.gradle.api.logging.Logger 10 | import org.gradle.api.logging.configuration.ConsoleOutput 11 | import org.gradle.api.model.ObjectFactory 12 | import org.gradle.api.plugins.HelpTasksPlugin 13 | import org.gradle.api.provider.Property 14 | import org.gradle.api.provider.Provider 15 | import org.gradle.api.provider.SetProperty 16 | import org.gradle.api.tasks.TaskProvider 17 | import org.gradle.kotlin.dsl.register 18 | import org.gradle.language.base.plugins.LifecycleBasePlugin 19 | import java.io.File 20 | import java.nio.file.Files 21 | import java.nio.file.Path 22 | 23 | internal inline fun Project.registerTask( 24 | name: String, 25 | vararg constructorArguments: Any = emptyArray(), 26 | noinline configuration: T.() -> Unit 27 | ): TaskProvider { 28 | return tasks 29 | .register(name, *constructorArguments) 30 | .apply { 31 | this.configure(configuration) 32 | } 33 | } 34 | 35 | internal const val EDITOR_CONFIG_FILE_NAME = ".editorconfig" 36 | 37 | internal fun getEditorConfigFiles(currentProjectDir: Path): Set { 38 | val result = mutableSetOf() 39 | searchEditorConfigFiles( 40 | currentProjectDir, 41 | result 42 | ) 43 | return result 44 | } 45 | 46 | private tailrec fun searchEditorConfigFiles( 47 | projectPath: Path, 48 | result: MutableSet 49 | ) { 50 | val editorConfigFC = projectPath.resolve(EDITOR_CONFIG_FILE_NAME) 51 | if (Files.exists(editorConfigFC)) { 52 | result.add(editorConfigFC.toAbsolutePath()) 53 | } 54 | 55 | val parentDir = projectPath.parent 56 | if (parentDir != null && 57 | !editorConfigFC.isRootEditorConfig() 58 | ) { 59 | searchEditorConfigFiles(parentDir, result) 60 | } 61 | } 62 | 63 | private val editorConfigRootRegex = "^root\\s?=\\s?true".toRegex() 64 | 65 | internal fun Path.isRootEditorConfig(): Boolean { 66 | if (!Files.exists(this) || !Files.isReadable(this)) return false 67 | 68 | toFile().useLines { lines -> 69 | val isRoot = lines.firstOrNull { it.contains(editorConfigRootRegex) } 70 | return@isRootEditorConfig isRoot != null 71 | } 72 | } 73 | 74 | internal const val VERIFICATION_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP 75 | internal const val FORMATTING_GROUP = "Formatting" 76 | internal const val HELP_GROUP = HelpTasksPlugin.HELP_GROUP 77 | internal const val CHECK_PARENT_TASK_NAME = "ktlintCheck" 78 | internal const val FORMAT_PARENT_TASK_NAME = "ktlintFormat" 79 | internal const val INSTALL_GIT_HOOK_CHECK_TASK = "addKtlintCheckGitPreCommitHook" 80 | internal const val INSTALL_GIT_HOOK_FORMAT_TASK = "addKtlintFormatGitPreCommitHook" 81 | internal val KOTLIN_EXTENSIONS = listOf("kt", "kts") 82 | internal val INTERMEDIATE_RESULTS_PATH = "intermediates${File.separator}ktLint${File.separator}" 83 | 84 | internal inline fun ObjectFactory.property( 85 | configuration: Property.() -> Unit = {} 86 | ): Property = property(T::class.java).apply(configuration) 87 | 88 | internal inline fun ObjectFactory.setProperty( 89 | configuration: SetProperty.() -> Unit = {} 90 | ): SetProperty = setProperty(T::class.java).apply(configuration) 91 | 92 | internal fun Project.isConsolePlain(): Boolean = gradle.startParameter.consoleOutput == ConsoleOutput.Plain 93 | 94 | /** 95 | * Get file path where tasks could put their intermediate results, that could be consumed by other plugin tasks. 96 | */ 97 | internal fun ProjectLayout.intermediateResultsBuildDir( 98 | resultsFile: String 99 | ): Provider = buildDirectory.file("$INTERMEDIATE_RESULTS_PATH$resultsFile") 100 | 101 | /** 102 | * Logs into Gradle console KtLint debug message. 103 | */ 104 | internal fun Logger.logKtLintDebugMessage( 105 | debugIsEnabled: Boolean, 106 | logProducer: () -> List 107 | ) { 108 | if (debugIsEnabled) { 109 | logProducer().forEach { 110 | warn("[KtLint DEBUG] $it") 111 | } 112 | } 113 | } 114 | 115 | internal fun checkMinimalSupportedKtLintVersion(ktLintVersion: String) { 116 | if (SemVer.parse(ktLintVersion) < SemVer(0, 47, 1)) { 117 | throw GradleException( 118 | "KtLint versions less than 0.47.1 are not supported. " + 119 | "Detected KtLint version: $ktLintVersion." 120 | ) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AndroidPluginsApplier.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import com.android.build.api.dsl.AndroidSourceDirectorySet 4 | import com.android.build.api.dsl.AndroidSourceSet 5 | import com.android.build.api.dsl.CommonExtension 6 | import com.android.build.gradle.internal.api.DefaultAndroidSourceDirectorySet 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.file.FileCollection 9 | import org.jlleitschuh.gradle.ktlint.KtlintPlugin 10 | import org.jlleitschuh.gradle.ktlint.addGenerateReportsTaskToProjectMetaCheckTask 11 | import org.jlleitschuh.gradle.ktlint.addGenerateReportsTaskToProjectMetaFormatTask 12 | import org.jlleitschuh.gradle.ktlint.createCheckTask 13 | import org.jlleitschuh.gradle.ktlint.createFormatTask 14 | import org.jlleitschuh.gradle.ktlint.createGenerateReportsTask 15 | import org.jlleitschuh.gradle.ktlint.setCheckTaskDependsOnGenerateReportsTask 16 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 17 | import java.util.concurrent.Callable 18 | import kotlin.reflect.full.memberProperties 19 | 20 | internal fun KtlintPlugin.PluginHolder.applyKtLintToAndroid(): (Plugin) -> Unit { 21 | return { 22 | target.plugins.withId( 23 | "com.android.application", 24 | androidPluginConfigureAction(this) 25 | ) 26 | target.plugins.withId( 27 | "com.android.library", 28 | androidPluginConfigureAction(this) 29 | ) 30 | target.plugins.withId( 31 | "com.android.test", 32 | androidPluginConfigureAction(this) 33 | ) 34 | target.plugins.withId( 35 | "com.android.dynamic-feature", 36 | androidPluginConfigureAction(this) 37 | ) 38 | } 39 | } 40 | 41 | /* 42 | * Variant manager returns all sources for variant, 43 | * so most probably main source set maybe checked several times. 44 | * This approach creates one check tasks per one source set. 45 | */ 46 | @Suppress("UnstableApiUsage") 47 | private fun androidPluginConfigureAction( 48 | pluginHolder: KtlintPlugin.PluginHolder 49 | ): (Plugin) -> Unit = { 50 | pluginHolder.target.extensions.configure(CommonExtension::class.java) { 51 | // kotlin property exists in AGP >= 7 52 | val kotlinProperty = AndroidSourceSet::class.memberProperties.firstOrNull { it.name == "kotlin" } 53 | if (kotlinProperty == null) { 54 | pluginHolder.target.logger.warn( 55 | buildString { 56 | append("In AGP <7 kotlin source directories are not auto-detected. ") 57 | append("In order to lint kotlin sources, manually add the directory to the source set. ") 58 | append("""For example: sourceSets.getByName("main").java.srcDirs("src/main/kotlin/")""") 59 | } 60 | ) 61 | } 62 | val sourceMember: AndroidSourceSet.() -> AndroidSourceDirectorySet = { 63 | kotlinProperty?.get(this) as AndroidSourceDirectorySet? ?: this.java 64 | } 65 | sourceSets.all { 66 | val androidSourceSet = sourceMember(this) as DefaultAndroidSourceDirectorySet 67 | // Passing Callable, so returned FileCollection, will lazy evaluate it 68 | // only when task will need it. 69 | // Solves the problem of having additional source dirs in 70 | // current AndroidSourceSet, that are not available on eager 71 | // evaluation. 72 | pluginHolder.createAndroidTasks( 73 | name, 74 | pluginHolder.target.files(Callable { androidSourceSet.srcDirs }) 75 | ) 76 | } 77 | } 78 | } 79 | 80 | private fun KtlintPlugin.PluginHolder.createAndroidTasks( 81 | sourceSetName: String, 82 | sources: FileCollection 83 | ) { 84 | val checkTask = createCheckTask( 85 | this, 86 | sourceSetName, 87 | sources 88 | ) 89 | val generateReportsCheckTask = createGenerateReportsTask( 90 | this, 91 | checkTask, 92 | GenerateReportsTask.LintType.CHECK, 93 | sourceSetName 94 | ) 95 | 96 | addGenerateReportsTaskToProjectMetaCheckTask(generateReportsCheckTask) 97 | setCheckTaskDependsOnGenerateReportsTask(generateReportsCheckTask) 98 | 99 | val formatTask = createFormatTask( 100 | this, 101 | sourceSetName, 102 | sources 103 | ) 104 | val generateReportsFormatTask = createGenerateReportsTask( 105 | this, 106 | formatTask, 107 | GenerateReportsTask.LintType.FORMAT, 108 | sourceSetName 109 | ) 110 | addGenerateReportsTaskToProjectMetaFormatTask(generateReportsFormatTask) 111 | } 112 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/tasks/GenerateBaselineTask.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.Task 5 | import org.gradle.api.file.ConfigurableFileCollection 6 | import org.gradle.api.file.ProjectLayout 7 | import org.gradle.api.file.RegularFileProperty 8 | import org.gradle.api.provider.Property 9 | import org.gradle.api.specs.Spec 10 | import org.gradle.api.tasks.CacheableTask 11 | import org.gradle.api.tasks.Classpath 12 | import org.gradle.api.tasks.Input 13 | import org.gradle.api.tasks.InputFiles 14 | import org.gradle.api.tasks.OutputFile 15 | import org.gradle.api.tasks.PathSensitive 16 | import org.gradle.api.tasks.PathSensitivity 17 | import org.gradle.api.tasks.TaskAction 18 | import org.gradle.workers.WorkerExecutor 19 | import org.jlleitschuh.gradle.ktlint.worker.GenerateBaselineWorkAction 20 | import javax.inject.Inject 21 | 22 | /** 23 | * Generates KtLint baseline file. 24 | * 25 | * If baseline file is already exists - it will be overwritten. 26 | */ 27 | @CacheableTask 28 | abstract class GenerateBaselineTask @Inject constructor( 29 | private val workerExecutor: WorkerExecutor, 30 | private val projectLayout: ProjectLayout 31 | ) : DefaultTask() { 32 | @get:Classpath 33 | internal abstract val ktLintClasspath: ConfigurableFileCollection 34 | 35 | @get:Classpath 36 | internal abstract val baselineReporterClasspath: ConfigurableFileCollection 37 | 38 | @get:PathSensitive(PathSensitivity.RELATIVE) 39 | @get:InputFiles 40 | internal abstract val discoveredErrors: ConfigurableFileCollection 41 | 42 | @get:Input 43 | internal abstract val ktLintVersion: Property 44 | 45 | @get:OutputFile 46 | abstract val baselineFile: RegularFileProperty 47 | 48 | final override fun onlyIf(spec: Spec) { 49 | super.onlyIf(spec) 50 | } 51 | 52 | @Suppress("UnstableApiUsage") 53 | @TaskAction 54 | fun generateBaseline() { 55 | // Classloader isolation is enough here as we just want to use some classes from KtLint classpath 56 | // to get errors and generate files/console reports. No KtLint main object is initialized/used in this case. 57 | val queue = workerExecutor.classLoaderIsolation { 58 | classpath.from(ktLintClasspath, baselineReporterClasspath) 59 | } 60 | val task = this 61 | queue.submit(GenerateBaselineWorkAction::class.java) { 62 | discoveredErrors.setFrom(task.discoveredErrors) 63 | ktLintVersion.set(task.ktLintVersion) 64 | baselineFile.set(task.baselineFile) 65 | projectDirectory.set(projectLayout.projectDirectory) 66 | } 67 | } 68 | 69 | companion object { 70 | const val NAME = "ktlintGenerateBaseline" 71 | const val DESCRIPTION = "Generates KtLint baseline file" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/tasks/KtLintCheckTask.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.tasks 2 | 3 | import org.gradle.api.file.ProjectLayout 4 | import org.gradle.api.model.ObjectFactory 5 | import org.gradle.api.tasks.CacheableTask 6 | import org.gradle.api.tasks.TaskAction 7 | import org.gradle.api.tasks.util.PatternFilterable 8 | import org.gradle.work.InputChanges 9 | import org.gradle.workers.WorkerExecutor 10 | import javax.inject.Inject 11 | 12 | @Suppress("UnstableApiUsage") 13 | @CacheableTask 14 | abstract class KtLintCheckTask @Inject constructor( 15 | objectFactory: ObjectFactory, 16 | projectLayout: ProjectLayout, 17 | workerExecutor: WorkerExecutor, 18 | patternFilterable: PatternFilterable 19 | ) : BaseKtLintCheckTask( 20 | objectFactory, 21 | projectLayout, 22 | workerExecutor, 23 | patternFilterable 24 | ) { 25 | 26 | @TaskAction 27 | fun lint(inputChanges: InputChanges) { 28 | runLint(inputChanges) 29 | } 30 | 31 | internal companion object { 32 | fun buildTaskNameForSourceSet( 33 | sourceSetName: String 34 | ): String = "runKtlintCheckOver${sourceSetName.capitalize()}SourceSet" 35 | 36 | const val KOTLIN_SCRIPT_TASK_NAME = "runKtlintCheckOverKotlinScripts" 37 | 38 | fun buildDescription( 39 | fileType: String 40 | ): String = "Lints all $fileType files to ensure that they are formatted according to the code style." 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/tasks/KtLintFormatTask.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.tasks 2 | 3 | import org.gradle.api.file.ProjectLayout 4 | import org.gradle.api.file.RegularFileProperty 5 | import org.gradle.api.model.ObjectFactory 6 | import org.gradle.api.tasks.CacheableTask 7 | import org.gradle.api.tasks.LocalState 8 | import org.gradle.api.tasks.TaskAction 9 | import org.gradle.api.tasks.util.PatternFilterable 10 | import org.gradle.work.InputChanges 11 | import org.gradle.workers.WorkerExecutor 12 | import org.jlleitschuh.gradle.ktlint.intermediateResultsBuildDir 13 | import org.jlleitschuh.gradle.ktlint.worker.KtLintWorkAction 14 | import org.jlleitschuh.gradle.ktlint.worker.KtLintWorkAction.FormatTaskSnapshot.Companion.contentHash 15 | import javax.inject.Inject 16 | 17 | @CacheableTask 18 | abstract class KtLintFormatTask @Inject constructor( 19 | objectFactory: ObjectFactory, 20 | projectLayout: ProjectLayout, 21 | workerExecutor: WorkerExecutor, 22 | patternFilterable: PatternFilterable 23 | ) : BaseKtLintCheckTask( 24 | objectFactory, 25 | projectLayout, 26 | workerExecutor, 27 | patternFilterable 28 | ) { 29 | @get:LocalState 30 | internal val previousRunSnapshot: RegularFileProperty = objectFactory 31 | .fileProperty() 32 | .convention( 33 | projectLayout.intermediateResultsBuildDir("$name-snapshot.bin") 34 | ) 35 | 36 | private val previousSnapshot get() = previousRunSnapshot.asFile.get() 37 | .run { 38 | if (exists()) { 39 | KtLintWorkAction.FormatTaskSnapshot.readFromFile(this) 40 | } else { 41 | KtLintWorkAction.FormatTaskSnapshot(emptyMap()) 42 | } 43 | } 44 | 45 | init { 46 | // Special UP-TO-DATE check to avoid situation when task does not check restored to pre-formatted state 47 | // files 48 | outputs.upToDateWhen { 49 | val inputSources = source.files 50 | previousSnapshot.formattedSources.none { 51 | inputSources.contains(it.key) && 52 | contentHash(it.key).contentEquals(it.value) 53 | } 54 | } 55 | } 56 | 57 | @TaskAction 58 | fun format(inputChanges: InputChanges) { 59 | runFormat(inputChanges, previousRunSnapshot.get().asFile) 60 | } 61 | 62 | internal companion object { 63 | fun buildTaskNameForSourceSet( 64 | sourceSetName: String 65 | ): String = "runKtlintFormatOver${sourceSetName.capitalize()}SourceSet" 66 | 67 | const val KOTLIN_SCRIPT_TASK_NAME = "runKtlintFormatOverKotlinScripts" 68 | 69 | fun buildDescription( 70 | fileType: String 71 | ): String = "Lints all $fileType files to ensure that they are formatted according to the code style " + 72 | " and, on error, tries to format code to conform code style." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/tasks/LoadReportersTask.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.tasks 2 | 3 | import net.swiftzer.semver.SemVer 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.file.ConfigurableFileCollection 6 | import org.gradle.api.file.ProjectLayout 7 | import org.gradle.api.file.RegularFileProperty 8 | import org.gradle.api.model.ObjectFactory 9 | import org.gradle.api.provider.Property 10 | import org.gradle.api.provider.SetProperty 11 | import org.gradle.api.tasks.CacheableTask 12 | import org.gradle.api.tasks.Classpath 13 | import org.gradle.api.tasks.Input 14 | import org.gradle.api.tasks.OutputFile 15 | import org.gradle.api.tasks.TaskAction 16 | import org.gradle.workers.WorkerExecutor 17 | import org.jlleitschuh.gradle.ktlint.checkMinimalSupportedKtLintVersion 18 | import org.jlleitschuh.gradle.ktlint.intermediateResultsBuildDir 19 | import org.jlleitschuh.gradle.ktlint.reporter.CustomReporter 20 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 21 | import org.jlleitschuh.gradle.ktlint.worker.LoadReportersWorkAction 22 | import javax.inject.Inject 23 | 24 | @Suppress("UnstableApiUsage") 25 | @CacheableTask 26 | internal abstract class LoadReportersTask @Inject constructor( 27 | private val workerExecutor: WorkerExecutor, 28 | objectFactory: ObjectFactory, 29 | projectLayout: ProjectLayout 30 | ) : DefaultTask() { 31 | 32 | @get:Classpath 33 | internal abstract val ktLintClasspath: ConfigurableFileCollection 34 | 35 | @get:Classpath 36 | internal abstract val reportersClasspath: ConfigurableFileCollection 37 | 38 | @get:Input 39 | internal abstract val debug: Property 40 | 41 | @get:Input 42 | internal abstract val ktLintVersion: Property 43 | 44 | @get:Input 45 | internal abstract val enabledReporters: SetProperty 46 | 47 | @get:Input 48 | internal abstract val customReporters: SetProperty 49 | 50 | @get:OutputFile 51 | internal val loadedReporters: RegularFileProperty = objectFactory.fileProperty().convention( 52 | projectLayout.intermediateResultsBuildDir("reporters.bin") 53 | ) 54 | 55 | @get:OutputFile 56 | internal val loadedReporterProviders: RegularFileProperty = objectFactory.fileProperty().convention( 57 | projectLayout.intermediateResultsBuildDir("reporterProviders.bin") 58 | ) 59 | 60 | @TaskAction 61 | fun loadReporters() { 62 | checkMinimalSupportedKtLintVersion(ktLintVersion.get()) 63 | 64 | // Classloader isolation is enough here as we just want to use some classes from KtLint classpath 65 | // to load reporters. No KtLint main object is initialized/used in this case. 66 | val queue = workerExecutor.classLoaderIsolation { 67 | classpath.from(ktLintClasspath, reportersClasspath) 68 | } 69 | 70 | queue.submit(LoadReportersWorkAction::class.java) { 71 | val task = this@LoadReportersTask 72 | debug.set(task.debug) 73 | enabledReporters.set( 74 | task.enabledReporters 75 | .map { reporters -> 76 | reporters.filter { it.isAvailable() } 77 | } 78 | ) 79 | customReporters.set(task.customReporters) 80 | loadedReporters.set(task.loadedReporters) 81 | loadedReporterProviders.set(task.loadedReporterProviders) 82 | ktLintVersion.set(task.ktLintVersion) 83 | } 84 | } 85 | 86 | private fun ReporterType.isAvailable() = 87 | SemVer.parse(ktLintVersion.get()) >= availableSinceVersion 88 | 89 | internal companion object { 90 | internal const val TASK_NAME = "loadKtlintReporters" 91 | internal const val DESCRIPTION = "Preloads required KtLint reporters." 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/ConsoleReportWorkAction.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import com.pinterest.ktlint.cli.reporter.baseline.loadBaseline 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.file.ConfigurableFileCollection 6 | import org.gradle.api.file.DirectoryProperty 7 | import org.gradle.api.file.RegularFileProperty 8 | import org.gradle.api.logging.Logging 9 | import org.gradle.api.provider.Property 10 | import org.gradle.workers.WorkAction 11 | import org.gradle.workers.WorkParameters 12 | import org.jetbrains.kotlin.util.prefixIfNot 13 | import java.io.File 14 | 15 | internal abstract class ConsoleReportWorkAction : WorkAction { 16 | 17 | private val logger = Logging.getLogger("ktlint-console-report-worker") 18 | 19 | override fun execute() { 20 | val errors = KtLintClassesSerializer 21 | .create() 22 | .loadErrors( 23 | parameters.discoveredErrors.asFile.get() 24 | ) 25 | 26 | val baselineRules = parameters.baseline.orNull?.asFile?.absolutePath 27 | ?.let { loadBaseline(it).lintErrorsPerFile } 28 | val projectDir = parameters.projectDirectory.asFile.get() 29 | 30 | val lintErrors = errors.associate { lintErrorResult -> 31 | val filePath = lintErrorResult.lintedFile.absolutePath 32 | val baselineLintErrors = baselineRules?.get( 33 | lintErrorResult.lintedFile.toRelativeString(projectDir).replace(File.separatorChar, '/') 34 | ) 35 | filePath to lintErrorResult 36 | .lintErrors 37 | .filter { 38 | !it.second && 39 | baselineLintErrors?.containsLintError(it.first.toCliError()) != true 40 | } 41 | .map { it.first } 42 | } 43 | 44 | val isLintErrorsFound = lintErrors.values.flatten().isNotEmpty() 45 | if (parameters.outputToConsole.getOrElse(false) && isLintErrorsFound) { 46 | val verbose = parameters.verbose.get() 47 | lintErrors.forEach { (filePath, errors) -> 48 | errors.forEach { it.logError(filePath, verbose) } 49 | } 50 | } 51 | 52 | if (!parameters.ignoreFailures.getOrElse(false) && isLintErrorsFound) { 53 | val reportsPaths = parameters 54 | .generatedReportsPaths 55 | .files.joinToString(separator = "\n") { it.absolutePath.prefixIfNot("|- ") } 56 | 57 | throw GradleException( 58 | """ 59 | |KtLint found code style violations. Please see the following reports: 60 | $reportsPaths 61 | """.trimMargin() 62 | ) 63 | } 64 | } 65 | 66 | private fun SerializableLintError.logError( 67 | filePath: String, 68 | verbose: Boolean 69 | ) { 70 | val verboseSuffix = if (verbose) " ($ruleId)" else "" 71 | val errorDetail = if (!canBeAutoCorrected) { 72 | "$detail (cannot be auto-corrected)" 73 | } else { 74 | detail 75 | } 76 | logger.warn("$filePath:$line:$col $errorDetail$verboseSuffix") 77 | } 78 | 79 | internal interface ConsoleReportParameters : WorkParameters { 80 | val discoveredErrors: RegularFileProperty 81 | val outputToConsole: Property 82 | val ignoreFailures: Property 83 | val verbose: Property 84 | val generatedReportsPaths: ConfigurableFileCollection 85 | val ktLintVersion: Property 86 | val baseline: RegularFileProperty 87 | val projectDirectory: DirectoryProperty 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/GenerateBaselineWorkAction.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection 4 | import org.gradle.api.file.DirectoryProperty 5 | import org.gradle.api.file.RegularFileProperty 6 | import org.gradle.api.logging.LogLevel 7 | import org.gradle.api.logging.Logging 8 | import org.gradle.api.provider.Property 9 | import org.gradle.workers.WorkAction 10 | import org.gradle.workers.WorkParameters 11 | import org.jlleitschuh.gradle.ktlint.selectReportersLoaderAdapter 12 | import java.io.File 13 | import java.io.PrintStream 14 | 15 | @Suppress("UnstableApiUsage") 16 | internal abstract class GenerateBaselineWorkAction : 17 | WorkAction { 18 | 19 | private val logger = Logging.getLogger("ktlint-generate-baseline-worker") 20 | 21 | override fun execute() { 22 | val ktLintClassesSerializer = KtLintClassesSerializer.create() 23 | 24 | val errors = parameters 25 | .discoveredErrors 26 | .files 27 | .filter { it.exists() } 28 | .map(ktLintClassesSerializer::loadErrors) 29 | .flatten() 30 | 31 | val baselineFile = parameters.baselineFile.asFile.get().apply { 32 | if (exists()) delete() else parentFile.mkdirs() 33 | } 34 | 35 | val projectDir = parameters.projectDirectory.asFile.get() 36 | 37 | PrintStream(baselineFile.outputStream()).use { file -> 38 | val baselineReporter = selectReportersLoaderAdapter(parameters.ktLintVersion.get()) 39 | .loadAllGenericReporterProviders() 40 | .first { it.id == "baseline" } 41 | .get(file, emptyMap()) 42 | 43 | baselineReporter.beforeAll() 44 | errors.forEach { lintErrorResult -> 45 | val filePath = lintErrorResult 46 | .lintedFile 47 | .toRelativeString(projectDir) 48 | .replace(File.separatorChar, '/') 49 | 50 | baselineReporter.before(filePath) 51 | lintErrorResult.lintErrors.forEach { 52 | baselineReporter.onLintError(filePath, it.first, it.second) 53 | } 54 | baselineReporter.after(filePath) 55 | } 56 | baselineReporter.afterAll() 57 | } 58 | 59 | logger.log( 60 | LogLevel.WARN, 61 | "Baseline was successfully generated into: ${parameters.baselineFile.get().asFile.absolutePath}" 62 | ) 63 | } 64 | 65 | internal interface GenerateBaselineParameters : WorkParameters { 66 | val discoveredErrors: ConfigurableFileCollection 67 | val baselineFile: RegularFileProperty 68 | val projectDirectory: DirectoryProperty 69 | val ktLintVersion: Property 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/GenerateReportsWorkAction.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import com.pinterest.ktlint.cli.reporter.baseline.loadBaseline 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.file.DirectoryProperty 6 | import org.gradle.api.file.RegularFileProperty 7 | import org.gradle.api.provider.MapProperty 8 | import org.gradle.api.provider.Property 9 | import org.gradle.workers.WorkAction 10 | import org.gradle.workers.WorkParameters 11 | import org.jlleitschuh.gradle.ktlint.selectReportersLoaderAdapter 12 | import java.io.File 13 | import java.io.PrintStream 14 | 15 | internal abstract class GenerateReportsWorkAction : WorkAction { 16 | 17 | override fun execute() { 18 | val ktLintClassesSerializer = KtLintClassesSerializer 19 | .create() 20 | 21 | val discoveredErrors = ktLintClassesSerializer.loadErrors(parameters.discoveredErrorsFile.get().asFile) 22 | val currentReporterId = parameters.reporterId.get() 23 | val reporterAdapter = selectReportersLoaderAdapter(parameters.ktLintVersion.get()) 24 | val reporterProvider = reporterAdapter.loadReporterProviders(parameters.loadedReporterProviders.asFile.get()) 25 | .find { it.id == currentReporterId } 26 | ?: throw GradleException("Could not find ReporterProvider \"$currentReporterId\"") 27 | 28 | val baselineRules = parameters.baseline.orNull?.asFile?.absolutePath 29 | ?.let { loadBaseline(it).lintErrorsPerFile } 30 | val projectDir = parameters.projectDirectory.asFile.get() 31 | 32 | PrintStream( 33 | parameters 34 | .reporterOutput 35 | .get() 36 | .asFile 37 | ).use { printStream -> 38 | val reporter = reporterProvider.get(printStream, parameters.reporterOptions.get()) 39 | 40 | reporter.beforeAll() 41 | discoveredErrors.forEach { lintErrorResult -> 42 | val filePath = filePathForReport(lintErrorResult.lintedFile) 43 | val baselineLintErrors = baselineRules?.get( 44 | lintErrorResult.lintedFile.toRelativeString(projectDir).replace(File.separatorChar, '/') 45 | ) 46 | reporter.before(filePath) 47 | lintErrorResult.lintErrors.forEach { 48 | if (baselineLintErrors?.containsLintError(it.first.toCliError()) != true) { 49 | reporter.onLintError(filePath, it.first, it.second) 50 | } 51 | } 52 | reporter.after(filePath) 53 | } 54 | reporter.afterAll() 55 | } 56 | } 57 | 58 | private fun filePathForReport(file: File): String { 59 | val rootDir = parameters.filePathsRelativeTo.orNull 60 | if (rootDir != null) { 61 | return file.toRelativeString(rootDir) 62 | } 63 | 64 | return file.absolutePath 65 | } 66 | 67 | internal interface GenerateReportsParameters : WorkParameters { 68 | val discoveredErrorsFile: RegularFileProperty 69 | val loadedReporterProviders: RegularFileProperty 70 | val reporterId: Property 71 | val reporterOutput: RegularFileProperty 72 | val reporterOptions: MapProperty 73 | val ktLintVersion: Property 74 | val baseline: RegularFileProperty 75 | val projectDirectory: DirectoryProperty 76 | val filePathsRelativeTo: Property 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintClassesSerializer.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import org.apache.commons.io.serialization.ValidatingObjectInputStream 4 | import org.gradle.api.GradleException 5 | import java.io.File 6 | import java.io.ObjectOutputStream 7 | import java.io.Serializable 8 | 9 | internal interface KtLintClassesSerializer { 10 | fun saveErrors( 11 | lintErrors: List, 12 | serializedErrors: File 13 | ) 14 | 15 | fun loadErrors( 16 | serializedErrors: File 17 | ): List 18 | 19 | fun saveReporterProviders( 20 | reporterProviders: List, 21 | serializedReporterProviders: File 22 | ) 23 | 24 | companion object { 25 | fun create(): KtLintClassesSerializer = 26 | CurrentKtLintClassesSerializer() 27 | } 28 | } 29 | 30 | private class CurrentKtLintClassesSerializer : KtLintClassesSerializer { 31 | override fun saveErrors( 32 | lintErrors: List, 33 | serializedErrors: File 34 | ) = ObjectOutputStream(serializedErrors.outputStream().buffered()).use { 35 | it.writeObject(lintErrors) 36 | } 37 | 38 | override fun loadErrors(serializedErrors: File): List = loadAnyErrors(serializedErrors) 39 | 40 | override fun saveReporterProviders( 41 | reporterProviders: List, 42 | serializedReporterProviders: File 43 | ) = ObjectOutputStream( 44 | serializedReporterProviders.outputStream().buffered() 45 | ).use { oos -> 46 | oos.writeObject( 47 | reporterProviders 48 | ) 49 | } 50 | } 51 | 52 | @Suppress("UNCHECKED_CAST") 53 | internal fun loadAnyErrors(file: File): List { 54 | val errors = ValidatingObjectInputStream( 55 | file.inputStream().buffered() 56 | ).use { 57 | it.accept( 58 | ArrayList::class.java, 59 | LintErrorResult::class.java, 60 | SerializableLintError::class.java, 61 | File::class.java, 62 | Pair::class.java, 63 | java.lang.Boolean::class.java 64 | ) 65 | it.accept("kotlin.Pair") 66 | it.readObject() 67 | } 68 | return when (errors) { 69 | is List<*> -> 70 | when (errors.first()) { 71 | null -> emptyList() 72 | is LintErrorResult -> errors as List 73 | else -> throw GradleException("invalid error format") 74 | } 75 | 76 | else -> throw GradleException("invalid error format") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintWorkAction.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import org.apache.commons.io.input.MessageDigestCalculatingInputStream 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.file.ConfigurableFileCollection 6 | import org.gradle.api.file.RegularFileProperty 7 | import org.gradle.api.logging.Logging 8 | import org.gradle.api.provider.MapProperty 9 | import org.gradle.api.provider.Property 10 | import org.gradle.workers.WorkAction 11 | import org.gradle.workers.WorkParameters 12 | import org.jlleitschuh.gradle.ktlint.selectInvocation 13 | import org.jlleitschuh.gradle.ktlint.worker.KtLintWorkAction.FormatTaskSnapshot.Companion.contentHash 14 | import java.io.File 15 | import java.io.ObjectInputStream 16 | import java.io.ObjectOutputStream 17 | import java.io.Serializable 18 | 19 | @Suppress("UnstableApiUsage") 20 | abstract class KtLintWorkAction : WorkAction { 21 | 22 | private val logger = Logging.getLogger("ktlint-worker") 23 | 24 | override fun execute() { 25 | val formatSource = parameters.formatSource.getOrElse(false) 26 | val results = mutableListOf() 27 | val formattedFiles = mutableMapOf() 28 | val ktlintInvoker: KtLintInvocation = when ( 29 | val ktlintInvokerFactory = selectInvocation(parameters.ktLintVersion.get()) 30 | ) { 31 | is KtLintInvocation100.Factory -> { 32 | ktlintInvokerFactory.initialize(parameters.additionalEditorconfig.get()) 33 | } 34 | 35 | else -> { 36 | throw GradleException("Incompatible ktlint version ${parameters.ktLintVersion}") 37 | } 38 | } 39 | 40 | resetEditorconfigCache(ktlintInvoker) 41 | 42 | parameters.filesToLint.files.forEach { 43 | try { 44 | if (formatSource) { 45 | val currentFileContent = it.readText() 46 | val result = ktlintInvoker.invokeFormat(it) 47 | results.add(result.second) 48 | val updatedFileContent = result.first 49 | 50 | if (updatedFileContent != currentFileContent) { 51 | formattedFiles[it] = contentHash(it) 52 | it.writeText(updatedFileContent) 53 | } 54 | } else { 55 | val result = ktlintInvoker.invokeLint(it) 56 | results.add(result) 57 | } 58 | } catch (e: RuntimeException) { 59 | logger.error(e.message) 60 | throw GradleException( 61 | "KtLint failed to parse file: ${it.absolutePath}", 62 | e 63 | ) 64 | } 65 | } 66 | 67 | KtLintClassesSerializer 68 | .create() 69 | .saveErrors( 70 | results, 71 | parameters.discoveredErrorsFile.asFile.get() 72 | ) 73 | 74 | if (formattedFiles.isNotEmpty()) { 75 | val snapshotFile = parameters.formatSnapshot.get().asFile 76 | .also { if (!it.exists()) it.createNewFile() } 77 | val snapshot = FormatTaskSnapshot(formattedFiles) 78 | FormatTaskSnapshot.writeIntoFile(snapshotFile, snapshot) 79 | } 80 | } 81 | 82 | private fun resetEditorconfigCache(ktLintInvocation: KtLintInvocation) { 83 | if (parameters.editorconfigFilesWereChanged.get()) { 84 | logger.info("Resetting KtLint caches") 85 | // Calling trimMemory() will also reset internal loaded `.editorconfig` cache 86 | ktLintInvocation.trimMemory() 87 | } 88 | } 89 | 90 | interface KtLintWorkParameters : WorkParameters { 91 | val filesToLint: ConfigurableFileCollection 92 | val android: Property 93 | val debug: Property 94 | val additionalEditorconfig: MapProperty 95 | val formatSource: Property 96 | val discoveredErrorsFile: RegularFileProperty 97 | val ktLintVersion: Property 98 | val editorconfigFilesWereChanged: Property 99 | val formatSnapshot: RegularFileProperty 100 | } 101 | 102 | /** 103 | * Represents pre-formatted files snapshot (file + it contents hash). 104 | */ 105 | internal class FormatTaskSnapshot( 106 | val formattedSources: Map 107 | ) : Serializable { 108 | companion object { 109 | private const val serialVersionUID = 1L 110 | 111 | fun readFromFile(snapshotFile: File) = 112 | ObjectInputStream(snapshotFile.inputStream().buffered()) 113 | .use { 114 | it.readObject() as FormatTaskSnapshot 115 | } 116 | 117 | fun writeIntoFile( 118 | snapshotFile: File, 119 | formatSnapshot: FormatTaskSnapshot 120 | ) = ObjectOutputStream(snapshotFile.outputStream().buffered()) 121 | .use { 122 | it.writeObject(formatSnapshot) 123 | } 124 | 125 | fun contentHash(file: File): ByteArray { 126 | return MessageDigestCalculatingInputStream(file.inputStream().buffered()).use { 127 | it.readBytes() 128 | it.messageDigest.digest() 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/worker/LoadReportersWorkAction.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import org.gradle.api.file.RegularFileProperty 4 | import org.gradle.api.logging.Logging 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.provider.SetProperty 7 | import org.gradle.workers.WorkAction 8 | import org.gradle.workers.WorkParameters 9 | import org.jlleitschuh.gradle.ktlint.reporter.CustomReporter 10 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 11 | import org.jlleitschuh.gradle.ktlint.selectReportersLoaderAdapter 12 | import java.io.ObjectOutputStream 13 | 14 | @Suppress("UnstableApiUsage") 15 | internal abstract class LoadReportersWorkAction : WorkAction { 16 | private val logger = Logging.getLogger("ktlint-load-reporters-worker") 17 | 18 | override fun execute() { 19 | val reportersLoaderAdapter = selectReportersLoaderAdapter(parameters.ktLintVersion.get()) 20 | val loadedReporters = reportersLoaderAdapter.allEnabledProviders( 21 | getEnabledReporters(), 22 | parameters.customReporters.get() 23 | ) 24 | 25 | val ktLintClassesSerializer = KtLintClassesSerializer.create() 26 | ktLintClassesSerializer.saveReporterProviders( 27 | loadedReporters.map { it.second }, 28 | parameters.loadedReporterProviders.asFile.get() 29 | ) 30 | 31 | ObjectOutputStream( 32 | parameters.loadedReporters.asFile.get().outputStream().buffered() 33 | ).use { oos -> 34 | oos.writeObject( 35 | loadedReporters.map { it.first } 36 | ) 37 | } 38 | } 39 | 40 | private fun getEnabledReporters() = parameters 41 | .enabledReporters 42 | .get() 43 | .run { 44 | if (isEmpty()) setOf(ReporterType.PLAIN) else this 45 | } 46 | internal interface LoadReportersParameters : WorkParameters { 47 | val enabledReporters: SetProperty 48 | val customReporters: SetProperty 49 | val debug: Property 50 | val loadedReporterProviders: RegularFileProperty 51 | val loadedReporters: RegularFileProperty 52 | val ktLintVersion: Property 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugin/src/test/java/org/jlleitschuh/gradle/ktlint/testdsl/CommonTest.java: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.testdsl; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.ArgumentsSource; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | // Has to be java annotation 12 | // Workaround for https://youtrack.jetbrains.com/issue/IDEA-265284 13 | @ArgumentsSource(GradleArgumentsProvider.class) 14 | @ParameterizedTest(name = "Gradle {0}: {displayName}") 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CommonTest {} 18 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt: -------------------------------------------------------------------------------- 1 | package org.assertj.core.api 2 | 3 | import org.gradle.testkit.runner.BuildTask 4 | import org.gradle.testkit.runner.TaskOutcome 5 | 6 | fun ObjectAssert.hasOutcome(outcome: TaskOutcome) { 7 | this.objects.assertNotNull(this.info, this.actual) 8 | this.objects.assertEqual(this.info, this.actual!!.outcome, outcome) 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/AbstractPluginTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 4 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 5 | import org.junit.jupiter.api.io.TempDir 6 | import java.io.File 7 | 8 | @GradleTestVersions 9 | abstract class AbstractPluginTest { 10 | 11 | @TempDir 12 | lateinit var temporaryFolder: File 13 | 14 | val projectRoot: File 15 | get() = temporaryFolder.resolve("plugin-test").apply { mkdirs() } 16 | 17 | val mainSourceSetCheckTaskName = GenerateReportsTask.generateNameForSourceSets( 18 | "main", 19 | GenerateReportsTask.LintType.CHECK 20 | ) 21 | 22 | val mainSourceSetFormatTaskName = GenerateReportsTask.generateNameForSourceSets( 23 | "main", 24 | GenerateReportsTask.LintType.FORMAT 25 | ) 26 | 27 | val kotlinScriptCheckTaskName = GenerateReportsTask.generateNameForKotlinScripts( 28 | GenerateReportsTask.LintType.CHECK 29 | ) 30 | 31 | protected fun File.withCleanSources() = createSourceFile( 32 | "src/main/kotlin/CleanSource.kt", 33 | """ 34 | val foo = "bar" 35 | 36 | """.trimIndent() 37 | ) 38 | 39 | protected fun File.withCleanKotlinScript() = createSourceFile( 40 | "kotlin-script.kts", 41 | """ 42 | println("zzz") 43 | 44 | """.trimIndent() 45 | ) 46 | 47 | protected fun File.withFailingKotlinScript() = createSourceFile( 48 | "kotlin-script-fail.kts", 49 | """ 50 | println("zzz") 51 | 52 | """.trimIndent() 53 | ) 54 | 55 | protected fun File.withAlternativeFailingSources(baseDir: String) = 56 | createSourceFile("$baseDir/FailSource.kt", """val foo = "bar"""") 57 | 58 | protected fun File.createSourceFile(sourceFilePath: String, contents: String) { 59 | val sourceFile = resolve(sourceFilePath) 60 | sourceFile.parentFile.mkdirs() 61 | sourceFile.writeText(contents) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/BuildCacheTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.testkit.runner.TaskOutcome 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 7 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 9 | import org.jlleitschuh.gradle.ktlint.testdsl.TestProject 10 | import org.jlleitschuh.gradle.ktlint.testdsl.build 11 | import org.jlleitschuh.gradle.ktlint.testdsl.getMajorJavaVersion 12 | import org.jlleitschuh.gradle.ktlint.testdsl.project 13 | import org.junit.jupiter.api.Assumptions 14 | import org.junit.jupiter.api.DisplayName 15 | import java.io.File 16 | 17 | @GradleTestVersions 18 | class BuildCacheTest : AbstractPluginTest() { 19 | private val originalRoot get() = temporaryFolder.resolve("original").apply { mkdirs() } 20 | private val relocatedRoot get() = temporaryFolder.resolve("relocated").apply { mkdirs() } 21 | private val localBuildCache get() = temporaryFolder.resolve("build-cache").apply { mkdirs() } 22 | 23 | @DisplayName("Check task should be relocatable") 24 | @CommonTest 25 | fun checkIsRelocatable(gradleVersion: GradleVersion) { 26 | val testSourceCheckTaskName = GenerateReportsTask.generateNameForSourceSets( 27 | "test", 28 | GenerateReportsTask.LintType.CHECK 29 | ) 30 | project(gradleVersion, projectPath = originalRoot) { 31 | configureDefaultProject() 32 | 33 | build(CHECK_PARENT_TASK_NAME, "--build-cache") { 34 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 35 | assertThat(task(":$testSourceCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 36 | } 37 | } 38 | 39 | project(gradleVersion, projectPath = relocatedRoot) { 40 | configureDefaultProject() 41 | 42 | build(CHECK_PARENT_TASK_NAME, "--build-cache") { 43 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE) 44 | assertThat(task(":$testSourceCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE) 45 | } 46 | } 47 | } 48 | 49 | @DisplayName("Check task with additional reporters should be relocatable") 50 | @CommonTest 51 | fun checkWithReportersIsRelocatable(gradleVersion: GradleVersion) { 52 | // custom reporter is compiled on java 11 53 | Assumptions.assumeTrue(getMajorJavaVersion() >= 11) 54 | project(gradleVersion, projectPath = originalRoot) { 55 | configureDefaultProject() 56 | useExternalKtLintReporter() 57 | 58 | build(CHECK_PARENT_TASK_NAME, "--build-cache") { 59 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 60 | } 61 | } 62 | 63 | project(gradleVersion, projectPath = relocatedRoot) { 64 | configureDefaultProject() 65 | useExternalKtLintReporter() 66 | 67 | build(CHECK_PARENT_TASK_NAME, "--build-cache") { 68 | assertThat(task(":$mainSourceSetCheckTaskName")!!.outcome).isEqualTo(TaskOutcome.FROM_CACHE) 69 | } 70 | } 71 | } 72 | 73 | private fun TestProject.useExternalKtLintReporter() = buildGradle 74 | .appendText( 75 | //language=Groovy 76 | """ 77 | ktlint.reporters { 78 | reporter "plain" 79 | reporter "checkstyle" 80 | customReporters { 81 | "github" { 82 | dependency = "de.musichin.ktlint.reporter:ktlint-reporter-github:3.1.0" 83 | } 84 | } 85 | } 86 | """.trimIndent() 87 | ) 88 | 89 | private fun File.addBuildCacheSettings() = appendText( 90 | //language=Groovy 91 | """ 92 | 93 | buildCache { 94 | local { 95 | directory = '${localBuildCache.toURI()}' 96 | } 97 | } 98 | """.trimIndent() 99 | ) 100 | 101 | private fun TestProject.configureDefaultProject() { 102 | settingsGradle.addBuildCacheSettings() 103 | 104 | withCleanSources() 105 | createSourceFile( 106 | "src/test/kotlin/Test.kt", 107 | """ 108 | class Test 109 | 110 | """.trimIndent() 111 | ) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/ConfigurationCacheTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.testkit.runner.TaskOutcome 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask 7 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 9 | import org.jlleitschuh.gradle.ktlint.testdsl.build 10 | import org.jlleitschuh.gradle.ktlint.testdsl.project 11 | import org.junit.jupiter.api.DisplayName 12 | 13 | @GradleTestVersions 14 | class ConfigurationCacheTest : AbstractPluginTest() { 15 | private val configurationCacheFlag = "--configuration-cache" 16 | 17 | @DisplayName("Should support configuration cache without errors on running linting") 18 | @CommonTest 19 | internal fun configurationCacheForCheckTask(gradleVersion: GradleVersion) { 20 | project(gradleVersion) { 21 | createSourceFile( 22 | "src/main/kotlin/CleanSource.kt", 23 | """ 24 | val foo = "bar" 25 | 26 | """.trimIndent() 27 | ) 28 | 29 | build( 30 | configurationCacheFlag, 31 | CHECK_PARENT_TASK_NAME 32 | ) { 33 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 34 | } 35 | 36 | build( 37 | configurationCacheFlag, 38 | CHECK_PARENT_TASK_NAME 39 | ) { 40 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 41 | assertThat(output).contains("Reusing configuration cache.") 42 | } 43 | } 44 | } 45 | 46 | @DisplayName("Should support configuration cache on running format tasks") 47 | @CommonTest 48 | fun configurationCacheForFormatTasks(gradleVersion: GradleVersion) { 49 | project(gradleVersion) { 50 | withCleanSources() 51 | val formatTaskName = KtLintFormatTask.buildTaskNameForSourceSet("main") 52 | build( 53 | configurationCacheFlag, 54 | FORMAT_PARENT_TASK_NAME 55 | ) { 56 | assertThat(task(":$formatTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 57 | assertThat(task(":$mainSourceSetFormatTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 58 | } 59 | build( 60 | configurationCacheFlag, 61 | FORMAT_PARENT_TASK_NAME, 62 | "--debug" 63 | ) { 64 | assertThat(task(":$formatTaskName")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 65 | assertThat(task(":$mainSourceSetFormatTaskName")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 66 | assertThat(output).contains("Reusing configuration cache.") 67 | } 68 | } 69 | } 70 | 71 | @DisplayName("Should support configuration cache on running format tasks with relative paths") 72 | @CommonTest 73 | fun configurationCacheForFormatTasksWithRelativePaths(gradleVersion: GradleVersion) { 74 | project(gradleVersion) { 75 | buildGradle.appendText( 76 | //language=Groovy 77 | """ 78 | ktlint { 79 | relative = true 80 | reporters { 81 | reporter "plain" 82 | reporter "checkstyle" 83 | } 84 | } 85 | """.trimIndent() 86 | ) 87 | withCleanSources() 88 | val formatTaskName = KtLintFormatTask.buildTaskNameForSourceSet("main") 89 | build( 90 | configurationCacheFlag, 91 | FORMAT_PARENT_TASK_NAME 92 | ) { 93 | assertThat(task(":$formatTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 94 | assertThat(task(":$mainSourceSetFormatTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 95 | } 96 | build( 97 | configurationCacheFlag, 98 | FORMAT_PARENT_TASK_NAME 99 | ) { 100 | assertThat(task(":$formatTaskName")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 101 | assertThat(task(":$mainSourceSetFormatTaskName")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 102 | assertThat(output).contains("Reusing configuration cache.") 103 | } 104 | } 105 | } 106 | 107 | @DisplayName("Should support configuration cache for git hook format install task") 108 | @CommonTest 109 | internal fun configurationCacheForGitHookFormatInstallTask(gradleVersion: GradleVersion) { 110 | project(gradleVersion) { 111 | projectPath.initGit() 112 | 113 | build( 114 | configurationCacheFlag, 115 | INSTALL_GIT_HOOK_FORMAT_TASK 116 | ) { 117 | assertThat(task(":$INSTALL_GIT_HOOK_FORMAT_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 118 | } 119 | 120 | build( 121 | configurationCacheFlag, 122 | INSTALL_GIT_HOOK_FORMAT_TASK 123 | ) { 124 | assertThat(task(":$INSTALL_GIT_HOOK_FORMAT_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 125 | assertThat(output).contains("Reusing configuration cache.") 126 | } 127 | } 128 | } 129 | 130 | @DisplayName("Should support configuration cache for git hook check install task") 131 | @CommonTest 132 | internal fun configurationCacheForGitHookCheckInstallTask(gradleVersion: GradleVersion) { 133 | project(gradleVersion) { 134 | projectPath.initGit() 135 | 136 | build( 137 | configurationCacheFlag, 138 | INSTALL_GIT_HOOK_CHECK_TASK 139 | ) { 140 | assertThat(task(":$INSTALL_GIT_HOOK_CHECK_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 141 | } 142 | 143 | build( 144 | configurationCacheFlag, 145 | INSTALL_GIT_HOOK_CHECK_TASK 146 | ) { 147 | assertThat(task(":$INSTALL_GIT_HOOK_CHECK_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 148 | assertThat(output).contains("Reusing configuration cache.") 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/DisabledRulesTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.api.logging.Logging 5 | import org.gradle.testkit.runner.TaskOutcome 6 | import org.gradle.util.GradleVersion 7 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 8 | import org.jlleitschuh.gradle.ktlint.testdsl.build 9 | import org.jlleitschuh.gradle.ktlint.testdsl.project 10 | import org.junit.jupiter.api.DisplayName 11 | import org.junit.jupiter.params.ParameterizedTest 12 | import org.junit.jupiter.params.provider.ArgumentsSource 13 | 14 | @GradleTestVersions 15 | class DisabledRulesTest : AbstractPluginTest() { 16 | private val logger = Logging.getLogger(DisabledRulesTest::class.java) 17 | 18 | @DisplayName("Should lint without errors when 'final-newline' rule is disabled via editorconfig") 19 | @ParameterizedTest(name = "{0} with KtLint {1}: {displayName}") 20 | @ArgumentsSource(KtLintSupportedVersionsTest.SupportedKtlintVersionsProvider::class) 21 | fun lintDisabledRuleFinalNewlineEditorconfig(gradleVersion: GradleVersion, ktLintVersion: String) { 22 | project(gradleVersion) { 23 | editorConfig.appendText( 24 | """ 25 | root = true 26 | 27 | [*.kt] 28 | ktlint_standard_final-newline = disabled 29 | """ 30 | ) 31 | 32 | //language=Groovy 33 | buildGradle.appendText( 34 | """ 35 | ktlint.version = "$ktLintVersion" 36 | """ 37 | ) 38 | 39 | createSourceFile( 40 | "src/main/kotlin/CleanSource.kt", 41 | "val foo = \"bar\"" 42 | ) 43 | 44 | build(CHECK_PARENT_TASK_NAME) { 45 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 46 | assertThat(output).doesNotContain("Property 'ktlint_disabled_rules' is deprecated") 47 | assertThat(output).doesNotContain("Property 'disabled_rules' is deprecated") 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KotlinJsPluginTests.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.testkit.runner.TaskOutcome 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 7 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 9 | import org.jlleitschuh.gradle.ktlint.testdsl.build 10 | import org.jlleitschuh.gradle.ktlint.testdsl.buildAndFail 11 | import org.jlleitschuh.gradle.ktlint.testdsl.project 12 | import org.jlleitschuh.gradle.ktlint.testdsl.projectSetup 13 | import org.junit.jupiter.api.DisplayName 14 | import java.io.File 15 | 16 | /** 17 | * Contains all tests related to "org.jetbrains.kotlin.js" plugin support. 18 | */ 19 | @GradleTestVersions 20 | class KotlinJsPluginTests : AbstractPluginTest() { 21 | private fun jsProjectSetup(): (File) -> Unit = { 22 | projectSetup("js").invoke(it) 23 | 24 | //language=Groovy 25 | it.resolve("build.gradle").appendText( 26 | """ 27 | 28 | kotlin { 29 | js(IR) { 30 | nodejs() 31 | } 32 | } 33 | """.trimIndent() 34 | ) 35 | } 36 | 37 | @DisplayName("Should add check tasks") 38 | @CommonTest 39 | fun addCheckTasks(gradleVersion: GradleVersion) { 40 | project(gradleVersion, projectSetup = jsProjectSetup()) { 41 | build("-m", CHECK_PARENT_TASK_NAME) { 42 | val ktlintTasks = output.lineSequence().toList() 43 | 44 | assertThat(ktlintTasks).anySatisfy { 45 | assertThat(it).contains(mainSourceSetCheckTaskName) 46 | } 47 | assertThat(ktlintTasks).anySatisfy { 48 | assertThat(it).contains( 49 | GenerateReportsTask.generateNameForSourceSets( 50 | "test", 51 | GenerateReportsTask.LintType.CHECK 52 | ) 53 | ) 54 | } 55 | } 56 | } 57 | } 58 | 59 | @DisplayName("Should add format tasks") 60 | @CommonTest 61 | fun addFormatTasks(gradleVersion: GradleVersion) { 62 | project(gradleVersion, projectSetup = jsProjectSetup()) { 63 | build("-m", FORMAT_PARENT_TASK_NAME) { 64 | val ktlintTasks = output.lineSequence().toList() 65 | 66 | assertThat(ktlintTasks).anySatisfy { 67 | assertThat(it).contains(mainSourceSetFormatTaskName) 68 | } 69 | assertThat(ktlintTasks).anySatisfy { 70 | assertThat(it).contains( 71 | GenerateReportsTask.generateNameForSourceSets( 72 | "test", 73 | GenerateReportsTask.LintType.FORMAT 74 | ) 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | 81 | @DisplayName("Should fail check task on un-formatted sources") 82 | @CommonTest 83 | fun failOnStyleViolation(gradleVersion: GradleVersion) { 84 | project(gradleVersion, projectSetup = jsProjectSetup()) { 85 | withFailingSources() 86 | 87 | buildAndFail(CHECK_PARENT_TASK_NAME) { 88 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FAILED) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KotlinMultiplatformPluginTests.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.util.GradleVersion 5 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 6 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 7 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 8 | import org.jlleitschuh.gradle.ktlint.testdsl.build 9 | import org.jlleitschuh.gradle.ktlint.testdsl.project 10 | import org.jlleitschuh.gradle.ktlint.testdsl.projectSetup 11 | import org.junit.jupiter.api.DisplayName 12 | import java.io.File 13 | 14 | /** 15 | * Contains all tests related to "org.jetbrains.kotlin.multiplatform" plugin support. 16 | */ 17 | @GradleTestVersions 18 | class KotlinMultiplatformPluginTests : AbstractPluginTest() { 19 | private fun multiplatformProjectSetup(): (File) -> Unit = { 20 | projectSetup("multiplatform").invoke(it) 21 | 22 | //language=Groovy 23 | it.resolve("build.gradle").appendText( 24 | """ 25 | | 26 | |kotlin { 27 | | js { 28 | | browser() 29 | | } 30 | | jvm() 31 | |} 32 | """.trimMargin() 33 | ) 34 | } 35 | 36 | @DisplayName("Should add check on all sources") 37 | @CommonTest 38 | fun addCheckTasks(gradleVersion: GradleVersion) { 39 | project(gradleVersion, projectSetup = multiplatformProjectSetup()) { 40 | build("-m", CHECK_PARENT_TASK_NAME) { 41 | val ktlintTasks = output.lineSequence().toList() 42 | 43 | assertThat(ktlintTasks).anySatisfy { 44 | assertThat(it).contains( 45 | GenerateReportsTask.generateNameForSourceSets( 46 | "commonMain", 47 | GenerateReportsTask.LintType.CHECK 48 | ) 49 | ) 50 | } 51 | assertThat(ktlintTasks).anySatisfy { 52 | assertThat(it).contains( 53 | GenerateReportsTask.generateNameForSourceSets( 54 | "JsMain", 55 | GenerateReportsTask.LintType.CHECK 56 | ) 57 | ) 58 | } 59 | assertThat(ktlintTasks).anySatisfy { 60 | assertThat(it).contains( 61 | GenerateReportsTask.generateNameForSourceSets( 62 | "JvmMain", 63 | GenerateReportsTask.LintType.CHECK 64 | ) 65 | ) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtLintClassesUsageScopeTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import com.tngtech.archunit.junit.AnalyzeClasses 4 | import com.tngtech.archunit.junit.ArchTest 5 | import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses 6 | 7 | @AnalyzeClasses( 8 | packages = ["org.jlleitschuh.gradle.ktlint.."] 9 | ) 10 | @Suppress("PropertyName") 11 | internal class KtLintClassesUsageScopeTest { 12 | @ArchTest 13 | val `Non-worker plugin classes should not use ktlint classes` = noClasses() 14 | .that() 15 | .resideInAnyPackage( 16 | "org.jlleitschuh.gradle.ktlint", 17 | "org.jlleitschuh.gradle.ktlint.android", 18 | "org.jlleitschuh.gradle.ktlint.tasks" 19 | ) 20 | .should() 21 | .dependOnClassesThat() 22 | .resideInAPackage("com.pinterest.ktlint..") 23 | } 24 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtlintBaselineSupportTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.testkit.runner.TaskOutcome 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateBaselineTask 7 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 9 | import org.jlleitschuh.gradle.ktlint.testdsl.TestProject.Companion.FAIL_SOURCE_FILE 10 | import org.jlleitschuh.gradle.ktlint.testdsl.build 11 | import org.jlleitschuh.gradle.ktlint.testdsl.buildAndFail 12 | import org.jlleitschuh.gradle.ktlint.testdsl.project 13 | import org.junit.jupiter.api.DisplayName 14 | import org.junit.jupiter.params.ParameterizedTest 15 | import org.junit.jupiter.params.provider.ArgumentsSource 16 | import java.io.File 17 | 18 | @GradleTestVersions 19 | class KtlintBaselineSupportTest : AbstractPluginTest() { 20 | 21 | @DisplayName("Should generate empty baseline file on style violations") 22 | @CommonTest 23 | internal fun emptyBaseline(gradleVersion: GradleVersion) { 24 | project(gradleVersion) { 25 | withCleanSources() 26 | 27 | build(GenerateBaselineTask.NAME) { 28 | assertThat(task(":${GenerateBaselineTask.NAME}")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 29 | val baselineFile = projectPath.defaultBaselineFile 30 | assertThat(baselineFile).exists() 31 | assertThat(baselineFile.readText()).isEqualToNormalizingNewlines( 32 | """ 33 | 34 | 35 | 36 | 37 | """.trimIndent() 38 | ) 39 | } 40 | } 41 | } 42 | 43 | @DisplayName("Should generate baseline with found style violations") 44 | @CommonTest 45 | fun generateBaseline(gradleVersion: GradleVersion) { 46 | project(gradleVersion) { 47 | withFailingSources() 48 | withFailingKotlinScript() 49 | 50 | build(GenerateBaselineTask.NAME) { 51 | assertThat(task(":${GenerateBaselineTask.NAME}")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 52 | val baselineFile = projectPath.defaultBaselineFile 53 | assertThat(baselineFile).exists() 54 | // WARN: baseline uses tabs for indentation! 55 | //language=xml 56 | assertThat(baselineFile.readText()).isEqualToIgnoringWhitespace( 57 | """ 58 | | 59 | | 60 | | 61 | | 62 | | 63 | | 64 | | 65 | | 66 | | 67 | | 68 | | 69 | | 70 | """.trimMargin() 71 | ) 72 | } 73 | } 74 | } 75 | 76 | @DisplayName("Should overwrite existing baseline file") 77 | @CommonTest 78 | fun overwriteBaselineFile(gradleVersion: GradleVersion) { 79 | project(gradleVersion) { 80 | withFailingSources() 81 | 82 | build(GenerateBaselineTask.NAME) 83 | 84 | removeSourceFile(FAIL_SOURCE_FILE) 85 | 86 | build(GenerateBaselineTask.NAME) { 87 | assertThat(task(":${GenerateBaselineTask.NAME}")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 88 | val baselineFile = projectPath.defaultBaselineFile 89 | assertThat(baselineFile).exists() 90 | assertThat(baselineFile.readText()).isEqualToNormalizingNewlines( 91 | """ 92 | 93 | 94 | 95 | 96 | """.trimIndent() 97 | ) 98 | } 99 | } 100 | } 101 | 102 | @DisplayName("Should consider existing issues in baseline") 103 | @ParameterizedTest(name = "{0} with KtLint {1}: {displayName}") 104 | @ArgumentsSource(KtLintSupportedVersionsTest.SupportedKtlintVersionsProvider::class) 105 | fun existingIssueFilteredByBaseline(gradleVersion: GradleVersion, ktLintVersion: String) { 106 | project(gradleVersion) { 107 | //language=Groovy 108 | buildGradle.appendText( 109 | """ 110 | ktlint.version = "$ktLintVersion" 111 | ktlint.debug = true 112 | """.trimIndent() 113 | ) 114 | withFailingSources() 115 | 116 | build(GenerateBaselineTask.NAME) 117 | build(CHECK_PARENT_TASK_NAME) { 118 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 119 | } 120 | } 121 | } 122 | 123 | @DisplayName("Check task should still fail on file style violation that is not present in the baseline") 124 | @CommonTest 125 | fun failOnNewStyleViolation(gradleVersion: GradleVersion) { 126 | project(gradleVersion) { 127 | withFailingSources() 128 | build(GenerateBaselineTask.NAME) 129 | 130 | withFailingKotlinScript() 131 | buildAndFail(CHECK_PARENT_TASK_NAME) { 132 | assertThat(task(":$kotlinScriptCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FAILED) 133 | } 134 | } 135 | } 136 | 137 | private val File.defaultBaselineFile 138 | get() = resolve("config") 139 | .resolve("ktlint") 140 | .resolve("baseline.xml") 141 | } 142 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPluginVersionTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.testkit.runner.TaskOutcome 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.tasks.LoadReportersTask 7 | import org.jlleitschuh.gradle.ktlint.testdsl.CommonTest 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 9 | import org.jlleitschuh.gradle.ktlint.testdsl.build 10 | import org.jlleitschuh.gradle.ktlint.testdsl.buildAndFail 11 | import org.jlleitschuh.gradle.ktlint.testdsl.project 12 | import org.junit.jupiter.api.DisplayName 13 | import java.io.File 14 | 15 | @GradleTestVersions 16 | class KtlintPluginVersionTest : AbstractPluginTest() { 17 | 18 | private fun File.useKtlintVersion(version: String) { 19 | //language=Groovy 20 | appendText( 21 | """ 22 | buildDir = file("directory with spaces") 23 | 24 | ktlint { 25 | version = "$version" 26 | } 27 | """.trimIndent() 28 | ) 29 | } 30 | 31 | @DisplayName("Should allow to use KtLint version 1.0.1") 32 | @CommonTest 33 | fun allowSupportedMinimalKtLintVersion(gradleVersion: GradleVersion) { 34 | project(gradleVersion) { 35 | withCleanSources() 36 | buildGradle.useKtlintVersion("1.0.1") 37 | 38 | build(CHECK_PARENT_TASK_NAME) { 39 | assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 40 | } 41 | } 42 | } 43 | 44 | @DisplayName("Should fail the build on using KtLint version <1") 45 | @CommonTest 46 | fun failOnUnsupportedOldKtLintVersion(gradleVersion: GradleVersion) { 47 | project(gradleVersion) { 48 | withCleanSources() 49 | buildGradle.useKtlintVersion("0.50.0") 50 | buildAndFail(CHECK_PARENT_TASK_NAME) { 51 | assertThat(task(":${LoadReportersTask.TASK_NAME}")?.outcome).isEqualTo(TaskOutcome.FAILED) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtilTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.junit.jupiter.api.Assertions 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.io.TempDir 6 | import java.io.File 7 | 8 | internal class PluginUtilTest { 9 | @TempDir 10 | lateinit var temporaryFolder: File 11 | 12 | @Test 13 | fun `test isRootEditorConfig returns true`() { 14 | temporaryFolder.resolve("test-pos.txt").apply { 15 | createNewFile() 16 | writeText( 17 | """ 18 | root=true 19 | """.trimIndent() 20 | ) 21 | Assertions.assertTrue(this.toPath().isRootEditorConfig()) { 22 | "correctly detects root" 23 | } 24 | delete() 25 | } 26 | } 27 | 28 | @Test 29 | fun `test isRootEditorConfig returns false`() { 30 | temporaryFolder.resolve("test-neg.txt").apply { 31 | createNewFile() 32 | writeText( 33 | """ 34 | [*.kt] 35 | """.trimIndent() 36 | ) 37 | Assertions.assertFalse(this.toPath().isRootEditorConfig()) { 38 | "correctly does not match non-root file" 39 | } 40 | delete() 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/TaskConfigurationAvoidanceTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.gradle.util.GradleVersion 4 | import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask 5 | import org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask 6 | import org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask 7 | import org.jlleitschuh.gradle.ktlint.tasks.LoadReportersTask 8 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleArgumentsProvider 9 | import org.jlleitschuh.gradle.ktlint.testdsl.GradleTestVersions 10 | import org.jlleitschuh.gradle.ktlint.testdsl.build 11 | import org.jlleitschuh.gradle.ktlint.testdsl.project 12 | import org.junit.jupiter.api.DisplayName 13 | import org.junit.jupiter.api.extension.ExtensionContext 14 | import org.junit.jupiter.params.ParameterizedTest 15 | import org.junit.jupiter.params.provider.Arguments 16 | import org.junit.jupiter.params.provider.ArgumentsSource 17 | import java.util.stream.Stream 18 | import kotlin.streams.asStream 19 | 20 | @GradleTestVersions 21 | class TaskConfigurationAvoidanceTest : AbstractPluginTest() { 22 | 23 | @DisplayName("should support configuration avoidance") 24 | @ParameterizedTest(name = "{0} - task {1} {displayName}") 25 | @ArgumentsSource(PluginTasksNamesProvider::class) 26 | internal fun checkTaskAvoidance( 27 | gradleVersion: GradleVersion, 28 | taskName: String 29 | ) { 30 | project(gradleVersion) { 31 | //language=Groovy 32 | buildGradle.appendText( 33 | """ 34 | 35 | tasks 36 | .withType(org.jlleitschuh.gradle.ktlint.tasks.$taskName.class) 37 | .configureEach { 38 | throw new RuntimeException("Created on configuration phase") 39 | } 40 | """.trimIndent() 41 | ) 42 | 43 | build("help", "-s") 44 | } 45 | } 46 | 47 | class PluginTasksNamesProvider : GradleArgumentsProvider() { 48 | private val pluginTaskNames = listOf( 49 | LoadReportersTask::class.simpleName!!, 50 | GenerateReportsTask::class.simpleName!!, 51 | KtLintCheckTask::class.simpleName!!, 52 | KtLintFormatTask::class.simpleName!! 53 | ) 54 | 55 | override fun provideArguments( 56 | context: ExtensionContext 57 | ): Stream { 58 | return getGradleVersions(context) 59 | .flatMap { gradleVersion -> 60 | pluginTaskNames.map { gradleVersion to it }.asSequence() 61 | } 62 | .map { 63 | Arguments.of(it.first, it.second) 64 | } 65 | .asStream() 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/TestsCommon.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.eclipse.jgit.lib.RepositoryBuilder 4 | import org.intellij.lang.annotations.Language 5 | import java.io.File 6 | 7 | fun File.buildFile() = resolve("build.gradle") 8 | 9 | fun File.ktlintBuildDir() = resolve("build/ktlint") 10 | 11 | @Language("Groovy") 12 | private fun pluginsBlockWithMainPluginAndKotlinPlugin( 13 | kotlinPluginId: String, 14 | kotlinVersion: String? = null 15 | ) = 16 | """ 17 | plugins { 18 | id '$kotlinPluginId'${if (kotlinVersion != null) " version '$kotlinVersion'" else ""} 19 | id 'org.jlleitschuh.gradle.ktlint' 20 | } 21 | """.trimIndent() 22 | 23 | fun File.defaultProjectSetup(kotlinVersion: String? = null) { 24 | kotlinPluginProjectSetup("org.jetbrains.kotlin.jvm", kotlinVersion) 25 | } 26 | 27 | fun File.kotlinPluginProjectSetup( 28 | kotlinPluginId: String, 29 | kotlinPluginVersion: String? = null 30 | ) { 31 | //language=Groovy 32 | buildFile().writeText( 33 | """ 34 | ${pluginsBlockWithMainPluginAndKotlinPlugin(kotlinPluginId, kotlinPluginVersion)} 35 | 36 | repositories { 37 | gradlePluginPortal() 38 | } 39 | """.trimIndent() 40 | ) 41 | } 42 | 43 | internal fun File.initGit(): File { 44 | val repo = RepositoryBuilder().setWorkTree(this).setMustExist(false).build() 45 | repo.create() 46 | return repo.directory 47 | } 48 | 49 | internal fun File.initGitWithoutHooksDir(): File { 50 | val repo = RepositoryBuilder().setWorkTree(this).setMustExist(false).build() 51 | repo.create() 52 | assert(repo.directory.resolve("hooks").delete()) 53 | return repo.directory 54 | } 55 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/UnsupportedGradleTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.gradle.util.GradleVersion 5 | import org.jlleitschuh.gradle.ktlint.testdsl.buildAndFail 6 | import org.jlleitschuh.gradle.ktlint.testdsl.getMajorJavaVersion 7 | import org.jlleitschuh.gradle.ktlint.testdsl.project 8 | import org.jlleitschuh.gradle.ktlint.testdsl.projectSetup 9 | import org.junit.jupiter.api.Assumptions 10 | import org.junit.jupiter.api.DisplayName 11 | import org.junit.jupiter.api.Test 12 | import org.junit.jupiter.api.condition.DisabledOnOs 13 | import org.junit.jupiter.api.condition.OS 14 | 15 | class UnsupportedGradleTest : AbstractPluginTest() { 16 | @DisplayName("Should raise exception on applying plugin for build using unsupported Gradle version.") 17 | @Test 18 | @DisabledOnOs(OS.WINDOWS) 19 | internal fun errorOnOldGradleVersion() { 20 | /** 21 | * This test ensures the proper error message is printed when an unsupported version of gradle is used. 22 | * However, our minimum version of gradle is still 7.x, which will not run at all on Java 21. 23 | * Gradle 8.5 is needed for Java 21. 24 | * So if java 21 is currently being used, skip this test 25 | */ 26 | Assumptions.assumeFalse(getMajorJavaVersion() >= 21) 27 | 28 | project( 29 | gradleVersion = GradleVersion.version("7.4.1"), 30 | projectSetup = projectSetup("jvm", "1.9.22") 31 | ) { 32 | buildAndFail(CHECK_PARENT_TASK_NAME) { 33 | assertThat(output).contains( 34 | "Current version of plugin supports minimal Gradle version: " + 35 | KtlintBasePlugin.LOWEST_SUPPORTED_GRADLE_VERSION 36 | ) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/android/KtlintPluginAndroidTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import net.swiftzer.semver.SemVer 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.assertj.core.api.hasOutcome 6 | import org.gradle.testkit.runner.TaskOutcome 7 | import org.gradle.util.GradleVersion 8 | import org.jlleitschuh.gradle.ktlint.AbstractPluginTest 9 | import org.jlleitschuh.gradle.ktlint.CHECK_PARENT_TASK_NAME 10 | import org.jlleitschuh.gradle.ktlint.testdsl.TestVersions 11 | import org.jlleitschuh.gradle.ktlint.testdsl.androidProjectSetup 12 | import org.jlleitschuh.gradle.ktlint.testdsl.build 13 | import org.jlleitschuh.gradle.ktlint.testdsl.getMajorJavaVersion 14 | import org.jlleitschuh.gradle.ktlint.testdsl.project 15 | import org.junit.jupiter.api.Assumptions.assumeFalse 16 | import org.junit.jupiter.params.ParameterizedTest 17 | import org.junit.jupiter.params.provider.EnumSource 18 | 19 | class KtlintPluginAndroidTest : AbstractPluginTest() { 20 | 21 | @ParameterizedTest 22 | @EnumSource(AndroidTestInput::class) 23 | fun `ktlint pass src java`(input: AndroidTestInput) { 24 | assumeFalse(input.minimumJava != null && getMajorJavaVersion() < input.minimumJava) 25 | assumeFalse(input.maximumJava != null && getMajorJavaVersion() > input.maximumJava) 26 | project( 27 | input.gradleVersion, 28 | projectSetup = androidProjectSetup(input.agpVersion, input.kotlinVersion, input.ktlintVersion) 29 | ) { 30 | withCleanSources("src/main/java/CleanSource.kt") 31 | build(CHECK_PARENT_TASK_NAME) { 32 | assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) 33 | assertThat(task(":$kotlinScriptCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) 34 | assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS) 35 | } 36 | } 37 | } 38 | 39 | @ParameterizedTest 40 | @EnumSource(AndroidTestInput::class) 41 | fun `ktlint pass src kotlin`(input: AndroidTestInput) { 42 | assumeFalse(input.minimumJava != null && getMajorJavaVersion() < input.minimumJava) 43 | assumeFalse(input.maximumJava != null && getMajorJavaVersion() > input.maximumJava) 44 | project( 45 | input.gradleVersion, 46 | projectSetup = androidProjectSetup(input.agpVersion, input.kotlinVersion, input.ktlintVersion) 47 | ) { 48 | withCleanSources("src/main/kotlin/CleanSource.kt") 49 | if (SemVer.parse(input.agpVersion) < SemVer(7)) { 50 | build(CHECK_PARENT_TASK_NAME) { 51 | assertThat(output).contains("In AGP <7 kotlin source directories are not auto-detected.") 52 | assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SKIPPED) 53 | assertThat(task(":$kotlinScriptCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) 54 | assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS) 55 | } 56 | } else { 57 | build(CHECK_PARENT_TASK_NAME) { 58 | assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) 59 | assertThat(task(":$kotlinScriptCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) 60 | assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS) 61 | } 62 | } 63 | } 64 | } 65 | 66 | enum class AndroidTestInput( 67 | val gradleVersion: GradleVersion, 68 | val agpVersion: String, 69 | val kotlinVersion: String, 70 | val ktlintVersion: String? = null, 71 | val minimumJava: Int? = null, 72 | val maximumJava: Int? = null 73 | ) { 74 | MIN( 75 | GradleVersion.version(TestVersions.minSupportedGradleVersion), 76 | TestVersions.minAgpVersion, 77 | TestVersions.minSupportedKotlinPluginVersion, 78 | // old AGP doesn't properly set variant info which ktlint >= 1 requires 79 | "1.0.1", 80 | maximumJava = 17 81 | ), 82 | AGP_7_4( 83 | GradleVersion.version(TestVersions.minSupportedGradleVersion), 84 | "7.4.2", 85 | TestVersions.minSupportedKotlinPluginVersion, 86 | // old AGP doesn't properly set variant info which ktlint >= 1 requires 87 | "1.0.1", 88 | minimumJava = 11, 89 | maximumJava = 17 90 | ), 91 | MAX_GRADLE_MIN_AGP( 92 | GradleVersion.version(TestVersions.maxSupportedGradleVersion), 93 | TestVersions.minAgpVersion, 94 | TestVersions.minSupportedKotlinPluginVersion 95 | ), 96 | MAX( 97 | GradleVersion.version(TestVersions.maxSupportedGradleVersion), 98 | TestVersions.maxAgpVersion, 99 | TestVersions.maxSupportedKotlinPluginVersion, 100 | minimumJava = 17 101 | ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/AndroidSetup.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.testdsl 2 | 3 | import net.swiftzer.semver.SemVer 4 | import java.io.File 5 | 6 | fun androidProjectSetup( 7 | agpVersion: String, 8 | kotlinPluginVersion: String, 9 | ktlintVersion: String? = null 10 | ): (File) -> Unit = { 11 | val ktLintOverride = ktlintVersion?.let { "ktlint { version = \"$it\" }\n" } ?: "" 12 | val setNamespace = if ((SemVer.parse(agpVersion) >= SemVer(7))) { 13 | " namespace = \"com.example.myapp\"\n" 14 | } else { 15 | "" 16 | } 17 | //language=Groovy 18 | it.resolve("build.gradle").writeText( 19 | """ 20 | |plugins { 21 | | id("com.android.application") 22 | | id("org.jetbrains.kotlin.android") 23 | | id("org.jlleitschuh.gradle.ktlint") 24 | |} 25 | | 26 | |repositories { 27 | | mavenCentral() 28 | |} 29 | |android { 30 | | compileSdk = 33 31 | |$setNamespace} 32 | |$ktLintOverride 33 | """.trimMargin() 34 | ) 35 | 36 | // before 4.2.0, AGP did not properly publish metadata for id resolution 37 | val oldAgpHack = if ((SemVer.parse(agpVersion) < SemVer(4, 2))) { 38 | """ 39 | | resolutionStrategy { 40 | | eachPlugin { 41 | | when (requested.id.id) { 42 | | "com.android.application" -> useModule("com.android.tools.build:gradle:$agpVersion") 43 | | } 44 | | } 45 | | } 46 | | 47 | """.trimMargin() 48 | } else { 49 | "" 50 | } 51 | val newAgp = if ((SemVer.parse(agpVersion) < SemVer(4, 2))) { 52 | "" 53 | } else { 54 | " id(\"com.android.application\") version \"$agpVersion\"\n " 55 | } 56 | 57 | //language=Groovy 58 | it.resolve("settings.gradle.kts").writeText( 59 | """ 60 | |pluginManagement { 61 | | repositories { 62 | | mavenLocal() 63 | | gradlePluginPortal() 64 | | google() 65 | | mavenCentral() 66 | | } 67 | | 68 | | plugins { 69 | | id("org.jetbrains.kotlin.android") version ("$kotlinPluginVersion") 70 | | id("org.jlleitschuh.gradle.ktlint") version ("${TestVersions.pluginVersion}") 71 | | $newAgp} 72 | |$oldAgpHack} 73 | | 74 | """.trimMargin() 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/JavaUtil.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.testdsl 2 | 3 | fun getMajorJavaVersion(): Int { 4 | val specVersion = System.getProperty("java.specification.version") 5 | return if (specVersion.startsWith("1.")) { 6 | specVersion.split(".")[1].toInt() 7 | } else { 8 | return specVersion.toInt() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestAnnotations.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.testdsl 2 | 3 | import org.gradle.util.GradleVersion 4 | import org.jlleitschuh.gradle.ktlint.KtlintPlugin 5 | import org.junit.jupiter.api.extension.ExtensionContext 6 | import org.junit.jupiter.params.provider.Arguments 7 | import org.junit.jupiter.params.provider.ArgumentsProvider 8 | import java.util.stream.Stream 9 | import kotlin.streams.asStream 10 | 11 | @Suppress("ConstPropertyName") 12 | object TestVersions { 13 | const val minSupportedGradleVersion = "7.6.3" // lowest version for testing 14 | const val maxSupportedGradleVersion = "8.13" 15 | val pluginVersion = System.getProperty("project.version") 16 | ?: KtlintPlugin::class.java.`package`.implementationVersion 17 | ?: error("Unable to determine plugin version.") 18 | const val minSupportedKotlinPluginVersion = "1.6.21" 19 | const val maxSupportedKotlinPluginVersion = "2.1.21" 20 | const val minAgpVersion = "4.1.0" 21 | const val maxAgpVersion = "8.8.0" 22 | } 23 | 24 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) 25 | @Retention(AnnotationRetention.RUNTIME) 26 | annotation class GradleTestVersions( 27 | val minVersion: String = TestVersions.minSupportedGradleVersion, 28 | val maxVersion: String = TestVersions.maxSupportedGradleVersion, 29 | val additionalVersions: Array = [] 30 | ) 31 | 32 | open class GradleArgumentsProvider : ArgumentsProvider { 33 | override fun provideArguments( 34 | context: ExtensionContext 35 | ): Stream { 36 | return getGradleVersions(context) 37 | .map { Arguments.of(it) } 38 | .asStream() 39 | } 40 | 41 | fun getGradleVersions( 42 | context: ExtensionContext 43 | ): Sequence { 44 | val versionsAnnotation: GradleTestVersions = context 45 | .testMethod 46 | .get() 47 | .annotations 48 | .filterIsInstance() 49 | .firstOrNull() 50 | ?: context.testClass.get().annotations.filterIsInstance().first() 51 | 52 | val minGradleVersion = if (getMajorJavaVersion() >= 21) { 53 | // Gradle 8.5 is needed to run on Java 21 54 | GradleVersion.version("8.5") 55 | } else { 56 | GradleVersion.version(versionsAnnotation.minVersion) 57 | } 58 | val maxGradleVersion = GradleVersion.version(versionsAnnotation.maxVersion) 59 | val additionalGradleVersions = versionsAnnotation 60 | .additionalVersions 61 | .map(GradleVersion::version) 62 | 63 | additionalGradleVersions.forEach { 64 | assert(it in minGradleVersion..maxGradleVersion) { 65 | "Additional Gradle version ${it.version} should be between ${minGradleVersion.version} " + 66 | "and ${maxGradleVersion.version}" 67 | } 68 | } 69 | 70 | return sequenceOf(minGradleVersion, *additionalGradleVersions.toTypedArray(), maxGradleVersion) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestDsl.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.testdsl 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.GradleRunner 5 | import org.gradle.util.GradleVersion 6 | import org.jlleitschuh.gradle.ktlint.AbstractPluginTest 7 | import org.jlleitschuh.gradle.ktlint.testdsl.TestVersions.maxSupportedKotlinPluginVersion 8 | import java.io.File 9 | 10 | fun AbstractPluginTest.project( 11 | gradleVersion: GradleVersion, 12 | projectPath: File = projectRoot, 13 | projectSetup: (File) -> Unit = defaultProjectSetup(), 14 | test: TestProject.() -> Unit = {} 15 | ): TestProject { 16 | projectSetup(projectPath) 17 | 18 | val gradleRunner = GradleRunner.create() 19 | .withGradleVersion(gradleVersion.version) 20 | .withTestKitDir(sharedTestKitDir) 21 | .forwardOutput() 22 | .withProjectDir(projectPath) 23 | 24 | val testProject = TestProject( 25 | gradleRunner, 26 | gradleVersion, 27 | projectPath 28 | ) 29 | 30 | testProject.test() 31 | return testProject 32 | } 33 | 34 | class TestProject( 35 | val gradleRunner: GradleRunner, 36 | val gradleVersion: GradleVersion, 37 | val projectPath: File 38 | ) { 39 | val buildGradle get() = projectPath.resolve("build.gradle") 40 | val settingsGradle get() = projectPath.resolve("settings.gradle") 41 | val editorConfig get() = projectPath.resolve(".editorconfig") 42 | 43 | fun withCleanSources(filePath: String = CLEAN_SOURCES_FILE) { 44 | createSourceFile( 45 | filePath, 46 | """ 47 | |val foo = "bar" 48 | | 49 | """.trimMargin() 50 | ) 51 | } 52 | 53 | fun withFailingSources() { 54 | createSourceFile( 55 | FAIL_SOURCE_FILE, 56 | """ 57 | |val foo = "bar" 58 | | 59 | """.trimMargin() 60 | ) 61 | } 62 | 63 | fun withFailingMaxLineSources() { 64 | createSourceFile( 65 | FAIL_SOURCE_FILE, 66 | buildString { 67 | append("val nameOfVariable =") 68 | append("\n") 69 | append(" listOf(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2)") 70 | append("\n") 71 | } 72 | ) 73 | } 74 | 75 | fun withCleanKotlinScript() { 76 | createSourceFile( 77 | "kotlin-script.kts", 78 | """ 79 | |println("zzz") 80 | | 81 | """.trimMargin() 82 | ) 83 | } 84 | 85 | fun withFailingKotlinScript() { 86 | createSourceFile( 87 | "kotlin-script-fail.kts", 88 | """ 89 | |println("zzz")@ 90 | | 91 | """.trimMargin() 92 | .replace('@', ' ') 93 | ) 94 | } 95 | 96 | fun createSourceFile( 97 | sourceFilePath: String, 98 | contents: String 99 | ) { 100 | val sourceFile = projectPath.resolve(sourceFilePath) 101 | sourceFile.parentFile.mkdirs() 102 | sourceFile.writeText(contents) 103 | } 104 | 105 | fun restoreFailingSources() { 106 | val sourceFile = projectPath.resolve(FAIL_SOURCE_FILE) 107 | sourceFile.delete() 108 | withFailingSources() 109 | } 110 | 111 | fun removeSourceFile(sourceFilePath: String) { 112 | val sourceFile = projectPath.resolve(sourceFilePath) 113 | sourceFile.delete() 114 | } 115 | 116 | companion object { 117 | const val CLEAN_SOURCES_FILE = "src/main/kotlin/CleanSource.kt" 118 | const val FAIL_SOURCE_FILE = "src/main/kotlin/FailSource.kt" 119 | } 120 | } 121 | 122 | fun TestProject.build( 123 | vararg buildArguments: String, 124 | assertions: BuildResult.() -> Unit = {} 125 | ) { 126 | gradleRunner 127 | .withArguments(buildArguments.toList() + "--stacktrace") 128 | .build() 129 | .run { assertions() } 130 | } 131 | 132 | fun TestProject.buildAndFail( 133 | vararg buildArguments: String, 134 | assertions: BuildResult.() -> Unit = {} 135 | ) { 136 | gradleRunner 137 | .withArguments(buildArguments.toList() + "--stacktrace") 138 | .buildAndFail() 139 | .run { assertions() } 140 | } 141 | 142 | fun defaultProjectSetup(): (File) -> Unit = 143 | projectSetup("jvm") 144 | 145 | fun projectSetup( 146 | kotlinPluginType: String, 147 | kotlinPluginVersion: String = maxSupportedKotlinPluginVersion 148 | ): (File) -> Unit = { 149 | //language=Groovy 150 | it.resolve("build.gradle").writeText( 151 | """ 152 | |plugins { 153 | | id 'org.jetbrains.kotlin.$kotlinPluginType' 154 | | id 'org.jlleitschuh.gradle.ktlint' 155 | |} 156 | | 157 | |repositories { 158 | | mavenCentral() 159 | |} 160 | | 161 | """.trimMargin() 162 | ) 163 | 164 | //language=Groovy 165 | it.resolve("settings.gradle").writeText( 166 | """ 167 | |pluginManagement { 168 | | repositories { 169 | | mavenLocal() 170 | | gradlePluginPortal() 171 | | } 172 | | 173 | | plugins { 174 | | id 'org.jetbrains.kotlin.$kotlinPluginType' version '$kotlinPluginVersion' 175 | | id 'org.jlleitschuh.gradle.ktlint' version '${TestVersions.pluginVersion}' 176 | | } 177 | |} 178 | | 179 | """.trimMargin() 180 | ) 181 | } 182 | 183 | private val sharedTestKitDir = File(".") 184 | .resolve(".gradle-test-kit") 185 | .absoluteFile 186 | .also { 187 | if (!it.exists()) it.mkdir() 188 | } 189 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/worker/KtLintClassesSerializerKtTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.worker 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.io.TempDir 6 | import java.io.File 7 | import java.io.ObjectOutputStream 8 | 9 | class KtLintClassesSerializerKtTest { 10 | @TempDir 11 | lateinit var temporaryFolder: File 12 | 13 | @Test 14 | internal fun `loadAnyErrors can read files written by our error`() { 15 | val serializableLintError = SerializableLintError(14, 154, "test-rule", "details about error", false) 16 | val file = temporaryFolder.resolve("lintError.test") 17 | 18 | val input = ArrayList().apply { 19 | add( 20 | LintErrorResult( 21 | lintedFile = file, 22 | lintErrors = ArrayList>() 23 | .apply { add(serializableLintError to true) } 24 | ) 25 | ) 26 | } 27 | ObjectOutputStream(file.outputStream()).use { 28 | it.writeObject(input) 29 | } 30 | val actual = loadAnyErrors(file) 31 | assertThat(actual).isEqualTo(input) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/android-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | id("org.jlleitschuh.gradle.ktlint") 5 | } 6 | 7 | android { 8 | compileSdkVersion(30) 9 | 10 | buildFeatures.viewBinding = true 11 | 12 | defaultConfig { 13 | minSdkVersion(23) 14 | targetSdkVersion(30) 15 | versionCode = 1 16 | versionName = "1.0" 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | getByName("release") { 22 | isMinifyEnabled = false 23 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 24 | } 25 | } 26 | 27 | flavorDimensions("beer") 28 | productFlavors { 29 | register("weissbier") 30 | register("kellerbier") 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_1_8 35 | targetCompatibility = JavaVersion.VERSION_1_8 36 | } 37 | 38 | sourceSets { 39 | val kotlinAdditionalSourceSets = project.file("src/main/kotlin") 40 | findByName("main")?.java?.srcDirs(kotlinAdditionalSourceSets) 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation(libs.androidx.fragment) 46 | implementation(libs.androidx.recycler.view) 47 | implementation(libs.android.material) 48 | 49 | testImplementation(libs.junit) 50 | 51 | androidTestImplementation(libs.androidx.test.runner) 52 | androidTestImplementation(libs.androidx.espresso) 53 | } 54 | 55 | ktlint { 56 | android.set(true) 57 | outputColorName.set("RED") 58 | } 59 | -------------------------------------------------------------------------------- /samples/android-app/gradle.properties: -------------------------------------------------------------------------------- 1 | # Workaround for IDEA, see 2 | # https://stackoverflow.com/questions/46634835/cant-use-android-gradle-plugin-3-0-with-intellij-idea/46634836#46634836 3 | android.injected.build.model.only.versioned=3 -------------------------------------------------------------------------------- /samples/android-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # https://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /samples/android-app/src/androidTest/java/org/jlleitschuh/gradle/ktlint/android/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | /** 8 | * Instrumented test, which will execute on an Android device. 9 | * 10 | * See [testing documentation](https://d.android.com/tools/testing). 11 | */ 12 | class ExampleInstrumentedTest { 13 | @Test 14 | fun useAppContext() { 15 | // Context of the app under test. 16 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 17 | assertEquals("org.jlleitschuh.gradle.ktlint.android", appContext.packageName) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/android-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /samples/android-app/src/main/java/org/jlleitschuh/gradle/ktlint/android/ItemDetailActivity.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.MenuItem 7 | import androidx.appcompat.app.AppCompatActivity 8 | import com.google.android.material.snackbar.Snackbar 9 | import org.jlleitschuh.gradle.ktlint.android.databinding.ActivityItemDetailBinding 10 | 11 | /** 12 | * An activity representing a single Item detail screen. This 13 | * activity is only used on narrow width devices. On tablet-size devices, 14 | * item details are presented side-by-side with a list of items 15 | * in a [ItemListActivity]. 16 | */ 17 | class ItemDetailActivity : AppCompatActivity() { 18 | internal var viewBinding: ActivityItemDetailBinding? = null 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | viewBinding = ActivityItemDetailBinding.inflate(LayoutInflater.from(this)) 23 | setContentView(viewBinding?.root) 24 | setSupportActionBar(viewBinding?.detailToolbar) 25 | 26 | viewBinding?.fab?.setOnClickListener { view -> 27 | Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG) 28 | .setAction("Action", null).show() 29 | } 30 | 31 | // Show the Up button in the action bar. 32 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 33 | 34 | // savedInstanceState is non-null when there is fragment state 35 | // saved from previous configurations of this activity 36 | // (e.g. when rotating the screen from portrait to landscape). 37 | // In this case, the fragment will automatically be re-added 38 | // to its container so we don't need to manually add it. 39 | // For more information, see the Fragments API guide at: 40 | // 41 | // https://developer.android.com/guide/components/fragments.html 42 | // 43 | if (savedInstanceState == null) { 44 | // Create the detail fragment and add it to the activity 45 | // using a fragment transaction. 46 | val arguments = Bundle() 47 | arguments.putString(ItemDetailFragment.ARG_ITEM_ID, intent.getStringExtra(ItemDetailFragment.ARG_ITEM_ID)) 48 | val fragment = ItemDetailFragment() 49 | fragment.arguments = arguments 50 | supportFragmentManager.beginTransaction() 51 | .add(R.id.item_detail_container, fragment) 52 | .commit() 53 | } 54 | } 55 | 56 | override fun onOptionsItemSelected(item: MenuItem) = 57 | when (item.itemId) { 58 | android.R.id.home -> { 59 | // This ID represents the Home or Up button. In the case of this 60 | // activity, the Up button is shown. For 61 | // more details, see the Navigation pattern on Android Design: 62 | // 63 | // https://developer.android.com/design/patterns/navigation.html#up-vs-back 64 | 65 | navigateUpTo(Intent(this, ItemListActivity::class.java)) 66 | true 67 | } 68 | else -> super.onOptionsItemSelected(item) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /samples/android-app/src/main/java/org/jlleitschuh/gradle/ktlint/android/ItemDetailFragment.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import org.jlleitschuh.gradle.ktlint.android.databinding.ItemDetailBinding 9 | import org.jlleitschuh.gradle.ktlint.android.dummy.DummyContent 10 | 11 | /** 12 | * A fragment representing a single Item detail screen. 13 | * This fragment is either contained in a [ItemListActivity] 14 | * in two-pane mode (on tablets) or a [ItemDetailActivity] 15 | * on handsets. 16 | */ 17 | class ItemDetailFragment : Fragment() { 18 | /** 19 | * The dummy content this fragment is presenting. 20 | */ 21 | private var mItem: DummyContent.DummyItem? = null 22 | 23 | private var viewBinding: ItemDetailBinding? = null 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | 28 | if (arguments?.containsKey(ARG_ITEM_ID) == true) { 29 | // Load the dummy content specified by the fragment 30 | // arguments. In a real-world scenario, use a Loader 31 | // to load content from a content provider. 32 | mItem = DummyContent.ITEM_MAP[arguments?.getString(ARG_ITEM_ID)] 33 | mItem?.let { 34 | (activity as? ItemDetailActivity)?.viewBinding?.detailToolbar?.title = it.content 35 | } 36 | } 37 | } 38 | 39 | override fun onCreateView( 40 | inflater: LayoutInflater, 41 | container: ViewGroup?, 42 | savedInstanceState: Bundle?, 43 | ): View? { 44 | viewBinding = ItemDetailBinding.inflate(inflater, container, false) 45 | 46 | // Show the dummy content as text in a TextView. 47 | mItem?.let { 48 | viewBinding?.itemDetail?.text = it.details 49 | } 50 | 51 | return viewBinding?.root 52 | } 53 | 54 | override fun onDestroyView() { 55 | viewBinding = null 56 | super.onDestroyView() 57 | } 58 | 59 | companion object { 60 | /** 61 | * The fragment argument representing the item ID that this fragment 62 | * represents. 63 | */ 64 | const val ARG_ITEM_ID = "item_id" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /samples/android-app/src/main/java/org/jlleitschuh/gradle/ktlint/android/ItemListActivity.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.google.android.material.snackbar.Snackbar 12 | import org.jlleitschuh.gradle.ktlint.android.ItemListActivity.SimpleItemRecyclerViewAdapter.ViewHolder 13 | import org.jlleitschuh.gradle.ktlint.android.databinding.ActivityItemListBinding 14 | import org.jlleitschuh.gradle.ktlint.android.dummy.DummyContent 15 | 16 | /** 17 | * An activity representing a list of Pings. This activity 18 | * has different presentations for handset and tablet-size devices. On 19 | * handsets, the activity presents a list of items, which when touched, 20 | * lead to a [ItemDetailActivity] representing 21 | * item details. On tablets, the activity presents the list of items and 22 | * item details side-by-side using two vertical panes. 23 | */ 24 | class ItemListActivity : AppCompatActivity() { 25 | private var viewBinding: ActivityItemListBinding? = null 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | viewBinding = ActivityItemListBinding.inflate(LayoutInflater.from(this)) 30 | setContentView(viewBinding?.root) 31 | 32 | setSupportActionBar(viewBinding?.toolbar) 33 | viewBinding?.toolbar?.title = title 34 | 35 | viewBinding?.fab?.setOnClickListener { view -> 36 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 37 | .setAction("Action", null).show() 38 | } 39 | 40 | setupRecyclerView(viewBinding?.itemListContainer?.itemList) 41 | } 42 | 43 | private fun setupRecyclerView(recyclerView: RecyclerView?) { 44 | recyclerView?.adapter = SimpleItemRecyclerViewAdapter(DummyContent.ITEMS) 45 | } 46 | 47 | class SimpleItemRecyclerViewAdapter( 48 | private val mValues: List, 49 | ) : RecyclerView.Adapter() { 50 | private val mOnClickListener: View.OnClickListener 51 | 52 | init { 53 | mOnClickListener = 54 | View.OnClickListener { v -> 55 | val item = v.tag as DummyContent.DummyItem 56 | val intent = 57 | Intent(v.context, ItemDetailActivity::class.java).apply { 58 | putExtra(ItemDetailFragment.ARG_ITEM_ID, item.id) 59 | } 60 | v.context.startActivity(intent) 61 | } 62 | } 63 | 64 | override fun onCreateViewHolder( 65 | parent: ViewGroup, 66 | viewType: Int, 67 | ): ViewHolder { 68 | val view = 69 | LayoutInflater.from(parent.context) 70 | .inflate(R.layout.item_list_content, parent, false) 71 | return ViewHolder(view) 72 | } 73 | 74 | override fun onBindViewHolder( 75 | holder: ViewHolder, 76 | position: Int, 77 | ) { 78 | val item = mValues[position] 79 | holder.mIdView.text = item.id 80 | holder.mContentView.text = item.content 81 | 82 | with(holder.itemView) { 83 | tag = item 84 | setOnClickListener(mOnClickListener) 85 | } 86 | } 87 | 88 | override fun getItemCount(): Int { 89 | return mValues.size 90 | } 91 | 92 | inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView) { 93 | val mIdView: TextView = mView.findViewById(R.id.id_text) 94 | val mContentView: TextView = mView.findViewById(R.id.content) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /samples/android-app/src/main/java/org/jlleitschuh/gradle/ktlint/android/dummy/DummyContent.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android.dummy 2 | 3 | import java.util.ArrayList 4 | import java.util.HashMap 5 | 6 | /** 7 | * Helper class for providing sample content for user interfaces created by 8 | * Android template wizards. 9 | * 10 | * TODO: Replace all uses of this class before publishing your app. 11 | */ 12 | object DummyContent { 13 | /** 14 | * An array of sample (dummy) items. 15 | */ 16 | val ITEMS: MutableList = ArrayList() 17 | 18 | /** 19 | * A map of sample (dummy) items, by ID. 20 | */ 21 | val ITEM_MAP: MutableMap = HashMap() 22 | 23 | private const val COUNT = 25 24 | 25 | init { 26 | // Add some sample items. 27 | for (i in 1..COUNT) { 28 | addItem(createDummyItem(i)) 29 | } 30 | } 31 | 32 | private fun addItem(item: DummyItem) { 33 | ITEMS.add(item) 34 | ITEM_MAP[item.id] = item 35 | } 36 | 37 | private fun createDummyItem(position: Int): DummyItem { 38 | return DummyItem(position.toString(), "Item $position", makeDetails(position)) 39 | } 40 | 41 | private fun makeDetails(position: Int): String { 42 | val builder = StringBuilder() 43 | builder.append("Details about Item: ").append(position) 44 | for (i in 0 until position) { 45 | builder.append("\nMore details information here.") 46 | } 47 | return builder.toString() 48 | } 49 | 50 | /** 51 | * A dummy item representing a piece of content. 52 | */ 53 | data class DummyItem(val id: String, val content: String, val details: String) { 54 | override fun toString(): String = content 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/android-app/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AlternativeSample.kt: -------------------------------------------------------------------------------- 1 | package org.jlleitschuh.gradle.ktlint.android 2 | 3 | data class AlternativeSample(val one: Int = 1) 4 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/layout/activity_item_detail.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/layout/activity_item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 22 | 23 | 24 | 25 | 30 | 31 | 35 | 36 | 37 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/layout/item_detail.xml: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/layout/item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/layout/item_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLLeitschuh/ktlint-gradle/15c739266dc63a0ef03653d78683f3d3e7f54ead/samples/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 200dp 5 | 200dp 6 | 16dp 7 | 8 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | Item Detail 4 | 5 | -------------------------------------------------------------------------------- /samples/android-app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |