diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index ccdb08d456..4b5b28b8b5 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -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 + * ... + */ 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; - expressions.push(part); - offsets.push(offset); - offset += part.length + interpolationConfig.end.length; - expressionSpans.push({start, end: offset}); + stringSpans.push({start, end: i}); + + atInterpolation = true; } else { - this._reportError( - 'Blank expressions are not allowed in interpolated strings', input, - `at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`, - location); - expressions.push('$implicit'); - offsets.push(offset); - expressionSpans.push({start: offset, end: offset}); + // 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); + } else { + this._reportError( + 'Blank expressions are not allowed in interpolated strings', input, + `at column ${i} in`, location); + expressions.push('$implicit'); + } + 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 { diff --git a/packages/compiler/test/expression_parser/parser_spec.ts b/packages/compiler/test/expression_parser/parser_spec.ts index 8c9f85651e..0c1f33c8f2 100644 --- a/packages/compiler/test/expression_parser/parser_spec.ts +++ b/packages/compiler/test/expression_parser/parser_spec.ts @@ -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(['', '']);