← Back to blog

Major Release of the Dart Custom Lint Package 0.2.x

Majid Hajian

Head of DevRel

6th March, 2023

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

On July 2022, we announced the custom_lint package, a powerful tool for building custom lint rules that allow package authors and Flutter and Dart developers to go beyond the standard lint rules. Since then, the package has been under active development. We listened to feedback and improved the package notably.

Remi Rousselet, the creator of Provider, Riverpod, and many more packages, leads the project. The aim is to simplify dealing with the analyzer_plugin, providing an abstraction for Dart developers to build custom lint rules.

We are pleased to announce a new major release of the package. Along with this release, we have also published a website to make organizing examples, announcements, and documents easier. Even though the website is still in progress, you can access it on custom-lint.dev.

What is new?

This new version introduces large changes to how lints, fixes, and assists are defined.

The entire syntax from the previous version has changed except for the createPlugin method.

This article will review the new changes and guide you through upgrading.

There are multiple benefits to the new syntax, for example:

  • Enabling/Disabling lints inside the analysis_options.yaml file:
    • Enabling/disabling lints is supported by default with the new syntax.
  • Performance improvement:
    • When using many lints the performance has improved since the workload of analyzing files is now shared between lints.
  • Simpler maintainability:
    • Before, the PluginBase.getLints rapidly ended up doing too much. Now, it is simple to split the implementation into multiple parts.

Quick Overview

Custom Lint is made up of two major packages: custom_lint and custom_lint_builder. The custom_lint package is used in the application where you will leverage the defined custom lints, and the custom_lint_builder is to be used in a package where you define your own custom lint rules.

Generally, when you use custom_lint you need to define your task into two parts:

  • Defining a custom_lint package
  • Installing package in the applications.

Creating a custom lint package

Our previous blog post details how to start creating a custom lint package. However, since then, there have been a few major changes. The following steps outline how to get started with the new release:

  1. Create a new Dart package using the dart CLI, e.g., dart create -t package <my_package_name>
  2. Update your pubspec.yaml to include custom_lint_builder as a dependency:
# pubspec.yaml
name: my_custom_lint_package
environment:
  sdk: ">=2.16.0 <3.0.0"

dependencies:
  analyzer:
  custom_lint_builder:
  1. Create a lib/<my_package_name>.dart file in your project with the following code:
PluginBase createPlugin() => _ExampleLinter();

class _ExampleLinter extends PluginBase {
  @override
  List<LintRule> getLintRules(CustomLintConfigs configs) => [
        MyCustomLintCode(),
      ];
}

class MyCustomLintCode extends DartLintRule {
  MyCustomLintCode() : super(code: _code);

  static const _code = LintCode(
    name: 'my_custom_lint_code',
    problemMessage: 'This is the description of our custom lint',
  );

  @override
  void run(
    CustomLintResolver resolver,
    ErrorReporter reporter,
    CustomLintContext context,
  ) {
    context.registry.addVariableDeclaration((node) {
      reporter.reportErrorForNode(code, node);
    });
  }
}

This lint will highlight all variable declarations with a custom warning.

  1. That’s it for defining a custom lint package! You can either use this package locally or push it to pub.dev so everyone can enjoy using it.
  2. To use your custom lint package in your application, you should add it as a dev_dependencies
name: example_app
environment:
  sdk: ">=2.16.0 <3.0.0"

dev_dependencies:
  custom_lint: 
  my_custom_lint_package:
  1. Next, you need to add the custom_lint plugin to your analysis_options.yaml file:
analyzer:
  plugins:
    - custom_lint
  1. Run dart pub get, and you should see the custom lints in their Dart files:
https://raw.githubusercontent.com/invertase/dart_custom_lint/main/resources/lint_showcase.png

Plugin libraries

There are plugins from the community that are now published on the pub.dev, which will help you to keep your project more maintainable. Plugins such as Riverpod Lints, Supernova Lints, and Equatable Lints.

We would love your feedback if you started building your lint package.

Advanced examples

We are working on advanced use cases and will publish them soon, along with supporting material such as tutorials and video courses. Please let us know what you would like to see first.

You can always open an issue or join our Discord custom lint channel to share your feedback.

Migration from 0.1.x to 0.2.x

Let’s look at the previous example for migrating to version 0.2.x.

Entry point

If you are coming from the previous version, you have noticed that we had bin/custom_lint.dart where the entry point of your custom linter starts with the main function where it will have two parameters, List<String> args, and SendPort sendPort.

// OLD, version 0.1.x
void main(List<String> args, SendPort sendPort) {
  startPlugin(sendPort, _ExampleLinter());
}

That has now changed to lib/{your_package_name}.dart and the following code:

// NEW, version 0.2.x
PluginBase createPlugin() => _ExampleLinter();

Plugin Base

A plugin class lists all the assists / lints defined by a plugin. You used to have getLints method to yield the Lint class.

// In previous version 
class _ExampleLinter extends PluginBase {
  @override
  Stream<Lint> getLints(resolvedUnitResult) async* {
    yield Lint(
      ...
    );
  }
}

In version 0.2.x, you should have a list of rules passed to getLintRules

// Verison 0.2.x
class _ExampleLinter extends PluginBase {
  @override
  List<LintRule> getLintRules(CustomLintConfigs configs) => [
        MyCustomLintCode(),
      ];
}

In this case, you can pass multiple instances of Analyzer Dart rules that extend DartLintRule to define your custom lints. You can notice that Lint class has also changed to LintCode

Therefore, the previous version code will become:

class MyCustomLintCode extends DartLintRule {
  static const _code = LintCode(
    name: 'my_custom_lint_code',
    problemMessage: 'This is the description of our custom lint',
  );

  MyCustomLintCode() : super(code: _code);

  @override
  void run(
    CustomLintResolver resolver,
    ErrorReporter reporter,
    CustomLintContext context,
  ) {
    context.registry.addVariableDeclaration((node) {
      reporter.reportErrorForNode(code, node);
    });
  }
}

Notice that you have a few changes here, especially looking at run method.

your lint will highlight all variable declarations with our custom warning done at context.registry.addVariableDeclaration() .

The node in that addVariableDeclaration callback exposes metadata about the variable declaration. We could check node to show the lint only in some conditions.

reporter.reportErrorForNode tells custom_lint to render a warning at the location of node. The warning shown will use your code variable defined above as a description.

Package folder structure

Your package folder structure will change to

|__ my_custom_lint_package
|_____ lib
|________ my_custom_lint_package.dart
|_____ analysis_options.yaml
|_____ pubspec.yaml

Conclusion

Building custom lints rule has never been easier in Flutter and Dart ecosystem, and by using custom_lint package, you can enforce a set of great best practices to your project. If you work in a big team, you may even enforce your company coding guideline as a custom lint package and let the analyzer be smarter and faster to catch faults.

Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase TwitterLinkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.

Majid Hajian

Head of DevRel

Categories

Tags