Start with a link to the previous custom Lint article
Customize lintDemo project
In my opinion, static scanning is an important tool to avoid bugs during development. But to this aspect of the introduction of the article is a little little, I actually write is not how, but at least pool wisdom, improve each other.
My previous article on Lint only described how to customize a Lint scan rule in terms of the implementation layer and so on, but it didn’t make it clear what Lint was written on, so here’s a further update.
AST (Abstract Syntax Tree) Abstract Syntax Tree
Abstract syntax code (AST) is a tree representation of the abstract syntax structure of the source code. Each node in the tree represents a structure in the source code. This is abstract because the abstract syntax tree does not represent every detail of the occurrence of real syntax. Nested parentheses are implicit in the structure of the tree and are not represented as nodes. The abstract syntax tree does not depend on the source language syntax, that is to say the grammar analysis phase method context, no text, because when writing grammar, often to equivalent transformation grammar (to eliminate left recursion, backtracking, ambiguity, etc.), this will give the grammar analysis is introduced into some redundant components, adverse effects on the later stages, even can make us a stage becomes chaos. For this reason, many compilers often have to construct parse trees independently to establish a clear interface for the front end and back end.
Let’s briefly look at the process of Java AST from this diagram.
Step 1: Lexical analysis, the source code character flow into the Token list.
Read the source code one by one and merge it into tokens according to predetermined rules. Tokens are the smallest elements of the compilation process. Keywords, variable names, literals, operators, etc., can all become tokens.
Step 2: Syntax analysis, according to the Token flow to construct the tree expression is also called AST.
Each node in the syntax tree represents a syntax structure in the program code, such as types, modifiers, operators, and so on. After this step, the compiler basically doesn’t operate on the source file anymore, and all subsequent operations are based on the abstract syntax tree.
Apt process is a annotation process after the source code is converted into AST, so we can get all annotation classes and annotation class information in APT process.
The Lombok library, which Java students are familiar with, is a modification based on the AST syntax tree, as described in this article.
public boolean isSubType(Element element, String className) {
returnelement ! =null && isSubType(element.asType(), className);
}
Copy the code
In fact, I have been having a strange feeling recently, why Apt Element and Lint feel very similar, hahaha. Just like the type determination code in APT above.
Android Lint
During the Android Lint iteration, the Scanner that scanned source code went through three versions of the AST.
-
Javascript is used initially. Lint parses Java source code through Lombok libraries into an AST(Abstract syntax tree), which is then scanned by javascript.
-
In Android Studio 2.2 and Lint-API 25.2.0, the Lint tool replaces Lombok AST with PSI and deprecates JavaScanner in favor of JavaPsiScanner.
PSI is an API provided by JetBrains after parsing Java source code in IDEA to generate a syntax tree. Compared to Lombok AST, it supports Java 1.8, type resolution, and more. Custom Lint rules implemented using JavaPsiScanner can be loaded into Android Studio version 2.2+ and executed in real time while writing Android code.
-
In Android Studio 3.0 and version 25.4.0 of the Lint-API, the Lint tool replaces PSI with UAST, and the new UastScanner is recommended.
UAST
UAST is JetBrains’ API for replacing PSI in the new version of IDEA. UAST is more language-independent, supporting Kotlin in addition to Java.
UAST is short for “Universal AST” and is an abstract syntax tree library which abstracts away details about Java versus Kotlin versus other similar languages and lets the client of the library access the AST in a unified way.
UAST isn’t actually a full replacement for PSI; it augments PSI. Essentially, UAST is used for the inside of methods (e.g. method bodies), and things like field initializers. PSI continues to be used at the outer level: for packages, classes, and methods (declarations and signatures). There are also wrappers around some of these for convenience.
I took a closer look at the official definition of UAST. First of all, as mentioned at the beginning, UAST is a more general AST that is not limited to Java code, but also supports Kotlin and similar languages.
But PSI is not exactly the trend that has been replaced by UAST, and can be used for other simple Java scanning tasks.
How best to write Lint without being familiar with the API?
In my own development experience, I look for logic that might work for me in the Native Android version of Lint rules. For example, I accidentally added a space to the front of the string when I was using buried points. I would reflect on this moment, whether I could do it by static scanning, but I am not familiar with the API at this time.
Who is not copied code, ha ha ha. In fact, I found earlier when I used TextView that lint would go yellow when I set a string directly into it. If you have ideas, you can copy the code. I went to find the SetTextDetector, and then I completed the development of this static scanning tool based on the code.
public class EventSpaceDetector extends Detector implements Detector.UastScanner {
static final Issue ISSUE = Issue.create(
"event_space_issue"./ / the only ID
"No Spaces are allowed in buried spots.".// A brief description
"Don't you know sometimes there's a problem with blanks?".// Detailed description
Category.CORRECTNESS, // Type of problem (correctness, security, etc.)
6./ / weight
Severity.WARNING, // Problem severity (ignore, warning, error)
new Implementation( // implementation, including handling instances and scopes
EventSpaceDetector.class,
Scope.JAVA_FILE_SCOPE));
private final String packageName = "com.kronos.sample";
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
List<Class<? extends UElement>> types = new ArrayList<>();
types.add(UCallExpression.class);
return types;
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitCallExpression(@NotNull UCallExpression node) {
checkIsConstructorCall(node);
}
private void checkIsConstructorCall(UCallExpression node) {
if(! UastExpressionUtils.isConstructorCall(node)) {return;
}
UReferenceExpression classRef = node.getClassReference();
if(classRef ! =null) {
String className = UastUtils.getQualifiedName(classRef);
String value = packageName + ".Event";
List<UExpression> args = node.getValueArguments();
for (UExpression element : args) {
if (element instanceof ULiteralExpression) {
Object stringValue = ((ULiteralExpression) element).getValue();
if (stringValue instanceof String && stringValue.toString().contains("")) {
if(! TextUtils.isEmpty(value) && className.equals(value)) { context.report(ISSUE, node, context.getLocation(node),"Who gave you a blank for your balls?"); } } } element.getExpressionType(); }}}}; }}Copy the code
Forgive my vulgarity, but this text is overdone. But in fact, I found ULiteralExpression in the SetTextDetector, which is the variable value in the current syntax tree. After I took out its value, I checked whether the content contained Spaces. If so, I would directly throw an issue in the current place. In this way, I can make all the code in the project to add a space to do a reminder, at least can avoid part of the development of careless.
conclusion
Personally, I don’t think there are many UAST resources available on the web, so developers who want to write particularly complex scan rules like this have to rely on the existing versions of Lint that are already defined, and then go into them and analyze how they’re written, so you can write your own custom Lint.