How to create a custom lint in Flutter with custom_lints
Getting started with custom_lints
custom_lints package did not exist, one had to painfully connect to the dart analyser: a terrible and tedious DX. Fortunately, that’s not the case anymore.
The easiest way to get start is to follow the README of custom_lints on pub.dev. You should have your first lint showing in less than 10 minutes !
Tips to work with the custom_lints package:
- Once you make a change to your lint, if it does not restart in the project where you visualize your lint run
flutter pub getand wait for them to appear
custom_lints.logfile (which appears at the same level as where your
pubspec.yamlis). I would advise you to never delete this file while working on your project since it can take some time before it first appears.
Create your first custom lint in Flutter
custom_lints package allows you to get started, it only provides you with a blank canvas. If you try to do anything, you will soon find that you have to learn to use paint, brushes and more - all things which are not included in the package. Let’s see how to start walking this new world of great possibilities through an example:
> Let’s create a lint which warns the user not to assign
List but to use
IListis as immutable equivalent to
Listfrom the fast_immutable_collections package. This package is awesome and will remove all the untraceable errors that classic
Lists lets slip by.
Note: The best way to learn the concept which follow is practice. I would advise you setup custom_lint and follow along. Here is a repo from which to start (where custom_lint is already setup). Every problem is separated in digestible steps with first the explanation of what we are trying to do and then the solution. The best way to read this article is to try each step before reading the solution.
- Create and place the lint
- What is the AST tree and how to get every variable declaration?
- Check for variable type
- Lint creation and placement from variable Element
- Implement lint quick fix
Create and place the lint
In this first part, we will try to place the lint under every variable to which a
List is assigned. Here is an example:
What is the AST tree and how to get every variable declaration?
The first step to creating a lint is to parse the code. In each file, the code is represented by two main trees:
- The Element tree
- The AST tree
The Element tree represents things that are declared with a name (e.g. classes, variables, methods, …) while the AST tree represents the structure of the code. Here is a concrete example:
What is important to remembers is that the AST tree is more complete but harder to parse. When you can, stick with the Element tree but if you really need, switch to AST.
In our case, we will need to have the
Expression in order to be able to wrap it with
IList (see next chapter). Therefore we will choose the AST tree.
In order to parse those trees, the easiest way is to use
Visitors, which are classes with built-in method to detect what they are visiting. In particular, the
RecursiveElementVisitor) are particularly useful. In our case, we want to override the
visitVariableDeclaration method of
You can then use this
Visitor to parse the current file inside the custom_lints
The getLints method is generative. If this does not sound familiar, you might want to check out the video on this topic from the Flutter team
So far so good, we have gathered all the variable declaration in the file!
Check for variable type
Next step is to filter only the
VariableDeclarations that are associated to list. To do so we need to:
VariableDeclaration.declaredElement.typeto get a special representation of the type of the variable
- Import the source_gen package and use the
Create the lint and place it at the right location
Finally, we can start building our Lint. The
Lint class is fairly easy to use, the only challenge is to find specify the
location of our lint. What you need to remember is that once you have any
Element, you will be able to use its
Here you go, now if we start the project in which our lint is imported we should see is appear where we want:
Now that you have successfully communicated the developers what not to do, let’s show them what to do with quick fixes.
Implement lint quick fix
Quick fixes are suggestions integrated in the editor which allows you to fix a lint with one click:
To implement quick fixes, you have to use the
getAnalysisErrorFixes parameter of the
Lint we just created:
The two things we have to customize are:
priority: A number ≥ 0 which states how much the lint is pertinent (the lower the most pertinent)
sourceChangewhich indicated which change to actually perform when the quick fix is clicked. You can easily build a
ChangeBuilderwhich has a
For some reason, change is not exported from the analyzer plugin so add an import to be able to export it:
To indicate the change you want to perform, use the
changeBuilder.addDartFileEdit method (which is
async: don’t forget to
await it!). This method takes two parameters:
path: the path to the file you want to edit, which will nearly always be the current one, accessible with:
buildFileEdit: a method which gives you a
fileEditBuilderwhich you can use to make the changes in the file
fileEditBuilder is quite simple to use. The only challenge, as for the lint, it to know the position of what you want to replace. In our example we want to wrap the
expression used to define our variable: This won’t be perfect and could be improved but will work well in most cases. The
expression can be obtained from
VariableDeclaration which we got in the first part:
The last thing you might want to specify is a message letting the user know what the quick fix does:
Go back to the project where you imported the lint and you should see and be able to apply the quick fix:
Tada! We have created our first lint!
What you need to remember
- Parse a file with
TypeCheckerfrom the source_gen package to check types
- Get the
Elementyou need and use its
nameLengthto position your lint
AST nodewhich represents a variable (in
VariableDeclaration.declaredElement) and its associated expression (in
package:analyzer_plugin/utilities/change_builder/change_builder_core.dart) to modify the file content through quick fixes