Skip to content

Commit 304a12f

Browse files
petebacondarwinAndrewKushnir
authored andcommittedApr 25, 2019
feat(compiler): support skipping leading trivia in template source-maps (#30095)
Leading trivia, such as whitespace or comments, is confusing for developers looking at source-mapped templates, since they expect the source-map segment to start after the trivia. This commit adds skipping trivial characters to the lexer; and then implements that in the template parser. PR Close #30095
1 parent acaf1aa commit 304a12f

File tree

3 files changed

+37
-7
lines changed

3 files changed

+37
-7
lines changed
 

‎packages/compiler/src/ml_parser/lexer.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ export interface TokenizeOptions {
9595
* but the new line should increment the current line for source mapping.
9696
*/
9797
escapedString?: boolean;
98+
/**
99+
* An array of characters that should be considered as leading trivia.
100+
* Leading trivia are characters that are not important to the developer, and so should not be
101+
* included in source-map segments. A common example is whitespace.
102+
*/
103+
leadingTriviaChars?: string[];
98104
}
99105

100106
export function tokenize(
@@ -123,11 +129,11 @@ class _Tokenizer {
123129
private _cursor: CharacterCursor;
124130
private _tokenizeIcu: boolean;
125131
private _interpolationConfig: InterpolationConfig;
132+
private _leadingTriviaCodePoints: number[]|undefined;
126133
private _currentTokenStart: CharacterCursor|null = null;
127134
private _currentTokenType: TokenType|null = null;
128135
private _expansionCaseStack: TokenType[] = [];
129136
private _inInterpolation: boolean = false;
130-
131137
tokens: Token[] = [];
132138
errors: TokenError[] = [];
133139

@@ -141,6 +147,8 @@ class _Tokenizer {
141147
options: TokenizeOptions) {
142148
this._tokenizeIcu = options.tokenizeExpansionForms || false;
143149
this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
150+
this._leadingTriviaCodePoints =
151+
options.leadingTriviaChars && options.leadingTriviaChars.map(c => c.codePointAt(0) || 0);
144152
const range =
145153
options.range || {endPos: _file.content.length, startPos: 0, startLine: 0, startCol: 0};
146154
this._cursor = options.escapedString ? new EscapedCharacterCursor(_file, range) :
@@ -236,8 +244,9 @@ class _Tokenizer {
236244
'Programming error - attempted to end a token which has no token type', null,
237245
this._cursor.getSpan(this._currentTokenStart));
238246
}
239-
const token =
240-
new Token(this._currentTokenType, parts, this._cursor.getSpan(this._currentTokenStart));
247+
const token = new Token(
248+
this._currentTokenType, parts,
249+
this._cursor.getSpan(this._currentTokenStart, this._leadingTriviaCodePoints));
241250
this.tokens.push(token);
242251
this._currentTokenStart = null;
243252
this._currentTokenType = null;
@@ -772,7 +781,7 @@ interface CharacterCursor {
772781
/** Advance the cursor by one parsed character. */
773782
advance(): void;
774783
/** Get a span from the marked start point to the current point. */
775-
getSpan(start?: this): ParseSourceSpan;
784+
getSpan(start?: this, leadingTriviaCodePoints?: number[]): ParseSourceSpan;
776785
/** Get the parsed characters from the marked start point to the current point. */
777786
getChars(start: this): string;
778787
/** The number of characters left before the end of the cursor. */
@@ -831,8 +840,14 @@ class PlainCharacterCursor implements CharacterCursor {
831840

832841
init(): void { this.updatePeek(this.state); }
833842

834-
getSpan(start?: this): ParseSourceSpan {
843+
getSpan(start?: this, leadingTriviaCodePoints?: number[]): ParseSourceSpan {
835844
start = start || this;
845+
if (leadingTriviaCodePoints) {
846+
start = start.clone() as this;
847+
while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
848+
start.advance();
849+
}
850+
}
836851
return new ParseSourceSpan(
837852
new ParseLocation(start.file, start.state.offset, start.state.line, start.state.column),
838853
new ParseLocation(this.file, this.state.offset, this.state.line, this.state.column));

‎packages/compiler/src/render3/view/template.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs';
5353
const GLOBAL_TARGET_RESOLVERS = new Map<string, o.ExternalReference>(
5454
[['window', R3.resolveWindow], ['document', R3.resolveDocument], ['body', R3.resolveBody]]);
5555

56+
const LEADING_TRIVIA_CHARS = [' ', '\n', '\r', '\t'];
57+
5658
// if (rf & flags) { .. }
5759
export function renderFlagCheckIfStmt(
5860
flags: core.RenderFlags, statements: o.Statement[]): o.IfStmt {
@@ -1733,8 +1735,9 @@ export function parseTemplate(
17331735
const {interpolationConfig, preserveWhitespaces} = options;
17341736
const bindingParser = makeBindingParser(interpolationConfig);
17351737
const htmlParser = new HtmlParser();
1736-
const parseResult =
1737-
htmlParser.parse(template, templateUrl, {...options, tokenizeExpansionForms: true});
1738+
const parseResult = htmlParser.parse(
1739+
template, templateUrl,
1740+
{...options, tokenizeExpansionForms: true, leadingTriviaChars: LEADING_TRIVIA_CHARS});
17381741

17391742
if (parseResult.errors && parseResult.errors.length > 0) {
17401743
return {errors: parseResult.errors, nodes: [], styleUrls: [], styles: []};

‎packages/compiler/test/ml_parser/lexer_spec.ts

+12
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
5252
[lex.TokenType.EOF, '2:5'],
5353
]);
5454
});
55+
56+
it('should skip over leading trivia for source-span start', () => {
57+
expect(tokenizeAndHumanizeLineColumn(
58+
'<t>\n \t a</t>', {leadingTriviaChars: ['\n', ' ', '\t']}))
59+
.toEqual([
60+
[lex.TokenType.TAG_OPEN_START, '0:0'],
61+
[lex.TokenType.TAG_OPEN_END, '0:2'],
62+
[lex.TokenType.TEXT, '1:3'],
63+
[lex.TokenType.TAG_CLOSE, '1:4'],
64+
[lex.TokenType.EOF, '1:8'],
65+
]);
66+
});
5567
});
5668

5769
describe('content ranges', () => {

0 commit comments

Comments
 (0)
Please sign in to comment.