Skip to content

Commit a1e3f2b

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(compiler): incorrect spans for left side of binary operation (#62641)
Fixes that the span for the `left` side of a `Binary` AST included the range up to and including the operator. Fixes #62617. PR Close #62641
1 parent eef258f commit a1e3f2b

File tree

3 files changed

+36
-6
lines changed

3 files changed

+36
-6
lines changed

packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ describe('type check blocks diagnostics', () => {
113113
it('should annotate property writes', () => {
114114
const TEMPLATE = `<div (click)='a.b.c = d'></div>`;
115115
expect(tcbWithSpans(TEMPLATE)).toContain(
116-
'(((((((this).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,21*/) = (((this).d /*22,23*/) /*22,23*/) /*14,23*/;',
116+
'(((((((this).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,19*/) = (((this).d /*22,23*/) /*22,23*/) /*14,23*/;',
117117
);
118118
});
119119

120-
it('should $event property writes', () => {
120+
it('should annotate $event property writes', () => {
121121
const TEMPLATE = `<div (click)='a = $event'></div>`;
122122
expect(tcbWithSpans(TEMPLATE)).toContain(
123-
'(((this).a /*14,15*/) /*14,17*/) = ($event /*18,24*/) /*14,24*/;',
123+
'(((this).a /*14,15*/) /*14,15*/) = ($event /*18,24*/) /*14,24*/; }) /*5,25*/;',
124124
);
125125
});
126126

@@ -134,7 +134,7 @@ describe('type check blocks diagnostics', () => {
134134
it('should annotate keyed property writes', () => {
135135
const TEMPLATE = `<div (click)="a[b] = c"></div>`;
136136
expect(tcbWithSpans(TEMPLATE)).toContain(
137-
'((((this).a /*14,15*/) /*14,15*/)[((this).b /*16,17*/) /*16,17*/] /*14,20*/) = (((this).c /*21,22*/) /*21,22*/) /*14,22*/;',
137+
'((((this).a /*14,15*/) /*14,15*/)[((this).b /*16,17*/) /*16,17*/] /*14,18*/) = (((this).c /*21,22*/) /*21,22*/) /*14,22*/;',
138138
);
139139
});
140140

packages/compiler/src/expression_parser/parser.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,9 +1257,9 @@ class _ParseAST {
12571257
} else {
12581258
if (this.isAssignmentOperator(this.next)) {
12591259
const operation = this.next.strValue;
1260-
this.advance();
12611260

12621261
if (!(this.parseFlags & ParseFlags.Action)) {
1262+
this.advance();
12631263
this.error('Bindings cannot contain assignments');
12641264
return new EmptyExpr(this.span(start), this.sourceSpan(start));
12651265
}
@@ -1270,6 +1270,7 @@ class _ParseAST {
12701270
readReceiver,
12711271
id,
12721272
);
1273+
this.advance();
12731274
const value = this.parseConditional();
12741275
return new Binary(this.span(start), this.sourceSpan(start), operation, receiver, value);
12751276
} else {
@@ -1398,9 +1399,9 @@ class _ParseAST {
13981399
this.expectCharacter(chars.$RBRACKET);
13991400
if (this.isAssignmentOperator(this.next)) {
14001401
const operation = this.next.strValue;
1401-
this.advance();
14021402

14031403
if (isSafe) {
1404+
this.advance();
14041405
this.error("The '?.' operator cannot be used in the assignment");
14051406
} else {
14061407
const binaryReceiver = new KeyedRead(
@@ -1409,6 +1410,7 @@ class _ParseAST {
14091410
receiver,
14101411
key,
14111412
);
1413+
this.advance();
14121414
const value = this.parseConditional();
14131415
return new Binary(
14141416
this.span(start),

packages/compiler/test/expression_parser/parser_spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TemplateBinding,
1919
VariableBinding,
2020
BindingPipeType,
21+
Binary,
2122
} from '../../src/expression_parser/ast';
2223
import {ParseError} from '../../src/parse_util';
2324
import {Lexer} from '../../src/expression_parser/lexer';
@@ -631,6 +632,33 @@ describe('parser', () => {
631632
]);
632633
});
633634

635+
it('should record spans for binary assignment operations', () => {
636+
expect(unparseWithSpan(parseAction('a.b ??= c'))).toEqual([
637+
['a.b ??= c', 'a.b ??= c'],
638+
['a.b', 'a.b'],
639+
['a.b', '[nameSpan] b'],
640+
['a', 'a'],
641+
['a', '[nameSpan] a'],
642+
['', ''],
643+
['c', 'c'],
644+
['c', '[nameSpan] c'],
645+
['', ' '],
646+
]);
647+
expect(unparseWithSpan(parseAction('a[b] ||= c'))).toEqual([
648+
['a[b] ||= c', 'a[b] ||= c'],
649+
['a[b]', 'a[b]'],
650+
['a', 'a'],
651+
['a', '[nameSpan] a'],
652+
['', ''],
653+
['b', 'b'],
654+
['b', '[nameSpan] b'],
655+
['', ''],
656+
['c', 'c'],
657+
['c', '[nameSpan] c'],
658+
['', ' '],
659+
]);
660+
});
661+
634662
it('should include parenthesis in spans', () => {
635663
// When a LHS expression is parenthesized, the parenthesis on the left used to be
636664
// excluded from the span. This test verifies that the parenthesis are properly included

0 commit comments

Comments
 (0)