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.tools.lint.detector.api.Category
22 import com.android.tools.lint.detector.api.Detector
23 import com.android.tools.lint.detector.api.Implementation
24 import com.android.tools.lint.detector.api.Issue
25 import com.android.tools.lint.detector.api.JavaContext
26 import com.android.tools.lint.detector.api.Scope
27 import com.android.tools.lint.detector.api.Severity
28 import com.android.tools.lint.detector.api.SourceCodeScanner
29 import com.intellij.lang.java.JavaLanguage
30 import java.util.EnumSet
31 import org.jetbrains.uast.UClass
32 
33 /** A [Detector] which ensures that every `ComponentInitializer` has a no argument constructor. */
34 class InitializerConstructorDetector : Detector(), SourceCodeScanner {
35     companion object {
36         private const val DESCRIPTION = "Missing Initializer no-arg constructor"
37         val ISSUE =
38             Issue.create(
39                 id = "EnsureInitializerNoArgConstr",
40                 briefDescription = DESCRIPTION,
41                 explanation =
42                     """
43                 Every `Initializer` must have a no argument constructor.
44             """,
45                 androidSpecific = true,
46                 category = Category.CORRECTNESS,
47                 severity = Severity.FATAL,
48                 implementation =
49                     Implementation(
50                         InitializerConstructorDetector::class.java,
51                         EnumSet.of(Scope.JAVA_FILE)
52                     )
53             )
54     }
55 
applicableSuperClassesnull56     override fun applicableSuperClasses() = listOf("androidx.startup.Initializer")
57 
58     override fun visitClass(context: JavaContext, declaration: UClass) {
59         val name = declaration.qualifiedName
60 
61         if (name == "androidx.startup.Initializer") {
62             // This is the component initializer itself.
63             return
64         }
65 
66         if (declaration.isInterface) {
67             return
68         }
69 
70         // isJava() is available in the latest Lint APIs but not the LINT_MIN API
71         val isJava = declaration.javaPsi.language == JavaLanguage.INSTANCE
72 
73         if (isJava && declaration.constructors.isEmpty()) {
74             // Java classes have a default no-arg constructor
75             return
76         }
77 
78         for (constructor in declaration.constructors) {
79             if (!constructor.hasParameters()) {
80                 // Found a no argument constructor.
81                 return
82             }
83         }
84 
85         // Did not find any no-arg constructors
86         val location = context.getLocation(declaration.javaPsi)
87         context.report(issue = ISSUE, location = location, message = DESCRIPTION)
88     }
89 }
90