Created
July 17, 2020 14:02
-
-
Save ataulm/0eca99d2c0d1e0f6afd5acc07eb9726e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Prohibits use of hardcoded colors in XML layouts and color resources. | |
* | |
* A hardcoded color includes: | |
* | |
* - a reference to a color resource which doesn't include "mds" in the name. | |
* "mds" is used as an allowlist filter, where we'll assume that the resource contains theme-friendly colors | |
* or it's an exception to the rule. | |
* - a color hexcode | |
* | |
* It's relatively important to keep this up-to-date with `HardcodedColorsDesignStatsProcessor` so that our stats | |
* tracking is kept reasonably accurate. | |
*/ | |
internal class UseThemeFriendlyColorsInXmlDetector : ResourceXmlDetector() { | |
companion object { | |
val ISSUE = createErrorThemesAndStylesIssue( | |
UseThemeFriendlyColorsInXmlDetector::class, | |
Scope.RESOURCE_FILE_SCOPE | |
) | |
} | |
override fun appliesTo(folderType: ResourceFolderType) = | |
folderType == LAYOUT || folderType == VALUES || folderType == COLOR | |
override fun getApplicableAttributes(): Collection<String> = XmlScannerConstants.ALL | |
override fun visitAttribute(context: XmlContext, attribute: Attr) { | |
when (context.resourceFolderType) { | |
COLOR, LAYOUT -> if (attribute.value.isColorHexcode() || attribute.value.isHardcodedColorResInXml()) { | |
reportIssue(context, attribute) | |
} | |
VALUES -> { | |
// we're only interested in style resources | |
val item = if (attribute.belongsToItem()) attribute.ownerElement else return | |
if (item.belongsToThemeOrThemeOverlay()) { | |
// we define attributes in themes/theme overlays so it's normal to have hardcoded colors here | |
// kind of, maybe | |
return | |
} | |
if (item.belongsToStyle()) { | |
val value = item.firstChild.nodeValue | |
if (value.isColorHexcode() || value.isHardcodedColorResInXml()) { | |
reportIssue(context, attribute) | |
} | |
} | |
} | |
} | |
} | |
private fun reportIssue(context: XmlContext, attribute: Attr) { | |
context.report( | |
issue = ISSUE, | |
scope = attribute, | |
location = context.getValueLocation(attribute), | |
message = "Avoid using hardcoded colors." | |
) | |
} | |
} | |
// These symbols are duplicated in `HardcodedColorsDesignStatsProcessor` | |
private val REGEX_HEX_COLOR = Regex("^#[0-9a-fA-F]{8}$|#[0-9a-fA-F]{6}$|#[0-9a-fA-F]{4}$|#[0-9a-fA-F]{3}$") | |
private fun String.isColorHexcode() = REGEX_HEX_COLOR.containsMatchIn(this) | |
/** | |
* "mds" is used as an allowlist filter, where we'll assume that the resource contains theme-friendly colors | |
* or it's an exception to the rule. | |
*/ | |
private fun String.isHardcodedColorResInXml() = startsWith("@color/") && !contains("mds") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment