Skip to content

Commit db94468

Browse files
[DON-2384] Add dimension lint detectors (padding, border radius, size) (#2581)
* Add dimension lint detectors (padding, border radius, size) - Add HardcodedPaddingDetector: Detects hardcoded padding values - Add HardcodedBorderRadiusDetector: Detects hardcoded border radius values - Add HardcodedSizeDetector: Detects hardcoded size values in Compose - Add unit tests for all three detectors - Register new detectors in IssueRegistry These detectors help enforce the use of Backpack spacing and dimension tokens instead of hardcoded dp values. * Allow named constants with .dp, only flag hardcoded literals - Update extractNumericDpValue to verify source text matches numeric pattern - This ensures IMAGE_HEIGHT.dp is allowed but 16.dp is flagged - Fix EXPLANATION to use const val with string interpolation --------- Co-authored-by: lokmane.krizou@skyscanner.net <lokmane.krizou@skyscanner.net>
1 parent 3204aa6 commit db94468

File tree

8 files changed

+1034
-6
lines changed

8 files changed

+1034
-6
lines changed

backpack-lint/src/main/java/net/skyscanner/backpack/lint/IssueRegistry.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import com.android.tools.lint.detector.api.Issue
2525
import net.skyscanner.backpack.lint.check.BpkComponentUsageDetector
2626
import net.skyscanner.backpack.lint.check.BpkComposeComponentUsageDetector
2727
import net.skyscanner.backpack.lint.check.BpkDeprecatedColorUsageDetector
28+
import net.skyscanner.backpack.lint.check.HardcodedBorderRadiusDetector
2829
import net.skyscanner.backpack.lint.check.HardcodedColorResourceDetector
2930
import net.skyscanner.backpack.lint.check.HardcodedColorUsageDetector
31+
import net.skyscanner.backpack.lint.check.HardcodedPaddingDetector
32+
import net.skyscanner.backpack.lint.check.HardcodedSizeDetector
3033
import net.skyscanner.backpack.lint.check.UnicodeIconUsageDetector
3134

3235
@Suppress("unused", "UnstableApiUsage")
@@ -40,6 +43,9 @@ class IssueRegistry : IssueRegistry() {
4043
HardcodedColorUsageDetector.ISSUE,
4144
HardcodedColorResourceDetector.ISSUE,
4245
BpkDeprecatedColorUsageDetector.ISSUE,
46+
HardcodedPaddingDetector.ISSUE,
47+
HardcodedBorderRadiusDetector.ISSUE,
48+
HardcodedSizeDetector.ISSUE,
4349
UnicodeIconUsageDetector.ISSUE,
4450
)
4551

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Backpack for Android - Skyscanner's Design System
3+
*
4+
* Copyright 2018 - 2026 Skyscanner Ltd
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package net.skyscanner.backpack.lint.check
20+
21+
import com.android.tools.lint.detector.api.Category
22+
import com.android.tools.lint.detector.api.Detector
23+
import com.android.tools.lint.detector.api.Implementation
24+
import com.android.tools.lint.detector.api.Issue
25+
import com.android.tools.lint.detector.api.JavaContext
26+
import com.android.tools.lint.detector.api.Scope
27+
import com.android.tools.lint.detector.api.Severity
28+
import com.android.tools.lint.detector.api.SourceCodeScanner
29+
import com.intellij.psi.PsiElement
30+
import net.skyscanner.backpack.lint.util.LintConstants
31+
import net.skyscanner.backpack.lint.util.TokenSuggestionBuilder
32+
import net.skyscanner.backpack.lint.util.UastTreeUtils
33+
import org.jetbrains.uast.UQualifiedReferenceExpression
34+
import org.jetbrains.uast.UReferenceExpression
35+
36+
/**
37+
* Detects hardcoded .dp values used in border radius/corner methods and suggests BpkBorderRadius tokens.
38+
*/
39+
@Suppress("UnstableApiUsage")
40+
class HardcodedBorderRadiusDetector : Detector(), SourceCodeScanner {
41+
42+
companion object {
43+
private const val EXPLANATION =
44+
"Use BpkBorderRadius.* tokens instead of hardcoded .dp values. " +
45+
"Hardcoding border radius bypasses the design system and creates inconsistent shapes.\n\n" +
46+
"${LintConstants.SUPPORT_MESSAGE}"
47+
48+
val ISSUE = Issue.create(
49+
id = "HardcodedBorderRadius",
50+
briefDescription = "Hardcoded border radius (.dp) detected",
51+
explanation = EXPLANATION,
52+
category = Category.CORRECTNESS,
53+
severity = Severity.ERROR,
54+
implementation = Implementation(
55+
HardcodedBorderRadiusDetector::class.java,
56+
Scope.JAVA_FILE_SCOPE,
57+
),
58+
)
59+
60+
private val BORDER_RADIUS_METHODS = setOf(
61+
"corner",
62+
"cornerRadius",
63+
"clip",
64+
"shape",
65+
"topStart",
66+
"topEnd",
67+
"bottomStart",
68+
"bottomEnd",
69+
"RoundedCornerShape",
70+
)
71+
72+
private val suggestionBuilder = TokenSuggestionBuilder(
73+
tokenMap = GeneratedBorderRadiusTokenMap.BORDER_RADIUS_TOKEN_MAP,
74+
tokenTypePrefix = "BpkBorderRadius",
75+
)
76+
}
77+
78+
override fun getApplicableReferenceNames(): List<String> = listOf("dp")
79+
80+
override fun visitReference(
81+
context: JavaContext,
82+
reference: UReferenceExpression,
83+
referenced: PsiElement,
84+
) {
85+
val parent = reference.uastParent
86+
if (parent is UQualifiedReferenceExpression) {
87+
val intValue = UastTreeUtils.extractNumericDpValue(parent)
88+
if (intValue != null && isInsideBorderRadiusMethod(parent)) {
89+
val message = suggestionBuilder.buildMessage(intValue)
90+
val fix = suggestionBuilder.buildLintFix(intValue)
91+
context.report(ISSUE, context.getLocation(parent), message, fix)
92+
}
93+
}
94+
}
95+
96+
private fun isInsideBorderRadiusMethod(dpExpression: UQualifiedReferenceExpression): Boolean {
97+
return UastTreeUtils.isInsideMethodCall(
98+
element = dpExpression,
99+
methodNames = BORDER_RADIUS_METHODS,
100+
additionalMatcher = { it.contains("corner", ignoreCase = true) },
101+
)
102+
}
103+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Backpack for Android - Skyscanner's Design System
3+
*
4+
* Copyright 2018 - 2026 Skyscanner Ltd
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package net.skyscanner.backpack.lint.check
20+
21+
import com.android.tools.lint.detector.api.Category
22+
import com.android.tools.lint.detector.api.Detector
23+
import com.android.tools.lint.detector.api.Implementation
24+
import com.android.tools.lint.detector.api.Issue
25+
import com.android.tools.lint.detector.api.JavaContext
26+
import com.android.tools.lint.detector.api.Scope
27+
import com.android.tools.lint.detector.api.Severity
28+
import com.android.tools.lint.detector.api.SourceCodeScanner
29+
import com.intellij.psi.PsiElement
30+
import net.skyscanner.backpack.lint.util.LintConstants
31+
import net.skyscanner.backpack.lint.util.TokenSuggestionBuilder
32+
import net.skyscanner.backpack.lint.util.UastTreeUtils
33+
import org.jetbrains.uast.UQualifiedReferenceExpression
34+
import org.jetbrains.uast.UReferenceExpression
35+
36+
/**
37+
* Detects hardcoded .dp values used in padding methods and suggests BpkSpacing tokens.
38+
*/
39+
@Suppress("UnstableApiUsage")
40+
class HardcodedPaddingDetector : Detector(), SourceCodeScanner {
41+
42+
companion object {
43+
private const val EXPLANATION =
44+
"Use BpkSpacing.* tokens instead of hardcoded .dp values for padding. " +
45+
"Hardcoding spacing bypasses the design system and creates inconsistent layouts.\n\n" +
46+
"${LintConstants.SUPPORT_MESSAGE}"
47+
48+
val ISSUE = Issue.create(
49+
id = "HardcodedPadding",
50+
briefDescription = "Hardcoded padding (.dp) detected",
51+
explanation = EXPLANATION,
52+
category = Category.CORRECTNESS,
53+
severity = Severity.ERROR,
54+
implementation = Implementation(
55+
HardcodedPaddingDetector::class.java,
56+
Scope.JAVA_FILE_SCOPE,
57+
),
58+
)
59+
60+
private val PADDING_METHODS = setOf(
61+
"padding",
62+
"paddingStart",
63+
"paddingEnd",
64+
"paddingTop",
65+
"paddingBottom",
66+
"paddingHorizontal",
67+
"paddingVertical",
68+
"absolutePadding",
69+
"windowInsetsPadding",
70+
"safeContentPadding",
71+
"systemBarsPadding",
72+
)
73+
74+
private val suggestionBuilder = TokenSuggestionBuilder(
75+
tokenMap = GeneratedSpacingTokenMap.SPACING_TOKEN_MAP,
76+
tokenTypePrefix = "BpkSpacing",
77+
)
78+
}
79+
80+
override fun getApplicableReferenceNames(): List<String> = listOf("dp")
81+
82+
override fun visitReference(
83+
context: JavaContext,
84+
reference: UReferenceExpression,
85+
referenced: PsiElement,
86+
) {
87+
val parent = reference.uastParent
88+
if (parent is UQualifiedReferenceExpression) {
89+
val intValue = UastTreeUtils.extractNumericDpValue(parent)
90+
if (intValue != null && UastTreeUtils.isInsideMethodCall(parent, PADDING_METHODS)) {
91+
val message = suggestionBuilder.buildMessage(intValue)
92+
val fix = suggestionBuilder.buildLintFix(intValue)
93+
context.report(ISSUE, context.getLocation(parent), message, fix)
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)