feat(ivy): introduce a new compiler API for operating on templates (#26203)

This commit introduces the "t2" API, which processes parsed template ASTs
and performs a number of functions such as binding (the process of
semantically interpreting cross-references within the template) and
directive matching. The API is modeled on TypeScript's TypeChecker API,
with oracle methods that give access to collected metadata.

This work is a prerequisite for the upcoming template type-checking
functionality, and will also become the basis for a refactored
TemplateDefinitionBuilder.

PR Close #26203
This commit is contained in:
Alex Rickabaugh
2018-09-21 15:42:07 -07:00
committed by Jason Aden
parent a2da485d90
commit 9ed4e3df60
5 changed files with 798 additions and 0 deletions

View File

@ -0,0 +1,61 @@
/**
* @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 * as e from '../../../src/expression_parser/ast';
import * as a from '../../../src/render3/r3_ast';
import {DirectiveMeta} from '../../../src/render3/view/t2_api';
import {R3TargetBinder} from '../../../src/render3/view/t2_binder';
import {parseTemplate} from '../../../src/render3/view/template';
import {CssSelector, SelectorMatcher} from '../../../src/selector';
import {findExpression} from './util';
function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
const matcher = new SelectorMatcher<DirectiveMeta>();
matcher.addSelectables(CssSelector.parse('[ngFor][ngForOf]'), {
name: 'NgFor',
exportAs: null,
inputs: {'ngForOf': 'ngForOf'},
outputs: {},
isComponent: false,
});
return matcher;
}
describe('t2 binding', () => {
it('should bind a simple template', () => {
const template =
parseTemplate('<div *ngFor="let item of items">{{item.name}}</div>', '', {}, '');
const binder = new R3TargetBinder(new SelectorMatcher<DirectiveMeta>());
const res = binder.bind({template: template.nodes});
const itemBinding = (findExpression(template.nodes, '{{item.name}}') !as e.Interpolation)
.expressions[0] as e.PropertyRead;
const item = itemBinding.receiver;
const itemTarget = res.getExpressionTarget(item);
if (!(itemTarget instanceof a.Variable)) {
return fail('Expected item to point to a Variable');
}
expect(itemTarget.value).toBe('$implicit');
const itemTemplate = res.getTemplateOfSymbol(itemTarget);
expect(itemTemplate).not.toBeNull();
expect(res.getNestingLevel(itemTemplate !)).toBe(1);
});
it('should match directives when binding a simple template', () => {
const template =
parseTemplate('<div *ngFor="let item of items">{{item.name}}</div>', '', {}, '');
const binder = new R3TargetBinder(makeSelectorMatcher());
const res = binder.bind({template: template.nodes});
const tmpl = template.nodes[0] as a.Template;
const directives = res.getDirectivesOfNode(tmpl) !;
expect(directives).not.toBeNull();
expect(directives.length).toBe(1);
expect(directives[0].name).toBe('NgFor');
});
});

View File

@ -0,0 +1,67 @@
/**
* @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 * as e from '../../../src/expression_parser/ast';
import * as a from '../../../src/render3/r3_ast';
export function findExpression(tmpl: a.Node[], expr: string): e.AST|null {
const res = tmpl.reduce((found, node) => {
if (found !== null) {
return found;
} else {
return findExpressionInNode(node, expr);
}
}, null as e.AST | null);
if (res instanceof e.ASTWithSource) {
return res.ast;
}
return res;
}
function findExpressionInNode(node: a.Node, expr: string): e.AST|null {
if (node instanceof a.Element || node instanceof a.Template) {
return findExpression(
[
...node.inputs,
...node.outputs,
...node.children,
],
expr);
} else if (node instanceof a.BoundAttribute || node instanceof a.BoundText) {
const ts = toStringExpression(node.value);
return toStringExpression(node.value) === expr ? node.value : null;
} else if (node instanceof a.BoundEvent) {
return toStringExpression(node.handler) === expr ? node.handler : null;
} else {
return null;
}
}
export function toStringExpression(expr: e.AST): string {
while (expr instanceof e.ASTWithSource) {
expr = expr.ast;
}
if (expr instanceof e.PropertyRead) {
if (expr.receiver instanceof e.ImplicitReceiver) {
return expr.name;
} else {
return `${toStringExpression(expr.receiver)}.${expr.name}`;
}
} else if (expr instanceof e.ImplicitReceiver) {
return '';
} else if (expr instanceof e.Interpolation) {
let str = '{{';
for (let i = 0; i < expr.expressions.length; i++) {
str += expr.strings[i] + toStringExpression(expr.expressions[i]);
}
str += expr.strings[expr.strings.length - 1] + '}}';
return str;
} else {
throw new Error(`Unsupported type: ${(expr as any).constructor.name}`);
}
}