|
42 | 42 |
|
43 | 43 | Anyway, you can see there is quite a bit of detail here -- tracking
|
44 | 44 | things like the keywords, the variables, references to for example the
|
45 |
| -package -- and higher level concepts like a class and a field which I've |
| 45 | +package -- and higher level concepts like a class and a field, which I've |
46 | 46 | marked with a thicker border.
|
47 | 47 |
|
48 | 48 | Here's the corresponding Kotlin program:
|
|
60 | 60 |
|
61 | 61 | 
|
62 | 62 |
|
63 |
| -This is for a program which is completely equivalent to the Java one. |
| 63 | +This program is equivalent to the Java one. |
64 | 64 | But notice that it has a completely different shape! They reference
|
65 | 65 | different element classes, `PsiClass` versus `KtClass`, and on and on
|
66 | 66 | all the way down.
|
|
70 | 70 |
|
71 | 71 | ## UAST
|
72 | 72 |
|
73 |
| -We can construct a new AST which represents the same concepts: |
| 73 | +We can construct a new AST that represents the same concepts: |
74 | 74 |
|
75 | 75 | 
|
76 | 76 |
|
|
84 | 84 | 
|
85 | 85 |
|
86 | 86 | As you can see, the ASTs are not always identical. For Strings, in
|
87 |
| -Kotlin, we often end up with an extra parent `UiInjectionHost`. But for |
| 87 | +Kotlin, we often end up with an extra parent `UInjectionHost`. But for |
88 | 88 | our purposes, you can see that the ASTs are mostly the same, so if you
|
89 | 89 | handle the Kotlin scenario, you'll handle the Java ones too.
|
90 | 90 |
|
91 | 91 | ## UAST: The Java View
|
92 | 92 |
|
93 | 93 | Note that “Unified” in the name here is a bit misleading. From the name
|
94 | 94 | you may assume that this is some sort of superset of the ASTs across
|
95 |
| -languages -- and AST that can represent everything needed by all |
| 95 | +languages -- an AST that can represent everything needed by all |
96 | 96 | languages. But that's not the case! Instead, a better way to think of it
|
97 | 97 | is as the **Java view** of the AST.
|
98 | 98 |
|
|
106 | 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
107 | 107 |
|
108 | 108 | This is a Kotlin data class with two properties. So you might expect
|
109 |
| -that UAST would have a way to represent these concepts -- properties, |
110 |
| -and java classes. This should be a `UDataClass` with two `UProperty` |
111 |
| -children, right? |
| 109 | +that UAST would have a way to represent these concepts. This should |
| 110 | +be a `UDataClass` with two `UProperty` children, right? |
112 | 111 |
|
113 | 112 | But Java doesn't support properties. If you try to access a `Person`
|
114 | 113 | instance from Java, you'll notice that it exposes a number of public
|
|
166 | 165 |
|
167 | 166 | ## UElement
|
168 | 167 |
|
169 |
| -Every node in UAST is a subclass of a UElement. There's a parent |
| 168 | +Every node in UAST is a subclass of a `UElement`. There's a parent |
170 | 169 | pointer, which is handy for navigating around in the AST.
|
171 | 170 |
|
172 | 171 | The real skill you need for writing lint checks is understanding the
|
173 | 172 | AST, and then doing pattern matching on it. And a simple trick for this
|
174 | 173 | is to create the Kotlin or Java code you want, in a unit test, and then
|
175 | 174 | in your detector, recursively print out the UAST as a tree.
|
176 | 175 |
|
177 |
| -Or in the debugger, anytime you have a UElement, you can call |
| 176 | +Or in the debugger, anytime you have a `UElement`, you can call |
178 | 177 | `UElement.asRecursiveLogString` on it, evaluate and see what you find.
|
179 | 178 |
|
180 | 179 | For example, for the following Kotlin code:
|
|
209 | 208 | ## Visiting
|
210 | 209 |
|
211 | 210 | You generally shouldn't visit a source file on your own. Lint has a
|
212 |
| -special `UElementHandler` for that which is used to ensure that we don't |
| 211 | +special `UElementHandler` for that, which is used to ensure we don't |
213 | 212 | repeat visiting a source file thousands of times, one per detector.
|
214 | 213 |
|
215 | 214 | But when you're doing local analysis, you sometimes need to visit a
|
|
246 | 245 |
|
247 | 246 | We have our UAST tree in the top right corner. And here's the Java PSI
|
248 | 247 | AST behind the scenes. We can access the underlying PSI node for a
|
249 |
| -UElement by accessing the sourcePsi element. So when you do need to dip |
| 248 | +`UElement` by accessing the `sourcePsi` property. So when you do need to dip |
250 | 249 | into something language specific, that's trivial to do.
|
251 | 250 |
|
252 | 251 | Note that in some cases, these references are null.
|
253 | 252 |
|
254 |
| -Each of the UElement nodes point back into the PSI AST - whether a Java |
| 253 | +Most `UElement` nodes point back to the PSI AST - whether a Java |
255 | 254 | AST or a Kotlin AST. Here's the same AST, but with the **type** of the
|
256 |
| -`sourcePsi` attribute for each node added. |
| 255 | +`sourcePsi` property for each node added. |
257 | 256 |
|
258 | 257 | 
|
259 | 258 |
|
260 |
| -You can see that the class generated to represent the top level |
261 |
| -functions here doesn't have a non-null `sourcePsi`, because in the |
| 259 | +You can see that the facade class generated to contain the top level |
| 260 | +functions has a null `sourcePsi`, because in the |
262 | 261 | Kotlin PSI, there is no real `KtClass` for a facade class. And for the
|
263 | 262 | three members, the private field and the getter and the setter, they all
|
264 | 263 | correspond to the exact same, single `KtProperty` instance, the single
|
|
288 | 287 | across the languages. Declarations. Function calls. Super classes.
|
289 | 288 | Assignments. If expressions. Return statements. And on and on.
|
290 | 289 |
|
291 |
| -There *are* lint checks which are language specific -- for example, if |
292 |
| -you write a lint check which forbids the use of companion objects -- in |
| 290 | +There *are* lint checks that are language specific -- for example, if |
| 291 | +you write a lint check that forbids the use of companion objects -- in |
293 | 292 | that case, there's no big advantage to using UAST over PSI; it's only
|
294 | 293 | ever going to run on Kotlin code. (Note however that lint's APIs and
|
295 | 294 | convenience callbacks are all targeting UAST, so it's easier to write
|
|
308 | 307 | language specific, and where the language details aren't exposed in UAST.
|
309 | 308 |
|
310 | 309 | For example, let's say you need to determine if a `UClass` is a Kotlin
|
311 |
| -"companion object“. You could cheat and look at the class name to see if |
312 |
| -it's ”Companion“. But that's not quite right; in Kotlin you can |
| 310 | +“companion object”. You could cheat and look at the class name to see if |
| 311 | +it's “Companion”. But that's not quite right; in Kotlin you can |
313 | 312 | specify a custom companion object name, and of course users are free
|
314 |
| -to create classes named ”Companion“ that aren't companion objects: |
| 313 | +to create classes named “Companion” that aren't companion objects: |
315 | 314 |
|
316 | 315 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
|
317 | 316 | class Test {
|
|
323 | 322 | }
|
324 | 323 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
325 | 324 |
|
326 |
| -The right way to do this, is using Kotlin PSI, via the |
327 |
| -`UElement.sourcePsi` attribute: |
| 325 | +The right way to do this is using Kotlin PSI, via the |
| 326 | +`UElement.sourcePsi` property: |
328 | 327 |
|
329 | 328 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
|
330 | 329 | // Skip companion objects
|
|
335 | 334 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
336 | 335 |
|
337 | 336 | (To figure out how to write the above code, use a debugger on a test
|
338 |
| -case and look at the `UClass.sourcePsi` attribute; you'll discover that |
| 337 | +case and look at the `UClass.sourcePsi` property; you'll discover that |
339 | 338 | it's some subclass of `KtObjectDeclaration`; look up its most general
|
340 | 339 | super interface or class, and then use code completion to discover
|
341 | 340 | available APIs, such as `isCompanion()`.)
|
|
350 | 349 | Lint doesn't actually give you access to everything you need if you want
|
351 | 350 | to try to look up types in Kotlin PSI; you need something called the
|
352 | 351 | "binding context”, which is not exposed anywhere! And this omission is
|
353 |
| -deliberate, because that was an implementation detail of the old |
| 352 | +deliberate, because this is an implementation detail of the old |
354 | 353 | compiler. The future is K2; a complete rewrite of the compiler front
|
355 | 354 | end, which is no longer using the old binding context. And as part of
|
356 | 355 | the tooling support for K2, there's a new API called the “Kotlin
|
|
398 | 397 |
|
399 | 398 | Before the Kotlin lint analysis API, lint didn't have a way to reason
|
400 | 399 | about the `Nothing` type. UAST only returns Java types, which maps to
|
401 |
| -void. So instead, lint had an ugly hack which just hardcoded well known |
| 400 | +void. So instead, lint had an ugly hack that just hardcoded well known |
402 | 401 | names of methods that don't return:
|
403 | 402 |
|
404 | 403 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
|
|
441 | 440 | Here, we have a `KtCallExpression`, and inside the `analyze` block we
|
442 | 441 | can call `resolveCall()` on it to reach the called method's symbol.
|
443 | 442 |
|
444 |
| -Similarly on a `KtDeclaration` (such as a named function or property) I |
| 443 | +Similarly, on a `KtDeclaration` (such as a named function or property) I |
445 | 444 | can call `getSymbol()` to get the symbol for that method or property, to
|
446 | 445 | for example look up parameter information. And on a `KtExpression` (such
|
447 | 446 | as an if statement) I can call `getKtType()` to get the Kotlin type.
|
|
452 | 451 | so on.
|
453 | 452 |
|
454 | 453 | In the new implementation of `callNeverReturns`, we resolve the call,
|
455 |
| -look up the corresponding function which of course is a `KtSymbol` |
| 454 | +look up the corresponding function, which of course is a `KtSymbol` |
456 | 455 | itself, and from that we get the return type, and then we can just check
|
457 | 456 | if it's the `Nothing` type.
|
458 | 457 |
|
|
0 commit comments