Skip to content

Commit 4da999f

Browse files
authored
Support fragments, element directives and annotatables in InvalidGenerationSource. (#774)
1 parent b6bd805 commit 4da999f

File tree

5 files changed

+156
-29
lines changed

5 files changed

+156
-29
lines changed

source_gen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- Prepare to stop using `dart:mirrors`: add `inPackage` and `inSdk` parameters
77
to `GenerateForAnnotation`. These will start being used in version `4.0.0`
88
when it switches to `TypeChecker.typeNamed`.
9+
- `InvalidGenerationSource` support for `Fragment`, `ElementDirective` and
10+
`Annotatable`.
911
- Allow `analyzer: '>=7.4.0 <9.0.0'`.
1012

1113
## 3.0.0

source_gen/lib/src/generator.dart

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:async';
77
import 'package:analyzer/dart/ast/ast.dart';
88
import 'package:analyzer/dart/element/element2.dart';
99
import 'package:build/build.dart';
10+
import 'package:source_span/source_span.dart';
1011

1112
import 'library.dart';
1213
import 'span_for_element.dart';
@@ -45,59 +46,92 @@ class InvalidGenerationSource implements Exception {
4546
/// May be an empty string if unknown.
4647
final String todo;
4748

48-
/// The code element associated with this error.
49-
///
50-
/// May be `null` if the error had no associated element, or if the location
51-
/// was passed with [node].
49+
/// The [Element2] associated with this error, if any.
5250
final Element2? element;
5351

54-
/// The AST Node associated with this error.
55-
///
56-
/// May be `null` if the error has no associated node in the input source
57-
/// code, or if the location was passed with [element].
52+
/// The [ElementDirective] associated with this error, if any.
53+
final ElementDirective? elementDirective;
54+
55+
/// The [AstNode] associated with this error, if any.
5856
final AstNode? node;
5957

58+
/// The [Fragment] associated with this error, if any.
59+
final Fragment? fragment;
60+
6061
InvalidGenerationSource(
6162
this.message, {
63+
Annotatable? annotatable,
6264
this.todo = '',
63-
this.element,
65+
Element2? element,
66+
ElementDirective? elementDirective,
67+
Fragment? fragment,
6468
this.node,
65-
});
69+
}) : element =
70+
element ??
71+
(annotatable is Element2 ? annotatable : null) as Element2?,
72+
elementDirective =
73+
elementDirective ??
74+
(annotatable is ElementDirective ? annotatable : null),
75+
fragment =
76+
fragment ??
77+
(annotatable is Fragment ? annotatable : null) as Fragment?;
6678

6779
@override
6880
String toString() {
6981
final buffer = StringBuffer(message);
7082

83+
// If possible render a span, if a span can't be computed show any cause
84+
// object.
85+
SourceSpan? span;
86+
Object? cause;
87+
7188
if (element case final element?) {
7289
try {
73-
final span = spanForElement(element);
74-
buffer
75-
..writeln()
76-
..writeln(span.start.toolString)
77-
..write(span.highlight());
90+
span = spanForElement(element);
7891
} catch (_) {
79-
// Source for `element` wasn't found, it must be in a summary with no
80-
// associated source. We can still give the name.
81-
buffer
82-
..writeln()
83-
..writeln('Cause: $element');
92+
cause = element;
8493
}
8594
}
8695

87-
if (node case final node?) {
96+
if (elementDirective case final elementDirective?) {
8897
try {
89-
final span = spanForNode(node);
90-
buffer
91-
..writeln()
92-
..writeln(span.start.toolString)
93-
..write(span.highlight());
98+
span = spanForElementDirective(elementDirective);
9499
} catch (_) {
95-
buffer
96-
..writeln()
97-
..writeln('Cause: $node');
100+
cause = elementDirective;
98101
}
99102
}
100103

104+
if (span == null) {
105+
if (node case final node?) {
106+
try {
107+
span = spanForNode(node);
108+
} catch (_) {
109+
cause = node;
110+
}
111+
}
112+
}
113+
114+
if (span == null) {
115+
if (fragment case final fragment?) {
116+
try {
117+
span = spanForFragment(fragment);
118+
} catch (_) {
119+
cause = fragment;
120+
}
121+
}
122+
}
123+
124+
if (span != null) {
125+
buffer
126+
..writeln()
127+
..writeln(span.start.toolString)
128+
..write(span.highlight());
129+
} else if (cause != null) {
130+
buffer
131+
..writeln()
132+
..writeln('Cause: $cause');
133+
}
134+
101135
return buffer.toString();
102136
}
103137
}

source_gen/lib/src/span_for_element.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ SourceSpan spanForElement(Element2 element, [SourceFile? file]) {
5454
);
5555
}
5656

57+
/// Returns a source span for the start character of [elementDirective].
58+
SourceSpan spanForElementDirective(ElementDirective elementDirective) {
59+
final libraryFragment = elementDirective.libraryFragment;
60+
final contents = libraryFragment.source.contents.data;
61+
final url = assetToPackageUrl(libraryFragment.source.uri);
62+
final file = SourceFile.fromString(contents, url: url);
63+
var offset = 0;
64+
if (elementDirective is LibraryExport) {
65+
offset = elementDirective.exportKeywordOffset;
66+
} else if (elementDirective is LibraryImport) {
67+
offset = elementDirective.importKeywordOffset;
68+
} else if (elementDirective is PartInclude) {
69+
// TODO(davidmorgan): no way to get this yet, see
70+
// https://github.com/dart-lang/source_gen/issues/769#issuecomment-3157032889
71+
}
72+
return file.span(offset, offset);
73+
}
74+
5775
/// Returns a source span that spans the location where [node] is written.
5876
SourceSpan spanForNode(AstNode node) {
5977
final unit = node.thisOrAncestorOfType<CompilationUnit>()!;
@@ -63,3 +81,14 @@ SourceSpan spanForNode(AstNode node) {
6381
final file = SourceFile.fromString(contents, url: url);
6482
return file.span(node.offset, node.offset + node.length);
6583
}
84+
85+
/// Returns a source span for the start character of [fragment].
86+
///
87+
/// If the fragment has a name, the start character is the start of the name.
88+
SourceSpan spanForFragment(Fragment fragment) {
89+
final libraryFragment = fragment.libraryFragment!;
90+
final contents = libraryFragment.source.contents.data;
91+
final url = assetToPackageUrl(libraryFragment.source.uri);
92+
final file = SourceFile.fromString(contents, url: url);
93+
return file.span(fragment.offset, fragment.offset);
94+
}

source_gen/test/builder_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,38 @@ $dartFormatWidth
207207
logs,
208208
contains(contains("Don't use classes with the word 'Error' in the name")),
209209
);
210+
// The class name starts at line 4, column 7.
211+
expect(logs, contains(contains(':4:7\n')));
212+
});
213+
214+
test('handle generator errors reported using fragments', () async {
215+
final srcs = _createPackageStub(
216+
testLibContent: _testLibContentWithErrorFragment,
217+
);
218+
final builder = PartBuilder([const CommentGenerator()], '.foo.dart');
219+
final logs = <String>[];
220+
await testBuilder(builder, srcs, onLog: (r) => logs.add(r.toString()));
221+
expect(
222+
logs,
223+
contains(contains("Don't use classes with the word 'Error' in the name")),
224+
);
225+
// The class name starts at line 3, column 7.
226+
expect(logs, contains(contains(':3:7\n')));
227+
});
228+
229+
test('handle generator errors reported using element directives', () async {
230+
final srcs = _createPackageStub(
231+
testLibContent: _testLibContentWithErrorElementDirective,
232+
);
233+
final builder = PartBuilder([const CommentGenerator()], '.foo.dart');
234+
final logs = <String>[];
235+
await testBuilder(builder, srcs, onLog: (r) => logs.add(r.toString()));
236+
expect(
237+
logs,
238+
contains(contains("Don't use classes with the word 'Error' in the name")),
239+
);
240+
// The export directive starts at line 2, column 1.
241+
expect(logs, contains(contains(':2:1\n')));
210242
});
211243

212244
test('throws when input library has syntax errors and allowSyntaxErrors '
@@ -996,6 +1028,19 @@ class MyError { }
9961028
class MyGoodError { }
9971029
''';
9981030

1031+
const _testLibContentWithErrorFragment = r'''
1032+
library test_lib;
1033+
part 'test_lib.foo.dart';
1034+
class MyFragmentError { }
1035+
''';
1036+
1037+
const _testLibContentWithErrorElementDirective = r'''
1038+
library test_lib;
1039+
export 'foo.dart';
1040+
part 'test_lib.foo.dart';
1041+
class MyElementDirectiveError { }
1042+
''';
1043+
9991044
const _testLibPartContent = r'''
10001045
part of 'test_lib.dart';
10011046
final int bar = 42;

source_gen/test/src/comment_generator.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ class CommentGenerator extends Generator {
3030
element: classElement,
3131
);
3232
}
33+
if (classElement.displayName.contains('FragmentError')) {
34+
throw InvalidGenerationSourceError(
35+
"Don't use classes with the word 'Error' in the name",
36+
todo: 'Rename ${classElement.displayName} to something else.',
37+
fragment: classElement.firstFragment,
38+
);
39+
}
40+
if (classElement.displayName.contains('ElementDirectiveError')) {
41+
throw InvalidGenerationSourceError(
42+
"Don't use classes with the word 'Error' in the name",
43+
todo: 'Rename ${classElement.displayName} to something else.',
44+
// No directive relates to the class, just throw with the first
45+
// export.
46+
elementDirective:
47+
classElement.library2.firstFragment.libraryExports2.first,
48+
);
49+
}
3350
output.add('// Code for "$classElement"');
3451
}
3552
}

0 commit comments

Comments
 (0)