Skip to content

Commit 1aee8b3

Browse files
petebacondarwinmhevery
authored andcommitted
refactor(compiler): store the fullStart location on ParseSourceSpans (#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
1 parent e67a331 commit 1aee8b3

File tree

8 files changed

+50
-11
lines changed

8 files changed

+50
-11
lines changed

packages/compiler/src/compiler_facade_interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,5 @@ export interface ParseSourceSpan {
193193
start: any;
194194
end: any;
195195
details: any;
196+
fullStart: any;
196197
}

packages/compiler/src/compiler_util/expression_converter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
901901
if (this.baseSourceSpan) {
902902
const start = this.baseSourceSpan.start.moveBy(span.start);
903903
const end = this.baseSourceSpan.start.moveBy(span.end);
904-
return new ParseSourceSpan(start, end);
904+
const fullStart = this.baseSourceSpan.fullStart.moveBy(span.start);
905+
return new ParseSourceSpan(start, end, fullStart);
905906
} else {
906907
return null;
907908
}

packages/compiler/src/ml_parser/parser.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ class _TreeBuilder {
128128
TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
129129
return;
130130
}
131-
const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end);
131+
const sourceSpan = new ParseSourceSpan(
132+
token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
132133
this._addToParent(new html.Expansion(
133134
switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
134135

@@ -162,8 +163,10 @@ class _TreeBuilder {
162163
return null;
163164
}
164165

165-
const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
166-
const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
166+
const sourceSpan =
167+
new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
168+
const expSourceSpan =
169+
new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
167170
return new html.ExpansionCase(
168171
value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
169172
}
@@ -257,8 +260,12 @@ class _TreeBuilder {
257260
selfClosing = false;
258261
}
259262
const end = this._peek.sourceSpan.start;
260-
const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
261-
const el = new html.Element(fullName, attrs, [], span, span, undefined);
263+
const span = new ParseSourceSpan(
264+
startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
265+
// Create a separate `startSpan` because `span` may be modified when there is an `end` span.
266+
const startSpan = new ParseSourceSpan(
267+
startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
268+
const el = new html.Element(fullName, attrs, [], span, startSpan, undefined);
262269
this._pushElement(el);
263270
if (selfClosing) {
264271
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
@@ -332,7 +339,9 @@ class _TreeBuilder {
332339
end = quoteToken.sourceSpan.end;
333340
}
334341
return new html.Attribute(
335-
fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end), valueSpan);
342+
fullName, value,
343+
new ParseSourceSpan(attrName.sourceSpan.start, end, attrName.sourceSpan.fullStart),
344+
valueSpan);
336345
}
337346

338347
private _getParentElement(): html.Element|null {

packages/compiler/src/parse_util.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,32 @@ export class ParseSourceFile {
100100
}
101101

102102
export class ParseSourceSpan {
103+
/**
104+
* Create an object that holds information about spans of tokens/nodes captured during
105+
* lexing/parsing of text.
106+
*
107+
* @param start
108+
* The location of the start of the span (having skipped leading trivia).
109+
* Skipping leading trivia makes source-spans more "user friendly", since things like HTML
110+
* elements will appear to begin at the start of the opening tag, rather than at the start of any
111+
* leading trivia, which could include newlines.
112+
*
113+
* @param end
114+
* The location of the end of the span.
115+
*
116+
* @param fullStart
117+
* The start of the token without skipping the leading trivia.
118+
* This is used by tooling that splits tokens further, such as extracting Angular interpolations
119+
* from text tokens. Such tooling creates new source-spans relative to the original token's
120+
* source-span. If leading trivia characters have been skipped then the new source-spans may be
121+
* incorrectly offset.
122+
*
123+
* @param details
124+
* Additional information (such as identifier names) that should be associated with the span.
125+
*/
103126
constructor(
104-
public start: ParseLocation, public end: ParseLocation, public details: string|null = null) {}
127+
public start: ParseLocation, public end: ParseLocation,
128+
public fullStart: ParseLocation = start, public details: string|null = null) {}
105129

106130
toString(): string {
107131
return this.start.file.content.substring(this.start.offset, this.end.offset);

packages/compiler/src/render3/view/i18n/localize_utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ function getSourceSpan(message: i18n.Message): ParseSourceSpan {
9090
const startNode = message.nodes[0];
9191
const endNode = message.nodes[message.nodes.length - 1];
9292
return new ParseSourceSpan(
93-
startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.details);
93+
startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.fullStart,
94+
startNode.sourceSpan.details);
9495
}
9596

9697
/**

packages/compiler/src/template_parser/binding_parser.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -548,5 +548,7 @@ function moveParseSourceSpan(
548548
// The difference of two absolute offsets provide the relative offset
549549
const startDiff = absoluteSpan.start - sourceSpan.start.offset;
550550
const endDiff = absoluteSpan.end - sourceSpan.end.offset;
551-
return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff));
551+
return new ParseSourceSpan(
552+
sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff),
553+
sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
552554
}

packages/compiler/src/template_parser/template_parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ class TemplateParseVisitor implements html.Visitor {
566566

567567
const directiveAsts = directives.map((directive) => {
568568
const sourceSpan = new ParseSourceSpan(
569-
elementSourceSpan.start, elementSourceSpan.end,
569+
elementSourceSpan.start, elementSourceSpan.end, elementSourceSpan.fullStart,
570570
`Directive ${identifierName(directive.type)}`);
571571

572572
if (directive.isComponent) {

packages/core/src/compiler/compiler_facade_interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,5 @@ export interface ParseSourceSpan {
193193
start: any;
194194
end: any;
195195
details: any;
196+
fullStart: any;
196197
}

0 commit comments

Comments
 (0)