Skip to content

Commit 00e9d49

Browse files
authored
Allow provider assignment from @DeferredApi methods. (#2525)
1 parent 125a493 commit 00e9d49

File tree

5 files changed

+114
-69
lines changed

5 files changed

+114
-69
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.lint.checks
16+
17+
import com.android.tools.lint.detector.api.JavaContext
18+
import com.intellij.lang.jvm.JvmModifier
19+
import com.intellij.psi.PsiClassType
20+
import com.intellij.psi.PsiMethod
21+
import org.jetbrains.uast.UAnonymousClass
22+
import org.jetbrains.uast.UElement
23+
import org.jetbrains.uast.ULambdaExpression
24+
import org.jetbrains.uast.UMethod
25+
import org.jetbrains.uast.getParentOfType
26+
27+
internal const val ANNOTATION = "com.google.firebase.annotations.DeferredApi"
28+
29+
fun hasDeferredApiAnnotation(context: JavaContext, methodCall: UElement): Boolean {
30+
lambdaMethod(methodCall)?.let {
31+
return hasDeferredApiAnnotation(context, it)
32+
}
33+
34+
val method = methodCall.getParentOfType<UElement>(
35+
UMethod::class.java, true,
36+
UAnonymousClass::class.java, ULambdaExpression::class.java
37+
) as? PsiMethod
38+
return hasDeferredApiAnnotation(context, method)
39+
}
40+
41+
fun hasDeferredApiAnnotation(context: JavaContext, calledMethod: PsiMethod?): Boolean {
42+
var method = calledMethod ?: return false
43+
44+
while (true) {
45+
for (annotation in method.modifierList.annotations) {
46+
annotation.qualifiedName?.let {
47+
if (it == ANNOTATION) {
48+
return@hasDeferredApiAnnotation true
49+
}
50+
}
51+
}
52+
method = context.evaluator.getSuperMethod(method) ?: break
53+
}
54+
55+
var cls = method.containingClass ?: return false
56+
57+
while (true) {
58+
val modifierList = cls.modifierList
59+
if (modifierList != null) {
60+
for (annotation in modifierList.annotations) {
61+
annotation.qualifiedName?.let {
62+
if (it == ANNOTATION) {
63+
return@hasDeferredApiAnnotation true
64+
}
65+
}
66+
}
67+
}
68+
cls = cls.superClass ?: break
69+
}
70+
return false
71+
}
72+
73+
fun lambdaMethod(element: UElement): PsiMethod? {
74+
val lambda = element.getParentOfType<ULambdaExpression>(
75+
ULambdaExpression::class.java, true, UMethod::class.java, UAnonymousClass::class.java)
76+
?: return null
77+
78+
val type = lambda.functionalInterfaceType
79+
if (type is PsiClassType) {
80+
val resolved = type.resolve()
81+
if (resolved != null) {
82+
return resolved.allMethods.firstOrNull { it.hasModifier(JvmModifier.ABSTRACT) }
83+
}
84+
}
85+
return null
86+
}

tools/lint/src/main/kotlin/DeferredApiDetector.kt

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,10 @@ import com.android.tools.lint.detector.api.JavaContext
2323
import com.android.tools.lint.detector.api.Scope
2424
import com.android.tools.lint.detector.api.Severity
2525
import com.android.tools.lint.detector.api.SourceCodeScanner
26-
import com.intellij.lang.jvm.JvmModifier
27-
import com.intellij.psi.PsiClassType
2826
import com.intellij.psi.PsiMethod
2927
import org.jetbrains.uast.UAnnotation
30-
import org.jetbrains.uast.UAnonymousClass
3128
import org.jetbrains.uast.UCallExpression
3229
import org.jetbrains.uast.UElement
33-
import org.jetbrains.uast.ULambdaExpression
34-
import org.jetbrains.uast.UMethod
35-
import org.jetbrains.uast.getParentOfType
36-
37-
internal const val ANNOTATION = "com.google.firebase.annotations.DeferredApi"
3830

3931
class DeferredApiDetector : Detector(), SourceCodeScanner {
4032
override fun applicableAnnotations(): List<String> = listOf(ANNOTATION)
@@ -70,64 +62,6 @@ class DeferredApiDetector : Detector(), SourceCodeScanner {
7062
includeArguments = true),
7163
"${method.name} is only safe to call in the context of a Deferred<T> dependency.")
7264
}
73-
private fun hasDeferredApiAnnotation(context: JavaContext, methodCall: UElement): Boolean {
74-
lambdaMethod(methodCall)?.let {
75-
return hasDeferredApiAnnotation(context, it)
76-
}
77-
78-
val method = methodCall.getParentOfType<UElement>(
79-
UMethod::class.java, true,
80-
UAnonymousClass::class.java, ULambdaExpression::class.java
81-
) as? PsiMethod
82-
return hasDeferredApiAnnotation(context, method)
83-
}
84-
85-
private fun lambdaMethod(element: UElement): PsiMethod? {
86-
val lambda = element.getParentOfType<ULambdaExpression>(
87-
ULambdaExpression::class.java, true, UMethod::class.java, UAnonymousClass::class.java)
88-
?: return null
89-
90-
val type = lambda.functionalInterfaceType
91-
if (type is PsiClassType) {
92-
val resolved = type.resolve()
93-
if (resolved != null) {
94-
return resolved.allMethods.firstOrNull { it.hasModifier(JvmModifier.ABSTRACT) }
95-
}
96-
}
97-
return null
98-
}
99-
100-
private fun hasDeferredApiAnnotation(context: JavaContext, calledMethod: PsiMethod?): Boolean {
101-
var method = calledMethod ?: return false
102-
103-
while (true) {
104-
for (annotation in method.modifierList.annotations) {
105-
annotation.qualifiedName?.let {
106-
if (it == ANNOTATION) {
107-
return@hasDeferredApiAnnotation true
108-
}
109-
}
110-
}
111-
method = context.evaluator.getSuperMethod(method) ?: break
112-
}
113-
114-
var cls = method.containingClass ?: return false
115-
116-
while (true) {
117-
val modifierList = cls.modifierList
118-
if (modifierList != null) {
119-
for (annotation in modifierList.annotations) {
120-
annotation.qualifiedName?.let {
121-
if (it == ANNOTATION) {
122-
return@hasDeferredApiAnnotation true
123-
}
124-
}
125-
}
126-
}
127-
cls = cls.superClass ?: break
128-
}
129-
return false
130-
}
13165

13266
companion object {
13367
private val IMPLEMENTATION = Implementation(

tools/lint/src/main/kotlin/ProviderAssignmentDetector.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class ProviderAssignmentDetector : Detector(), SourceCodeScanner {
5050
if (assignmentExpression != node.uastParent?.uastParent) {
5151
return
5252
}
53+
54+
if (hasDeferredApiAnnotation(context, assignmentExpression)) {
55+
return
56+
}
57+
5358
assignmentTarget.resolve()?.let {
5459
if (it is PsiField) {
5560
context.report(

tools/lint/src/test/kotlin/DeferredApiDetectorTests.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ package com.google.firebase.lint.checks
1616

1717
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
1818

19-
private fun annotationSource(): String {
19+
fun annotationSource(): String {
2020
return """
2121
package com.google.firebase.annotations;
2222
@@ -25,7 +25,7 @@ private fun annotationSource(): String {
2525
""".trimIndent()
2626
}
2727

28-
private fun deferredSource(): String {
28+
fun deferredSource(): String {
2929
return """
3030
package com.google.firebase.inject;
3131
import com.google.firebase.annotations.DeferredApi;
@@ -51,7 +51,7 @@ fun providerSource(): String {
5151
""".trimIndent()
5252
}
5353

54-
private fun annotatedInterface() = """
54+
fun annotatedInterface() = """
5555
import com.google.firebase.annotations.DeferredApi;
5656
5757
interface MyApi {

tools/lint/src/test/kotlin/ProviderAssignmentDetectorTests.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,24 @@ class ProviderAssignmentDetectorTests : LintDetectorTest() {
104104
.run()
105105
.expectClean()
106106
}
107+
108+
fun test_assignmentFromWithinADeferredApiMethod_shouldSucceed() {
109+
lint().files(
110+
java(providerSource()),
111+
java(annotationSource()),
112+
java(deferredSource()),
113+
java("""
114+
import com.google.firebase.inject.Deferred;
115+
class Foo {
116+
private String s;
117+
Foo(Deferred<String> d) {
118+
d.whenAvailable(p -> {
119+
s = p.get();
120+
});
121+
}
122+
}
123+
""".trimIndent()))
124+
.run()
125+
.expectClean()
126+
}
107127
}

0 commit comments

Comments
 (0)