Skip to content

Instantly share code, notes, and snippets.

@sma
Created March 7, 2024 16:27
Show Gist options
  • Save sma/6a3bf30ebabd8219760f984937841d52 to your computer and use it in GitHub Desktop.
Save sma/6a3bf30ebabd8219760f984937841d52 to your computer and use it in GitHub Desktop.
A quick & dirty tutorial how to create a custom linter for Dart
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
class MyLintPlugin extends PluginBase {
@override
List<LintRule> getLintRules(CustomLintConfigs configs) {
return [
MyLintRule(),
];
}
}
class MyLintRule extends DartLintRule {
MyLintRule() : super(code: _code);
static const _code = LintCode(
name: 'my_lint_be_no_fool',
problemMessage: 'do not use `foo` or `bar` as variable names',
errorSeverity: ErrorSeverity.ERROR,
);
@override
void run(CustomLintResolver resolver, ErrorReporter reporter, CustomLintContext context) {
context.registry.addVariableDeclaration((node) {
final name = node.name.lexeme;
if (name == 'foo' || name == 'bar') {
reporter.reportErrorForNode(_code, node);
}
});
}
}
PluginBase createPlugin() => MyLintPlugin();

Create a new Dart project:

dart create my_lint

Enter it:

cd my_lint

Remove what we don't need:

rm -rf bin/ test/ CHANGELOG.md

Then add a few packages:

dart pub add analyzer
dart pub add analyzer_plugin
dart pub add custom_lint_builder

In lib/my_lint.dart, create a new plugin that will implement your very own linter rule. I will create a rule that disallows the usage of foo or bar as variable names. The plugin must inherit from PluginBase and implement at least one method, that returns all custom rules:

class MyLintPlugin extends PluginBase {
  @override
  List<LintRule> getLintRules(CustomLintConfigs configs) {
    return [
      MyLintRule(),
    ];
  }
}

Next implement MyLintRule, which is, because I want to analyze Dart code, a subclass of DartLintRule and needs to be initialized with the issue it can raise. We need at least a unique name (which can be used to disable the rule) and a message that describes the issue. I also change the severity to ERROR. The rule must implement a run method (more on that later):

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

  static const _code = LintCode(
    name: 'my_lint_be_no_fool',
    problemMessage: 'do not use `foo` or `bar` as variable names',
    errorSeverity: ErrorSeverity.ERROR,
  );

  @override
  void run(CustomLintResolver resolver, ErrorReporter reporter, CustomLintContext context) {}
}

Let's implement run. We need to register a callback that will automatically be called if a Dart file is modified and the plugin framework did already all the heavy lifting of parsing the source code into an AST and resolving all types (I hope). So all we need to do is checking the name of a variable declaration:

  @override
  void run(CustomLintResolver resolver, ErrorReporter reporter, CustomLintContext context) {
    context.registry.addVariableDeclaration((node) {
      final name = node.name.lexeme;
      if (name == 'foo' || name == 'bar') {
        reporter.reportErrorForNode(_code, node);
      }
    });
  }

Next, we need to create an example project where we can use our custom linter:

dart create example
cd example
rm -rf bin/ test/ CHANGELOG.md

Add my_lint as a development dependency in pubspec.yaml:

dart pub add 'dev:my_lint:{path: ..}'

Next, modify analysis_options.yaml to add custom_lint as a plugin:

analyzer:
  plugins:
    - custom_lint

To test the new linter rule, go to lib/example.dart and write:

var foo = 42;

You should get an error

example.dart (example/lib):
do not use `foo` or `bar` as variable names
dart(my_lint_be_no_fool) [Ln 1, Col 7]

Changing foo to fool should clear the error.

Alternatively, you could add // ignore: my_lint_be_no_fool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment