Skip to content

Commit

Permalink
fix(compiler): handle strings inside bindings that contain binding ch…
Browse files Browse the repository at this point in the history
…aracters

Currently the compiler treats something like `{{  '{{a}}' }}` as a nested
binding and throws an error, because it doesn't account for quotes
when it looks for binding characters. These changes add a bit of
logic to skip over text inside quotes when parsing.

Fixes angular#39601.
  • Loading branch information
crisbeto committed Nov 28, 2020
1 parent 68d4a74 commit 332ba69
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 3 deletions.
26 changes: 23 additions & 3 deletions packages/compiler/src/expression_parser/parser.ts
Expand Up @@ -244,10 +244,10 @@ export class Parser {

atInterpolation = true;
} else {
// parse from starting {{ to ending }}
// parse from starting {{ to ending }} while ignoring content inside quotes.
const fullStart = i;
const exprStart = fullStart + interpStart.length;
const exprEnd = input.indexOf(interpEnd, exprStart);
const exprEnd = this._indexOfSkipQuoted(input, 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.
Expand Down Expand Up @@ -340,10 +340,30 @@ export class Parser {

return errLocation.length;
}

/** Like `String.prototype.indexOf`, but skips content inside quotes. */
private _indexOfSkipQuoted(input: string, value: string, start: number): number {
const valueLength = value.length;
let currentQuote: string|null = null;
for (let i = start; i < input.length; i++) {
const char = input[i];
// Skip the characters inside quotes. Note that we only care about the
// outer-most quotes matching up and we need to account for escape characters.
if (isQuote(input.charCodeAt(i)) && (currentQuote === null || currentQuote === char) &&
input[i - 1] !== '\\') {
currentQuote = currentQuote === null ? char : null;
} else if (
currentQuote === null && char === value[0] &&
(valueLength === 0 || input.substring(i, i + valueLength) === value)) {
return i;
}
}
return -1;
}
}

export class IvyParser extends Parser {
simpleExpressionChecker = IvySimpleExpressionChecker; //
simpleExpressionChecker = IvySimpleExpressionChecker;
}

/** Describes a stateful context an expression parser is in. */
Expand Down
18 changes: 18 additions & 0 deletions packages/compiler/test/template_parser/template_parser_spec.ts
Expand Up @@ -540,6 +540,24 @@ describe('TemplateParser', () => {
expect(humanizeTplAst(parse('{{a}}', []))).toEqual([[BoundTextAst, '{{ a }}']]);
});

it('should parse bound text nodes inside quotes', () => {
expect(humanizeTplAst(parse('"{{a}}"', []))).toEqual([[BoundTextAst, '"{{ a }}"']]);
});

it('should parse bound text nodes with interpolations inside quotes', () => {
expect(humanizeTplAst(parse('{{ "{{a}}" }}', []))).toEqual([[BoundTextAst, '{{ "{{a}}" }}']]);
});

it('should parse bound text nodes with escaped quotes', () => {
expect(humanizeTplAst(parse(`{{'It\\'s just Angular'}}`, []))).toEqual([
[BoundTextAst, `{{ "It's just Angular" }}`]
]);
});

it('should not parse bound text nodes with mismatching quotes', () => {
expect(humanizeTplAst(parse(`{{ "{{a}}' }}`, []))).toEqual([[TextAst, `{{ "{{a}}' }}`]]);
});

it('should parse with custom interpolation config',
inject([TemplateParser], (parser: TemplateParser) => {
const component = CompileDirectiveMetadata.create({
Expand Down
14 changes: 14 additions & 0 deletions packages/core/test/acceptance/text_spec.ts
Expand Up @@ -129,4 +129,18 @@ describe('text instructions', () => {

expect(div.innerHTML).toBe('function foo() { }');
});

it('should handle binding syntax used inside quoted text', () => {
@Component({
template: `{{'Interpolations look like {{this}}'}}`,
})
class App {
}

TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();

expect(fixture.nativeElement.textContent).toBe('Interpolations look like {{this}}');
});
});

0 comments on commit 332ba69

Please sign in to comment.