Skip to content

Commit ed2d52b

Browse files
authored
Mustachio: Implement and use key spans (#2519)
Mustachio: Implement and use key spans
1 parent ab44754 commit ed2d52b

File tree

4 files changed

+173
-87
lines changed

4 files changed

+173
-87
lines changed

lib/src/mustachio/parser.dart

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,9 @@ class MustachioParser {
177177
_index += 2;
178178

179179
var key = template.substring(startIndex, endIndex);
180-
return _TagParseResult.ok(
181-
Partial(key, span: _sourceFile.span(tagStartIndex, _index)));
180+
var keySpan = _sourceFile.span(startIndex, endIndex);
181+
return _TagParseResult.ok(Partial(key,
182+
span: _sourceFile.span(tagStartIndex, _index), keySpan: keySpan));
182183
}
183184

184185
/// Tries to parse a section tag at [_index].
@@ -208,20 +209,30 @@ class MustachioParser {
208209
// inside the section, are the children of the [three] section. The
209210
// [three] section is the singular child node of the [two] section, and
210211
// the [two] section is the singular child of the [one] section.
211-
var section =
212-
Section([parsedKey.names.last], children, invert: invert, span: span);
213-
for (var sectionKey in parsedKey.names.reversed.skip(1)) {
212+
var lastName = parsedKey.names.last;
213+
var keySpanEndOffset = parsedKey.span.end.offset;
214+
var lastNameSpan = _sourceFile.span(
215+
keySpanEndOffset - lastName.length, keySpanEndOffset);
216+
var section = Section([lastName], children,
217+
invert: invert, span: span, keySpan: lastNameSpan);
218+
//for (var sectionKey in parsedKey.names.reversed.skip(1)) {
219+
for (var i = parsedKey.names.length - 2; i >= 0; i--) {
220+
var sectionKey = parsedKey.names[i];
221+
// To find the start offset of the ith name, take the length of all of
222+
// the names 0 through `i - 1` re-joined with '.', and a final '.' at
223+
// the end.
224+
var sectionKeyStartOffset = parsedKey.span.start.offset +
225+
(i == 0 ? 0 : parsedKey.names.take(i).join('.').length + 1);
226+
var keySpan = _sourceFile.span(
227+
sectionKeyStartOffset, sectionKeyStartOffset + sectionKey.length);
214228
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);
229+
invert: false, span: span, keySpan: keySpan);
219230
}
220231
return _TagParseResult.ok(section);
221232
}
222233

223-
return _TagParseResult.ok(
224-
Section(parsedKey.names, children, invert: invert, span: span));
234+
return _TagParseResult.ok(Section(parsedKey.names, children,
235+
invert: invert, span: span, keySpan: parsedKey.span));
225236
}
226237

227238
/// Tries to parse an end tag at [_index].
@@ -257,8 +268,9 @@ class MustachioParser {
257268
return _TagParseResult.endOfFile;
258269
}
259270

271+
var span = _sourceFile.span(tagStartIndex, _index);
260272
return _TagParseResult.ok(Variable(parsedKey.names,
261-
escape: escape, span: _sourceFile.span(tagStartIndex, _index)));
273+
escape: escape, span: span, keySpan: parsedKey.span));
262274
}
263275

264276
/// Tries to parse a key at [_index].
@@ -289,6 +301,7 @@ class MustachioParser {
289301
}
290302

291303
var key = template.substring(startIndex, _index);
304+
var span = _sourceFile.span(startIndex, _index);
292305

293306
if (key.length > 1 &&
294307
(key.codeUnitAt(0) == $dot || key.codeUnitAt(key.length - 1) == $dot)) {
@@ -306,7 +319,7 @@ class MustachioParser {
306319
if (escape) {
307320
if (char0 == $rbrace && char1 == $rbrace) {
308321
_index += 2;
309-
return _KeyParseResult(_KeyParseResultType.parsedKey, key);
322+
return _KeyParseResult(_KeyParseResultType.parsedKey, key, span: span);
310323
} else {
311324
return _KeyParseResult.notKey;
312325
}
@@ -318,7 +331,7 @@ class MustachioParser {
318331
var char2 = _nextNextChar;
319332
if (char0 == $rbrace && char1 == $rbrace && char2 == $rbrace) {
320333
_index += 3;
321-
return _KeyParseResult(_KeyParseResultType.parsedKey, key);
334+
return _KeyParseResult(_KeyParseResultType.parsedKey, key, span: span);
322335
} else {
323336
return _KeyParseResult.notKey;
324337
}
@@ -391,7 +404,10 @@ class Variable implements MustachioNode {
391404
@override
392405
final SourceSpan span;
393406

394-
Variable(this.key, {@required this.escape, @required this.span});
407+
final SourceSpan keySpan;
408+
409+
Variable(this.key,
410+
{@required this.escape, @required this.span, @required this.keySpan});
395411

396412
@override
397413
String toString() => 'Variable[$key, escape=$escape]';
@@ -410,8 +426,10 @@ class Section implements MustachioNode {
410426
@override
411427
final SourceSpan span;
412428

429+
final SourceSpan keySpan;
430+
413431
Section(this.key, this.children,
414-
{@required this.invert, @required this.span});
432+
{@required this.invert, @required this.span, @required this.keySpan});
415433

416434
@override
417435
String toString() => 'Section[$key, invert=$invert]';
@@ -425,7 +443,9 @@ class Partial implements MustachioNode {
425443
@override
426444
final SourceSpan span;
427445

428-
Partial(this.key, {@required this.span});
446+
final SourceSpan keySpan;
447+
448+
Partial(this.key, {@required this.span, @required this.keySpan});
429449
}
430450

431451
/// An enumeration of types of tag parse results.
@@ -490,13 +510,18 @@ class _KeyParseResult {
490510

491511
final List<String> names;
492512

493-
const _KeyParseResult._(this.type, this.names);
513+
/// The source span from where this key was parsed, if this represents a
514+
/// parsed key, othwerwise `null`.
515+
final SourceSpan /*?*/ span;
516+
517+
const _KeyParseResult._(this.type, this.names, {this.span});
494518

495-
factory _KeyParseResult(_KeyParseResultType type, String key) {
519+
factory _KeyParseResult(_KeyParseResultType type, String key,
520+
{@required SourceSpan span}) {
496521
if (key == '.') {
497-
return _KeyParseResult._(type, [key]);
522+
return _KeyParseResult._(type, [key], span: span);
498523
} else {
499-
return _KeyParseResult._(type, key.split('.'));
524+
return _KeyParseResult._(type, key.split('.'), span: span);
500525
}
501526
}
502527

lib/src/mustachio/renderer_base.dart

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,8 @@ abstract class RendererBase<T> {
164164
/// [names] may have multiple dot-separate names, and [names] may not be a
165165
/// valid property of _this_ context type, in which the [parent] renderer is
166166
/// referenced.
167-
// TODO(srawlins): Accept the [MustachioNode] here, so that the various errors
168-
// can use the span.
169-
String getFields(List<String> names) {
167+
String getFields(Variable node) {
168+
var names = node.key;
170169
if (names.length == 1 && names.single == '.') {
171170
return context.toString();
172171
}
@@ -178,18 +177,18 @@ abstract class RendererBase<T> {
178177
} on PartialMustachioResolutionError catch (e) {
179178
// The error thrown by [Property.renderVariable] does not have all of
180179
// the names required for a decent error. We throw a new error here.
181-
throw MustachioResolutionError(
180+
throw MustachioResolutionError(node.keySpan.message(
182181
"Failed to resolve '${e.name}' on ${e.contextType} while resolving "
183182
'$remainingNames as a property chain on any types in the context '
184183
"chain: $contextChainString, after first resolving '${names.first}' "
185-
'to a property on $T');
184+
'to a property on $T'));
186185
}
187186
} else if (parent != null) {
188-
return parent.getFields(names);
187+
return parent.getFields(node);
189188
} else {
190-
throw MustachioResolutionError(
191-
'Failed to resolve ${names.first} as a property on any types in the '
192-
'context chain: $contextChainString');
189+
throw MustachioResolutionError(node.keySpan.message(
190+
"Failed to resolve '${names.first}' as a property on any types in the "
191+
'context chain: $contextChainString'));
193192
}
194193
}
195194

@@ -199,7 +198,7 @@ abstract class RendererBase<T> {
199198
if (node is Text) {
200199
write(node.content);
201200
} else if (node is Variable) {
202-
var content = getFields(node.key);
201+
var content = getFields(node);
203202
write(content);
204203
} else if (node is Section) {
205204
section(node);
@@ -214,10 +213,9 @@ abstract class RendererBase<T> {
214213
var property = getProperty(key);
215214
if (property == null) {
216215
if (parent == null) {
217-
// TODO(srawlins): use the span of the key of [node] when implemented.
218-
throw MustachioResolutionError(
219-
'Failed to resolve $key as a property on any types in the current '
220-
'context');
216+
throw MustachioResolutionError(node.keySpan.message(
217+
"Failed to resolve '$key' as a property on any types in the current "
218+
'context'));
221219
} else {
222220
return parent.section(node);
223221
}
@@ -277,12 +275,13 @@ class SimpleRenderer extends RendererBase<Object> {
277275
Property<Object> getProperty(String key) => null;
278276

279277
@override
280-
String getFields(List<String> keyParts) {
281-
if (keyParts.length == 1 && keyParts.single == '.') {
278+
String getFields(Variable node) {
279+
var names = node.key;
280+
if (names.length == 1 && names.single == '.') {
282281
return context.toString();
283282
}
284283
if (parent != null) {
285-
return parent.getFields(keyParts);
284+
return parent.getFields(node);
286285
} else {
287286
return 'null';
288287
}

0 commit comments

Comments
 (0)