1 /*
2  * Copyright (C) 2020 The Dagger Authors.
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 package dagger.lint
17 
18 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
19 import com.android.tools.lint.detector.api.Detector
20 import com.android.tools.lint.detector.api.Issue
21 import org.junit.Test
22 import org.junit.runner.RunWith
23 import org.junit.runners.JUnit4
24 
25 @Suppress("UnstableApiUsage")
26 @RunWith(JUnit4::class)
27 class DaggerKotlinIssueDetectorTest : LintDetectorTest() {
28 
29   private companion object {
30     private val javaxInjectStubs = kotlin(
31       """
32         package javax.inject
33 
34         annotation class Inject
35         annotation class Qualifier
36       """
37     ).indented()
38 
39     private val daggerStubs = kotlin(
40       """
41         package dagger
42 
43         annotation class Provides
44         annotation class Module
45       """
46     ).indented()
47 
48     // For some reason in Bazel the stdlib dependency on the classpath isn't visible to the
49     // LintTestTask, so we just include it ourselves here for now.
50     private val jvmStaticStubs = kotlin(
51       """
52         package kotlin.jvm
53 
54         annotation class JvmStatic
55       """
56     ).indented()
57   }
58 
getDetectornull59   override fun getDetector(): Detector = DaggerKotlinIssueDetector()
60 
61   override fun getIssues(): List<Issue> = DaggerKotlinIssueDetector.issues
62 
63   @Test
64   fun simpleSmokeTestForQualifiersAndProviders() {
65     lint()
66       .allowMissingSdk()
67       .files(
68         javaxInjectStubs,
69         daggerStubs,
70         jvmStaticStubs,
71         kotlin(
72           """
73           package foo
74           import javax.inject.Inject
75           import javax.inject.Qualifier
76           import kotlin.jvm.JvmStatic
77           import dagger.Provides
78           import dagger.Module
79 
80           @Qualifier
81           annotation class MyQualifier
82 
83           class InjectedTest {
84             // This should fail because of `:field`
85             @Inject
86             @field:MyQualifier
87             lateinit var prop: String
88 
89             // This is fine!
90             @Inject
91             @MyQualifier
92             lateinit var prop2: String
93           }
94 
95           @Module
96           object ObjectModule {
97             // This should fail because it uses `@JvmStatic`
98             @JvmStatic
99             @Provides
100             fun provideFoo(): String {
101 
102             }
103 
104             // This is fine!
105             @Provides
106             fun provideBar(): String {
107 
108             }
109           }
110 
111           @Module
112           class ClassModule {
113             companion object {
114               // This should fail because the companion object is part of ClassModule, so this is unnecessary.
115               @JvmStatic
116               @Provides
117               fun provideBaz(): String {
118 
119               }
120             }
121           }
122 
123           @Module
124           class ClassModuleQualified {
125             companion object {
126               // This should fail because the companion object is part of ClassModule, so this is unnecessary.
127               // This specifically tests a fully qualified annotation
128               @kotlin.jvm.JvmStatic
129               @Provides
130               fun provideBaz(): String {
131 
132               }
133             }
134           }
135 
136           @Module
137           class ClassModule2 {
138             // This should fail because the companion object is part of ClassModule
139             @Module
140             companion object {
141               @Provides
142               fun provideBaz(): String {
143 
144               }
145             }
146           }
147 
148           @Module
149           class ClassModule2Qualified {
150             // This should fail because the companion object is part of ClassModule
151             // This specifically tests a fully qualified annotation
152             @dagger.Module
153             companion object {
154               @Provides
155               fun provideBaz(): String {
156 
157               }
158             }
159           }
160 
161           // This is correct as of Dagger 2.26!
162           @Module
163           class ClassModule3 {
164             companion object {
165               @Provides
166               fun provideBaz(): String {
167 
168               }
169             }
170           }
171 
172           class ClassModule4 {
173             // This is should fail because this should be extracted to a standalone object.
174             @Module
175             companion object {
176               @Provides
177               fun provideBaz(): String {
178 
179               }
180             }
181           }
182         """
183         ).indented()
184       )
185       .allowCompilationErrors(false)
186       .run()
187       .expect(
188         """
189         src/foo/MyQualifier.kt:14: Warning: Redundant 'field:' used for Dagger qualifier annotation. [FieldSiteTargetOnQualifierAnnotation]
190           @field:MyQualifier
191           ~~~~~~~~~~~~~~~~~~
192         src/foo/MyQualifier.kt:26: Warning: @JvmStatic used for @Provides function in an object class [JvmStaticProvidesInObjectDetector]
193           @JvmStatic
194           ~~~~~~~~~~
195         src/foo/MyQualifier.kt:43: Warning: @JvmStatic used for @Provides function in an object class [JvmStaticProvidesInObjectDetector]
196             @JvmStatic
197             ~~~~~~~~~~
198         src/foo/MyQualifier.kt:56: Warning: @JvmStatic used for @Provides function in an object class [JvmStaticProvidesInObjectDetector]
199             @kotlin.jvm.JvmStatic
200             ~~~~~~~~~~~~~~~~~~~~~
201         src/foo/MyQualifier.kt:66: Warning: Module companion objects should not be annotated with @Module. [ModuleCompanionObjects]
202           // This should fail because the companion object is part of ClassModule
203           ^
204         src/foo/MyQualifier.kt:78: Warning: Module companion objects should not be annotated with @Module. [ModuleCompanionObjects]
205           // This should fail because the companion object is part of ClassModule
206           ^
207         src/foo/MyQualifier.kt:101: Warning: Module companion objects should not be annotated with @Module. [ModuleCompanionObjects]
208           // This is should fail because this should be extracted to a standalone object.
209           ^
210         0 errors, 7 warnings
211         """.trimIndent()
212       )
213       .expectFixDiffs(
214         """
215         Fix for src/foo/MyQualifier.kt line 14: Remove 'field:':
216         @@ -14 +14
217         -   @field:MyQualifier
218         +   @MyQualifier
219         Fix for src/foo/MyQualifier.kt line 26: Remove @JvmStatic:
220         @@ -26 +26
221         -   @JvmStatic
222         +
223         Fix for src/foo/MyQualifier.kt line 43: Remove @JvmStatic:
224         @@ -43 +43
225         -     @JvmStatic
226         +
227         Fix for src/foo/MyQualifier.kt line 56: Remove @JvmStatic:
228         @@ -56 +56
229         -     @kotlin.jvm.JvmStatic
230         +
231         Fix for src/foo/MyQualifier.kt line 66: Remove @Module:
232         @@ -67 +67
233         -   @Module
234         +
235         Fix for src/foo/MyQualifier.kt line 78: Remove @Module:
236         @@ -80 +80
237         -   @dagger.Module
238         +
239         Fix for src/foo/MyQualifier.kt line 101: Remove @Module:
240         @@ -102 +102
241         -   @Module
242         +
243         """.trimIndent()
244       )
245   }
246 }
247