Skip to content

Instantly share code, notes, and snippets.

@ataulm
Created July 17, 2020 14:02
Show Gist options
  • Save ataulm/0eca99d2c0d1e0f6afd5acc07eb9726e to your computer and use it in GitHub Desktop.
Save ataulm/0eca99d2c0d1e0f6afd5acc07eb9726e to your computer and use it in GitHub Desktop.
/**
* 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