f65db20c6d
Currently, template expressions and statements have their location recorded relative to the HTML element they are in, with no handle to absolute location in a source file except for a line/column location. However, the line/column location is also not entirely accurate, as it points an entire semantic expression, and not necessarily the start of an expression recorded by the expression parser. To support record of the source code expressions originate from, add a new `sourceSpan` field to `ASTWithSource` that records the absolute byte offset of an expression within a source code. Implement part 2 of [refactoring template parsing for stability](https://hackmd.io/@X3ECPVy-RCuVfba-pnvIpw/BkDUxaW84/%2FMA1oxh6jRXqSmZBcLfYdyw?type=book). PR Close #31391
75 lines
3.0 KiB
TypeScript
75 lines
3.0 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 {ASTWithSource, AbsoluteSourceSpan, NullAstVisitor} from '@angular/compiler';
|
|
import * as t from '../../src/render3/r3_ast';
|
|
import {parseR3 as parse} from './view/util';
|
|
|
|
class ExpressionLocationHumanizer extends NullAstVisitor implements t.Visitor {
|
|
result: any[] = [];
|
|
|
|
visitASTWithSource(ast: ASTWithSource) { this.result.push([ast.source, ast.sourceSpan]); }
|
|
|
|
visitTemplate(ast: t.Template) { t.visitAll(this, ast.children); }
|
|
visitElement(ast: t.Element) {
|
|
t.visitAll(this, ast.children);
|
|
t.visitAll(this, ast.inputs);
|
|
t.visitAll(this, ast.outputs);
|
|
}
|
|
visitReference(ast: t.Reference) {}
|
|
visitVariable(ast: t.Variable) {}
|
|
visitEvent(ast: t.BoundEvent) { ast.handler.visit(this); }
|
|
visitTextAttribute(ast: t.TextAttribute) {}
|
|
visitBoundAttribute(ast: t.BoundAttribute) { ast.value.visit(this); }
|
|
visitBoundEvent(ast: t.BoundEvent) { ast.handler.visit(this); }
|
|
visitBoundText(ast: t.BoundText) { ast.value.visit(this); }
|
|
visitContent(ast: t.Content) {}
|
|
visitText(ast: t.Text) {}
|
|
visitIcu(ast: t.Icu) {}
|
|
}
|
|
|
|
function humanizeExpressionLocation(templateAsts: t.Node[]): any[] {
|
|
const humanizer = new ExpressionLocationHumanizer();
|
|
t.visitAll(humanizer, templateAsts);
|
|
return humanizer.result;
|
|
}
|
|
|
|
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(humanizeExpressionLocation(
|
|
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(humanizeExpressionLocation(parse('<div>{{foo}}</div>').nodes)).toContain([
|
|
'{{foo}}', new AbsoluteSourceSpan(5, 12)
|
|
]);
|
|
});
|
|
|
|
it('should provide absolute offsets of an expression in a bound event', () => {
|
|
expect(humanizeExpressionLocation(parse('<div (click)="foo();bar();"></div>').nodes))
|
|
.toContain(['foo();bar();', new AbsoluteSourceSpan(14, 26)]);
|
|
|
|
expect(humanizeExpressionLocation(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(
|
|
humanizeExpressionLocation(parse('<input [disabled]="condition ? true : false" />').nodes))
|
|
.toContain(['condition ? true : false', new AbsoluteSourceSpan(19, 43)]);
|
|
|
|
expect(humanizeExpressionLocation(
|
|
parse('<input bind-disabled="condition ? true : false" />').nodes))
|
|
.toContain(['condition ? true : false', new AbsoluteSourceSpan(22, 46)]);
|
|
});
|
|
});
|