1 /*
<lambda>null2  * Copyright 2023 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 package androidx.build.lint
18 
19 import com.android.tools.lint.detector.api.JavaContext
20 import com.android.tools.lint.model.DefaultLintModelAndroidLibrary
21 import com.android.tools.lint.model.DefaultLintModelJavaLibrary
22 import com.android.tools.lint.model.LintModelLibrary
23 import com.android.tools.lint.model.LintModelMavenName
24 import com.intellij.psi.PsiElement
25 import com.intellij.psi.PsiMember
26 import org.jetbrains.kotlin.asJava.namedUnwrappedElement
27 import org.jetbrains.kotlin.konan.file.File
28 import org.jetbrains.kotlin.psi.KtNamedDeclaration
29 
30 /*
31  * See ag/25264517. This was previously a public extension function that is now private.
32  * It's currently used by [BanRestrictToTestsScope] and [BanVisibleForTestingParams].
33  */
34 internal fun PsiElement.getFqName(): String? =
35     when (val element = namedUnwrappedElement) {
36         is PsiMember ->
37             element.getName()?.let { name ->
38                 val prefix = element.containingClass?.qualifiedName
39                 (if (prefix != null) "$prefix.$name" else name)
40             }
41         is KtNamedDeclaration -> element.fqName.toString()
42         else -> null
43     }
44 
45 /** Attempts to find the Maven coordinate for the library containing [member]. */
findMavenCoordinatenull46 internal fun JavaContext.findMavenCoordinate(member: PsiMember): LintModelMavenName? {
47     val mavenName =
48         evaluator.getLibrary(member) ?: evaluator.getProject(member)?.mavenCoordinate ?: return null
49 
50     // If the lint model is missing a Maven coordinate for this class, try to infer one from the
51     // JAR's owner library. If we fail, return the broken Maven name anyway.
52     if (mavenName == LintModelMavenName.NONE) {
53         return evaluator
54             .findJarPath(member)
55             ?.let { jarPath ->
56                 evaluator.findOwnerLibrary(jarPath.replace('/', File.separatorChar))
57             }
58             ?.getMavenNameFromIdentifier() ?: mavenName
59     }
60 
61     // If the lint model says the class lives in a "local AAR", try a little bit harder to match
62     // that to an artifact in a real library based on build directory containment.
63     if (mavenName.groupId == "__local_aars__") {
64         val artifactPath = mavenName.artifactId
65 
66         // The artifact is being repackaged within this project. Assume that means it's in the same
67         // Maven group.
68         if (artifactPath.startsWith(project.buildModule.buildFolder.path)) {
69             return project.mavenCoordinate
70         }
71 
72         val lastIndexOfBuild = artifactPath.lastIndexOf("/build/")
73         if (lastIndexOfBuild < 0) return null
74 
75         // Otherwise, try to find a dependency with a matching path and use its Maven group.
76         val path = artifactPath.substring(0, lastIndexOfBuild)
77         return evaluator.dependencies?.getAll()?.findMavenNameWithJarFileInPath(path, mavenName)
78             ?: mavenName
79     }
80 
81     return mavenName
82 }
83 
84 /**
85  * Attempts to find the Maven name for the library with at least one JAR file matching the [path].
86  */
findMavenNameWithJarFileInPathnull87 internal fun List<LintModelLibrary>.findMavenNameWithJarFileInPath(
88     path: String,
89     excludeMavenName: LintModelMavenName? = null
90 ): LintModelMavenName? {
91     return firstNotNullOfOrNull { library ->
92         val resolvedCoordinates =
93             when {
94                 library is DefaultLintModelJavaLibrary -> library.resolvedCoordinates
95                 library is DefaultLintModelAndroidLibrary -> library.resolvedCoordinates
96                 else -> null
97             }
98 
99         if (resolvedCoordinates == null || resolvedCoordinates == excludeMavenName) {
100             return@firstNotNullOfOrNull null
101         }
102 
103         val hasMatchingJarFile =
104             when {
105                 library == excludeMavenName -> emptyList()
106                 library is DefaultLintModelJavaLibrary -> library.jarFiles
107                 library is DefaultLintModelAndroidLibrary -> library.jarFiles
108                 else -> emptyList()
109             }.any { jarFile -> jarFile.path.startsWith(path) }
110 
111         if (hasMatchingJarFile) {
112             return@firstNotNullOfOrNull resolvedCoordinates
113         }
114 
115         return@firstNotNullOfOrNull null
116     }
117 }
118