Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf5aab2b

Browse files
crisbetoalxhub
authored andcommitted
fix(compiler): handle strings inside bindings that contain binding characters (#39826)
Currently the compiler treats something like `{{ '{{a}}' }}` as a nestedbinding and throws an error, because it doesn't account for quoteswhen it looks for binding characters. These changes add a bit oflogic to skip over text inside quotes when parsing.Fixes#39601.PRClose#39826
1 parent46fcfe0 commitf5aab2b

File tree

4 files changed

+133
-4
lines changed

4 files changed

+133
-4
lines changed

‎packages/compiler/src/expression_parser/parser.ts‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,10 @@ export class Parser {
244244

245245
atInterpolation=true;
246246
}else{
247-
// parse from starting {{ to ending }}
247+
// parse from starting {{ to ending }} while ignoring content inside quotes.
248248
constfullStart=i;
249249
constexprStart=fullStart+interpStart.length;
250-
constexprEnd=input.indexOf(interpEnd,exprStart);
250+
constexprEnd=this._getExpressiondEndIndex(input,interpEnd,exprStart);
251251
if(exprEnd===-1){
252252
// Could not find the end of the interpolation; do not parse an expression.
253253
// Instead we should extend the content on the last raw string.
@@ -340,10 +340,39 @@ export class Parser {
340340

341341
returnerrLocation.length;
342342
}
343+
344+
/**
345+
* Finds the index of the end of an interpolation expression
346+
* while ignoring comments and quoted content.
347+
*/
348+
private_getExpressiondEndIndex(input:string,expressionEnd:string,start:number):number{
349+
letcurrentQuote:string|null=null;
350+
letescapeCount=0;
351+
for(leti=start;i<input.length;i++){
352+
constchar=input[i];
353+
// Skip the characters inside quotes. Note that we only care about the
354+
// outer-most quotes matching up and we need to account for escape characters.
355+
if(isQuote(input.charCodeAt(i))&&(currentQuote===null||currentQuote===char)&&
356+
escapeCount%2===0){
357+
currentQuote=currentQuote===null ?char :null;
358+
}elseif(currentQuote===null){
359+
if(input.startsWith(expressionEnd,i)){
360+
returni;
361+
}
362+
// Nothing else in the expression matters after we've
363+
// hit a comment so look directly for the end token.
364+
if(input.startsWith('//',i)){
365+
returninput.indexOf(expressionEnd,i);
366+
}
367+
}
368+
escapeCount=char==='\\' ?escapeCount+1 :0;
369+
}
370+
return-1;
371+
}
343372
}
344373

345374
exportclassIvyParserextendsParser{
346-
simpleExpressionChecker=IvySimpleExpressionChecker;//
375+
simpleExpressionChecker=IvySimpleExpressionChecker;
347376
}
348377

349378
/** Describes a stateful context an expression parser is in. */

‎packages/compiler/test/expression_parser/parser_spec.ts‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,37 @@ describe('parser', () => {
762762
expect(ast.expressions[0].name).toEqual('a');
763763
});
764764

765+
it('should parse interpolation inside quotes',()=>{
766+
constast=parseInterpolation('"{{a}}"')!.astasInterpolation;
767+
expect(ast.strings).toEqual(['"','"']);
768+
expect(ast.expressions.length).toEqual(1);
769+
expect(ast.expressions[0].name).toEqual('a');
770+
});
771+
772+
it('should parse interpolation with interpolation characters inside quotes',()=>{
773+
checkInterpolation('{{"{{a}}"}}','{{ "{{a}}" }}');
774+
checkInterpolation('{{"{{"}}','{{ "{{" }}');
775+
checkInterpolation('{{"}}"}}','{{ "}}" }}');
776+
checkInterpolation('{{"{"}}','{{ "{" }}');
777+
checkInterpolation('{{"}"}}','{{ "}" }}');
778+
});
779+
780+
it('should parse interpolation with escaped quotes',()=>{
781+
checkInterpolation(`{{'It\\'s just Angular'}}`,`{{ "It's just Angular" }}`);
782+
checkInterpolation(`{{'It\\'s {{ just Angular'}}`,`{{ "It's {{ just Angular" }}`);
783+
checkInterpolation(`{{'It\\'s }} just Angular'}}`,`{{ "It's }} just Angular" }}`);
784+
});
785+
786+
it('should parse interpolation with escaped backslashes',()=>{
787+
checkInterpolation(`{{foo.split('\\\\')}}`,`{{ foo.split("\\") }}`);
788+
checkInterpolation(`{{foo.split('\\\\\\\\')}}`,`{{ foo.split("\\\\") }}`);
789+
checkInterpolation(`{{foo.split('\\\\\\\\\\\\')}}`,`{{ foo.split("\\\\\\") }}`);
790+
});
791+
792+
it('should not parse interpolation with mismatching quotes',()=>{
793+
expect(parseInterpolation(`{{ "{{a}}' }}`)).toBeNull();
794+
});
795+
765796
it('should parse prefix/suffix with multiple interpolation',()=>{
766797
constoriginalExp='before {{ a }} middle {{ b }} after';
767798
constast=parseInterpolation(originalExp)!.ast;
@@ -819,6 +850,10 @@ describe('parser', () => {
819850
it('should retain // in nested, unterminated strings',()=>{
820851
checkInterpolation(`{{ "a\'b\`" //comment}}`,`{{ "a\'b\`" }}`);
821852
});
853+
854+
it('should ignore quotes inside a comment',()=>{
855+
checkInterpolation(`"{{name // " }}"`,`"{{ name }}"`);
856+
});
822857
});
823858
});
824859

@@ -999,8 +1034,11 @@ function parseSimpleBindingIvy(
9991034
}
10001035

10011036
functioncheckInterpolation(exp:string,expected?:string){
1002-
constast=parseInterpolation(exp)!;
1037+
constast=parseInterpolation(exp);
10031038
if(expected==null)expected=exp;
1039+
if(ast===null){
1040+
throwError(`Failed to parse expression "${exp}"`);
1041+
}
10041042
expect(unparse(ast)).toEqual(expected);
10051043
validate(ast);
10061044
}

‎packages/compiler/test/template_parser/template_parser_spec.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,54 @@ describe('TemplateParser', () => {
540540
expect(humanizeTplAst(parse('{{a}}',[]))).toEqual([[BoundTextAst,'{{ a }}']]);
541541
});
542542

543+
it('should parse bound text nodes inside quotes',()=>{
544+
expect(humanizeTplAst(parse('"{{a}}"',[]))).toEqual([[BoundTextAst,'"{{ a }}"']]);
545+
});
546+
547+
it('should parse bound text nodes with interpolations inside quotes',()=>{
548+
expect(humanizeTplAst(parse('{{ "{{a}}" }}',[]))).toEqual([[BoundTextAst,'{{ "{{a}}" }}']]);
549+
expect(humanizeTplAst(parse('{{"{{"}}',[]))).toEqual([[BoundTextAst,'{{ "{{" }}']]);
550+
expect(humanizeTplAst(parse('{{"}}"}}',[]))).toEqual([[BoundTextAst,'{{ "}}" }}']]);
551+
expect(humanizeTplAst(parse('{{"{"}}',[]))).toEqual([[BoundTextAst,'{{ "{" }}']]);
552+
expect(humanizeTplAst(parse('{{"}"}}',[]))).toEqual([[BoundTextAst,'{{ "}" }}']]);
553+
});
554+
555+
it('should parse bound text nodes with escaped quotes',()=>{
556+
expect(humanizeTplAst(parse(`{{'It\\'s just Angular'}}`,[]))).toEqual([
557+
[BoundTextAst,`{{ "It's just Angular" }}`]
558+
]);
559+
560+
expect(humanizeTplAst(parse(`{{'It\\'s {{ just Angular'}}`,[]))).toEqual([
561+
[BoundTextAst,`{{ "It's {{ just Angular" }}`]
562+
]);
563+
564+
expect(humanizeTplAst(parse(`{{'It\\'s }} just Angular'}}`,[]))).toEqual([
565+
[BoundTextAst,`{{ "It's }} just Angular" }}`]
566+
]);
567+
});
568+
569+
it('should not parse bound text nodes with mismatching quotes',()=>{
570+
expect(humanizeTplAst(parse(`{{ "{{a}}' }}`,[]))).toEqual([[TextAst,`{{ "{{a}}' }}`]]);
571+
});
572+
573+
it('should parse interpolation with escaped backslashes',()=>{
574+
expect(humanizeTplAst(parse(`{{foo.split('\\\\')}}`,[]))).toEqual([
575+
[BoundTextAst,`{{ foo.split("\\") }}`]
576+
]);
577+
expect(humanizeTplAst(parse(`{{foo.split('\\\\\\\\')}}`,[]))).toEqual([
578+
[BoundTextAst,`{{ foo.split("\\\\") }}`]
579+
]);
580+
expect(humanizeTplAst(parse(`{{foo.split('\\\\\\\\\\\\')}}`,[]))).toEqual([
581+
[BoundTextAst,`{{ foo.split("\\\\\\") }}`]
582+
]);
583+
});
584+
585+
it('should ignore quotes inside a comment',()=>{
586+
expect(humanizeTplAst(parse(`"{{name // " }}"`,[]))).toEqual([
587+
[BoundTextAst,`"{{ name }}"`]
588+
]);
589+
});
590+
543591
it('should parse with custom interpolation config',
544592
inject([TemplateParser],(parser:TemplateParser)=>{
545593
constcomponent=CompileDirectiveMetadata.create({

‎packages/core/test/acceptance/text_spec.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,18 @@ describe('text instructions', () => {
171171
// `Symbol(hello)_p.sc8s398cplk`, whereas the native one is `Symbol(hello)`.
172172
expect(fixture.nativeElement.textContent).toContain('Symbol(hello)');
173173
});
174+
175+
it('should handle binding syntax used inside quoted text',()=>{
176+
@Component({
177+
template:`{{'Interpolations look like {{this}}'}}`,
178+
})
179+
classApp{
180+
}
181+
182+
TestBed.configureTestingModule({declarations:[App]});
183+
constfixture=TestBed.createComponent(App);
184+
fixture.detectChanges();
185+
186+
expect(fixture.nativeElement.textContent).toBe('Interpolations look like {{this}}');
187+
});
174188
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp