Skip to content

Commit 95e2159

Browse files
authored
Add inspections. Bumped version to 0.3.0. (#31)
* Added inspections to the plugin. * Bumped version to 0.3.0. Updated changelog.
1 parent 8bd44bd commit 95e2159

21 files changed

+630
-22
lines changed

.github/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ A CLI and Android Studio plugin for generating Clean Architecture boilerplate.
3131
| Generate a data source | ✔️ | ✔️ |
3232
| Automatic git staging | ✔️ | ✔️ |
3333
| Configurable | ✔️ | ✔️ |
34+
| Inline inspections | ✔️ | ❌️ |
3435

3536
**Android Studio Plugin** is available on the IDE Plugins Marketplace.
3637

3738
**Terminal command** is available via Homebrew.
3839

3940
## Android Studio plugin
4041

41-
Adds multiple time-saving code generation shortcuts to Android Studio.
42+
Adds multiple time-saving code generation shortcuts to Android Studio.
4243

4344
### Usage
4445

@@ -53,6 +54,9 @@ Settings are available under `Tools` > `Clean Architecture`.
5354

5455
For a working project example, visit [Clean Architecture For Android](https://github.com/EranBoudjnah/CleanArchitectureForAndroid).
5556

57+
#### Inspections
58+
Work out of the box. You can disable any or all inspections via Android Studio's settings.
59+
5660
## CLI
5761

5862
Generates Clean Architecture Android code from your terminal.

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
## [0.3.0]
4+
5+
### Added
6+
- Added inspections to the plugin
7+
- Added a reference project to README for easier onboarding (#29)
8+
- Added Homebrew installation instructions to README (#28)
9+
- Added backwards compatibility for Android Studio Meerkat (#25)
10+
- Added settings for custom git path (#20)
11+
- Added version output to the CLI (#26)
12+
- Added git automation to the CLI for staging and commits (#19)
13+
14+
### Changed
15+
- Renamed generated binary from cli to cag (#27)
16+
- Migrated UI to use JetBrains UI DSL v2 (#24)
17+
- Deduplicated version catalog entries (#23)
18+
- Refreshed and updated the README content (#30)
19+
20+
### Fixed
21+
- Removed dexmaker from presentation-test module (#22)
22+
- Ensured new project minimum SDK version respects IDE-specified value (#21)
23+
324
## [0.2.0]
425

526
### Added

cli/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group = "com.mitteloupe.cag"
9-
version = "0.0.2"
9+
version = "0.3.0"
1010

1111
repositories {
1212
mavenCentral()

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ mockk = "1.13.11"
77
junit4 = "4.13.2"
88
androidStudio = "2025.1.4.7"
99
hamcrest = "3.0"
10+
lint = "31.13.0"
1011

1112
[libraries]
1213
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
1314
junit4 = { module = "junit:junit", version.ref = "junit4" }
1415
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
16+
lint-api = { module = "com.android.tools.lint:lint-api", version.ref = "lint" }
17+
lint-checks = { module = "com.android.tools.lint:lint-checks", version.ref = "lint" }
1518

1619
[plugins]
1720
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

lint/build.gradle.kts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
3+
plugins {
4+
id("java-library")
5+
alias(libs.plugins.kotlin.jvm)
6+
}
7+
8+
kotlin {
9+
compilerOptions {
10+
jvmTarget.set(JvmTarget.JVM_21)
11+
}
12+
}
13+
14+
repositories {
15+
google()
16+
mavenCentral()
17+
}
18+
19+
dependencies {
20+
implementation(kotlin("stdlib"))
21+
22+
compileOnly(libs.lint.api)
23+
compileOnly(libs.lint.checks)
24+
25+
testImplementation(libs.junit4)
26+
}
27+
28+
tasks.register<Jar>("lintJar") {
29+
dependsOn(tasks.named("compileKotlin"))
30+
from(sourceSets.main.get().output)
31+
archiveBaseName.set("lint")
32+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.mitteloupe.cag.cleanarchitecturegenerator.lint
2+
3+
import com.android.tools.lint.client.api.IssueRegistry
4+
import com.android.tools.lint.detector.api.CURRENT_API
5+
import com.android.tools.lint.detector.api.Issue
6+
7+
class CagIssueRegistry : IssueRegistry() {
8+
override val issues: List<Issue>
9+
get() = listOf(ViewModelPublicFunctionShouldStartWithOnDetector.ISSUE)
10+
11+
override val api: Int = CURRENT_API
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.mitteloupe.cag.cleanarchitecturegenerator.lint
2+
3+
import com.android.tools.lint.client.api.UElementHandler
4+
import com.android.tools.lint.detector.api.Category
5+
import com.android.tools.lint.detector.api.Detector
6+
import com.android.tools.lint.detector.api.Implementation
7+
import com.android.tools.lint.detector.api.Issue
8+
import com.android.tools.lint.detector.api.JavaContext
9+
import com.android.tools.lint.detector.api.Scope
10+
import com.android.tools.lint.detector.api.Severity
11+
import com.mitteloupe.cag.cleanarchitecturegenerator.rule.ViewModelPublicFunctionShouldStartWithOnRule
12+
import org.jetbrains.uast.UMethod
13+
import org.jetbrains.uast.UastVisibility
14+
15+
class ViewModelPublicFunctionShouldStartWithOnDetector : Detector(), Detector.UastScanner {
16+
17+
override fun getApplicableUastTypes() = listOf(UMethod::class.java)
18+
19+
override fun createUastHandler(context: JavaContext) =
20+
object : UElementHandler() {
21+
override fun visitMethod(node: UMethod) {
22+
val functionName = node.name
23+
val isValid = ViewModelPublicFunctionShouldStartWithOnRule.isValid(
24+
className = node.containingClass?.name,
25+
functionName = functionName,
26+
isPublic = node.visibility == UastVisibility.PUBLIC
27+
)
28+
29+
if (!isValid) {
30+
context.report(
31+
ISSUE,
32+
node,
33+
context.getNameLocation(node),
34+
ViewModelPublicFunctionShouldStartWithOnRule.violationMessage(functionName)
35+
)
36+
}
37+
}
38+
}
39+
40+
companion object {
41+
val ISSUE: Issue = Issue.create(
42+
id = "ViewModelPublicFunctionShouldStartWithOn",
43+
briefDescription = "Public ViewModel functions should start with 'on'",
44+
explanation = """
45+
Public functions in ViewModel classes should start with 'on' to clearly indicate that they represent user or system events, \
46+
following Clean Architecture naming conventions for better readability and maintainability.
47+
""".trimIndent(),
48+
category = Category.CORRECTNESS,
49+
priority = 5,
50+
severity = Severity.WARNING,
51+
implementation = Implementation(
52+
ViewModelPublicFunctionShouldStartWithOnDetector::class.java,
53+
Scope.JAVA_FILE_SCOPE
54+
)
55+
)
56+
}
57+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.mitteloupe.cag.cleanarchitecturegenerator.rule
2+
3+
import com.intellij.codeInsight.intention.FileModifier.SafeTypeForPreview
4+
5+
private val uiTermsRegex =
6+
"(${firstLetter("c")}lick(ed)?|${firstLetter("s")}croll(ed)?|${firstLetter("s")}wipe(d)?|${firstLetter("t")}ap(ped)?|${firstLetter("l")}ongPress(ed)?|${firstLetter("t")}ouch(ed)?)".toRegex()
7+
8+
private fun firstLetter(firstCharacter: String) =
9+
"((?<=\\w)${firstCharacter.uppercase()}|^${firstCharacter.lowercase()})"
10+
11+
object ViewModelFunctionShouldNotContainUiTermsRule {
12+
fun validate(
13+
className: String?,
14+
functionName: String
15+
): Result {
16+
if (className?.endsWith("ViewModel") != true) {
17+
return Result.Valid
18+
}
19+
20+
val uiTermsMatchResult = uiTermsRegex.find(functionName) ?: return Result.Valid
21+
22+
return Result.Invalid(functionName = functionName, offendingValue = uiTermsMatchResult.value)
23+
}
24+
25+
fun violationMessage(offendingValue: String): String =
26+
"Function name contains UI term '$offendingValue', consider replacing it with 'Action'"
27+
28+
sealed interface Result {
29+
data object Valid : Result
30+
31+
@SafeTypeForPreview
32+
data class Invalid(
33+
private val functionName: String,
34+
val offendingValue: String
35+
) : Result {
36+
fun fixFunctionName(): String {
37+
val capitalizedA =
38+
if (functionName.indexOf(offendingValue) == 0) {
39+
"a"
40+
} else {
41+
"A"
42+
}
43+
return functionName.replaceFirst(offendingValue.toRegex(RegexOption.IGNORE_CASE), "${capitalizedA}ction")
44+
}
45+
}
46+
}
47+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.mitteloupe.cag.cleanarchitecturegenerator.rule
2+
3+
object ViewModelPublicFunctionShouldStartWithOnRule {
4+
private val validFunctionNameRegex = Regex("^on[A-Z0-9].*$")
5+
6+
fun isValid(
7+
className: String?,
8+
functionName: String,
9+
isPublic: Boolean
10+
): Boolean =
11+
!isPublic ||
12+
className?.endsWith("ViewModel") != true ||
13+
functionName.matches(validFunctionNameRegex)
14+
15+
fun violationMessage(functionName: String): String =
16+
"ViewModel public function name '$functionName' should start with 'on'"
17+
}

0 commit comments

Comments
 (0)