Skip to content

Commit

Permalink
fixup! fix(compiler): handle strings inside bindings that contain bin…
Browse files Browse the repository at this point in the history
…ding characters
  • Loading branch information
crisbeto committed Dec 8, 2020
1 parent f56464e commit 1a96c5e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
25 changes: 18 additions & 7 deletions packages/compiler/src/expression_parser/parser.ts
Expand Up @@ -247,7 +247,7 @@ export class Parser {
// parse from starting {{ to ending }} while ignoring content inside quotes.
const fullStart = i;
const exprStart = fullStart + interpStart.length;
const exprEnd = this._indexOfSkipQuoted(input, interpEnd, exprStart);
const exprEnd = this._getExpressiondEndIndex(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 @@ -341,20 +341,31 @@ 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;
/**
* Finds the index of the end of an interpolation expression
* while ignoring comments and quoted content.
*/
private _getExpressiondEndIndex(input: string, expressionEnd: string, start: number): number {
let currentQuote: string|null = null;
let escapeCount = 0;
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] !== '\\') {
escapeCount % 2 === 0) {
currentQuote = currentQuote === null ? char : null;
} else if (currentQuote === null && char === value[0] && input.startsWith(value, i)) {
return i;
} else if (currentQuote === null) {
if (input.startsWith(expressionEnd, i)) {
return i;
}
// Nothing else in the expression matters after we've
// hit a comment so look directly for the end token.
if (input.startsWith('//', i)) {
return input.indexOf(expressionEnd, i);
}
}
escapeCount = char === '\\' ? escapeCount + 1 : 0;
}
return -1;
}
Expand Down
15 changes: 14 additions & 1 deletion packages/compiler/test/expression_parser/parser_spec.ts
Expand Up @@ -859,6 +859,12 @@ describe('parser', () => {
checkInterpolation(`{{'It\\'s }} just Angular'}}`, `{{ "It's }} just Angular" }}`);
});

it('should parse interpolation with escaped backslashes', () => {
checkInterpolation(`{{foo.split('\\\\')}}`, `{{ foo.split("\\") }}`);
checkInterpolation(`{{foo.split('\\\\\\\\')}}`, `{{ foo.split("\\\\") }}`);
checkInterpolation(`{{foo.split('\\\\\\\\\\\\')}}`, `{{ foo.split("\\\\\\") }}`);
});

it('should not parse interpolation with mismatching quotes', () => {
expect(parseInterpolation(`{{ "{{a}}' }}`)).toBeNull();
});
Expand Down Expand Up @@ -920,6 +926,10 @@ describe('parser', () => {
it('should retain // in nested, unterminated strings', () => {
checkInterpolation(`{{ "a\'b\`" //comment}}`, `{{ "a\'b\`" }}`);
});

it('should ignore quotes inside a comment', () => {
checkInterpolation(`"{{name // " }}"`, `"{{ name }}"`);
});
});
});

Expand Down Expand Up @@ -1100,8 +1110,11 @@ function parseSimpleBindingIvy(
}

function checkInterpolation(exp: string, expected?: string) {
const ast = parseInterpolation(exp)!;
const ast = parseInterpolation(exp);
if (expected == null) expected = exp;
if (ast === null) {
throw Error(`Failed to parse expression "${exp}"`);
}
expect(unparse(ast)).toEqual(expected);
validate(ast);
}
Expand Down
18 changes: 18 additions & 0 deletions packages/compiler/test/template_parser/template_parser_spec.ts
Expand Up @@ -570,6 +570,24 @@ describe('TemplateParser', () => {
expect(humanizeTplAst(parse(`{{ "{{a}}' }}`, []))).toEqual([[TextAst, `{{ "{{a}}' }}`]]);
});

it('should parse interpolation with escaped backslashes', () => {
expect(humanizeTplAst(parse(`{{foo.split('\\\\')}}`, []))).toEqual([
[BoundTextAst, `{{ foo.split("\\") }}`]
]);
expect(humanizeTplAst(parse(`{{foo.split('\\\\\\\\')}}`, []))).toEqual([
[BoundTextAst, `{{ foo.split("\\\\") }}`]
]);
expect(humanizeTplAst(parse(`{{foo.split('\\\\\\\\\\\\')}}`, []))).toEqual([
[BoundTextAst, `{{ foo.split("\\\\\\") }}`]
]);
});

it('should ignore quotes inside a comment', () => {
expect(humanizeTplAst(parse(`"{{name // " }}"`, []))).toEqual([
[BoundTextAst, `"{{ name }}"`]
]);
});

it('should parse with custom interpolation config',
inject([TemplateParser], (parser: TemplateParser) => {
const component = CompileDirectiveMetadata.create({
Expand Down

0 comments on commit 1a96c5e

Please sign in to comment.