Skip to content

Commit c7e945f

Browse files
committed
Add lint rule for migrate Entries
1 parent 7844647 commit c7e945f

6 files changed

Lines changed: 186 additions & 0 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ android {
5656
}
5757

5858
dependencies {
59+
lintChecks(project(":lint"))
60+
5961
implementation("androidx.appcompat:appcompat:1.7.1")
6062
implementation("com.google.android.material:material:1.13.0")
6163
implementation(project(":chartLib"))

chartLib/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ android {
5050
}
5151

5252
dependencies {
53+
lintChecks(project(":lint"))
54+
5355
implementation("androidx.annotation:annotation:1.10.0")
5456
implementation("androidx.core:core:1.18.0")
5557
implementation("androidx.activity:activity-ktx:1.13.0")

lint/build.gradle.kts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
kotlin("jvm")
6+
}
7+
8+
java {
9+
sourceCompatibility = JavaVersion.VERSION_17
10+
targetCompatibility = JavaVersion.VERSION_17
11+
}
12+
13+
tasks.withType<KotlinCompile>().configureEach {
14+
compilerOptions {
15+
jvmTarget.set(JvmTarget.JVM_17)
16+
}
17+
}
18+
19+
// Lint API version tracks AGP: AGP 9.1.0 → Lint 32.1.0
20+
val lintVersion = "32.1.0"
21+
22+
dependencies {
23+
compileOnly("com.android.tools.lint:lint-api:$lintVersion")
24+
compileOnly("com.android.tools.lint:lint-checks:$lintVersion")
25+
26+
testImplementation("com.android.tools.lint:lint:$lintVersion")
27+
testImplementation("com.android.tools.lint:lint-tests:$lintVersion")
28+
testImplementation("junit:junit:4.13.2")
29+
}
30+
31+
// The manifest attribute tells the Android Lint tooling which IssueRegistry to load.
32+
tasks.jar {
33+
manifest {
34+
attributes["Lint-Registry-v2"] = "info.appdev.charting.lint.LintRegistry"
35+
}
36+
}
37+
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package info.appdev.charting.lint
2+
3+
import com.android.tools.lint.detector.api.Category
4+
import com.android.tools.lint.detector.api.Detector
5+
import com.android.tools.lint.detector.api.Implementation
6+
import com.android.tools.lint.detector.api.Issue
7+
import com.android.tools.lint.detector.api.JavaContext
8+
import com.android.tools.lint.detector.api.Scope
9+
import com.android.tools.lint.detector.api.Severity
10+
import com.android.tools.lint.detector.api.SourceCodeScanner
11+
import com.intellij.psi.PsiClass
12+
import com.intellij.psi.PsiElement
13+
import org.jetbrains.uast.UReferenceExpression
14+
import org.jetbrains.uast.UImportStatement
15+
import org.jetbrains.uast.getParentOfType
16+
17+
/**
18+
* Lint detector that flags all usages of deprecated legacy entry classes and offers an
19+
* auto-fix to replace them with their `*Float` equivalents.
20+
*
21+
* Covered classes → replacements:
22+
* - Entry → EntryFloat
23+
* - BarEntry → BarEntryFloat
24+
* - BubbleEntry → BubbleEntryFloat
25+
* - CandleEntry → CandleEntryFloat
26+
* - PieEntry → PieEntryFloat
27+
* - RadarEntry → RadarEntryFloat
28+
*/
29+
class EntryUsageDetector : Detector(), SourceCodeScanner {
30+
31+
companion object {
32+
private const val PKG = "info.appdev.charting.data"
33+
34+
/**
35+
* Maps deprecated FQN → replacement FQN.
36+
* Add future deprecated classes here; everything else is derived automatically.
37+
*/
38+
private val REPLACEMENTS: Map<String, String> = mapOf(
39+
"$PKG.Entry" to "$PKG.EntryFloat",
40+
"$PKG.BarEntry" to "$PKG.BarEntryFloat",
41+
"$PKG.BubbleEntry" to "$PKG.BubbleEntryFloat",
42+
"$PKG.CandleEntry" to "$PKG.CandleEntryFloat",
43+
"$PKG.PieEntry" to "$PKG.PieEntryFloat",
44+
"$PKG.RadarEntry" to "$PKG.RadarEntryFloat",
45+
)
46+
47+
@JvmField
48+
val ISSUE: Issue = Issue.create(
49+
id = "LegacyEntryUsage",
50+
briefDescription = "Replace deprecated legacy entry class with its `*Float` equivalent",
51+
explanation = """
52+
Several entry classes (`Entry`, `BarEntry`, `BubbleEntry`, `CandleEntry`, \
53+
`PieEntry`, `RadarEntry`) are deprecated and will be removed in a future version. \
54+
Replace them with their `*Float` equivalents (e.g. `BarEntryFloat`) for identical \
55+
precision, or with `*Double` equivalents for higher precision.
56+
""",
57+
category = Category.CORRECTNESS,
58+
priority = 6,
59+
severity = Severity.WARNING,
60+
implementation = Implementation(
61+
EntryUsageDetector::class.java,
62+
Scope.JAVA_FILE_SCOPE
63+
)
64+
)
65+
66+
/** Pre-computed set of simple names for fast look-up in visitReference(). */
67+
private val DEPRECATED_SIMPLE_NAMES: Set<String> =
68+
REPLACEMENTS.keys.map { it.substringAfterLast('.') }.toSet()
69+
}
70+
71+
// -----------------------------------------------------------------------
72+
// Tell Lint to invoke visitReference() for every one of these identifiers.
73+
// -----------------------------------------------------------------------
74+
override fun getApplicableReferenceNames(): List<String> =
75+
DEPRECATED_SIMPLE_NAMES.toList()
76+
77+
override fun visitReference(
78+
context: JavaContext,
79+
reference: UReferenceExpression,
80+
referenced: PsiElement
81+
) {
82+
if (referenced !is PsiClass) return
83+
val deprecatedFqn = referenced.qualifiedName ?: return
84+
val replacementFqn = REPLACEMENTS[deprecatedFqn] ?: return
85+
86+
val deprecatedSimple = deprecatedFqn.substringAfterLast('.')
87+
val replacementSimple = replacementFqn.substringAfterLast('.')
88+
89+
// --- Import statement: replace the whole FQN ---
90+
val importNode = reference.getParentOfType<UImportStatement>()
91+
if (importNode != null) {
92+
context.report(
93+
ISSUE,
94+
importNode,
95+
context.getLocation(importNode),
96+
"Replace deprecated `$deprecatedSimple` import with `$replacementSimple`",
97+
fix().replace()
98+
.text(deprecatedFqn)
99+
.with(replacementFqn)
100+
.autoFix()
101+
.build()
102+
)
103+
return
104+
}
105+
106+
// --- All other references (type annotations, constructor calls, …) ---
107+
context.report(
108+
ISSUE,
109+
reference,
110+
context.getLocation(reference),
111+
"Replace deprecated `$deprecatedSimple` with `$replacementSimple`",
112+
fix().replace()
113+
.text(deprecatedSimple)
114+
.with(replacementSimple)
115+
.autoFix()
116+
.build()
117+
)
118+
}
119+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package info.appdev.charting.lint
2+
3+
import com.android.tools.lint.client.api.IssueRegistry
4+
import com.android.tools.lint.client.api.Vendor
5+
import com.android.tools.lint.detector.api.CURRENT_API
6+
import com.android.tools.lint.detector.api.Issue
7+
8+
class LintRegistry : IssueRegistry() {
9+
10+
override val issues: List<Issue> = listOf(
11+
EntryUsageDetector.ISSUE
12+
)
13+
14+
/** Must match the Lint API version used at compile time. */
15+
override val api: Int = CURRENT_API
16+
17+
/** Minimum Lint API version this registry works with. */
18+
override val minApi: Int = 14
19+
20+
override val vendor: Vendor = Vendor(
21+
vendorName = "AndroidChart",
22+
feedbackUrl = "https://github.com/AppDevNext/AndroidChart/issues"
23+
)
24+
}
25+

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
include("chartLib")
22
include("chartLibCompose")
33
include("app")
4+
include("lint")

0 commit comments

Comments
 (0)