Recently, we’ve released our first Dart open-source package, netglade_analysis, that we use internally at netglade. The package contains carefully selected Dart linter rules and a handful of Dart Code Metrics rules that help us build code while preserving high standards. We have a lot of projects, and having to copy-paste all rules and keep them in sync is a pretty challenging task. Until we realized we would be better off with a custom package and keeping the rules consistent across many projects.
While rules selection might sound easy, we carefully analyzed every single one and also scrutinized packages used by other teams. This included very_good_analysis from Very Good Ventures, leancode_lint from LeanCode, official lints and flutter_lints packages from Dart and Flutter team, Mike Rydstorm’s comparison, and many more sources. A while ago, when there were no official packages (except the pedantic package (deprecated) used internally at Google), I did make an effective_dart package (deprecated) that followed the Effective Dart guide. It was used in many packages before I deprecated it in favor of the newly created official packages. Despite that, more than 200 lints have been made since then, and even just going through them was a challenge of its own. These packages are generally a good start, but from time to time, we encountered rules that we didn’t want enforced, while also coming across a few we did. Therefore, we decided to create our own package — and what’s stopping us from making it public?
Initially, the tricky part was determining which lints we currently use across packages we want to keep and which rules we might add. We found out that A LOT of linter rules were added in recent years, so we also created an extensive list based on all the supported lint rules.
To start, I marked each rule with 🟢, 🔴 or ❓ flags and had the team look at the proposal, so they could point out those they didn’t like. Together, we cherry-picked the lints most beneficial for our projects.
Fig. 1: Cherry-picking lints in progress (netglade)
Okay, is that it? Not so fast, cowboy.
Then we found this fantastic package called Dart Code Metrics, DCM for short, with custom lints one can use to extend possibilities of statically analyzing code. While Dart lints are good enough for most use cases, we would benefit from many DCM lints, so we selected a bunch of them, too, and had a great time with the results.
Static analysis in all its forms helps developers determine issues with the code. It also helps to keep the code consistent and compliant with standards. And linting is a part of static analysis that forces us to keep the code boring by screaming at us when we break a particular style rule.
Let’s be frank: boring code is good code!
We should always strive for code to be boring because that makes the code easy to read. The code has almost no surprises, and if it does, they’re rarely welcome. Even the language tries to be boring, with very little care for trends, and brings consistent syntax and tools instead. And it matters — the more boring the code, the more teams can build similarly accomplished code to a point when you’ll stop caring who wrote a particular piece of it. As a result, it draws attention back to detail.
Linting can help you with enforcing rules but also preventing a myriad of issues. Type mismatch, missing cases in switch, redundant code, missing test assertions, detecting unreachable code, or warning for security issues, just to name a few.
Sometimes you’ll feel like fighting against overly strict rules, yet the opposite is true. Lints are the unified voice of all authors who made a deal to follow conventions enforced by lints. They are, in a way, your friend. A bit pedantic, yes, but a friend nonetheless.
Another big topic is their use in formatting. Most of all in cases without pre-set conventions in your project, when one person ends up using different indentation or bracket style than the other. Imagine pull requests full of style switching in favor of “my style is better than yours.” You must have some conventions, so why not force them with lints? While it’s best to automatize as much as possible, some conventions cannot be enforced into lints, and you still have to keep an eye on them in your code reviews. That includes vertical formatting and density, horizontal openness, density, and alignment; indentation; unified style; comments, commented-out code, redundant comments, and others. Most of the mentioned cases can be solved using the dart format tool, so don’t forget to use it.
We wanted to include all of the listed guidelines and avoid all issues we might face in the future. To unify our rules, we created the netglade_analysis package that focuses on a few things using both Dart and DCM lints:
- Strict types: Forces the type inference to always choose a non-'dynamic' type, or fail if it cannot be determined. This makes the code safer.
- Code optimization: Ensures proper use of const and final declarations and the use of more appropriate types, expressions, or widgets.
- Pedantic: Ensures that code is used correctly and checks for unnecessary verbosity.
- Consistency: Ensures that code is consistent and in compliance with guidelines.
- Dart Code Metrics: Provides a list of DCM rules that can further enhance linting experience.
Fig. 2: netglade_analysis on pub.dev (netglade)
To customize static analysis in Dart and Flutter, you must set up the
analysis_options.yaml file. To follow our guidelines, you must include our lints and DCM rules. You can do that by following the example file below. It’s quite simple, just include lints using
include: package:netglade_analysis/lints.yaml, setup DCM plugin, and extend our DCM rules.
# analysis_options.yaml - using netglade_analysis include: package:netglade_analysis/lints.yaml analyzer: plugins: - dart_code_metrics dart_code_metrics: extends: - package:netglade_analysis/dcm.yaml
You can also set up all the guidelines by yourself. To do that, set up an analyzer by setting the
analyzer subtree. Inside its
language subtree, you can set up the analyzer to be stricter with
strict-raw-types set to
true. This enforces strict type inference, meaning that if the analyzer cannot determine a type, it throws a warning, while typically, it would fall back to using
dynamic type. Using
dynamic type is not recommended as it can lead to errors in many places, so enabling these strict language rules is definitely encouraged. In the
analyzer subtree, you can also set up a list
exclude where you can put file paths to exclude specific files, such as generated
# analysis_options.yaml - generic example analyzer: language: strict-casts: true strict-inference: true strict-raw-types: true exclude: - "**/*.g.dart" - "**/*.freezed.dart" linter: rules: - avoid_empty_else # ...
Many lints also have an auto fix, which is helpful for automatically applying as many changes as possible. It saves a lot of time, so be sure to run it whenever you can.
While lints and other analyzer settings might sometimes be a bit constraining, their benefits largely outweigh their shortcomings. The code is more unified across teams and projects, and other programmers will appreciate its consistency and structure devoid of any unwarranted surprises. So invest your time in enhancing your internal lints as much as possible. It will pay off in the long run.
And if you don’t want to go through all the rules and picking your dream lints, just use the packes listed in this article or our very own netglade_analysis. You’ll find instructions on how to make it work in its readme.