refactor(compiler): iteratively parse interpolations (#38977)
This patch refactors the interpolation parser to do so iteratively rather than using a regex. Doing so prepares us for supporting granular recovery on poorly-formed interpolations, for example when an interpolation does not terminate (`{{ 1 + 2`) or is not terminated properly (`{{ 1 + 2 {{ 2 + 3 }}`). Part of #38596 PR Close #38977
This commit is contained in:
parent
5dbf357224
commit
89c5255b8c
|
@ -185,47 +185,82 @@ export class Parser {
|
|||
location, absoluteOffset, this.errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string of text into "raw" text segments and expressions present in interpolations in
|
||||
* the string.
|
||||
* Returns `null` if there are no interpolations, otherwise a
|
||||
* `SplitInterpolation` with splits that look like
|
||||
* <raw text> <expression> <raw text> ... <raw text> <expression> <raw text>
|
||||
*/
|
||||
splitInterpolation(
|
||||
input: string, location: string,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation
|
||||
|null {
|
||||
const regexp = _getInterpolateRegExp(interpolationConfig);
|
||||
const parts = input.split(regexp);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
const strings: string[] = [];
|
||||
const expressions: string[] = [];
|
||||
const offsets: number[] = [];
|
||||
const stringSpans: {start: number, end: number}[] = [];
|
||||
const expressionSpans: {start: number, end: number}[] = [];
|
||||
let offset = 0;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part: string = parts[i];
|
||||
if (i % 2 === 0) {
|
||||
// fixed string
|
||||
let i = 0;
|
||||
let atInterpolation = false;
|
||||
let extendLastString = false;
|
||||
let {start: interpStart, end: interpEnd} = interpolationConfig;
|
||||
while (i < input.length) {
|
||||
if (!atInterpolation) {
|
||||
// parse until starting {{
|
||||
const start = i;
|
||||
i = input.indexOf(interpStart, i);
|
||||
if (i === -1) {
|
||||
i = input.length;
|
||||
}
|
||||
const part = input.substring(start, i);
|
||||
strings.push(part);
|
||||
const start = offset;
|
||||
offset += part.length;
|
||||
stringSpans.push({start, end: offset});
|
||||
} else if (part.trim().length > 0) {
|
||||
const start = offset;
|
||||
offset += interpolationConfig.start.length;
|
||||
stringSpans.push({start, end: i});
|
||||
|
||||
atInterpolation = true;
|
||||
} else {
|
||||
// parse from starting {{ to ending }}
|
||||
const fullStart = i;
|
||||
const exprStart = fullStart + interpStart.length;
|
||||
const exprEnd = input.indexOf(interpEnd, exprStart);
|
||||
if (exprEnd === -1) {
|
||||
// Could not find the end of the interpolation; do not parse an expression.
|
||||
// Instead we should extend the content on the last raw string.
|
||||
atInterpolation = false;
|
||||
extendLastString = true;
|
||||
break;
|
||||
}
|
||||
const fullEnd = exprEnd + interpEnd.length;
|
||||
|
||||
const part = input.substring(exprStart, exprEnd);
|
||||
if (part.trim().length > 0) {
|
||||
expressions.push(part);
|
||||
offsets.push(offset);
|
||||
offset += part.length + interpolationConfig.end.length;
|
||||
expressionSpans.push({start, end: offset});
|
||||
} else {
|
||||
this._reportError(
|
||||
'Blank expressions are not allowed in interpolated strings', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`,
|
||||
location);
|
||||
`at column ${i} in`, location);
|
||||
expressions.push('$implicit');
|
||||
offsets.push(offset);
|
||||
expressionSpans.push({start: offset, end: offset});
|
||||
}
|
||||
offsets.push(exprStart);
|
||||
expressionSpans.push({start: fullStart, end: fullEnd});
|
||||
|
||||
i = fullEnd;
|
||||
atInterpolation = false;
|
||||
}
|
||||
}
|
||||
return new SplitInterpolation(strings, stringSpans, expressions, expressionSpans, offsets);
|
||||
if (!atInterpolation) {
|
||||
// If we are now at a text section, add the remaining content as a raw string.
|
||||
if (extendLastString) {
|
||||
strings[strings.length - 1] += input.substring(i);
|
||||
stringSpans[stringSpans.length - 1].end = input.length;
|
||||
} else {
|
||||
strings.push(input.substring(i));
|
||||
stringSpans.push({start: i, end: input.length});
|
||||
}
|
||||
}
|
||||
return expressions.length === 0 ?
|
||||
null :
|
||||
new SplitInterpolation(strings, stringSpans, expressions, expressionSpans, offsets);
|
||||
}
|
||||
|
||||
wrapLiteralPrimitive(input: string|null, location: any, absoluteOffset: number): ASTWithSource {
|
||||
|
|
|
@ -728,6 +728,13 @@ describe('parser', () => {
|
|||
expect(parseInterpolation('nothing')).toBe(null);
|
||||
});
|
||||
|
||||
it('should not parse malformed interpolations as strings', () => {
|
||||
const ast = parseInterpolation('{{a}} {{example}<!--->}')!.ast as Interpolation;
|
||||
expect(ast.strings).toEqual(['', ' {{example}<!--->}']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
});
|
||||
|
||||
it('should parse no prefix/suffix interpolation', () => {
|
||||
const ast = parseInterpolation('{{a}}')!.ast as Interpolation;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
|
|
Loading…
Reference in New Issue