analysis_plugin_test_helper
Utilities to help write tests for the analyzer plugins.
Installation
From pub.dev
Add this to your pubspec.yaml
dependencies:
analysis_plugin_test_helper: ^1.0.5
Or, From Git repo
dependencies:
analysis_plugin_test_helper:
git:
url: https://github.com/Ragibn5/dart-flutter-packages.git
path: analysis_plugin_test_helper
ref: analysis_plugin_test_helper-1.0.5
๐ฏ Overview
Testing analyzer plugins means writing resolved Dart snippets and asserting on specific AST nodes. That requires a lot of repetitive setup: temp files, resolving the source tree, manual tree-walking, cleanup, and much more โ over and over again. analysis_plugin_helper collapses all of it into two things: a one-call resolver and a set of helpers.
| Feature | What it does |
|---|---|
DartUnitResolver |
Converts a Dart source string into a fully resolved ResolvedUnitResult. |
| Annotation parsers | Find/get annotations by resolved type name. |
| Method parsers | Find/get methods by name across classes, mixins, extensions, and enums. |
| Constructor parsers | Find/get named, default, and factory constructors. |
| Import parsers | Find/get import directives. |
Every parser comes in two flavors: find* (returns null) and get* (fails the test via fail()).
๐ง Usage
DartUnitResolver
Parsing Dart source with analyzer's parseString gives you an unresolved AST โ nodes are syntax only, not linked to declarations. Analyzer plugins need the resolved picture. Normally that means:
- Creating an
AnalysisContextCollection - Writing source to a temp file
- Resolving it and extracting a
ResolvedUnitResult - Cleaning up
DartUnitResolver does all of this in one call:
import 'package:analysis_plugin_test_helper/analysis_plugin_test_helper.dart';
import 'package:test/test.dart';
void main() {
final resolver = DartUnitResolver();
setUpAll(() async => resolver.setUp());
tearDownAll(() async => resolver.tearDown());
test('resolve source', () async {
final result = await resolver.resolveSource('''
class MyAnnotation {
const MyAnnotation();
}
@MyAnnotation()
class Foo {
void myMethod() {}
}
''');
expect(result.diagnostics, isEmpty);
// result.unit โ fully resolved CompilationUnit
});
}
โ ๏ธ The source must be self-contained โ every class, annotation, and import it references must be declared inline. There's no surrounding project context.
โน๏ธ
resolveSource()doesn't throw on errors in the source. Syntactically invalid code still returns aResolvedUnitResult. Checkresult.diagnosticsif you need to assert validity. It only throwsStateErrorwhen the analyzer can't produce a result at all.
setUp() / tearDown() manage the temp directory only. Each resolveSource() call spins up its own AnalysisContextCollection โ no state leaks between calls.
๐ Parser Helpers
Once you have result.unit, these functions locate AST nodes without writing a visitor.
find* vs. get*
| Prefix | Returns | When not found |
|---|---|---|
find* |
null |
returns null |
get* |
value | fails the test via fail() |
Use find* when asserting something is absent; get* when you expect it to exist.
๐ Annotations
Annotation? findAnnotation<D extends CompilationUnitMember>(
CompilationUnit unit, {
required String annotationName,
})
Matches by the annotation's resolved type (computeConstantValue().type.element.name), not source text. So these all match annotationName: 'MyAnnotation':
@MyAnnotation() // direct
@MY_ANNOTATION // const variable
@MAN // typedef alias
Searches top-level declarations only (unit.declarations). Use the type parameter to narrow: getAnnotation<ClassDeclaration>(...).
final annotation = getAnnotation(result.unit, annotationName: 'MyAnnotation');
๐จ Methods
MethodDeclaration? findMethodDeclaration(CompilationUnit unit, String name)
Searches members of classes, mixins, extensions, extension types, and enums. Does not cover top-level functions.
final method = getMethodDeclaration(result.unit, 'myMethod');
๐๏ธ Constructors
ConstructorDeclaration? findConstructorDeclaration(CompilationUnit unit, String? name)
name is the constructor's own name ('named', not 'Foo.named'). Pass null for the default constructor. Matches both regular and factory constructors โ use findFactoryConstructorDeclaration when you need to distinguish.
final defaultCtor = getConstructorDeclaration(result.unit, null);
final namedCtor = getConstructorDeclaration(result.unit, 'named');
๐ญ Factory Constructors
ConstructorDeclaration? findFactoryConstructorDeclaration(CompilationUnit unit, String? name)
Same as findConstructorDeclaration, restricted to factory constructors. null finds the default factory.
final factoryCtor = getFactoryConstructorDeclaration(result.unit, 'create');
๐ฅ Import Directives
ImportDirective? findImportDirective(CompilationUnit unit)
Returns the first import directive. No URI filtering.
final importDirective = getImportDirective(result.unit);
๐งฉ Putting it together
test('plugin flags classes annotated with @MyAnnotation', () async {
final result = await resolver.resolveSource('''
class MyAnnotation {
const MyAnnotation();
}
@MyAnnotation()
class Foo {
void myMethod() {}
}
''');
final annotation = getAnnotation(result.unit, annotationName: 'MyAnnotation');
final method = getMethodDeclaration(result.unit, 'myMethod');
expect(annotation, isNotNull);
expect(method.name.lexeme, 'myMethod');
});
This is the shape most plugin tests take: Resolve โ pull nodes โ assert. No temp files, no cleanup.
๐งช Example
See example.dart and tests for a complete demonstration.
Libraries
- analysis_plugin_test_helper
- Utilities to help write tests for the analyzer plugins.