Skip to content

Mustachio: Store SourceSpans on MustachioNodes, for errors #2515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 50 additions & 19 deletions lib/src/mustachio/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:charcode/charcode.dart';
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

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

final SourceFile _sourceFile;

/// The index of the character currently being parsed.
int _index = 0;

MustachioParser(this.template) : _templateLength = template.length;
MustachioParser(this.template, Uri url)
: _templateLength = template.length,
_sourceFile = SourceFile.fromString(template, url: url);

/// Parses [template] into a sequence of [MustachioNode]s.
///
Expand Down Expand Up @@ -46,7 +51,8 @@ class MustachioParser {

void addTextNode(int startIndex, int endIndex) {
if (endIndex > startIndex) {
children.add(Text(template.substring(startIndex, endIndex)));
children.add(Text(template.substring(startIndex, endIndex),
span: _sourceFile.span(startIndex, endIndex)));
}
}

Expand Down Expand Up @@ -96,28 +102,29 @@ class MustachioParser {
/// [_index] should be at the character immediately following the open
/// delimiter `{{`.
_TagParseResult _parseTag() {
var tagStartIndex = _index - 2;
_walkPastWhitespace();
if (_atEnd) {
return _TagParseResult.endOfFile;
}
var char = _thisChar;
if (char == $hash) {
_index++;
return _parseSection(invert: false);
return _parseSection(invert: false, tagStartIndex: tagStartIndex);
} else if (char == $caret) {
_index++;
return _parseSection(invert: true);
return _parseSection(invert: true, tagStartIndex: tagStartIndex);
} else if (char == $slash) {
_index++;
return _parseEndSection();
} else if (char == $gt) {
_index++;
return _parsePartial();
return _parsePartial(tagStartIndex: tagStartIndex);
} else if (char == $exclamation) {
_index++;
return _parseComment();
} else {
return _parseVariable();
return _parseVariable(tagStartIndex: tagStartIndex);
}
}

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

var key = template.substring(startIndex, endIndex);
return _TagParseResult.ok(Partial(key));
return _TagParseResult.ok(
Partial(key, span: _sourceFile.span(tagStartIndex, _index)));
}

/// Tries to parse a section tag at [_index].
///
/// [_index] should be at the character immediately following the `#`
/// character which opens a possible section tag.
_TagParseResult _parseSection({@required bool invert}) {
_TagParseResult _parseSection(
{@required bool invert, @required int tagStartIndex}) {
var parsedKey = _parseKey();
if (parsedKey.type == _KeyParseResultType.notKey) {
return _TagParseResult.notTag;
Expand All @@ -186,6 +195,7 @@ class MustachioParser {
}

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

if (parsedKey.names.length > 1) {
// Desugar section with dots into nested sections.
Expand All @@ -198,15 +208,20 @@ class MustachioParser {
// inside the section, are the children of the [three] section. The
// [three] section is the singular child node of the [two] section, and
// the [two] section is the singular child of the [one] section.
var section = Section([parsedKey.names.last], children, invert: invert);
var section =
Section([parsedKey.names.last], children, invert: invert, span: span);
for (var sectionKey in parsedKey.names.reversed.skip(1)) {
section = Section([sectionKey], [section], invert: false);
section = Section([sectionKey], [section],
invert: false,
// TODO(srawlins): It may not make sense to use [span] here; we
// might want to do the work to find the span of [sectionKey].
span: span);
}
return _TagParseResult.ok(section);
}

return _TagParseResult.ok(
Section(parsedKey.names, children, invert: invert));
Section(parsedKey.names, children, invert: invert, span: span));
}

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

return _TagParseResult.ok(Variable(parsedKey.names, escape: escape));
return _TagParseResult.ok(Variable(parsedKey.names,
escape: escape, span: _sourceFile.span(tagStartIndex, _index)));
}

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

/// An interface for various types of node in a Mustache template.
@sealed
abstract class MustachioNode {}
abstract class MustachioNode {
SourceSpan get span;
}

/// A Text node, representing literal text.
@immutable
class Text implements MustachioNode {
final String content;

Text(this.content);
@override
final SourceSpan span;

Text(this.content, {@required this.span});

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

final bool escape;

Variable(this.key, {@required this.escape});
@override
final SourceSpan span;

Variable(this.key, {@required this.escape, @required this.span});

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

final List<MustachioNode> children;

Section(this.key, this.children, {@required this.invert});
@override
final SourceSpan span;

Section(this.key, this.children,
{@required this.invert, @required this.span});

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

Partial(this.key);
@override
final SourceSpan span;

Partial(this.key, {@required this.span});
}

/// An enumeration of types of tag parse results.
Expand Down
10 changes: 6 additions & 4 deletions lib/src/mustachio/renderer_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Template {
// 2) In the case of a reference from a top-level template, user code has
// called [Template.parse], and the user is responsible for handling the
// exception.
var ast = MustachioParser(file.readAsStringSync()).parse();
var ast = MustachioParser(file.readAsStringSync(), file.toUri()).parse();
var nodeQueue = Queue.of(ast);
var partials = <String, File>{};

Expand All @@ -115,9 +115,8 @@ class Template {
partialTemplates: {...partialTemplates});
partialTemplates[partialFile] = partialTemplate;
} on FileSystemException catch (e) {
throw MustachioResolutionError(
'FileSystemException when reading partial "$key" found in '
'template "${file.path}": ${e.message}');
throw MustachioResolutionError(node.span.message(
'FileSystemException (${e.message}) when reading partial:'));
}
}
}
Expand Down Expand Up @@ -165,6 +164,8 @@ abstract class RendererBase<T> {
/// [names] may have multiple dot-separate names, and [names] may not be a
/// valid property of _this_ context type, in which the [parent] renderer is
/// referenced.
// TODO(srawlins): Accept the [MustachioNode] here, so that the various errors
// can use the span.
String getFields(List<String> names) {
if (names.length == 1 && names.single == '.') {
return context.toString();
Expand Down Expand Up @@ -213,6 +214,7 @@ abstract class RendererBase<T> {
var property = getProperty(key);
if (property == null) {
if (parent == null) {
// TODO(srawlins): use the span of the key of [node] when implemented.
throw MustachioResolutionError(
'Failed to resolve $key as a property on any types in the current '
'context');
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies:
package_config: '>=0.1.5 <2.0.0'
path: ^1.3.0
pub_semver: ^1.3.7
source_span: ^1.5.2
yaml: ^2.1.0

dev_dependencies:
Expand Down
2 changes: 1 addition & 1 deletion test/mustachio/foo.renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// To change the contents of this library, make changes to the builder source
// files in the tool/mustachio/ directory.

// ignore_for_file: camel_case_types, unnecessary_cast, unused_element, unused_import, non_constant_identifier_names
// ignore_for_file: camel_case_types, unnecessary_cast, unused_element, unused_import, non_constant_identifier_names, deprecated_member_use_from_same_package
import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/generator/template_data.dart';
Expand Down
Loading