refactor(compiler): make keySpan available for BoundAttributes (#38898)

Though we currently have the knowledge of where the `key` for an
attribute binding appears during parsing, we do not propagate this
information to the output AST. This means that once we produce the
template AST, we have no way of mapping a template position to the key
span alone. The best we can currently do is map back to the
`sourceSpan`. This presents problems downstream, specifically for the
language service, where we cannot provide correct information about a
position in a template because the AST is not granular enough.

PR Close #38898
This commit is contained in:
Andrew Scott
2020-09-17 13:01:32 -07:00
committed by Misko Hevery
parent c8f056beb6
commit ba3f4c26bb
6 changed files with 162 additions and 54 deletions

View File

@ -65,8 +65,10 @@ class R3AstSourceSpans implements t.Visitor<void> {
}
visitBoundAttribute(attribute: t.BoundAttribute) {
this.result.push(
['BoundAttribute', humanizeSpan(attribute.sourceSpan), humanizeSpan(attribute.valueSpan)]);
this.result.push([
'BoundAttribute', humanizeSpan(attribute.sourceSpan), humanizeSpan(attribute.keySpan),
humanizeSpan(attribute.valueSpan)
]);
}
visitBoundEvent(event: t.BoundEvent) {
@ -144,28 +146,35 @@ describe('R3 AST source spans', () => {
it('is correct for bound properties', () => {
expectFromHtml('<div [someProp]="v"></div>').toEqual([
['Element', '<div [someProp]="v"></div>', '<div [someProp]="v">', '</div>'],
['BoundAttribute', '[someProp]="v"', 'v'],
['BoundAttribute', '[someProp]="v"', 'someProp', 'v'],
]);
});
it('is correct for bound properties without value', () => {
expectFromHtml('<div [someProp]></div>').toEqual([
['Element', '<div [someProp]></div>', '<div [someProp]>', '</div>'],
['BoundAttribute', '[someProp]', '<empty>'],
['BoundAttribute', '[someProp]', 'someProp', '<empty>'],
]);
});
it('is correct for bound properties via bind- ', () => {
expectFromHtml('<div bind-prop="v"></div>').toEqual([
['Element', '<div bind-prop="v"></div>', '<div bind-prop="v">', '</div>'],
['BoundAttribute', 'bind-prop="v"', 'v'],
['BoundAttribute', 'bind-prop="v"', 'prop', 'v'],
]);
});
it('is correct for bound properties via {{...}}', () => {
expectFromHtml('<div prop="{{v}}"></div>').toEqual([
['Element', '<div prop="{{v}}"></div>', '<div prop="{{v}}">', '</div>'],
['BoundAttribute', 'prop="{{v}}"', '{{v}}'],
['BoundAttribute', 'prop="{{v}}"', 'prop', '{{v}}'],
]);
});
it('is correct for bound properties via data-', () => {
expectFromHtml('<div data-prop="{{v}}"></div>').toEqual([
['Element', '<div data-prop="{{v}}"></div>', '<div data-prop="{{v}}">', '</div>'],
['BoundAttribute', 'data-prop="{{v}}"', 'prop', '{{v}}'],
]);
});
});
@ -208,6 +217,16 @@ describe('R3 AST source spans', () => {
]);
});
it('is correct for reference via data-ref-...', () => {
expectFromHtml('<ng-template data-ref-a></ng-template>').toEqual([
[
'Template', '<ng-template data-ref-a></ng-template>', '<ng-template data-ref-a>',
'</ng-template>'
],
['Reference', 'data-ref-a', '<empty>'],
]);
});
it('is correct for variables via let-...', () => {
expectFromHtml('<ng-template let-a="b"></ng-template>').toEqual([
[
@ -218,6 +237,16 @@ describe('R3 AST source spans', () => {
]);
});
it('is correct for variables via data-let-...', () => {
expectFromHtml('<ng-template data-let-a="b"></ng-template>').toEqual([
[
'Template', '<ng-template data-let-a="b"></ng-template>', '<ng-template data-let-a="b">',
'</ng-template>'
],
['Variable', 'data-let-a="b"', 'b'],
]);
});
it('is correct for attributes', () => {
expectFromHtml('<ng-template k1="v1"></ng-template>').toEqual([
[
@ -234,7 +263,7 @@ describe('R3 AST source spans', () => {
'Template', '<ng-template [k1]="v1"></ng-template>', '<ng-template [k1]="v1">',
'</ng-template>'
],
['BoundAttribute', '[k1]="v1"', 'v1'],
['BoundAttribute', '[k1]="v1"', 'k1', 'v1'],
]);
});
});
@ -252,7 +281,7 @@ describe('R3 AST source spans', () => {
'</div>'
],
['TextAttribute', 'ngFor', '<empty>'],
['BoundAttribute', '*ngFor="let item of items"', 'items'],
['BoundAttribute', '*ngFor="let item of items"', 'of', 'items'],
['Variable', 'let item ', '<empty>'],
[
'Element', '<div *ngFor="let item of items"></div>', '<div *ngFor="let item of items">',
@ -270,10 +299,28 @@ describe('R3 AST source spans', () => {
[
'Template', '<div *ngFor="item of items"></div>', '<div *ngFor="item of items">', '</div>'
],
['BoundAttribute', '*ngFor="item of items"', 'item'],
['BoundAttribute', '*ngFor="item of items"', 'items'],
['BoundAttribute', '*ngFor="item of items"', 'ngFor', 'item'],
['BoundAttribute', '*ngFor="item of items"', 'of', 'items'],
['Element', '<div *ngFor="item of items"></div>', '<div *ngFor="item of items">', '</div>'],
]);
expectFromHtml('<div *ngFor="let item of items; trackBy: trackByFn"></div>').toEqual([
[
'Template', '<div *ngFor="let item of items; trackBy: trackByFn"></div>',
'<div *ngFor="let item of items; trackBy: trackByFn">', '</div>'
],
['TextAttribute', 'ngFor', '<empty>'],
['BoundAttribute', '*ngFor="let item of items; trackBy: trackByFn"', 'of', 'items'],
[
'BoundAttribute', '*ngFor="let item of items; trackBy: trackByFn"', 'trackBy', 'trackByFn'
],
['Variable', 'let item ', '<empty>'],
[
'Element', '<div *ngFor="let item of items; trackBy: trackByFn"></div>',
'<div *ngFor="let item of items; trackBy: trackByFn">', '</div>'
],
]);
});
it('is correct for variables via let ...', () => {
@ -288,7 +335,7 @@ describe('R3 AST source spans', () => {
it('is correct for variables via as ...', () => {
expectFromHtml('<div *ngIf="expr as local"></div>').toEqual([
['Template', '<div *ngIf="expr as local"></div>', '<div *ngIf="expr as local">', '</div>'],
['BoundAttribute', '*ngIf="expr as local"', 'expr'],
['BoundAttribute', '*ngIf="expr as local"', 'ngIf', 'expr'],
['Variable', 'ngIf="expr as local', 'ngIf'],
['Element', '<div *ngIf="expr as local"></div>', '<div *ngIf="expr as local">', '</div>'],
]);
@ -310,10 +357,17 @@ describe('R3 AST source spans', () => {
]);
});
it('is correct for bound events via data-on-', () => {
expectFromHtml('<div data-on-event="v"></div>').toEqual([
['Element', '<div data-on-event="v"></div>', '<div data-on-event="v">', '</div>'],
['BoundEvent', 'data-on-event="v"', 'v'],
]);
});
it('is correct for bound events and properties via [(...)]', () => {
expectFromHtml('<div [(prop)]="v"></div>').toEqual([
['Element', '<div [(prop)]="v"></div>', '<div [(prop)]="v">', '</div>'],
['BoundAttribute', '[(prop)]="v"', 'v'],
['BoundAttribute', '[(prop)]="v"', 'prop', 'v'],
['BoundEvent', '[(prop)]="v"', 'v'],
]);
});
@ -321,10 +375,18 @@ describe('R3 AST source spans', () => {
it('is correct for bound events and properties via bindon-', () => {
expectFromHtml('<div bindon-prop="v"></div>').toEqual([
['Element', '<div bindon-prop="v"></div>', '<div bindon-prop="v">', '</div>'],
['BoundAttribute', 'bindon-prop="v"', 'v'],
['BoundAttribute', 'bindon-prop="v"', 'prop', 'v'],
['BoundEvent', 'bindon-prop="v"', 'v'],
]);
});
it('is correct for bound events and properties via data-bindon-', () => {
expectFromHtml('<div data-bindon-prop="v"></div>').toEqual([
['Element', '<div data-bindon-prop="v"></div>', '<div data-bindon-prop="v">', '</div>'],
['BoundAttribute', 'data-bindon-prop="v"', 'prop', 'v'],
['BoundEvent', 'data-bindon-prop="v"', 'v'],
]);
});
});
describe('references', () => {
@ -348,5 +410,12 @@ describe('R3 AST source spans', () => {
['Reference', 'ref-a', '<empty>'],
]);
});
it('is correct for references via data-ref-', () => {
expectFromHtml('<div ref-a></div>').toEqual([
['Element', '<div ref-a></div>', '<div ref-a>', '</div>'],
['Reference', 'ref-a', '<empty>'],
]);
});
});
});