1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 @file:Suppress("UnstableApiUsage") 18 19 package androidx.startup.lint 20 21 import com.android.SdkConstants.ANDROID_URI 22 import com.android.SdkConstants.ATTR_NAME 23 import com.android.SdkConstants.ATTR_VALUE 24 import com.android.tools.lint.detector.api.Category 25 import com.android.tools.lint.detector.api.Context 26 import com.android.tools.lint.detector.api.Detector 27 import com.android.tools.lint.detector.api.Implementation 28 import com.android.tools.lint.detector.api.Issue 29 import com.android.tools.lint.detector.api.JavaContext 30 import com.android.tools.lint.detector.api.Location 31 import com.android.tools.lint.detector.api.Scope 32 import com.android.tools.lint.detector.api.Severity 33 import com.android.tools.lint.detector.api.SourceCodeScanner 34 import com.android.tools.lint.detector.api.XmlContext 35 import com.android.tools.lint.detector.api.XmlScanner 36 import com.intellij.psi.impl.source.PsiClassReferenceType 37 import java.util.EnumSet 38 import org.jetbrains.uast.UClass 39 import org.jetbrains.uast.UClassLiteralExpression 40 import org.jetbrains.uast.visitor.AbstractUastVisitor 41 import org.w3c.dom.Element 42 43 /** 44 * A [Detector] which ensures that every `ComponentInitializer` is accompanied by a corresponding 45 * entry in the `AndroidManifest.xml`. 46 */ 47 class EnsureInitializerMetadataDetector : Detector(), SourceCodeScanner, XmlScanner { 48 // all declared components 49 private val components = mutableMapOf<UClass, Location>() 50 // all reachable components 51 // Synthetic access 52 val reachable = mutableSetOf<String>() 53 54 companion object { 55 private const val DESCRIPTION = 56 "Every Initializer needs to be accompanied by a " + 57 "corresponding <meta-data> entry in the AndroidManifest.xml file." 58 59 val ISSUE = 60 Issue.create( 61 id = "EnsureInitializerMetadata", 62 briefDescription = DESCRIPTION, 63 explanation = 64 """ 65 When a library defines a Initializer, it needs to be accompanied by a \ 66 corresponding <meta-data> entry in the AndroidManifest.xml file. 67 """, 68 androidSpecific = true, 69 category = Category.CORRECTNESS, 70 severity = Severity.FATAL, 71 implementation = 72 Implementation( 73 EnsureInitializerMetadataDetector::class.java, 74 EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST) 75 ) 76 ) 77 } 78 applicableSuperClassesnull79 override fun applicableSuperClasses() = listOf("androidx.startup.Initializer") 80 81 override fun getApplicableElements() = listOf("meta-data") 82 83 override fun visitClass(context: JavaContext, declaration: UClass) { 84 val name = declaration.qualifiedName 85 86 if (name == "androidx.startup.Initializer") { 87 // This is the component initializer itself. 88 return 89 } 90 91 if (!declaration.isInterface) { 92 val location = context.getLocation(declaration.javaPsi) 93 components[declaration] = location 94 } 95 96 // Check every dependencies() method for reachable Initializer's 97 val method = 98 declaration.methods.first { it.name == "dependencies" && it.parameterList.isEmpty } 99 val visitor = 100 object : AbstractUastVisitor() { 101 override fun visitClassLiteralExpression(node: UClassLiteralExpression): Boolean { 102 val qualifiedName = 103 (node.type as? PsiClassReferenceType)?.resolve()?.qualifiedName 104 if (qualifiedName != null) { 105 reachable += qualifiedName 106 } 107 return true 108 } 109 } 110 111 method.accept(visitor) 112 } 113 visitElementnull114 override fun visitElement(context: XmlContext, element: Element) { 115 // Track all <meta-data> elements with value androidx.startup 116 val name = element.getAttributeNS(ANDROID_URI, ATTR_NAME) 117 val value = element.getAttributeNS(ANDROID_URI, ATTR_VALUE) 118 // There does not seem to be a way to evaluate resources defined in the manifest. 119 // Figure out if there is a better way. 120 if (value == "androidx.startup" || value == "@string/androidx_startup") { 121 reachable += name 122 } 123 } 124 afterCheckRootProjectnull125 override fun afterCheckRootProject(context: Context) { 126 for ((declaration, location) in components) { 127 if (declaration.qualifiedName !in reachable) { 128 context.report(issue = ISSUE, location = location, message = DESCRIPTION) 129 } 130 } 131 } 132 } 133