angular-docs-cn/packages/compiler/test/render3/r3_ast_absolute_span_spec.ts
ayazhafiz df890d7629 fix(compiler): record correct end of expression (#34690)
This commit fixes a bug with the expression parser wherein the end index
of an expression node was recorded as the start index of the next token,
not the end index of the current token.

Closes #33477
Closes https://github.com/angular/vscode-ng-language-service/issues/433

PR Close #34690
2020-03-20 10:19:02 -07:00

330 lines
12 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteSourceSpan} from '@angular/compiler';
import {humanizeExpressionSource} from './util/expression';
import {parseR3 as parse} from './view/util';
describe('expression AST absolute source spans', () => {
// TODO(ayazhafiz): duplicate this test without `preserveWhitespaces` once whitespace rewriting is
// moved to post-R3AST generation.
it('should provide absolute offsets with arbitrary whitespace', () => {
expect(humanizeExpressionSource(
parse('<div>\n \n{{foo}}</div>', {preserveWhitespaces: true}).nodes))
.toContain(['\n \n{{ foo }}', new AbsoluteSourceSpan(5, 16)]);
});
it('should provide absolute offsets of an expression in a bound text', () => {
expect(humanizeExpressionSource(parse('<div>{{foo}}</div>').nodes)).toContain([
'{{ foo }}', new AbsoluteSourceSpan(5, 12)
]);
});
it('should provide absolute offsets of an expression in a bound event', () => {
expect(humanizeExpressionSource(parse('<div (click)="foo();bar();"></div>').nodes)).toContain([
'foo(); bar();', new AbsoluteSourceSpan(14, 26)
]);
expect(humanizeExpressionSource(parse('<div on-click="foo();bar();"></div>').nodes)).toContain([
'foo(); bar();', new AbsoluteSourceSpan(15, 27)
]);
});
it('should provide absolute offsets of an expression in a bound attribute', () => {
expect(humanizeExpressionSource(parse('<input [disabled]="condition ? true : false" />').nodes))
.toContain(['condition ? true : false', new AbsoluteSourceSpan(19, 43)]);
expect(
humanizeExpressionSource(parse('<input bind-disabled="condition ? true : false" />').nodes))
.toContain(['condition ? true : false', new AbsoluteSourceSpan(22, 46)]);
});
it('should provide absolute offsets of an expression in a template attribute', () => {
expect(humanizeExpressionSource(parse('<div *ngIf="value | async"></div>').nodes)).toContain([
'(value | async)', new AbsoluteSourceSpan(12, 25)
]);
});
describe('binary expression', () => {
it('should provide absolute offsets of a binary expression', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>').nodes)).toContain([
'1 + 2', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a binary expression', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['1', new AbsoluteSourceSpan(7, 8)],
['2', new AbsoluteSourceSpan(11, 12)],
]));
});
});
describe('conditional', () => {
it('should provide absolute offsets of a conditional', () => {
expect(humanizeExpressionSource(parse('<div>{{bool ? 1 : 0}}<div>').nodes)).toContain([
'bool ? 1 : 0', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in a conditional', () => {
expect(humanizeExpressionSource(parse('<div>{{bool ? 1 : 0}}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['bool', new AbsoluteSourceSpan(7, 11)],
['1', new AbsoluteSourceSpan(14, 15)],
['0', new AbsoluteSourceSpan(18, 19)],
]));
});
});
describe('chain', () => {
it('should provide absolute offsets of a chain', () => {
expect(humanizeExpressionSource(parse('<div (click)="a(); b();"><div>').nodes)).toContain([
'a(); b();', new AbsoluteSourceSpan(14, 23)
]);
});
it('should provide absolute offsets of expressions in a chain', () => {
expect(humanizeExpressionSource(parse('<div (click)="a(); b();"><div>').nodes))
.toEqual(jasmine.arrayContaining([
['a()', new AbsoluteSourceSpan(14, 17)],
['b()', new AbsoluteSourceSpan(19, 22)],
]));
});
});
describe('function call', () => {
it('should provide absolute offsets of a function call', () => {
expect(humanizeExpressionSource(parse('<div>{{fn()()}}<div>').nodes)).toContain([
'fn()()', new AbsoluteSourceSpan(7, 13)
]);
});
it('should provide absolute offsets of expressions in a function call', () => {
expect(humanizeExpressionSource(parse('<div>{{fn()(param)}}<div>').nodes)).toContain([
'param', new AbsoluteSourceSpan(12, 17)
]);
});
});
it('should provide absolute offsets of an implicit receiver', () => {
expect(humanizeExpressionSource(parse('<div>{{a.b}}<div>').nodes)).toContain([
'', new AbsoluteSourceSpan(7, 7)
]);
});
describe('interpolation', () => {
it('should provide absolute offsets of an interpolation', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + foo.length}}<div>').nodes)).toContain([
'{{ 1 + foo.length }}', new AbsoluteSourceSpan(5, 23)
]);
});
it('should provide absolute offsets of expressions in an interpolation', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['1', new AbsoluteSourceSpan(7, 8)],
['2', new AbsoluteSourceSpan(11, 12)],
]));
});
});
describe('keyed read', () => {
it('should provide absolute offsets of a keyed read', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key]}}<div>').nodes)).toContain([
'obj[key]', new AbsoluteSourceSpan(7, 15)
]);
});
it('should provide absolute offsets of expressions in a keyed read', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key]}}<div>').nodes)).toContain([
'key', new AbsoluteSourceSpan(11, 14)
]);
});
});
describe('keyed write', () => {
it('should provide absolute offsets of a keyed write', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key] = 0}}<div>').nodes)).toContain([
'obj[key] = 0', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in a keyed write', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key] = 0}}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['key', new AbsoluteSourceSpan(11, 14)],
['0', new AbsoluteSourceSpan(18, 19)],
]));
});
});
it('should provide absolute offsets of a literal primitive', () => {
expect(humanizeExpressionSource(parse('<div>{{100}}<div>').nodes)).toContain([
'100', new AbsoluteSourceSpan(7, 10)
]);
});
describe('literal array', () => {
it('should provide absolute offsets of a literal array', () => {
expect(humanizeExpressionSource(parse('<div>{{[0, 1, 2]}}<div>').nodes)).toContain([
'[0, 1, 2]', new AbsoluteSourceSpan(7, 16)
]);
});
it('should provide absolute offsets of expressions in a literal array', () => {
expect(humanizeExpressionSource(parse('<div>{{[0, 1, 2]}}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['0', new AbsoluteSourceSpan(8, 9)],
['1', new AbsoluteSourceSpan(11, 12)],
['2', new AbsoluteSourceSpan(14, 15)],
]));
});
});
describe('literal map', () => {
it('should provide absolute offsets of a literal map', () => {
expect(humanizeExpressionSource(parse('<div>{{ {a: 0} }}<div>').nodes)).toContain([
'{a: 0}', new AbsoluteSourceSpan(8, 14)
]);
});
it('should provide absolute offsets of expressions in a literal map', () => {
expect(humanizeExpressionSource(parse('<div>{{ {a: 0} }}<div>').nodes))
.toEqual(jasmine.arrayContaining([
['0', new AbsoluteSourceSpan(12, 13)],
]));
});
});
describe('method call', () => {
it('should provide absolute offsets of a method call', () => {
expect(humanizeExpressionSource(parse('<div>{{method()}}</div>').nodes)).toContain([
'method()', new AbsoluteSourceSpan(7, 15)
]);
});
it('should provide absolute offsets of expressions in a method call', () => {
expect(humanizeExpressionSource(parse('<div>{{method(param)}}<div>').nodes)).toContain([
'param', new AbsoluteSourceSpan(14, 19)
]);
});
});
describe('non-null assert', () => {
it('should provide absolute offsets of a non-null assert', () => {
expect(humanizeExpressionSource(parse('<div>{{prop!}}</div>').nodes)).toContain([
'prop!', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a non-null assert', () => {
expect(humanizeExpressionSource(parse('<div>{{prop!}}<div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
describe('pipe', () => {
it('should provide absolute offsets of a pipe', () => {
expect(humanizeExpressionSource(parse('<div>{{prop | pipe}}<div>').nodes)).toContain([
'(prop | pipe)', new AbsoluteSourceSpan(7, 18)
]);
});
it('should provide absolute offsets expressions in a pipe', () => {
expect(humanizeExpressionSource(parse('<div>{{prop | pipe}}<div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
it('should provide absolute offsets of a property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop}}</div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
describe('property write', () => {
it('should provide absolute offsets of a property write', () => {
expect(humanizeExpressionSource(parse('<div (click)="prop = 0"></div>').nodes)).toContain([
'prop = 0', new AbsoluteSourceSpan(14, 22)
]);
});
it('should provide absolute offsets of expressions in a property write', () => {
expect(humanizeExpressionSource(parse('<div (click)="prop = 0"></div>').nodes)).toContain([
'0', new AbsoluteSourceSpan(21, 22)
]);
});
});
describe('"not" prefix', () => {
it('should provide absolute offsets of a "not" prefix', () => {
expect(humanizeExpressionSource(parse('<div>{{!prop}}</div>').nodes)).toContain([
'!prop', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a "not" prefix', () => {
expect(humanizeExpressionSource(parse('<div>{{!prop}}<div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(8, 12)
]);
});
});
describe('safe method call', () => {
it('should provide absolute offsets of a safe method call', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe()}}<div>').nodes)).toContain([
'prop?.safe()', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in safe method call', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe()}}<div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
describe('safe property read', () => {
it('should provide absolute offsets of a safe property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe}}<div>').nodes)).toContain([
'prop?.safe', new AbsoluteSourceSpan(7, 17)
]);
});
it('should provide absolute offsets of expressions in safe property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe}}<div>').nodes)).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
it('should provide absolute offsets of a quote', () => {
expect(humanizeExpressionSource(parse('<div [prop]="a:b"></div>').nodes)).toContain([
'a:b', new AbsoluteSourceSpan(13, 16)
]);
});
describe('absolute offsets for template expressions', () => {
it('should work for simple cases', () => {
expect(
humanizeExpressionSource(parse('<div *ngFor="let item of items">{{item}}</div>').nodes))
.toContain(['items', new AbsoluteSourceSpan(25, 30)]);
});
it('should work with multiple bindings', () => {
expect(humanizeExpressionSource(parse('<div *ngFor="let a of As; let b of Bs"></div>').nodes))
.toEqual(jasmine.arrayContaining(
[['As', new AbsoluteSourceSpan(22, 24)], ['Bs', new AbsoluteSourceSpan(35, 37)]]));
});
});
});