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