Skip to content

Commit

Permalink
refactor(compiler): store the fullStart location on `ParseSourceSpa…
Browse files Browse the repository at this point in the history
…n`s (#39589)

The lexer is able to skip leading trivia in the `start` location of tokens.
This makes the source-span more friendly since things like elements
appear to begin at the start of the opening tag, rather than at the
start of any leading whitespace, which could include newlines.

But some tooling requires the full source-span to be available, such
as when tokenizing a text span into an Angular expression.

This commit simply adds the `fullStart` location to the `ParseSourceSpan`
class, and ensures that places where such spans are cloned, this
property flows through too.

PR Close #39589
  • Loading branch information
petebacondarwin authored and mhevery committed Nov 6, 2020
1 parent e67a331 commit 1aee8b3
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/compiler/src/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,5 @@ export interface ParseSourceSpan {
start: any;
end: any;
details: any;
fullStart: any;
}
3 changes: 2 additions & 1 deletion packages/compiler/src/compiler_util/expression_converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
if (this.baseSourceSpan) {
const start = this.baseSourceSpan.start.moveBy(span.start);
const end = this.baseSourceSpan.start.moveBy(span.end);
return new ParseSourceSpan(start, end);
const fullStart = this.baseSourceSpan.fullStart.moveBy(span.start);
return new ParseSourceSpan(start, end, fullStart);
} else {
return null;
}
Expand Down
21 changes: 15 additions & 6 deletions packages/compiler/src/ml_parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ class _TreeBuilder {
TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
return;
}
const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end);
const sourceSpan = new ParseSourceSpan(
token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new html.Expansion(
switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));

Expand Down Expand Up @@ -162,8 +163,10 @@ class _TreeBuilder {
return null;
}

const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
const sourceSpan =
new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
const expSourceSpan =
new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
return new html.ExpansionCase(
value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
}
Expand Down Expand Up @@ -257,8 +260,12 @@ class _TreeBuilder {
selfClosing = false;
}
const end = this._peek.sourceSpan.start;
const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
const el = new html.Element(fullName, attrs, [], span, span, undefined);
const span = new ParseSourceSpan(
startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` may be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(
startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
const el = new html.Element(fullName, attrs, [], span, startSpan, undefined);
this._pushElement(el);
if (selfClosing) {
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
Expand Down Expand Up @@ -332,7 +339,9 @@ class _TreeBuilder {
end = quoteToken.sourceSpan.end;
}
return new html.Attribute(
fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end), valueSpan);
fullName, value,
new ParseSourceSpan(attrName.sourceSpan.start, end, attrName.sourceSpan.fullStart),
valueSpan);
}

private _getParentElement(): html.Element|null {
Expand Down
26 changes: 25 additions & 1 deletion packages/compiler/src/parse_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,32 @@ export class ParseSourceFile {
}

export class ParseSourceSpan {
/**
* Create an object that holds information about spans of tokens/nodes captured during
* lexing/parsing of text.
*
* @param start
* The location of the start of the span (having skipped leading trivia).
* Skipping leading trivia makes source-spans more "user friendly", since things like HTML
* elements will appear to begin at the start of the opening tag, rather than at the start of any
* leading trivia, which could include newlines.
*
* @param end
* The location of the end of the span.
*
* @param fullStart
* The start of the token without skipping the leading trivia.
* This is used by tooling that splits tokens further, such as extracting Angular interpolations
* from text tokens. Such tooling creates new source-spans relative to the original token's
* source-span. If leading trivia characters have been skipped then the new source-spans may be
* incorrectly offset.
*
* @param details
* Additional information (such as identifier names) that should be associated with the span.
*/
constructor(
public start: ParseLocation, public end: ParseLocation, public details: string|null = null) {}
public start: ParseLocation, public end: ParseLocation,
public fullStart: ParseLocation = start, public details: string|null = null) {}

toString(): string {
return this.start.file.content.substring(this.start.offset, this.end.offset);
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler/src/render3/view/i18n/localize_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ function getSourceSpan(message: i18n.Message): ParseSourceSpan {
const startNode = message.nodes[0];
const endNode = message.nodes[message.nodes.length - 1];
return new ParseSourceSpan(
startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.details);
startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.fullStart,
startNode.sourceSpan.details);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler/src/template_parser/binding_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,5 +548,7 @@ function moveParseSourceSpan(
// The difference of two absolute offsets provide the relative offset
const startDiff = absoluteSpan.start - sourceSpan.start.offset;
const endDiff = absoluteSpan.end - sourceSpan.end.offset;
return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff));
return new ParseSourceSpan(
sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff),
sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
}
2 changes: 1 addition & 1 deletion packages/compiler/src/template_parser/template_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ class TemplateParseVisitor implements html.Visitor {

const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end,
elementSourceSpan.start, elementSourceSpan.end, elementSourceSpan.fullStart,
`Directive ${identifierName(directive.type)}`);

if (directive.isComponent) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/compiler/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,5 @@ export interface ParseSourceSpan {
start: any;
end: any;
details: any;
fullStart: any;
}

0 comments on commit 1aee8b3

Please sign in to comment.