Skip to content

Commit cb67cc2

Browse files
committed
Add a few more examples, including a Kotlin Analysis API usage
Also update dependencies. And format with ktfmt.
1 parent 9e7779e commit cb67cc2

10 files changed

+415
-96
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.lint.checks
17+
18+
import com.android.tools.lint.detector.api.Category
19+
import com.android.tools.lint.detector.api.Detector
20+
import com.android.tools.lint.detector.api.Implementation
21+
import com.android.tools.lint.detector.api.Issue
22+
import com.android.tools.lint.detector.api.JavaContext
23+
import com.android.tools.lint.detector.api.Scope
24+
import com.android.tools.lint.detector.api.Severity
25+
import com.android.tools.lint.detector.api.SourceCodeScanner
26+
import com.intellij.psi.PsiMethod
27+
import org.jetbrains.uast.UCallExpression
28+
29+
class AvoidDateDetector : Detector(), SourceCodeScanner {
30+
companion object Issues {
31+
private val IMPLEMENTATION =
32+
Implementation(AvoidDateDetector::class.java, Scope.JAVA_FILE_SCOPE)
33+
34+
@JvmField
35+
val ISSUE =
36+
Issue.create(
37+
id = "OldDate",
38+
briefDescription = "Avoid Date and Calendar",
39+
explanation =
40+
"""
41+
The `java.util.Date` and `java.util.Calendar` classes should not be used; instead \
42+
use the `java.time` package, such as `LocalDate` and `LocalTime`.
43+
""",
44+
category = Category.CORRECTNESS,
45+
priority = 6,
46+
severity = Severity.ERROR,
47+
androidSpecific = true,
48+
implementation = IMPLEMENTATION,
49+
)
50+
}
51+
52+
// java.util.Date()
53+
override fun getApplicableConstructorTypes(): List<String> = listOf("java.util.Date")
54+
55+
override fun visitConstructor(
56+
context: JavaContext,
57+
node: UCallExpression,
58+
constructor: PsiMethod,
59+
) {
60+
context.report(
61+
ISSUE,
62+
node,
63+
context.getLocation(node),
64+
"Don't use `Date`; use `java.time.*` instead",
65+
fix()
66+
.alternatives(
67+
fix().replace().all().with("java.time.LocalTime.now()").shortenNames().build(),
68+
fix().replace().all().with("java.time.LocalDate.now()").shortenNames().build(),
69+
fix().replace().all().with("java.time.LocalDateTime.now()").shortenNames().build(),
70+
),
71+
)
72+
}
73+
74+
// java.util.Calendar.getInstance()
75+
override fun getApplicableMethodNames(): List<String> = listOf("getInstance")
76+
77+
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
78+
val evaluator = context.evaluator
79+
if (!evaluator.isMemberInClass(method, "java.util.Calendar")) {
80+
return
81+
}
82+
context.report(
83+
ISSUE,
84+
node,
85+
context.getLocation(node),
86+
"Don't use `Calendar.getInstance`; use `java.time.*` instead",
87+
)
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.lint.checks
17+
18+
import com.android.tools.lint.client.api.UElementHandler
19+
import com.android.tools.lint.detector.api.Category
20+
import com.android.tools.lint.detector.api.Detector
21+
import com.android.tools.lint.detector.api.Implementation
22+
import com.android.tools.lint.detector.api.Incident
23+
import com.android.tools.lint.detector.api.Issue
24+
import com.android.tools.lint.detector.api.JavaContext
25+
import com.android.tools.lint.detector.api.Scope
26+
import com.android.tools.lint.detector.api.Severity
27+
import com.android.tools.lint.detector.api.SourceCodeScanner
28+
import org.jetbrains.kotlin.analysis.api.analyze
29+
import org.jetbrains.kotlin.codegen.optimization.common.analyze
30+
import org.jetbrains.kotlin.psi.KtExpression
31+
import org.jetbrains.uast.UElement
32+
import org.jetbrains.uast.UPostfixExpression
33+
34+
class NotNullAssertionDetector : Detector(), SourceCodeScanner {
35+
companion object Issues {
36+
private val IMPLEMENTATION =
37+
Implementation(NotNullAssertionDetector::class.java, Scope.JAVA_FILE_SCOPE)
38+
39+
@JvmField
40+
val ISSUE =
41+
Issue.create(
42+
id = "NotNullAssertion",
43+
briefDescription = "Avoid `!!`",
44+
explanation =
45+
"""
46+
Do not use the `!!` operator. It can lead to null pointer exceptions. \
47+
Please use the `?` operator instead, or assign to a local variable with \
48+
`?:` initialization if necessary.
49+
""",
50+
category = Category.CORRECTNESS,
51+
priority = 6,
52+
severity = Severity.WARNING,
53+
implementation = IMPLEMENTATION,
54+
)
55+
}
56+
57+
override fun getApplicableUastTypes(): List<Class<out UElement>>? {
58+
return listOf(UPostfixExpression::class.java)
59+
}
60+
61+
override fun createUastHandler(context: JavaContext): UElementHandler {
62+
return object : UElementHandler() {
63+
override fun visitPostfixExpression(node: UPostfixExpression) {
64+
if (node.operator.text == "!!") {
65+
var message = "Do not use `!!`"
66+
67+
// Kotlin Analysis API example
68+
val sourcePsi = node.operand.sourcePsi
69+
if (sourcePsi is KtExpression) {
70+
analyze(sourcePsi) {
71+
val type = sourcePsi.getKtType()
72+
if (type != null && !type.canBeNull) {
73+
message += " -- it's not even needed here"
74+
}
75+
}
76+
}
77+
78+
val incident = Incident(ISSUE, node, context.getLocation(node), message)
79+
context.report(incident)
80+
}
81+
}
82+
}
83+
}
84+
}

checks/src/main/java/com/example/lint/checks/SampleCodeDetector.kt

+50-53
Original file line numberDiff line numberDiff line change
@@ -29,64 +29,61 @@ import org.jetbrains.uast.ULiteralExpression
2929
import org.jetbrains.uast.evaluateString
3030

3131
/**
32-
* Sample detector showing how to analyze Kotlin/Java code. This example
33-
* flags all string literals in the code that contain the word "lint".
32+
* Sample detector showing how to analyze Kotlin/Java code. This example flags all string literals
33+
* in the code that contain the word "lint".
3434
*/
35-
@Suppress("UnstableApiUsage")
3635
class SampleCodeDetector : Detector(), UastScanner {
37-
override fun getApplicableUastTypes(): List<Class<out UElement?>> {
38-
return listOf(ULiteralExpression::class.java)
39-
}
36+
override fun getApplicableUastTypes(): List<Class<out UElement?>> {
37+
return listOf(ULiteralExpression::class.java)
38+
}
4039

41-
override fun createUastHandler(context: JavaContext): UElementHandler {
42-
// Note: Visiting UAST nodes is a pretty general purpose mechanism;
43-
// Lint has specialized support to do common things like "visit every class
44-
// that extends a given super class or implements a given interface", and
45-
// "visit every call site that calls a method by a given name" etc.
46-
// Take a careful look at UastScanner and the various existing lint check
47-
// implementations before doing things the "hard way".
48-
// Also be aware of context.getJavaEvaluator() which provides a lot of
49-
// utility functionality.
50-
return object : UElementHandler() {
51-
override fun visitLiteralExpression(node: ULiteralExpression) {
52-
val string = node.evaluateString() ?: return
53-
if (string.contains("lint") && string.matches(Regex(".*\\blint\\b.*"))) {
54-
context.report(
55-
ISSUE, node, context.getLocation(node),
56-
"This code mentions `lint`: **Congratulations**"
57-
)
58-
}
59-
}
40+
override fun createUastHandler(context: JavaContext): UElementHandler {
41+
// Note: Visiting UAST nodes is a pretty general purpose mechanism;
42+
// Lint has specialized support to do common things like "visit every class
43+
// that extends a given super class or implements a given interface", and
44+
// "visit every call site that calls a method by a given name" etc.
45+
// Take a careful look at UastScanner and the various existing lint check
46+
// implementations before doing things the "hard way".
47+
// Also be aware of context.getJavaEvaluator() which provides a lot of
48+
// utility functionality.
49+
return object : UElementHandler() {
50+
override fun visitLiteralExpression(node: ULiteralExpression) {
51+
val string = node.evaluateString() ?: return
52+
if (string.contains("lint") && string.matches(Regex(".*\\blint\\b.*"))) {
53+
context.report(
54+
ISSUE,
55+
node,
56+
context.getLocation(node),
57+
"This code mentions `lint`: **Congratulations**",
58+
)
6059
}
60+
}
6161
}
62+
}
6263

63-
companion object {
64-
/**
65-
* Issue describing the problem and pointing to the detector
66-
* implementation.
67-
*/
68-
@JvmField
69-
val ISSUE: Issue = Issue.create(
70-
// ID: used in @SuppressLint warnings etc
71-
id = "SampleId",
72-
// Title -- shown in the IDE's preference dialog, as category headers in the
73-
// Analysis results window, etc
74-
briefDescription = "Lint Mentions",
75-
// Full explanation of the issue; you can use some markdown markup such as
76-
// `monospace`, *italic*, and **bold**.
77-
explanation = """
78-
This check highlights string literals in code which mentions the word `lint`. \
79-
Blah blah blah.
64+
companion object {
65+
/** Issue describing the problem and pointing to the detector implementation. */
66+
@JvmField
67+
val ISSUE: Issue =
68+
Issue.create(
69+
// ID: used in @SuppressLint warnings etc
70+
id = "SampleId",
71+
// Title -- shown in the IDE's preference dialog, as category headers in the
72+
// Analysis results window, etc
73+
briefDescription = "Lint Mentions",
74+
// Full explanation of the issue; you can use some markdown markup such as
75+
// `monospace`, *italic*, and **bold**.
76+
explanation =
77+
"""
78+
This check highlights string literals in code which mentions the word `lint`. \
79+
Blah blah blah.
8080
81-
Another paragraph here.
82-
""", // no need to .trimIndent(), lint does that automatically
83-
category = Category.CORRECTNESS,
84-
priority = 6,
85-
severity = Severity.WARNING,
86-
implementation = Implementation(
87-
SampleCodeDetector::class.java,
88-
Scope.JAVA_FILE_SCOPE
89-
)
90-
)
91-
}
81+
Another paragraph here.
82+
""", // no need to .trimIndent(), lint does that automatically
83+
category = Category.CORRECTNESS,
84+
priority = 6,
85+
severity = Severity.WARNING,
86+
implementation = Implementation(SampleCodeDetector::class.java, Scope.JAVA_FILE_SCOPE),
87+
)
88+
}
9289
}

checks/src/main/java/com/example/lint/checks/SampleIssueRegistry.kt

+13-12
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,22 @@ import com.android.tools.lint.detector.api.CURRENT_API
2222
/*
2323
* The list of issues that will be checked when running <code>lint</code>.
2424
*/
25-
@Suppress("UnstableApiUsage")
2625
class SampleIssueRegistry : IssueRegistry() {
27-
override val issues = listOf(SampleCodeDetector.ISSUE)
26+
override val issues =
27+
listOf(SampleCodeDetector.ISSUE, AvoidDateDetector.ISSUE, NotNullAssertionDetector.ISSUE)
2828

29-
override val api: Int
30-
get() = CURRENT_API
29+
override val api: Int
30+
get() = CURRENT_API
3131

32-
override val minApi: Int
33-
get() = 8 // works with Studio 4.1 or later; see com.android.tools.lint.detector.api.Api / ApiKt
32+
override val minApi: Int
33+
get() = 8 // works with Studio 4.1 or later; see com.android.tools.lint.detector.api.Api / ApiKt
3434

35-
// Requires lint API 30.0+; if you're still building for something
36-
// older, just remove this property.
37-
override val vendor: Vendor = Vendor(
38-
vendorName = "Android Open Source Project",
39-
feedbackUrl = "https://github.com/googlesamples/android-custom-lint-rules/issues",
40-
contact = "https://github.com/googlesamples/android-custom-lint-rules"
35+
// Requires lint API 30.0+; if you're still building for something
36+
// older, just remove this property.
37+
override val vendor: Vendor =
38+
Vendor(
39+
vendorName = "Android Open Source Project",
40+
feedbackUrl = "https://github.com/googlesamples/android-custom-lint-rules/issues",
41+
contact = "https://github.com/googlesamples/android-custom-lint-rules",
4142
)
4243
}

0 commit comments

Comments
 (0)