| name | new-lint |
| description | Creates a new lint rule with quick fix and tests for the many_lints package. Use when the user wants to add a new lint rule. |
You are creating a new lint rule for the many_lints Dart linter package. The user will provide context describing what the lint should detect, possibly a lint name, and optionally reference links.
Step 1: Parse the user's input
Extract from the $ARGUMENTS:
- Lint name (snake_case) โ if not provided, derive one from the description
- Description โ what the lint should detect/warn about
- Reference links โ any URLs for documentation or examples
Step 2: Research
Before writing any code:
-
๐ ALWAYS START HERE: Read the Lint Rule Cookbooks (in this directory)
- rules-patterns.md โ Rule structure, type checking, AST navigation, visitors, reporting, utilities, analyzer APIs
- rules-recipes.md โ Copy-paste ready recipes for common scenarios
- These are your primary reference for all implementation patterns
- Check the cookbooks FIRST before researching elsewhere
-
Read these reference docs to understand the framework:
-
Read any reference links the user provided in $ARGUMENTS.
-
If the cookbook doesn't cover your specific pattern, read 1-2 existing rules in lib/src/rules/ and their corresponding fixes in lib/src/fixes/ and tests in test/ to understand the codebase patterns. Pick rules that are most similar to the new lint being created.
-
Read lib/src/type_checker.dart and lib/src/ast_node_analysis.dart for reusable utilities.
Step 3: Ask clarifying questions
Before implementing, use AskUserQuestion to clarify:
- What specific AST nodes/patterns should trigger the lint?
- What should the quick fix do exactly? (e.g., replace widget, rename, remove argument)
- Are there edge cases to consider? (e.g., const constructors, nested expressions, generics)
- Does the lint need to check types from a specific package? (determines TypeChecker usage)
Only ask questions that aren't already answered by the user's input.
Step 4: Create the lint rule
Create lib/src/rules/<lint_name>.dart following this exact pattern:
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/error/error.dart';
// Add if needed:
// import 'package:many_lints/src/type_checker.dart';
// import 'package:many_lints/src/ast_node_analysis.dart';
/// <doc comment describing what the rule does>
class <RuleClass> extends AnalysisRule {
static const LintCode code = LintCode(
'<lint_name>',
'<problem message describing what is wrong>',
// Optional: correctionMessage: '<suggestion for how to fix>',
);
<RuleClass>()
: super(
name: '<lint_name>',
description: '<short description>',
);
@override
LintCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this);
// Register for the appropriate AST node type, e.g.:
// registry.addInstanceCreationExpression(this, visitor);
// registry.addClassDeclaration(this, visitor);
// registry.addMethodInvocation(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final <RuleClass> rule;
_Visitor(this.rule);
// Use TypeChecker for type checks:
// static const _checker = TypeChecker.fromName('WidgetName', packageName: 'flutter');
@override
void visit<NodeType>(<NodeType> node) {
// Detection logic here
// Report with: rule.reportAtNode(node) or rule.reportAtToken(node.name)
}
}
Key conventions:
- Rule class name: PascalCase version of lint name (e.g.,
use_cubit_suffix -> UseCubitSuffix)
- Use
TypeChecker.fromName() or TypeChecker.fromUrl() for type checks
- Use Dart 3.0+ pattern matching for AST analysis
- Use helpers from
lib/src/ast_node_analysis.dart when applicable
Step 5: Create the quick fix
๐ Consult the Quick Fix Cookbook: See fixes-cookbook.md for comprehensive patterns and examples.
Create lib/src/fixes/<lint_name>_fix.dart following this exact pattern:
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
/// Fix that <description of what the fix does>.
class <FixClass> extends ResolvedCorrectionProducer {
static const _fixKind = FixKind(
'many_lints.fix.<lintNameCamelCase>',
DartFixKindPriority.standard,
'<Short description of the fix action>',
);
<FixClass>({required super.context});
@override
CorrectionApplicability get applicability => CorrectionApplicability.singleLocation;
@override
FixKind get fixKind => _fixKind;
@override
Future<void> compute(ChangeBuilder builder) async {
// Access the reported node:
final targetNode = node;
// Navigate to the relevant AST node
// Apply fix using builder.addDartFileEdit(file, (builder) { ... });
}
}
Key conventions:
- Fix class name: PascalCase rule name +
Fix suffix (e.g., PreferCenterOverAlignFix)
- FixKind ID:
many_lints.fix.<lintNameInCamelCase>
- Use
range.node() for replacing nodes, range.nodeInList() for removing from argument lists
- Use
addSimpleReplacement() for simple text replacements
- Use
addDeletion() for removing code
Step 6: Register in many_lints.dart
Edit lib/many_lints.dart:
- Add imports for the new rule and fix
- Add
registry.registerWarningRule(<RuleClass>()); in the rules section
- Add
registry.registerFixForRule(<RuleClass>.code, <FixClass>.new); in the fixes section
Step 7: Create tests
Create test/<lint_name>_test.dart following this exact pattern:
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:many_lints/src/rules/<lint_name>.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
void main() {
defineReflectiveSuite(() => defineReflectiveTests(<RuleClass>Test));
}
@reflectiveTest
class <RuleClass>Test extends AnalysisRuleTest {
@override
void setUp() {
rule = <RuleClass>();
// Mock external packages if needed:
// newPackage('package_name').addFile('lib/file.dart', r'''
// class SomeClass {}
// ''');
super.setUp();
}
// Test cases that SHOULD trigger the lint:
Future<void> test_<descriptive_case_name>() async {
await assertDiagnostics(
r'''
<code that should trigger the lint>
''',
[lint(<offset>, <length>)],
);
}
// Test cases that should NOT trigger the lint:
Future<void> test_<descriptive_valid_case>() async {
await assertNoDiagnostics(r'''
<code that should not trigger the lint>
''');
}
}
Key conventions:
- Test class name:
<RuleClass>Test
- Use
assertDiagnostics(code, [lint(offset, length)]) for code that triggers the lint
- Use
assertNoDiagnostics(code) for code that should NOT trigger the lint
- Use
newPackage('name').addFile() to mock external package dependencies
- Include at least: 2 positive cases (triggers lint), 2 negative cases (no lint), 1 edge case
lint(offset, length) โ offset is the character position, length is the length of the reported node/token
- Method names start with
test_ and use camelCase
Step 8: Update the Cookbooks (MANDATORY when discovering new patterns)
๐จ CRITICAL: If you discovered or researched any new patterns, you MUST update the cookbooks before completing this task.
You must update if you:
- โ
Discovered a new analyzer API pattern (e.g., different way to access elements, types, or AST nodes)
- โ
Researched AST traversal techniques not shown in the cookbook
- โ
Found a new type checking method or TypeChecker usage pattern
- โ
Implemented a complex visitor pattern for deep analysis
- โ
Created a new helper utility function
- โ
Had to dig into analyzer source code or documentation for APIs not covered in the cookbook
- โ
Found analyzer ^11.0.0 specific behaviors different from what you "know" from training data
How to update (two places):
-
Full details โ Add to the appropriate cookbook file in this directory:
-
Brief mention โ Add a short entry to the lean quick reference at lib/src/rules/AGENTS.md (or lib/src/fixes/AGENTS.md for fix patterns)
This keeps the cookbooks as living documents that improve with each new rule!
Step 9: Create a documentation page
Create docs/src/content/docs/docs/rules/<category>/<lint-name>.md for the new rule.
Determine the category by matching the lint's domain to one of the existing sidebar categories:
class-naming โ Suffix/naming rules for classes (Bloc, Cubit, Notifier, etc.)
bloc-riverpod โ Bloc/Riverpod architecture rules
riverpod-state โ Riverpod state/ref usage rules
async-safety โ Async/await safety rules
widget-best-practices โ Widget usage best practices
widget-replacement โ Prefer simpler/more specific widgets
state-management โ StatefulWidget/setState rules
control-flow โ Control flow, cascades, exceptions, switches
collection-type โ Collection/Iterable/Map rules
pattern-matching โ Dart pattern matching rules
type-annotations โ Type annotation preferences
code-organization โ Code structure and organization
shorthand-patterns โ Shorthand/constructor sugar
hook-rules โ Flutter Hooks rules
testing-rules โ Test-related rules
resource-management โ Disposal, listeners, subscriptions
code-quality โ General code quality
If no existing category fits, create a new directory AND add a matching autogenerate entry in docs/astro.config.mjs sidebar config.
Use this template (match the format of existing pages):
---
title: <lint_name>
description: "<Short description>"
sidebar:
badge:
text: "Fix"
variant: "tip"
label: <lint_name>
---
<span class="rule-badge rule-badge--version">vX.Y.Z</span>
<span class="rule-badge rule-badge--warning">Warning</span>
<span class="rule-badge rule-badge--fix">Fix</span>
<span class="rule-badge rule-badge--category"><Category Name></span>
<2-3 sentence human-friendly description of what the lint detects and why it matters.>
## Why use this rule
<Real-world context explaining why this pattern is problematic. Include "See also" links to relevant official docs.>
**See also:** [Link](url) | [Link](url)
## Don't
```dart
// Bad example with comment explaining why it's wrong
<code that triggers the lint>
Do
// Good example
<correct code>
Configuration
To disable this rule:
plugins:
many_lints:
diagnostics:
<lint_name>: false
Key notes:
- Omit the `sidebar.badge` block entirely if the lint has **no quick fix**
- Omit the `<span class="rule-badge rule-badge--fix">Fix</span>` badge if there is no fix
- Determine the version tag: check the latest version in `pubspec.yaml` โ if this is a new unreleased rule, use the next version that will be released
- Use the lint name with underscores (snake_case) for `title` and `label`
- Use dashes (kebab-case) for the filename (e.g., `prefer-center-over-align.md`)
## Step 10: Create an example file
Create `example/lib/<lint_name>_example.dart` to demonstrate the lint rule. Look at existing example files in `example/lib/` for the pattern.
The example file should include:
- A file-level `// ignore_for_file: unused_local_variable` (or similar) to suppress unrelated warnings
- A comment header with the lint name and brief description
- **Bad examples** (code that triggers the lint) with `// LINT:` comments explaining each case
- **Good examples** (correct code that does NOT trigger the lint)
- **Edge cases** where the lint intentionally does NOT trigger (e.g., when `.from()` is needed for downcasting)
Example structure:
```dart
// ignore_for_file: unused_local_variable
// <lint_name>
//
// Brief description of what the lint detects.
// โ Bad: Description of bad pattern
class BadExamples {
void example() {
// LINT: Explanation of why this triggers
final x = badCode();
}
}
// โ
Good: Description of correct pattern
class GoodExamples {
void example() {
final x = goodCode();
}
}
Step 11: Verify
Run the following commands from the project root to ensure everything works:
dart analyze - Ensure there are no issues at all (errors, warnings, or infos). Fix any that appear before proceeding.
dart test - Ensure all tests pass
If either command fails or reports issues, fix them and re-run until both are fully clean.