1 /* 2 * Copyright 2022 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.build.lint 20 21 import com.android.tools.lint.client.api.UElementHandler 22 import com.android.tools.lint.detector.api.Category 23 import com.android.tools.lint.detector.api.Detector 24 import com.android.tools.lint.detector.api.Implementation 25 import com.android.tools.lint.detector.api.Incident 26 import com.android.tools.lint.detector.api.Issue 27 import com.android.tools.lint.detector.api.JavaContext 28 import com.android.tools.lint.detector.api.LintFix 29 import com.android.tools.lint.detector.api.Scope 30 import com.android.tools.lint.detector.api.Severity 31 import com.intellij.psi.PsiMethod 32 import java.util.EnumSet 33 import org.jetbrains.uast.UAnnotation 34 import org.jetbrains.uast.UClass 35 36 /** Checks for usages of @org.junit.Ignore at the class level. */ 37 class IgnoreClassLevelDetector : Detector(), Detector.UastScanner { 38 getApplicableUastTypesnull39 override fun getApplicableUastTypes() = listOf(UAnnotation::class.java) 40 41 override fun createUastHandler(context: JavaContext): UElementHandler { 42 return AnnotationChecker(context) 43 } 44 45 private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() { visitAnnotationnull46 override fun visitAnnotation(node: UAnnotation) { 47 if (node.qualifiedName == "org.junit.Ignore" && node.uastParent is UClass) { 48 val incident = 49 Incident(context) 50 .issue(ISSUE) 51 .location(context.getNameLocation(node)) 52 .message( 53 "@Ignore should not be used at the class level. Move the annotation " + 54 "to each test individually." 55 ) 56 .scope(node) 57 context.report(incident) 58 } 59 } 60 } 61 62 /** 63 * Creates a LintFix which removes the @Ignore annotation from the class and adds it to each 64 * individual test method. 65 * 66 * TODO(b/235340679): This is currently unused because of issues described in the method body. 67 */ 68 @Suppress("unused") createFixnull69 private fun createFix(testClass: UClass, context: JavaContext, annotation: String): LintFix { 70 val fix = 71 fix() 72 .name("Annotate each test method and remove the class-level annotation") 73 .composite() 74 75 for (method in testClass.allMethods) { 76 if (method.isTestMethod()) { 77 val methodFix = 78 fix() 79 // The replace param on annotate doesn't work: if @Ignore is already present 80 // on 81 // the method, the annotation is added again instead of being replaced. 82 .annotate("org.junit.Ignore", context, method, true) 83 .build() 84 fix.add(methodFix) 85 } 86 } 87 88 val classFix = 89 fix() 90 .replace() 91 // This requires the exact text of the class annotation to be passed to this 92 // function. 93 // This can be gotten with `node.sourcePsi?.node?.text!!`, but `text`'s doc says 94 // using 95 // it should be avoided, so this isn't the best solution. 96 .text(annotation) 97 .with("") 98 .reformat(true) 99 .build() 100 fix.add(classFix) 101 102 return fix.build() 103 } 104 105 /** Checks if this PsiMethod has a @org.junit.Test annotation */ PsiMethodnull106 private fun PsiMethod.isTestMethod(): Boolean { 107 for (annotation in this.annotations) { 108 if (annotation.qualifiedName == "org.junit.Test") { 109 return true 110 } 111 } 112 return false 113 } 114 115 companion object { 116 val ISSUE = 117 Issue.create( 118 "IgnoreClassLevelDetector", 119 "@Ignore should not be used at the class level.", 120 "Using @Ignore at the class level instead of annotating each individual " + 121 "test causes errors in Android Test Hub.", 122 Category.CORRECTNESS, 123 5, 124 Severity.ERROR, 125 Implementation( 126 IgnoreClassLevelDetector::class.java, 127 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 128 ) 129 ) 130 } 131 } 132