diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa7b37b615..c88a9d59a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+# 0.12.4
+
+### 🐛 Bug Fixes
+
+* don't crash if Anvil isn't in the buildScript classpath by @RBusarow
+ in https://github.com/RBusarow/ModuleCheck/pull/944
+* ignore suppress/noinspection names which don't fit the FindingName pattern by @RBusarow
+ in https://github.com/RBusarow/ModuleCheck/pull/945
+
+### Other Changes
+
+* GitHub release notes config by @RBusarow in https://github.com/RBusarow/ModuleCheck/pull/946
+
+**Full Changelog**: https://github.com/RBusarow/ModuleCheck/compare/0.12.3...0.12.4
+
# 0.12.3
### 🐛 Bug Fixes
@@ -149,7 +164,9 @@
- revert KaptMatcher name
to `modulecheck.api.KaptMatcher` [@RBusarow](https://github.com/RBusarow) ([#613](https://github.com/rbusarow/ModuleCheck/pull/613))
-
+
delete `ConfiguredModule` [@RBusarow](https://github.com/RBusarow) ([#609](https://github.com/rbusarow/ModuleCheck/pull/609))
+
- disable the "use tab character" option in IDE
codestyle [@RBusarow](https://github.com/RBusarow) ([#607](https://github.com/rbusarow/ModuleCheck/pull/607))
- replace `java-test-fixtures` usages with `-testing`
diff --git a/README.md b/README.md
index 5ffd04b735..3979de24b1 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ pluginManagement {
// top-level build.gradle.kts
plugins {
- id("com.rickbusarow.module-check") version "0.12.3"
+ id("com.rickbusarow.module-check") version "0.12.4"
}
```
diff --git a/build-logic/mcbuild/src/main/kotlin/modulecheck/builds/publishing.kt b/build-logic/mcbuild/src/main/kotlin/modulecheck/builds/publishing.kt
index 9fd2f3484d..04ca707f19 100644
--- a/build-logic/mcbuild/src/main/kotlin/modulecheck/builds/publishing.kt
+++ b/build-logic/mcbuild/src/main/kotlin/modulecheck/builds/publishing.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021-2022 Rick Busarow
+ * Copyright (C) 2021-2023 Rick Busarow
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -34,7 +34,7 @@ import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask
const val GROUP = "com.rickbusarow.modulecheck"
const val PLUGIN_ID = "com.rickbusarow.module-check"
-const val VERSION_NAME = "0.13.0-SNAPSHOT"
+const val VERSION_NAME = "0.12.4"
const val SOURCE_WEBSITE = "https://github.com/rbusarow/ModuleCheck"
const val DOCS_WEBSITE = "https://rbusarow.github.io/ModuleCheck"
diff --git a/website/src/pages/changelog.md b/website/src/pages/changelog.md
index 5ece60d4c3..d2f608cb68 100644
--- a/website/src/pages/changelog.md
+++ b/website/src/pages/changelog.md
@@ -1,3 +1,18 @@
+## 0.12.4
+
+#### 🐛 Bug Fixes
+
+* don't crash if Anvil isn't in the buildScript classpath by @RBusarow
+ in https://github.com/RBusarow/ModuleCheck/pull/944
+* ignore suppress/noinspection names which don't fit the FindingName pattern by @RBusarow
+ in https://github.com/RBusarow/ModuleCheck/pull/945
+
+#### Other Changes
+
+* GitHub release notes config by @RBusarow in https://github.com/RBusarow/ModuleCheck/pull/946
+
+**Full Changelog**: https://github.com/RBusarow/ModuleCheck/compare/0.12.3...0.12.4
+
## 0.12.3
#### 🐛 Bug Fixes
@@ -149,7 +164,9 @@
- revert KaptMatcher name
to `modulecheck.api.KaptMatcher` [@RBusarow](https://github.com/RBusarow) ([#613](https://github.com/rbusarow/ModuleCheck/pull/613))
-
+
delete `ConfiguredModule` [@RBusarow](https://github.com/RBusarow) ([#609](https://github.com/rbusarow/ModuleCheck/pull/609))
+
- disable the "use tab character" option in IDE
codestyle [@RBusarow](https://github.com/RBusarow) ([#607](https://github.com/rbusarow/ModuleCheck/pull/607))
- replace `java-test-fixtures` usages with `-testing`
diff --git a/website/versioned_docs/version-0.12.4/ci_workflow.md b/website/versioned_docs/version-0.12.4/ci_workflow.md
new file mode 100644
index 0000000000..5a410bf3b6
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/ci_workflow.md
@@ -0,0 +1,145 @@
+---
+id: ci-workflow
+
+sidebar_label: CI Workflow
+
+title: CI Workflow
+---
+
+ModuleCheck will automatically fix most issues. Most CI platforms are able to commit changes, and
+automatically cancel out-of-date jobs when the branch has been updated. This tooling can be used to
+apply ModuleCheck's automatic fixes (if any) as part of a CI run, then cancel and start a new run.
+This is similar to a git pre-commit hook, except the work is delegated to a build server.
+
+### Using CI over git hooks
+
+The traditional method for applying changes automatically is with a git hook, such as pre-commit or
+pre-push. But if the task-to-be-automated has a runtime of more than a few seconds, this is a poor
+developer experience. With a CI task, the execution is done automatically and asynchronously, while
+the developer is already moving on to something else.
+
+A git hook also technically doesn't guarantee that a task is executed before code is checked in to a
+main branch, since there's no guarantee that a hook is enabled. With CI, the task will output a
+status check. If a branch protection rule is enabled, that status check can be required. This will
+then guarantee that the task has run (successfully) before any code is checked in to the protected
+branch.
+
+### Example Flow chart
+
+This is a simplified flowchart of how I would run ModuleCheck with unit tests in CI. The
+cancellation, test, and ModuleCheck jobs run in parallel on three different runners. This is an
+"optimistic" workflow, in that it assumes that the `modulecheck` task will not generate changes
+which would trigger a restart.
+
+```mermaid
+flowchart TB
+ Start(CI Start):::good --> mGraph
+ Start --> tGraph
+ Start --> cGraph
+
+ subgraph mGraph [runner 1]
+ direction TB
+ ModuleCheck(./gradlew moduleCheckAuto):::code --> ChangesModuleCheck
+ ChangesModuleCheck{Graph changes?} --- yesM[yes]:::lineLabel --> CommitModuleCheck(Commit changes and push):::stop
+ ChangesModuleCheck --- noM[no]:::lineLabel --> EndModuleCheck("#10003;"):::good
+ end
+
+ subgraph tGraph [runner 2]
+ direction TB
+ Tests(./gradlew test):::code --> EndTests("#10003;"):::good
+ end
+
+ subgraph cGraph [runner 3]
+ direction TB
+ Cancel(Cancel previous CI run):::code
+ end
+
+ style tGraph fill:#EEE,stroke:#000
+ style cGraph fill:#EEE,stroke:#000
+ style mGraph fill:#EEE,stroke:#000
+
+ classDef good fill:#0B0,stroke:#000
+ classDef stop fill:#E33,stroke:#000
+
+ classDef code fill:#AAA,stroke:#000
+
+ style ChangesModuleCheck fill:#CD1,stroke:#000
+
+ classDef lineLabel fill:#FFF,stroke:#FFF
+```
+
+### Example GitHub Action
+
+Here's an Action which will run ModuleCheck, then commit any changes
+using [Stefanzweifel's auto-commit](https://github.com/stefanzweifel/git-auto-commit-action). This
+requires a personal access token secret, or the commit step will fail.
+
+```yaml title=.github/workflows.module-check.yml
+name: ModuleCheck
+
+on:
+ pull_request:
+
+jobs:
+
+ cancel-stale-jobs:
+ name: Cancel stale jobs
+ runs-on: ubuntu-latest
+
+ steps:
+ # cancel previous jobs
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.0
+ env:
+ GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+
+ ModuleCheck:
+ name: ModuleCheck
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
+ # Must use a personal access token in order to commit changes
+ token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ fetch-depth: 0
+
+ - name: Set up JDK
+ uses : actions/setup-java@v2
+ with :
+ distribution : 'temurin'
+ java-version : '11'
+
+ # performs tree-shaking on the Gradle dependency graph
+ - name: modulecheck
+ run: ./gradlew moduleCheckAuto --no-daemon
+
+ # If ModuleCheck generated changes, commit and push those changes.
+ # If there are no changes, then this is a no-op.
+ - name: commit changes
+ uses: stefanzweifel/git-auto-commit-action@v4
+ with:
+ commit_message: Apply ModuleCheck changes
+ commit_options: '--no-verify --signoff'
+
+ tests:
+ name: Unit tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
+ token: ${{ secrets.GITHUB_TOKEN }}
+ fetch-depth: 0
+
+ - name: Set up JDK
+ uses : actions/setup-java@v2
+ with :
+ distribution : 'temurin'
+ java-version : '14'
+
+ - name: all tests
+ run: ./gradlew test --no-daemon
+```
diff --git a/website/versioned_docs/version-0.12.4/configuration.mdx b/website/versioned_docs/version-0.12.4/configuration.mdx
new file mode 100644
index 0000000000..9a6ba8b2f2
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/configuration.mdx
@@ -0,0 +1,170 @@
+---
+id: configuration
+sidebar_label: Configuration
+---
+
+import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
+
+
+
+
+
+ ``` kotlin title="root/build.gradle.kts"
+ plugins {
+ id("com.rickbusarow.module-check") version "0.13.0-SNAPSHOT"
+ }
+
+ moduleCheck {
+
+ deleteUnused = true // default is false
+
+ checks {
+ overShotDependency = true // default is true
+ redundantDependency = false // default is false
+ unusedDependency = true // default is true
+ mustBeApi = true // default is true
+ inheritedDependency = true // default is true
+ sortDependencies = false // default is false
+ sortPlugins = false // default is false
+ unusedKapt = true // default is true
+ anvilFactoryGeneration = true // default is true
+ disableAndroidResources = false // default is false
+ disableViewBinding = false // default is false
+ unusedKotlinAndroidExtensions = false // default is false
+ depths = false // default is false
+ }
+
+ // allow these modules to be declared as dependency anywhere,
+ // regardless of whether they're used
+ ignoreUnusedFinding = setOf(":test:core-jvm", ":test:core-android")
+
+ // do not check the dependencies of these modules.
+ // in this case, :app could declare any module it wants without issue
+ doNotCheck = setOf(":app")
+
+ additionalCodeGenerators = listOf(
+ modulecheck.config.CodeGeneratorBinding.AnnotationProcessor(
+ name = "My Processor",
+ generatorMavenCoordinates = "my-project.codegen:processor",
+ annotationNames = listOf(
+ "myproject.MyInject",
+ "myproject.MyInject.Factory",
+ "myproject.MyInjectParam",
+ "myproject.MyInjectModule"
+ )
+ )
+ )
+
+ reports {
+ checkstyle {
+ enabled = true // default is false
+ outputPath = "${project.buildDir}/reports/modulecheck/checkstyle.xml"
+ }
+ sarif {
+ enabled = true // default is false
+ outputPath = "${project.buildDir}/reports/modulecheck/modulecheck.sarif"
+ }
+ depths {
+ enabled = true // default is false
+ outputPath = "${project.buildDir}/reports/modulecheck/depths.txt"
+ }
+ graphs {
+ enabled = true // default is false
+ // The root directory of all generated graphs. If set, directories will be created
+ // for each module, mirroring the structure of the project. If this property is null,
+ // graphs will be created in the `build/reports/modulecheck/graphs/` relative
+ // directory of each project.
+ outputDir = "${project.buildDir}/reports/modulecheck/graphs"
+ }
+ text {
+ enabled = true // default is false
+ outputPath = "${project.buildDir}/reports/modulecheck/report.txt"
+ }
+ }
+ }
+ ```
+
+
+
+
+
+ ``` groovy title="root/build.gradle"
+ plugins {
+ id 'com.rickbusarow.module-check' version '0.13.0-SNAPSHOT'
+ }
+
+ moduleCheck {
+ deleteUnused = true // default is false
+
+ checks {
+ overShotDependency = true // default is true
+ redundantDependency = false // default is false
+ unusedDependency = true // default is true
+ mustBeApi = true // default is true
+ inheritedDependency = true // default is true
+ sortDependencies = false // default is false
+ sortPlugins = false // default is false
+ unusedKapt = true // default is true
+ anvilFactoryGeneration = true // default is true
+ disableAndroidResources = false // default is false
+ disableViewBinding = false // default is false
+ unusedKotlinAndroidExtensions = false // default is false
+ depths = false // default is false
+ }
+
+ // allow these modules to be declared as dependency anywhere,
+ // regardless of whether they're used
+ ignoreUnusedFinding = [':test:core-jvm', ':test:core-android']
+
+ // do not check the dependencies of these modules.
+ // in this case, :app could declare any module it wants without issue
+ doNotCheck = [':app']
+
+ additionalCodeGenerators = [
+ new modulecheck.config.CodeGeneratorBinding.AnnotationProcessor(
+ 'My Processor',
+ 'my-project.codegen:processor',
+ [
+ "myproject.MyInject",
+ "myproject.MyInject.Factory",
+ "myproject.MyInjectParam",
+ "myproject.MyInjectModule"
+ ]
+ )
+ ]
+
+ reports {
+ checkstyle {
+ it.enabled = true // default is false
+ it.outputPath = "${project.buildDir}/reports/modulecheck/checkstyle.xml"
+ }
+ sarif {
+ it.enabled = true // default is false
+ it.outputPath = "${project.buildDir}/reports/modulecheck/modulecheck.sarif"
+ }
+ depths {
+ it.enabled = true // default is false
+ it.outputPath = "${project.buildDir}/reports/modulecheck/depths.txt"
+ }
+ graphs {
+ it.enabled = true // default is false
+ // The root directory of all generated graphs. If set, directories will be created
+ // for each module, mirroring the structure of the project. If this property is null,
+ // graphs will be created in the `build/reports/modulecheck/graphs/` relative
+ // directory of each project.
+ it.outputDir = "${project.buildDir}/reports/modulecheck/graphs"
+ }
+ text {
+ it.enabled = true // default is false
+ it.outputPath = "${project.buildDir}/reports/modulecheck/report.txt"
+ }
+ }
+
+ }
+ ```
+
+
diff --git a/website/versioned_docs/version-0.12.4/quickstart.mdx b/website/versioned_docs/version-0.12.4/quickstart.mdx
new file mode 100644
index 0000000000..c8afcaa2e6
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/quickstart.mdx
@@ -0,0 +1,158 @@
+---
+id: quickstart
+title: Quick Start
+sidebar_label: Quick Start
+slug: /
+---
+
+
+import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
+
+
+## Dependencies
+
+
+
+
+
+ ```kotlin
+ // settings.gradle.kts
+
+ pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ // Add for SNAPSHOT builds
+ maven("https://oss.sonatype.org/content/repositories/snapshots/")
+ }
+ }
+ ```
+
+ ```kotlin
+ // top-level build.gradle.kts
+
+ plugins {
+ id("com.rickbusarow.module-check") version "0.13.0-SNAPSHOT"
+ }
+ ```
+
+
+
+
+
+ ```groovy
+ // settings.gradle
+
+ pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ // Add for SNAPSHOT builds
+ maven {
+ url "https://oss.sonatype.org/content/repositories/snapshots"
+ }
+ }
+ }
+ ```
+
+ ```groovy
+ // top-level build.gradle
+
+ plugins {
+ id 'com.rickbusarow.module-check' version '0.13.0-SNAPSHOT'
+ }
+ ```
+
+
+
+
+## Tasks
+
+all checks
+```shell
+./gradlew moduleCheck
+```
+
+all checks with auto-correct
+```shell
+./gradlew moduleCheckAuto
+```
+
+check sorting
+```shell
+./gradlew moduleCheckSortPlugins moduleCheckSortDependencies
+```
+
+apply sorting
+```shell
+./gradlew moduleCheckSortPluginsAuto moduleCheckSortDependenciesAuto
+```
+
+report depths of each module
+```shell
+./gradlew moduleCheckDepths
+```
+
+generate (module-only) dependency graphs
+```shell
+./gradlew moduleCheckGraphs
+```
+
+## Configuration
+
+See [configuration](./configuration.mdx) for a full list of options.
+
+
+
+
+
+
+ ```kotlin title="root/build.gradle.kts"
+ configure
+ {
+
+ alwaysIgnore.set(setOf(":app"))
+
+ checks {
+ redundant.set(false)
+ }
+ }
+ ```
+
+ --or--
+
+ ```kotlin title="root/build.gradle.kts"
+ moduleCheck {
+
+ alwaysIgnore.set(setOf(":app"))
+
+ checks {
+ redundant.set(false)
+ }
+ }
+ ```
+
+
+
+
+
+
+ ```groovy title="root/build.gradle"
+ moduleCheck {
+
+ alwaysIgnore.set(setOf(":app"))
+
+ checks {
+ redundant.set(false)
+ }
+ }
+ ```
+
+
+
diff --git a/website/versioned_docs/version-0.12.4/rules/android/disable_android_resources.md b/website/versioned_docs/version-0.12.4/rules/android/disable_android_resources.md
new file mode 100644
index 0000000000..71ec297305
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/android/disable_android_resources.md
@@ -0,0 +1,17 @@
+---
+id: disable_android_resources
+slug: /rules/disable_android_resources
+title: Disable Android Resources
+sidebar_label: Disable Android Resources
+---
+
+If an Android module doesn't actually have any resources in the `src/__/res` directory,
+then `android.buildFeatures.androidResources` can be disabled.
+
+```kotlin
+android {
+ buildFeatures {
+ androidResource = false
+ }
+}
+```
diff --git a/website/versioned_docs/version-0.12.4/rules/android/disable_view_binding.md b/website/versioned_docs/version-0.12.4/rules/android/disable_view_binding.md
new file mode 100644
index 0000000000..45a27be804
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/android/disable_view_binding.md
@@ -0,0 +1,17 @@
+---
+id: disable_view_binding
+slug: /rules/disable_view_binding
+title: Disable ViewBinding
+sidebar_label: Disable ViewBinding
+---
+
+If an Android module has `viewBinding` enabled, but doesn't contribute any generated `____Binding`
+objects from layout files which are actually used, then `viewBinding` can be disabled.
+
+```kotlin
+android {
+ buildFeatures {
+ viewBinding = false
+ }
+}
+```
diff --git a/website/versioned_docs/version-0.12.4/rules/android/unused_kotlin_android_extensions.md b/website/versioned_docs/version-0.12.4/rules/android/unused_kotlin_android_extensions.md
new file mode 100644
index 0000000000..1c376f3c86
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/android/unused_kotlin_android_extensions.md
@@ -0,0 +1,9 @@
+---
+id: unused_kotlin_android_extensions
+slug: /rules/unused_kotlin_android_extensions
+title: Unused Kotlin Android Extensions
+sidebar_label: Unused Kotlin Android Extensions
+---
+
+Finds modules which have deprecated Kotlin Android Extensions enabled, but don't actually use any
+synthetic imports or deprecated @Parcelize annotation
diff --git a/website/versioned_docs/version-0.12.4/rules/compiler/custom_kapt_matchers.md b/website/versioned_docs/version-0.12.4/rules/compiler/custom_kapt_matchers.md
new file mode 100644
index 0000000000..f6323ac4b8
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/compiler/custom_kapt_matchers.md
@@ -0,0 +1,30 @@
+---
+id: custom_kapt_matchers
+slug: /rules/custom_kapt_matchers
+title: Custom Kapt Matchers
+sidebar_label: Custom Kapt Matchers
+---
+
+It's simple to add a custom matcher for an internal-use annotation processor.
+
+Just define a list of regex strings for all of the fully qualified names of its annotations.
+
+```kotlin
+moduleCheck {
+ additionalKaptMatchers.set(
+ listOf(
+ modulecheck.api.KaptMatcher(
+ name = "MyProcessor",
+ processor = "my-project.codegen:processor",
+ annotationImports = listOf(
+ "myproject\\.\\*",
+ "myproject\\.MyInject",
+ "myproject\\.MyInject\\.Factory",
+ "myproject\\.MyInjectParam",
+ "myproject\\.MyInjectModule"
+ )
+ )
+ )
+ )
+}
+```
diff --git a/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_plugin.md b/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_plugin.md
new file mode 100644
index 0000000000..9a45eadfa1
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_plugin.md
@@ -0,0 +1,9 @@
+---
+id: unused_kapt_plugin
+slug: /rules/unused_kapt_plugin
+title: Unused Kapt Plugin
+sidebar_label: Unused Kapt Plugin
+---
+
+If there are no `kapt`/`kaptTest`/etc. processor dependencies in a module, there's no point in
+applying the `org.jetbrains.kotlin.kapt` plugin.
diff --git a/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_processor.md b/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_processor.md
new file mode 100644
index 0000000000..325423fd94
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/compiler/unused_kapt_processor.md
@@ -0,0 +1,31 @@
+---
+id: unused_kapt_processor
+slug: /rules/unused_kapt_processor
+title: Unused Kapt Processor
+sidebar_label: Unused Kapt Processor
+---
+
+Annotation processors act upon a defined set of annotations. If an annotation processor is
+sufficiently popular and its api is stable, then it's relatively simple to define a list of
+annotations to search for. For instance, Dagger looks for the following annotations:
+
+- `javax.inject.Inject`
+- `dagger.Binds`
+- `dagger.Module`
+- `dagger.multibindings.IntoMap`
+- `dagger.multibindings.IntoSet`
+- `dagger.BindsInstance`
+- `dagger.Component`
+- `dagger.assisted.Assisted`
+- `dagger.assisted.AssistedInject`
+- `dagger.assisted.AssistedFactory`
+- `com.squareup.anvil.annotations.ContributesTo`
+- `com.squareup.anvil.annotations.MergeComponent`
+- `com.squareup.anvil.annotations.MergeSubomponent`
+
+If a module has the Dagger `kapt` dependency, and that module *does not* have one of the above
+annotations somewhere, then Dagger isn't actually doing anything and can be removed.
+
+This is simply a best-effort approach, and it isn't maintenance-free. Over time, the list of
+annotations for any processor may change. If this rule gives a false-positive finding because of a
+new annotation, please open an issue and/or pull request.
diff --git a/website/versioned_docs/version-0.12.4/rules/compiler/use_anvil_factory_generation.md b/website/versioned_docs/version-0.12.4/rules/compiler/use_anvil_factory_generation.md
new file mode 100644
index 0000000000..681ca679d9
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/compiler/use_anvil_factory_generation.md
@@ -0,0 +1,19 @@
+---
+id: use_anvil_factory_generation
+slug: /rules/use_anvil_factory_generation
+title: Could Use Anvil Factory Generation
+sidebar_label: Could Use Anvil Factory Generation
+---
+
+Anvil's [factory generation](https://github.com/square/anvil#dagger-factory-generation) is faster
+than Dagger's generation using Kapt. However, it doesn't support generating Components or
+Subcomponents, and it doesn't work in Java code.
+
+This rule detects whether a module could switch from Dagger's kapt to Anvil factory generation.
+
+Criteria:
+
+- Anvil plugin applied with a version greater than 2.0.11
+- Anvil's factory generation isn't already enabled (nothing to do in this case)
+- No `@MergeComponent`, `@MergeSubcomponent`, `@Component` or `@Subcomponent` annotations
+- No Dagger annotations in `.java` files
diff --git a/website/versioned_docs/version-0.12.4/rules/inherited_dependency.md b/website/versioned_docs/version-0.12.4/rules/inherited_dependency.md
new file mode 100644
index 0000000000..3c84cd7625
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/inherited_dependency.md
@@ -0,0 +1,12 @@
+---
+id: inherited_dependency
+slug: /rules/inherited_dependency
+title: Inherited Dependency
+sidebar_label: Inherited Dependency
+---
+
+Assume that `:moduleA` depends upon `:moduleB`, and `:moduleB` depends upon `:moduleC` via
+an `api` configuration. Also assume that `:moduleA` uses something from `:moduleC`, but doesn't
+have an explicit dependency for it. It just inherits that dependency from `:moduleB`.
+
+ModuleCheck will recommend adding a direct, explicit dependency for `:moduleA` -> `:moduleC`.
diff --git a/website/versioned_docs/version-0.12.4/rules/must_be_api.md b/website/versioned_docs/version-0.12.4/rules/must_be_api.md
new file mode 100644
index 0000000000..c650fdba66
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/must_be_api.md
@@ -0,0 +1,14 @@
+---
+id: must_be_api
+slug: /rules/must_be_api
+title: Must Be Api
+sidebar_label: Must Be Api
+---
+
+Dependencies are considered to be part of a module's public "ABI" if that module exposes some aspect
+of the dependency in its own API.
+
+For instance, if a `:moduleA` extends a class/interface from `:moduleB`, or takes a type
+from `:moduleB` as a function parameter, then any consumer of `:moduleA`'s API must also have a
+dependency upon `:moduleB`. In scenarios like this, the dependency module(s) should be declared
+using Gradle's `api` configuration.
diff --git a/website/versioned_docs/version-0.12.4/rules/overshot_dependency.md b/website/versioned_docs/version-0.12.4/rules/overshot_dependency.md
new file mode 100644
index 0000000000..0dc9a3fdeb
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/overshot_dependency.md
@@ -0,0 +1,27 @@
+---
+id: overshot_dependency
+slug: /rules/overshot_dependency
+title: Overshot Dependency
+sidebar_label: Overshot Dependency
+---
+
+Finds project dependencies which aren't used by the declaring configuration, but are used by a
+dependent, downstream configuration.
+
+For instance, assume that `:moduleB` declares an `implementation` dependency upon `:moduleA`.
+
+```kotlin title="moduleB/build.gradle.kts"
+dependencies {
+ implementation(project(":moduleA"))
+}
+```
+
+If `:moduleB` doesn't actually use `:moduleA` in its `main` source, but it _does_ use it in `test`
+source, it's an __overshot dependency__. The declaration should be changed to
+use `testImplementation`:
+
+```kotlin title="moduleB/build.gradle.kts"
+dependencies {
+ testImplementation(project(":moduleA"))
+}
+```
diff --git a/website/versioned_docs/version-0.12.4/rules/project_depth.md b/website/versioned_docs/version-0.12.4/rules/project_depth.md
new file mode 100644
index 0000000000..11b79760bd
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/project_depth.md
@@ -0,0 +1,194 @@
+---
+id: project_depth
+slug: /rules/project_depth
+title: Project Depth
+sidebar_label: Project Depth
+---
+
+TL;DR - Low depth values mean faster builds and better all-around scalability.
+
+---
+
+It's often useful to think of module dependencies as a directed tree
+or [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph). If a module is a
+node, then each module dependency is a child node, and the dependencies of those dependencies are
+grand-child nodes.
+
+This is especially useful when thinking about **build performance**, because the parent-child
+relationship is clear: _child nodes must build before parent nodes_.
+
+```mermaid
+flowchart TB
+
+ classDef depth2 fill:#BBF,stroke:#000,color:#000
+ classDef depth1 fill:#B9B,stroke:#000,color:#000
+ classDef depth0 fill:#FBB,stroke:#000,color:#000
+
+ linkStyle default stroke-width:2px,fill:none,stroke:green;
+
+ app(:app):::depth2
+
+ screen1(:screen-1):::depth1
+ screen2(:screen-2):::depth1
+
+ lib1(:lib-1):::depth0
+ lib2(:lib-2):::depth0
+
+ app --> screen1
+ app --> screen2
+
+ screen1 --> lib1
+ screen1 --> lib2
+ screen2 --> lib2
+```
+
+In the above example,
+
+- `:lib-1` and `:lib-2` must be built before `:screen-1`.
+- `:lib-2` must be build before `:screen-2`.
+- `:screen-1` and `:screen-2` must be built before `:app`.
+
+It's worth pointing out that this relationship is recursive, as well. Grand-child nodes must build
+before their parents.
+
+### Dependencies and Build Concurrency
+
+Individual module builds are always done single-threaded, but multiple modules may build in parallel
+so long as no module in the set depends upon another module in that set. In the above graph,
+
+- `:lib-1` and `:lib-2` may build in parallel
+- `:lib-1` and `:screen-2` may build in parallel
+- `:scren-1` and `:screen-2` may build in parallel
+
+The maximum number of parallel module builds is determined by the structure of the dependency graph
+and the number of available processor cores on the machine which is performing the build.
+
+### Depth
+
+**Depth** refers to the maximum number of edges between a module and each of its leaf nodes in the
+project dependency graph.
+
+Low depth values indicate a shallow or flat project structure with loose (or no) coupling between
+modules. In a full build, these projects scale well with hardware upgrades because they're able to
+build all those independent modules in parallel.
+
+```mermaid
+flowchart TB
+
+ subgraph sg [A shallow graph]
+ direction TB
+
+ classDef depth3 fill:#F7B,stroke:#000,color:#000
+ classDef depth2 fill:#BBF,stroke:#000,color:#000
+ classDef depth1 fill:#B9B,stroke:#000,color:#000
+ classDef depth0 fill:#FBB,stroke:#000,color:#000
+
+ linkStyle default stroke-width:2px,fill:none,stroke:green;
+
+ app(depth: 2):::depth2
+
+ screen1(depth: 1):::depth1
+ screen2(depth: 1):::depth1
+ screen3(depth: 1):::depth1
+ screen4(depth: 1):::depth1
+
+ lib1(depth: 0):::depth0
+ lib2(depth: 0):::depth0
+ lib3(depth: 0):::depth0
+ lib4(depth: 0):::depth0
+ lib5(depth: 0):::depth0
+
+ app --> screen1
+ app --> screen2
+ app --> screen3
+ app --> screen4
+
+ screen1 --> lib1
+ screen1 --> lib4
+
+ screen2 --> lib1
+ screen2 --> lib3
+ screen2 --> lib4
+
+ screen3 --> lib2
+ screen3 --> lib3
+ screen3 --> lib4
+
+ screen4 --> lib3
+ screen4 --> lib5
+
+ end
+
+ style sg opacity:0.0
+
+```
+
+On the other hand, "deep" projects do not offer many opportunities for parallelization. They have
+project dependencies which must be built *sequentially*. They also perform poorly in incremental
+builds, because a single change to even a mid-level module invalidates cached builds for half of the
+project.
+
+```mermaid
+flowchart TB
+
+ style sg opacity:0.0
+ subgraph sg [A deep graph]
+ direction TB
+
+ classDef depth6 fill:#800,stroke:#000,color:#FFF
+ classDef depth5 fill:#A50,stroke:#000,color:#FFF
+ classDef depth4 fill:#C0B,stroke:#000,color:#000
+ classDef depth3 fill:#F7B,stroke:#000,color:#000
+ classDef depth2 fill:#BBF,stroke:#000,color:#000
+ classDef depth1 fill:#B9B,stroke:#000,color:#000
+ classDef depth0 fill:#FBB,stroke:#000,color:#000
+
+ linkStyle default stroke-width:2px,fill:none,stroke:green;
+
+ app(depth: 6):::depth6
+
+ screen1(depth: 5):::depth5
+ screen2(depth: 5):::depth5
+
+ screen3(depth: 4):::depth4
+ screen4(depth: 4):::depth4
+
+ lib1(depth: 3):::depth3
+ lib2(depth: 3):::depth3
+
+ lib3(depth: 2):::depth2
+ lib4(depth: 2):::depth2
+
+ lib5(depth: 1):::depth1
+
+ lib6(depth: 0):::depth0
+
+ app --> screen1
+ app --> screen2
+ app --> screen3
+ app --> screen4
+
+ screen1 --> screen3
+ screen1 --> screen4
+
+ screen2 --> screen4
+
+ screen3 --> lib1
+ screen3 --> lib2
+
+ screen4 --> lib1
+ screen4 --> lib4
+
+ lib1 --> lib3
+ lib1 --> lib4
+
+ lib2 --> lib3
+
+ lib3 --> lib5
+ lib4 --> lib5
+
+ lib5 --> lib6
+
+ end
+
+```
diff --git a/website/versioned_docs/version-0.12.4/rules/redundant_dependency.md b/website/versioned_docs/version-0.12.4/rules/redundant_dependency.md
new file mode 100644
index 0000000000..92a0da6bf7
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/redundant_dependency.md
@@ -0,0 +1,67 @@
+---
+id: redundant_dependency
+slug: /rules/redundant_dependency
+title: Redundant Dependency
+sidebar_label: Redundant Dependency
+---
+:::caution
+
+This rule creates a brittle dependency graph, because some necessary dependencies are only provided
+transitively by other dependencies. Any manual changes to dependencies can have unexpected
+consequences downstream.
+
+This rule is **not recommended** and disabled by default, but it's still available for those who
+want to keep their build files as small as possible.
+
+:::
+
+Finds project dependencies which are declared as `api` in other dependency projects, but also
+declared in the current project. These dependencies can be removed without actually breaking the
+build, since they're still provided by an upstream dependency through the `api` configuration.
+
+```mermaid
+flowchart LR
+
+ linkStyle default stroke-width:2px,fill:none,stroke:green;
+
+ classDef depth2 fill:#BBF,stroke:#000,color:#000
+ classDef depth1 fill:#B9B,stroke:#000,color:#000
+ classDef depth0 fill:#FBB,stroke:#000,color:#000
+
+ subgraph sg_redundant [A redundant graph]
+ direction TB
+
+ lib1_redundant(:lib-1):::depth0
+ lib2_redundant(:lib-2):::depth1
+ app_redundant(:app):::depth2
+
+ app_redundant --> |api| lib1_redundant
+ app_redundant --> |api| lib2_redundant
+
+ lib2_redundant --> |api| lib1_redundant
+ end
+
+ subgraph sg_minimalist [A graph with no redundancy]
+ direction TB
+
+ lib1_minimalist(:lib-1):::depth0
+ lib2_minimalist(:lib-2):::depth1
+ app_minimalist(:app):::depth2
+
+ app_minimalist --> |api| lib2_minimalist
+
+ lib2_minimalist --> |api| lib1_minimalist
+ end
+
+ style sg_redundant fill:#C66,stroke:#000,color:#FFF
+ style sg_minimalist fill:#696,stroke:#000,color:#FFF
+
+ sg_redundant --> |./gradlew moduleCheck| sg_minimalist
+
+```
+
+This is the opposite of the [inherited dependency] rule, which ensures a stable graph by explicitly
+declaring each dependency. [Inherited dependency] is enabled by default, and is the recommended
+approach. Both rules may not be enabled at the same time.
+
+[Inherited dependency]:inherited_dependency.md
diff --git a/website/versioned_docs/version-0.12.4/rules/sorting/sort_dependencies.md b/website/versioned_docs/version-0.12.4/rules/sorting/sort_dependencies.md
new file mode 100644
index 0000000000..34e6f7feee
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/sorting/sort_dependencies.md
@@ -0,0 +1,6 @@
+---
+id: sort_dependencies
+slug: /rules/sort_dependencies
+title: Sort Dependencies
+sidebar_label: Sort Dependencies
+---
diff --git a/website/versioned_docs/version-0.12.4/rules/sorting/sort_plugins.md b/website/versioned_docs/version-0.12.4/rules/sorting/sort_plugins.md
new file mode 100644
index 0000000000..c293df4633
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/sorting/sort_plugins.md
@@ -0,0 +1,6 @@
+---
+id: sort_plugins
+slug: /rules/sort_plugins
+title: Sort Plugins
+sidebar_label: Sort Plugins
+---
diff --git a/website/versioned_docs/version-0.12.4/rules/unused_dependency.md b/website/versioned_docs/version-0.12.4/rules/unused_dependency.md
new file mode 100644
index 0000000000..b8285c7c73
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/rules/unused_dependency.md
@@ -0,0 +1,14 @@
+---
+id: unused_dependency
+slug: /rules/unused_dependency
+title: Unused Dependency
+sidebar_label: Unused Dependency
+---
+
+Unused module dependencies which are unused create unnecessary bottlenecks in a build task. Instead
+of building modules concurrently, Gradle must wait until the dependency module is built before
+beginning to build the dependent one.
+
+ModuleCheck determines whether a dependency is unused by looking for all fully qualified names
+declared in its API, then searching the dependent module's code for references to any of those
+names. If there are no references, the dependency module is considered to be unused.
diff --git a/website/versioned_docs/version-0.12.4/suppressing-findings.mdx b/website/versioned_docs/version-0.12.4/suppressing-findings.mdx
new file mode 100644
index 0000000000..3f04ea26c1
--- /dev/null
+++ b/website/versioned_docs/version-0.12.4/suppressing-findings.mdx
@@ -0,0 +1,66 @@
+---
+id: suppressing-findings
+title: Suppressing Findings
+sidebar_label: Suppressing Findings
+---
+
+
+import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
+
+You can disable individual ModuleCheck findings via annotation, just like with any other lint tool.
+
+The name of the check to disable can be found in the `name` column of console output:
+
+```
+> Task :moduleCheck
+ModuleCheck found 3 issues in 6.157 seconds
+
+:app
+ dependency name build file
+ :fat-and-leaky inherited-dependency /Users/rbusarow/projects/sample/app/build.gradle.kts: (15, 3):
+ :fat-and-leaky must-be-api /Users/rbusarow/projects/sample/app/build.gradle.kts: (15, 3):
+ :unused-lib unused-dependency /Users/rbusarow/projects/sample/app/build.gradle.kts: (49, 3):
+
+```
+
+
+
+
+
+ ```kotlin title="build.gradle.kts"
+ @Suppress("must-be-api") // don't switch anything to an api config
+ dependencies {
+
+ @Suppress("unused-dependency") // don't comment out or delete this dependency
+ implementation(project(":unused-lib"))
+
+ @Suppress("inherited-dependency") // don't add dependencies which are inherited from this library
+ implementation(project(":leaky"))
+ }
+ ```
+
+
+
+
+
+ ```groovy title="build.gradle"
+ // don't switch anything to an api config
+ //noinspection must-be-api
+ dependencies {
+
+ // don't comment out or delete this dependency
+ //noinspection unused-dependency
+ implementation(project(":unused-lib"))
+
+ // don't add dependencies which are inherited from this library
+ //noinspection inherited-dependency
+ implementation(project(":leaky"))
+ }
+ ```
+
+
+
diff --git a/website/versioned_sidebars/version-0.12.4-sidebars.json b/website/versioned_sidebars/version-0.12.4-sidebars.json
new file mode 100644
index 0000000000..f7a5440a8d
--- /dev/null
+++ b/website/versioned_sidebars/version-0.12.4-sidebars.json
@@ -0,0 +1,51 @@
+{
+ "Docs": [
+ "quickstart",
+ "configuration",
+ "suppressing-findings",
+ "ci-workflow",
+ {
+ "type": "category",
+ "label": "Rules",
+ "collapsed": false,
+ "items": [
+ "rules/unused_dependency",
+ "rules/must_be_api",
+ "rules/inherited_dependency",
+ "rules/redundant_dependency",
+ "rules/overshot_dependency",
+ "rules/project_depth",
+ {
+ "type": "category",
+ "label": "Compiler",
+ "collapsed": false,
+ "items": [
+ "rules/compiler/use_anvil_factory_generation",
+ "rules/compiler/unused_kapt_processor",
+ "rules/compiler/unused_kapt_plugin",
+ "rules/compiler/custom_kapt_matchers"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "Sorting",
+ "collapsed": false,
+ "items": [
+ "rules/sorting/sort_dependencies",
+ "rules/sorting/sort_plugins"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "Android",
+ "collapsed": false,
+ "items": [
+ "rules/android/disable_android_resources",
+ "rules/android/disable_view_binding",
+ "rules/android/unused_kotlin_android_extensions"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/website/versions.json b/website/versions.json
index 62db22e983..6fce7751cf 100644
--- a/website/versions.json
+++ b/website/versions.json
@@ -1,4 +1,5 @@
[
+ "0.12.4",
"0.12.3",
"0.12.2",
"0.12.1",