From 414e905b7673ff15f27fd2ed13334b6a9f45ca17 Mon Sep 17 00:00:00 2001 From: ekager Date: Tue, 3 Mar 2020 10:44:54 -0800 Subject: [PATCH] Closes #8944 - Adds custom lint checks --- app/build.gradle | 2 + mozilla-lint-rules/.gitignore | 1 + mozilla-lint-rules/build.gradle | 21 ++++ .../fenix/lintrules/AndroidSrcXmlDetector.kt | 80 +++++++++++++++ .../ImageViewAndroidTintXmlDetector.kt | 81 ++++++++++++++++ .../fenix/lintrules/LintIssueRegistry.kt | 21 ++++ .../TextViewAndroidSrcXmlDetector.kt | 97 +++++++++++++++++++ settings.gradle | 1 + 8 files changed, 304 insertions(+) create mode 100644 mozilla-lint-rules/.gitignore create mode 100644 mozilla-lint-rules/build.gradle create mode 100644 mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/AndroidSrcXmlDetector.kt create mode 100644 mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/ImageViewAndroidTintXmlDetector.kt create mode 100644 mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/LintIssueRegistry.kt create mode 100644 mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/TextViewAndroidSrcXmlDetector.kt diff --git a/app/build.gradle b/app/build.gradle index 12c475f92..349e66385 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -581,6 +581,8 @@ dependencies { // For the initial release of Glean 19, we require consumer applications to // depend on a separate library for unit tests. This will be removed in future releases. testImplementation "org.mozilla.telemetry:glean-forUnitTests:${project.ext.glean_version}" + + lintChecks project(":mozilla-lint-rules") } if (project.hasProperty("raptor")) { diff --git a/mozilla-lint-rules/.gitignore b/mozilla-lint-rules/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/mozilla-lint-rules/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mozilla-lint-rules/build.gradle b/mozilla-lint-rules/build.gradle new file mode 100644 index 000000000..9a8aa848e --- /dev/null +++ b/mozilla-lint-rules/build.gradle @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +apply plugin: 'java-library' +apply plugin: 'kotlin' + +targetCompatibility = JavaVersion.VERSION_1_8 +sourceCompatibility = JavaVersion.VERSION_1_8 + +dependencies { + compileOnly "org.jetbrains.kotlin:kotlin-stdlib:1.3.61" + compileOnly "com.android.tools.lint:lint-api:26.6.1" + compileOnly "com.android.tools.lint:lint-checks:26.6.1" +} + +jar { + manifest { + attributes('Lint-Registry-v2': 'org.mozilla.fenix.lintrules.LintIssueRegistry') + } +} diff --git a/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/AndroidSrcXmlDetector.kt b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/AndroidSrcXmlDetector.kt new file mode 100644 index 000000000..9b9b8eb39 --- /dev/null +++ b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/AndroidSrcXmlDetector.kt @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.lintrules + +import com.android.SdkConstants.ATTR_SRC +import com.android.SdkConstants.FQCN_IMAGE_BUTTON +import com.android.SdkConstants.FQCN_IMAGE_VIEW +import com.android.SdkConstants.IMAGE_BUTTON +import com.android.SdkConstants.IMAGE_VIEW +import com.android.resources.ResourceFolderType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.ResourceXmlDetector +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.XmlContext +import org.w3c.dom.Element + +/** + * A custom lint check that prohibits not using the app:srcCompat for ImageViews + */ +class AndroidSrcXmlDetector : ResourceXmlDetector() { + companion object { + const val SCHEMA = "http://schemas.android.com/apk/res/android" + const val FULLY_QUALIFIED_APP_COMPAT_IMAGE_BUTTON = + "androidx.appcompat.widget.AppCompatImageButton" + const val FULLY_QUALIFIED_APP_COMPAT_VIEW_CLASS = + "androidx.appcompat.widget.AppCompatImageView" + const val APP_COMPAT_IMAGE_BUTTON = "AppCompatImageButton" + const val APP_COMPAT_IMAGE_VIEW = "AppCompatImageView" + + const val ERROR_MESSAGE = "Using android:src to define resource instead of app:srcCompat" + + val ISSUE_XML_SRC_USAGE = Issue.create( + id = "AndroidSrcXmlDetector", + briefDescription = "Prohibits using android:src in ImageViews and ImageButtons", + explanation = "ImageView (and descendants) images should be declared using app:srcCompat", + category = Category.CORRECTNESS, + severity = Severity.ERROR, + implementation = Implementation( + AndroidSrcXmlDetector::class.java, + Scope.RESOURCE_FILE_SCOPE + ) + ) + } + + override fun appliesTo(folderType: ResourceFolderType): Boolean { + // Return true if we want to analyze resource files in the specified resource + // folder type. In this case we only need to analyze layout resource files. + return folderType == ResourceFolderType.LAYOUT + } + + override fun getApplicableElements(): Collection? { + return setOf( + FQCN_IMAGE_VIEW, + IMAGE_VIEW, + FQCN_IMAGE_BUTTON, + IMAGE_BUTTON, + FULLY_QUALIFIED_APP_COMPAT_IMAGE_BUTTON, + FULLY_QUALIFIED_APP_COMPAT_VIEW_CLASS, + APP_COMPAT_IMAGE_BUTTON, + APP_COMPAT_IMAGE_VIEW + ) + } + + override fun visitElement(context: XmlContext, element: Element) { + if (!element.hasAttributeNS(SCHEMA, ATTR_SRC)) return + val node = element.getAttributeNodeNS(SCHEMA, ATTR_SRC) + + context.report( + issue = ISSUE_XML_SRC_USAGE, + scope = node, + location = context.getLocation(node), + message = ERROR_MESSAGE + ) + } +} diff --git a/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/ImageViewAndroidTintXmlDetector.kt b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/ImageViewAndroidTintXmlDetector.kt new file mode 100644 index 000000000..6f4e74536 --- /dev/null +++ b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/ImageViewAndroidTintXmlDetector.kt @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.lintrules + +import com.android.SdkConstants.ATTR_TINT +import com.android.SdkConstants.FQCN_IMAGE_BUTTON +import com.android.SdkConstants.FQCN_IMAGE_VIEW +import com.android.SdkConstants.IMAGE_BUTTON +import com.android.SdkConstants.IMAGE_VIEW +import com.android.resources.ResourceFolderType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.ResourceXmlDetector +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.XmlContext +import org.w3c.dom.Element + +/** + * A custom lint check that prohibits not using the app:tint for ImageViews + */ +class ImageViewAndroidTintXmlDetector : ResourceXmlDetector() { + companion object { + const val SCHEMA = "http://schemas.android.com/apk/res/android" + const val FULLY_QUALIFIED_APP_COMPAT_IMAGE_BUTTON = + "androidx.appcompat.widget.AppCompatImageButton" + const val FULLY_QUALIFIED_APP_COMPAT_VIEW_CLASS = + "androidx.appcompat.widget.AppCompatImageView" + const val APP_COMPAT_IMAGE_BUTTON = "AppCompatImageButton" + const val APP_COMPAT_IMAGE_VIEW = "AppCompatImageView" + + const val ERROR_MESSAGE = + "Using android:tint to tint ImageView instead of app:tint with AppCompatImageView" + + val ISSUE_XML_SRC_USAGE = Issue.create( + id = "AndroidSrcXmlDetector", + briefDescription = "Prohibits using android:tint in ImageViews and ImageButtons", + explanation = "ImageView (and descendants) should be tinted using app:tint", + category = Category.CORRECTNESS, + severity = Severity.ERROR, + implementation = Implementation( + ImageViewAndroidTintXmlDetector::class.java, + Scope.RESOURCE_FILE_SCOPE + ) + ) + } + + override fun appliesTo(folderType: ResourceFolderType): Boolean { + // Return true if we want to analyze resource files in the specified resource + // folder type. In this case we only need to analyze layout resource files. + return folderType == ResourceFolderType.LAYOUT + } + + override fun getApplicableElements(): Collection? { + return setOf( + FQCN_IMAGE_VIEW, + IMAGE_VIEW, + FQCN_IMAGE_BUTTON, + IMAGE_BUTTON, + FULLY_QUALIFIED_APP_COMPAT_IMAGE_BUTTON, + FULLY_QUALIFIED_APP_COMPAT_VIEW_CLASS, + APP_COMPAT_IMAGE_BUTTON, + APP_COMPAT_IMAGE_VIEW + ) + } + + override fun visitElement(context: XmlContext, element: Element) { + if (!element.hasAttributeNS(SCHEMA, ATTR_TINT)) return + val node = element.getAttributeNodeNS(SCHEMA, ATTR_TINT) + + context.report( + issue = ISSUE_XML_SRC_USAGE, + scope = node, + location = context.getLocation(node), + message = ERROR_MESSAGE + ) + } +} diff --git a/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/LintIssueRegistry.kt b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/LintIssueRegistry.kt new file mode 100644 index 000000000..8603c718b --- /dev/null +++ b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/LintIssueRegistry.kt @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.lintrules + +import com.android.tools.lint.client.api.IssueRegistry +import com.android.tools.lint.detector.api.Issue + +/** + * Registry which provides a list of our custom lint checks to be performed on an Android project. + */ +@Suppress("unused") +class LintIssueRegistry : IssueRegistry() { + override val api: Int = com.android.tools.lint.detector.api.CURRENT_API + override val issues: List = listOf( + AndroidSrcXmlDetector.ISSUE_XML_SRC_USAGE, + TextViewAndroidSrcXmlDetector.ISSUE_XML_SRC_USAGE, + ImageViewAndroidTintXmlDetector.ISSUE_XML_SRC_USAGE + ) +} diff --git a/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/TextViewAndroidSrcXmlDetector.kt b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/TextViewAndroidSrcXmlDetector.kt new file mode 100644 index 000000000..e582df218 --- /dev/null +++ b/mozilla-lint-rules/src/main/java/org/mozilla/fenix/lintrules/TextViewAndroidSrcXmlDetector.kt @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.lintrules + +import com.android.SdkConstants.ATTR_DRAWABLE_BOTTOM +import com.android.SdkConstants.ATTR_DRAWABLE_END +import com.android.SdkConstants.ATTR_DRAWABLE_LEFT +import com.android.SdkConstants.ATTR_DRAWABLE_RIGHT +import com.android.SdkConstants.ATTR_DRAWABLE_START +import com.android.SdkConstants.ATTR_DRAWABLE_TOP +import com.android.SdkConstants.FQCN_TEXT_VIEW +import com.android.SdkConstants.TEXT_VIEW +import com.android.resources.ResourceFolderType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.ResourceXmlDetector +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.XmlContext +import org.w3c.dom.Element + +/** + * A custom lint check that prohibits not using the app:srcCompat for ImageViews + */ +class TextViewAndroidSrcXmlDetector : ResourceXmlDetector() { + companion object { + const val SCHEMA = "http://schemas.android.com/apk/res/android" + + const val ERROR_MESSAGE = + "Using android:drawableX to define resource instead of app:drawableXCompat" + + val ISSUE_XML_SRC_USAGE = Issue.create( + id = "TextViewAndroidSrcXmlDetector", + briefDescription = "Prohibits using android namespace to define drawables in TextViews", + explanation = "TextView drawables should be declared using app:drawableXCompat", + category = Category.CORRECTNESS, + severity = Severity.ERROR, + implementation = Implementation( + TextViewAndroidSrcXmlDetector::class.java, + Scope.RESOURCE_FILE_SCOPE + ) + ) + } + + override fun appliesTo(folderType: ResourceFolderType): Boolean { + // Return true if we want to analyze resource files in the specified resource + // folder type. In this case we only need to analyze layout resource files. + return folderType == ResourceFolderType.LAYOUT + } + + override fun getApplicableElements(): Collection? { + return setOf( + FQCN_TEXT_VIEW, + TEXT_VIEW + ) + } + + override fun visitElement(context: XmlContext, element: Element) { + val node = when { + element.hasAttributeNS(SCHEMA, ATTR_DRAWABLE_BOTTOM) -> element.getAttributeNodeNS( + SCHEMA, + ATTR_DRAWABLE_BOTTOM + ) + element.hasAttributeNS(SCHEMA, ATTR_DRAWABLE_END) -> element.getAttributeNodeNS( + SCHEMA, + ATTR_DRAWABLE_END + ) + element.hasAttributeNS(SCHEMA, ATTR_DRAWABLE_LEFT) -> element.getAttributeNodeNS( + SCHEMA, + ATTR_DRAWABLE_LEFT + ) + element.hasAttributeNS( + SCHEMA, + ATTR_DRAWABLE_RIGHT + ) -> element.getAttributeNodeNS(SCHEMA, ATTR_DRAWABLE_RIGHT) + element.hasAttributeNS( + SCHEMA, + ATTR_DRAWABLE_START + ) -> element.getAttributeNodeNS(SCHEMA, ATTR_DRAWABLE_START) + element.hasAttributeNS(SCHEMA, ATTR_DRAWABLE_TOP) -> element.getAttributeNodeNS( + SCHEMA, + ATTR_DRAWABLE_TOP + ) + else -> null + } ?: return + + context.report( + issue = ISSUE_XML_SRC_USAGE, + scope = node, + location = context.getLocation(node), + message = ERROR_MESSAGE + ) + } +} diff --git a/settings.gradle b/settings.gradle index 2b707c8b6..d94386cfc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ include ':app' include ':mozilla-detekt-rules' +include ':mozilla-lint-rules' def log(message) { logger.lifecycle("[settings] ${message}") }