Skip to content

Lint checks for colorAccent and colorPrimaryDark #1523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions catalog/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies {

api compatibility("recyclerViewSelection")

lintChecks project(':lint-checks')

androidTestImplementation "androidx.test:core:${project.rootProject.ext.testRunnerVersion}"
androidTestImplementation "androidx.test:runner:${project.rootProject.ext.testRunnerVersion}"
androidTestImplementation "androidx.test:rules:${project.rootProject.ext.testRunnerVersion}"
Expand Down
59 changes: 59 additions & 0 deletions lint-checks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Lint checks

- `ThemeCheckColorAccent` : Detects usages colorAccent in style resources
- `ThemeCheckColorPrimaryDark` : Detects usages colorPrimaryDark in style resources

## Setup in your project

In order to get the lint checks running on the code in your module:

1. Open the `build.gradle` file for your application.
2. Make sure that the `repositories` section includes Google's Maven Repository
`google()`. For example:

```groovy
allprojects {
repositories {
google()
jcenter()
}
}
```

3. Add the library to the `dependencies` section:

```groovy
dependencies {
// ...
lintChecks 'com.google.android.material:material-lint-checks:x.x.x'
// ...
}
```

Visit
[Google's Maven Repository](https://maven.google.com/web/index.html#com.google.android.material:material-lint-checks)
or
[MVN Repository](https://mvnrepository.com/artifact/com.google.android.material/material-lint-checks)
to find the latest version of the library.

## Usage

Run lint on your app module with the following Gradle command:

```
./gradlew app:lintDebug
```

## Disable Lint checks:

If you only need some of lint checks included you can disable them as follows:

In your `build.gradle` file:

```
android {
lintOptions {
disable 'ThemeCheckColorAccent', 'ThemeCheckColorPrimaryDark'
}
}
```
17 changes: 17 additions & 0 deletions lint-checks/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apply plugin: 'kotlin'

dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compileOnly "com.android.tools.lint:lint-api:27.0.1"
compileOnly "com.android.tools.lint:lint-checks:27.0.1"

testImplementation "com.android.tools.lint:lint-tests:27.0.1"
testImplementation "junit:junit:4.12"
testImplementation "org.assertj:assertj-core:3.9.0"
}

jar {
manifest {
attributes("Lint-Registry-v2": "com.google.android.material.lint.checks.IssueRegistry")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.google.android.material.lint.checks

import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.detector.api.Issue

/**
* The class contains the list of issues that will be checked when running lint.
*/

class IssueRegistry : IssueRegistry() {

override val api: Int = CURRENT_API

override val issues: List<Issue> = listOf(
ThemeCheckColorAccentDetector.ISSUE,
ThemeCheckColorPrimaryDarkDetector.ISSUE
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.google.android.material.lint.checks

import com.android.resources.ResourceFolderType
import com.android.tools.lint.detector.api.*
import org.w3c.dom.Attr
import org.w3c.dom.Element
import org.w3c.dom.Node

class ThemeCheckColorAccentDetector : ResourceXmlDetector() {

companion object {
val ISSUE = Issue.create(
id = "ThemeCheckColorAccent",
briefDescription = "Detects usages colorAccent in style resources",
explanation = "The colorAccent is superseded by newer theme attribute colorSecondary in style resources",
category = Category.CORRECTNESS,
severity = Severity.WARNING,
implementation = Implementation(
ThemeCheckColorAccentDetector::class.java,
Scope.RESOURCE_FILE_SCOPE
)
)
private const val NODE_STYLE = "style"
private const val NODE_ITEM = "item"
private const val ATTR_NAME = "name"
private const val COLOR_ACCENT = "colorAccent"
}

override fun appliesTo(folderType: ResourceFolderType): Boolean {
//we only need to analyze 'style' in the 'values' resource folder.
return folderType == ResourceFolderType.VALUES
}

override fun getApplicableElements(): Collection<String>? {
//we want to analyze every `<item>` element that is declared in XML.
return setOf(NODE_ITEM)
}

override fun visitElement(context: XmlContext, element: Element) {

if (element.hasAttribute(ATTR_NAME) && element.parentNode.nodeName == NODE_STYLE) {
visitAttribute(context,element.getAttributeNode(ATTR_NAME))
}
}

override fun visitAttribute(context: XmlContext, attribute: Attr) {

val attributeName = attribute.nodeValue

if (attributeName == COLOR_ACCENT) {

context.report(
issue = ISSUE,
scope = attribute,
location = context.getLocation(attribute.ownerElement),
message = "colorAccent is superseded by newer theme attribute colorSecondary",
quickfixData = LintFix.create()
.replace()
.text("colorAccent")
.with("colorSecondary")
.build()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.google.android.material.lint.checks

import com.android.resources.ResourceFolderType
import com.android.tools.lint.detector.api.*
import org.w3c.dom.Attr
import org.w3c.dom.Element
import org.w3c.dom.Node

class ThemeCheckColorPrimaryDarkDetector : ResourceXmlDetector() {

companion object {
val ISSUE = Issue.create(
id = "ThemeCheckColorPrimaryDark",
briefDescription = "Detects usages colorPrimaryDark in style resources",
explanation = "The colorPrimaryDark is superseded by newer theme attribute colorPrimaryVariant in style resources",
category = Category.CORRECTNESS,
severity = Severity.WARNING,
implementation = Implementation(
ThemeCheckColorPrimaryDarkDetector::class.java,
Scope.RESOURCE_FILE_SCOPE
)
)
private const val NODE_STYLE = "style"
private const val NODE_ITEM = "item"
private const val ATTR_NAME = "name"
private const val COLOR_PRIMARY_DARK = "colorPrimaryDark"
}

override fun appliesTo(folderType: ResourceFolderType): Boolean {
//we only need to analyze 'style' in the 'values' resource folder.
return folderType == ResourceFolderType.VALUES
}

override fun getApplicableElements(): Collection<String>? {
//we want to analyze every `<item>` element that is declared in XML.
return setOf(NODE_ITEM)
}

override fun visitElement(context: XmlContext, element: Element) {

if (element.hasAttribute(ATTR_NAME) && element.parentNode.nodeName == NODE_STYLE) {
visitAttribute(context,element.getAttributeNode(ATTR_NAME))
}
}

override fun visitAttribute(context: XmlContext, attribute: Attr) {

val attributeName = attribute.nodeValue

if (attributeName == COLOR_PRIMARY_DARK) {

context.report(
issue = ISSUE,
scope = attribute,
location = context.getLocation(attribute.ownerElement),
message = "colorPrimaryDark is superseded by newer theme attribute colorPrimaryVariant",
quickfixData = LintFix.create()
.replace()
.text("colorPrimaryDark")
.with("colorPrimaryVariant")
.build()
)
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.google.android.material.lint.checks

import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class ThemeCheckColorAccentDetectorTest : LintDetectorTest() {

override fun getIssues(): MutableList<Issue> = mutableListOf(ThemeCheckColorAccentDetector.ISSUE)

override fun getDetector(): Detector = ThemeCheckColorAccentDetector()

@Test
fun expectPass() {
lint()
.allowMissingSdk(true)
.files(
xml(
"res/values/styles.xml", """
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<item name="colorPrimary">@color/blu500</item>
</style>
</resources>
"""
)
)
.run()
.expectClean()
}

@Test
fun expectFail() {
lint()
.allowMissingSdk(true)
.files(
xml(
"res/values/styles.xml", """
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<item name="colorAccent">@color/blu500</item>
</style>
</resources>
"""
)
)
.run()
.expect(
"""
res/values/styles.xml:4: Warning: colorAccent is superseded by newer theme attribute colorSecondary [ThemeCheckColorAccent]
<item name="colorAccent">@color/blu500</item>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.google.android.material.lint.checks

import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class ThemeCheckColorPrimaryDarkDetectorTest : LintDetectorTest() {

override fun getIssues(): MutableList<Issue> = mutableListOf(ThemeCheckColorPrimaryDarkDetector.ISSUE)

override fun getDetector(): Detector = ThemeCheckColorPrimaryDarkDetector()

@Test
fun expectPass() {
lint()
.allowMissingSdk(true)
.files(
xml(
"res/values/styles.xml", """
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<item name="colorPrimary">@color/blu500</item>
</style>
</resources>
"""
)
)
.run()
.expectClean()
}

@Test
fun expectFail() {
lint()
.allowMissingSdk(true)
.files(
xml(
"res/values/styles.xml", """
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<item name="colorPrimaryDark">@color/blu500</item>
</style>
</resources>
"""
)
)
.run()
.expect(
"""
res/values/styles.xml:4: Warning: colorPrimaryDark is superseded by newer theme attribute colorPrimaryVariant [ThemeCheckColorPrimaryDark]
<item name="colorPrimaryDark">@color/blu500</item>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
}
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ include ':testing:java:com:google:android:material:testapp:base'
include ':testing:java:com:google:android:material:testapp:custom'
include ':testing:java:com:google:android:material:testapp:theme'

include ':lint-checks'

include ':catalog'

rootProject.children.each { p ->
Expand Down