Skip to content

Commit ab44754

Browse files
authored
Mustachio: Store SourceSpans on MustachioNodes, for errors (#2515)
Mustachio: Store SourceSpans on MustachioNodes, for errors
1 parent 8934e55 commit ab44754

File tree

6 files changed

+227
-125
lines changed

6 files changed

+227
-125
lines changed

lib/src/mustachio/parser.dart

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:charcode/charcode.dart';
66
import 'package:meta/meta.dart';
7+
import 'package:source_span/source_span.dart';
78

89
/// A [Mustache](https://mustache.github.io/mustache.5.html) parser for use by a
910
/// generated Mustachio renderer.
@@ -14,10 +15,14 @@ class MustachioParser {
1415
/// The length of the template, in code units.
1516
final int _templateLength;
1617

18+
final SourceFile _sourceFile;
19+
1720
/// The index of the character currently being parsed.
1821
int _index = 0;
1922

20-
MustachioParser(this.template) : _templateLength = template.length;
23+
MustachioParser(this.template, Uri url)
24+
: _templateLength = template.length,
25+
_sourceFile = SourceFile.fromString(template, url: url);
2126

2227
/// Parses [template] into a sequence of [MustachioNode]s.
2328
///
@@ -46,7 +51,8 @@ class MustachioParser {
4651

4752
void addTextNode(int startIndex, int endIndex) {
4853
if (endIndex > startIndex) {
49-
children.add(Text(template.substring(startIndex, endIndex)));
54+
children.add(Text(template.substring(startIndex, endIndex),
55+
span: _sourceFile.span(startIndex, endIndex)));
5056
}
5157
}
5258

@@ -96,28 +102,29 @@ class MustachioParser {
96102
/// [_index] should be at the character immediately following the open
97103
/// delimiter `{{`.
98104
_TagParseResult _parseTag() {
105+
var tagStartIndex = _index - 2;
99106
_walkPastWhitespace();
100107
if (_atEnd) {
101108
return _TagParseResult.endOfFile;
102109
}
103110
var char = _thisChar;
104111
if (char == $hash) {
105112
_index++;
106-
return _parseSection(invert: false);
113+
return _parseSection(invert: false, tagStartIndex: tagStartIndex);
107114
} else if (char == $caret) {
108115
_index++;
109-
return _parseSection(invert: true);
116+
return _parseSection(invert: true, tagStartIndex: tagStartIndex);
110117
} else if (char == $slash) {
111118
_index++;
112119
return _parseEndSection();
113120
} else if (char == $gt) {
114121
_index++;
115-
return _parsePartial();
122+
return _parsePartial(tagStartIndex: tagStartIndex);
116123
} else if (char == $exclamation) {
117124
_index++;
118125
return _parseComment();
119126
} else {
120-
return _parseVariable();
127+
return _parseVariable(tagStartIndex: tagStartIndex);
121128
}
122129
}
123130

@@ -144,7 +151,7 @@ class MustachioParser {
144151
///
145152
/// [_index] should be at the character immediately following the `>`
146153
/// character which opens a possible partial tag.
147-
_TagParseResult _parsePartial() {
154+
_TagParseResult _parsePartial({@required int tagStartIndex}) {
148155
var startIndex = _index;
149156
int endIndex;
150157
while (true) {
@@ -170,14 +177,16 @@ class MustachioParser {
170177
_index += 2;
171178

172179
var key = template.substring(startIndex, endIndex);
173-
return _TagParseResult.ok(Partial(key));
180+
return _TagParseResult.ok(
181+
Partial(key, span: _sourceFile.span(tagStartIndex, _index)));
174182
}
175183

176184
/// Tries to parse a section tag at [_index].
177185
///
178186
/// [_index] should be at the character immediately following the `#`
179187
/// character which opens a possible section tag.
180-
_TagParseResult _parseSection({@required bool invert}) {
188+
_TagParseResult _parseSection(
189+
{@required bool invert, @required int tagStartIndex}) {
181190
var parsedKey = _parseKey();
182191
if (parsedKey.type == _KeyParseResultType.notKey) {
183192
return _TagParseResult.notTag;
@@ -186,6 +195,7 @@ class MustachioParser {
186195
}
187196

188197
var children = _parseBlock(sectionKey: parsedKey.joinedNames);
198+
var span = _sourceFile.span(tagStartIndex, _index);
189199

190200
if (parsedKey.names.length > 1) {
191201
// Desugar section with dots into nested sections.
@@ -198,15 +208,20 @@ class MustachioParser {
198208
// inside the section, are the children of the [three] section. The
199209
// [three] section is the singular child node of the [two] section, and
200210
// the [two] section is the singular child of the [one] section.
201-
var section = Section([parsedKey.names.last], children, invert: invert);
211+
var section =
212+
Section([parsedKey.names.last], children, invert: invert, span: span);
202213
for (var sectionKey in parsedKey.names.reversed.skip(1)) {
203-
section = Section([sectionKey], [section], invert: false);
214+
section = Section([sectionKey], [section],
215+
invert: false,
216+
// TODO(srawlins): It may not make sense to use [span] here; we
217+
// might want to do the work to find the span of [sectionKey].
218+
span: span);
204219
}
205220
return _TagParseResult.ok(section);
206221
}
207222

208223
return _TagParseResult.ok(
209-
Section(parsedKey.names, children, invert: invert));
224+
Section(parsedKey.names, children, invert: invert, span: span));
210225
}
211226

212227
/// Tries to parse an end tag at [_index].
@@ -228,7 +243,7 @@ class MustachioParser {
228243
///
229244
/// [_index] should be at the character immediately following the `{{`
230245
/// characters which open a possible variable tag.
231-
_TagParseResult _parseVariable() {
246+
_TagParseResult _parseVariable({@required int tagStartIndex}) {
232247
var escape = true;
233248
if (_thisChar == $lbrace) {
234249
escape = false;
@@ -242,7 +257,8 @@ class MustachioParser {
242257
return _TagParseResult.endOfFile;
243258
}
244259

245-
return _TagParseResult.ok(Variable(parsedKey.names, escape: escape));
260+
return _TagParseResult.ok(Variable(parsedKey.names,
261+
escape: escape, span: _sourceFile.span(tagStartIndex, _index)));
246262
}
247263

248264
/// Tries to parse a key at [_index].
@@ -347,14 +363,19 @@ class MustachioParser {
347363

348364
/// An interface for various types of node in a Mustache template.
349365
@sealed
350-
abstract class MustachioNode {}
366+
abstract class MustachioNode {
367+
SourceSpan get span;
368+
}
351369

352370
/// A Text node, representing literal text.
353371
@immutable
354372
class Text implements MustachioNode {
355373
final String content;
356374

357-
Text(this.content);
375+
@override
376+
final SourceSpan span;
377+
378+
Text(this.content, {@required this.span});
358379

359380
@override
360381
String toString() => 'Text["$content"]';
@@ -367,7 +388,10 @@ class Variable implements MustachioNode {
367388

368389
final bool escape;
369390

370-
Variable(this.key, {@required this.escape});
391+
@override
392+
final SourceSpan span;
393+
394+
Variable(this.key, {@required this.escape, @required this.span});
371395

372396
@override
373397
String toString() => 'Variable[$key, escape=$escape]';
@@ -383,7 +407,11 @@ class Section implements MustachioNode {
383407

384408
final List<MustachioNode> children;
385409

386-
Section(this.key, this.children, {@required this.invert});
410+
@override
411+
final SourceSpan span;
412+
413+
Section(this.key, this.children,
414+
{@required this.invert, @required this.span});
387415

388416
@override
389417
String toString() => 'Section[$key, invert=$invert]';
@@ -394,7 +422,10 @@ class Section implements MustachioNode {
394422
class Partial implements MustachioNode {
395423
final String key;
396424

397-
Partial(this.key);
425+
@override
426+
final SourceSpan span;
427+
428+
Partial(this.key, {@required this.span});
398429
}
399430

400431
/// An enumeration of types of tag parse results.

lib/src/mustachio/renderer_base.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class Template {
8989
// 2) In the case of a reference from a top-level template, user code has
9090
// called [Template.parse], and the user is responsible for handling the
9191
// exception.
92-
var ast = MustachioParser(file.readAsStringSync()).parse();
92+
var ast = MustachioParser(file.readAsStringSync(), file.toUri()).parse();
9393
var nodeQueue = Queue.of(ast);
9494
var partials = <String, File>{};
9595

@@ -115,9 +115,8 @@ class Template {
115115
partialTemplates: {...partialTemplates});
116116
partialTemplates[partialFile] = partialTemplate;
117117
} on FileSystemException catch (e) {
118-
throw MustachioResolutionError(
119-
'FileSystemException when reading partial "$key" found in '
120-
'template "${file.path}": ${e.message}');
118+
throw MustachioResolutionError(node.span.message(
119+
'FileSystemException (${e.message}) when reading partial:'));
121120
}
122121
}
123122
}
@@ -165,6 +164,8 @@ abstract class RendererBase<T> {
165164
/// [names] may have multiple dot-separate names, and [names] may not be a
166165
/// valid property of _this_ context type, in which the [parent] renderer is
167166
/// referenced.
167+
// TODO(srawlins): Accept the [MustachioNode] here, so that the various errors
168+
// can use the span.
168169
String getFields(List<String> names) {
169170
if (names.length == 1 && names.single == '.') {
170171
return context.toString();
@@ -213,6 +214,7 @@ abstract class RendererBase<T> {
213214
var property = getProperty(key);
214215
if (property == null) {
215216
if (parent == null) {
217+
// TODO(srawlins): use the span of the key of [node] when implemented.
216218
throw MustachioResolutionError(
217219
'Failed to resolve $key as a property on any types in the current '
218220
'context');

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies:
2222
package_config: '>=0.1.5 <2.0.0'
2323
path: ^1.3.0
2424
pub_semver: ^1.3.7
25+
source_span: ^1.5.2
2526
yaml: ^2.1.0
2627

2728
dev_dependencies:

test/mustachio/foo.renderers.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// To change the contents of this library, make changes to the builder source
44
// files in the tool/mustachio/ directory.
55

6-
// ignore_for_file: camel_case_types, unnecessary_cast, unused_element, unused_import, non_constant_identifier_names
6+
// ignore_for_file: camel_case_types, unnecessary_cast, unused_element, unused_import, non_constant_identifier_names, deprecated_member_use_from_same_package
77
import 'package:analyzer/file_system/file_system.dart';
88
import 'package:dartdoc/dartdoc.dart';
99
import 'package:dartdoc/src/generator/template_data.dart';

0 commit comments

Comments
 (0)