How to implement your first custom lint rule in Android using TDD (Part 1)

Guilherme Krzisch
The Startup
Published in
4 min readJun 10, 2020

In this article I will demonstrate how to implement your first custom lint check, while using TDD. This will be the first post of a series about this not so common subject.

Custom Lint Check

Android comes with a set of lint checks, which can be augmented with custom ones. I could not find any good and updated official documentation explaining how to do this, but fortunately there are some great articles and examples in the community ([1][2][3][4][5]). I will just give an overview of the bare minimum for this article, so I recommend you to read one of the mentioned articles in order to get a better understanding of how they work.

The basic pieces to build a lint check are:

  • Detector: it is how you traverse the code to report issues
  • Issue: it is what will be show when the Detector finds an error with your code. They can have different severities, priorities and categories
  • Implementation: where you configure your Detector and what set of files it will handle (e.g. Java source code, XML files,…)
  • Registry: contains a list of all the Issues it can run. The BuiltinIssueRegistry contains the default ones.

Detect if a method declares more than 5 parameters

For our first custom lint check, we want to add a check for us to be warned whenever we have declared a method with more than 5 parameters. This can be useful in order to keep our methods short and intelligible at first glance, so the reader doesn’t have to keep context of multiple parameters when looking at a method.

So let’s start. We are going to try following TDD practices while implementing this check, so the first thing would be to write a unit test. For lint unit tests, there is a handy class which does the heavy lifting in setting up what we need, so we extend from it: LintDetectorTest.

There are two methods which we need to override: one to declare which Detector we are testing and other to list all the Issues under testing. As we don’t have a Detector class yet, we need to create it and declare the single Issue we are going to try to detect and subsequently report.

You can see that we are creating and configuring our issue with the message we are going to show, and setting the severity as a warning. Furthermore, we need to create an Implementation and declare that we are interested in Java and Kotlin source code files (scope). Finally, we will add an utility method so we can report that we have detected this warning in a specific location.

Now that we have our Detector and associated Issue, we can fill the TODOs we left in the test file. We will also add our first unit test, which will guarantee that lint does not report errors for a class with a parameter-less method.

Running this unit test, it should pass, as there is nothing in our Detector yet, and there is nothing to detect here. We can go ahead and create other unit tests for cases where we should not report warnings: methods with 1 to 5 parameters, both in Java and Kotlin. These all should pass successfully. It is important to have this baseline of unit tests, so we can make sure we do not break them later.

Now it is time to create our unit test with a method declaring 6 parameters, which should trigger a warning. Running this test should fail, so now we can go back to our Detector class to actually implement the required logic.

How can we identify if a method declares more than 5 parameters? It turns out this is quite easy using our Detector class.

But first, let’s look at the parsed tree for this Kotlin file we are creating in our unit test. You can generate this by calling “asRecursiveLogString()” in a given UElement. Note that what we want is to get a reference for the UAnnotationMethod and check how many parameters it contains.

In order to do this we need to declare we are interested only in method elements. This is done by overriding the getApplicableUastTypes() function, and returning the UMethod class. Then, we need to provide an UElementHandler, which contains a set of functions we can override to be notified of specific constructions while parsing our source code. Again, as we are interested only in methods, we override the visitMethod() function which receives the corresponding UMethod instance. Having that, we can check if the number of parameters is greater than our magic number, 5. If it is, we can report its usage, which will actually trigger the lint warning.

Conclusion

And that’s it! We have implemented our first custom lint check. You can find the complete code here and the tests here. In the repository you can see other more complex custom lint rules, which I will describe in the next articles.

This article is part of a series about creating custom lint rules in Android:
Part 1: How to implement your first custom lint rule in Android using TDD
Part 2: How to implement a custom lint rule in Android that requires an overall view of the project
Part 3: How to implement a custom lint rule in Android to warn against checked exception thrown from Kotlin
Part 4: How to implement a custom lint rule in Android to handle differences in Exception handling between Java and Kotlin
Part 5: How to implement a custom lint rule in Android to detect if we are setting the fragment manager before calling Activity onCreate method
Part 6: (Maybe) How to implement a custom lint rule in Android to detect if we are passing an immutable collection to a Java method which needs a mutable one

--

--

Guilherme Krzisch
The Startup

Software Engineer | Mobile Developer | Android Developer