feat(compiler): integrate compiler with view engine - change detection tests work (#14412)

Included refactoring:
- make ViewData.parentIndex point to component provider index
- split NodeType.Provider into Provider / Directive / Pipe
- make purePipe take the real pipe as argument to detect changes
- order change detection:
  1) directive props
  2) renderer props

Part of #14013

PR Close #14412
This commit is contained in:
Tobias Bosch
2017-02-09 14:59:57 -08:00
committed by Miško Hevery
parent 1dc9be4b7d
commit e4e9dbe33d
39 changed files with 942 additions and 768 deletions
@@ -51,6 +51,7 @@ export class BoundProperty {
*/
export class BindingParser {
pipesByName: Map<string, CompilePipeSummary> = new Map();
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
@@ -59,6 +60,8 @@ export class BindingParser {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundElementPropertyAst[] {
if (dirMeta.hostProperties) {
@@ -377,11 +380,14 @@ export class BindingParser {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => {
if (!this.pipesByName.has(pipeName)) {
const pipeMeta = this.pipesByName.get(pipeName);
if (!pipeMeta) {
this._reportError(
`The pipe '${pipeName}' could not be found`,
new ParseSourceSpan(
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
} else {
this._usedPipes.set(pipeName, pipeMeta);
}
});
}
@@ -7,7 +7,9 @@
*/
import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
@@ -25,10 +27,12 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver';
import {syntaxError} from '../util';
import {BindingParser, BoundProperty} from './binding_parser';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser';
// Group 1 = "bind-"
// Group 2 = "let-"
// Group 3 = "ref-/#"
@@ -76,7 +80,9 @@ export class TemplateParseError extends ParseError {
}
export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
constructor(
public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[],
public errors?: ParseError[]) {}
}
@CompilerInjectable()
@@ -88,7 +94,8 @@ export class TemplateParser {
parse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] {
pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
@@ -102,7 +109,7 @@ export class TemplateParser {
throw syntaxError(`Template parse errors:\n${errorString}`);
}
return result.templateAst;
return {template: result.templateAst, pipes: result.usedPipes};
}
tryParse(
@@ -121,6 +128,7 @@ export class TemplateParser {
templateUrl: string): TemplateParseResult {
let result: TemplateAst[];
const errors = htmlAstWithErrors.errors;
const usedPipes: CompilePipeSummary[] = [];
if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeSummaryDuplicates(pipes);
@@ -140,13 +148,14 @@ export class TemplateParser {
errors);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...providerViewContext.errors);
usedPipes.push(...bindingParser.getUsedPipes());
} else {
result = [];
}
this._assertNoReferenceDuplicationOnTemplate(result, errors);
if (errors.length > 0) {
return new TemplateParseResult(result, errors);
return new TemplateParseResult(result, usedPipes, errors);
}
if (this.transforms) {
@@ -154,7 +163,7 @@ export class TemplateParser {
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
return new TemplateParseResult(result, errors);
return new TemplateParseResult(result, usedPipes, errors);
}
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
@@ -303,11 +312,12 @@ class TemplateParseVisitor implements html.Visitor {
const {directives: directiveMetas, matchElement} =
this._parseDirectives(this.selectorMatcher, elementCssSelector);
const references: ReferenceAst[] = [];
const boundDirectivePropNames = new Set<string>();
const directiveAsts = this._createDirectiveAsts(
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references);
const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames);
const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, elementOrDirectiveProps, boundDirectivePropNames);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext(
@@ -372,11 +382,12 @@ class TemplateParseVisitor implements html.Visitor {
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateCssSelector);
const templateBoundDirectivePropNames = new Set<string>();
const templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []);
element.sourceSpan, [], templateBoundDirectivePropNames);
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
const templateProviderContext = new ProviderElementContext(
@@ -544,7 +555,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;
@@ -557,13 +569,14 @@ class TemplateParseVisitor implements html.Visitor {
component = directive;
}
const directiveProperties: BoundDirectivePropertyAst[] = [];
const hostProperties =
let hostProperties =
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
// Note: We need to check the host properties here as well,
// as we don't know the element name in the DirectiveWrapperCompiler yet.
this._checkPropertiesInSchema(elementName, hostProperties);
hostProperties = this._checkPropertiesInSchema(elementName, hostProperties);
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
this._createDirectivePropertyAsts(
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
(directive.exportAs == elOrDirRef.value)) {
@@ -596,7 +609,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectivePropertyAsts(
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
targetBoundDirectiveProps: BoundDirectivePropertyAst[],
targetBoundDirectivePropNames: Set<string>) {
if (directiveProperties) {
const boundPropsByName = new Map<string, BoundProperty>();
boundProps.forEach(boundProp => {
@@ -613,8 +627,11 @@ class TemplateParseVisitor implements html.Visitor {
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if (boundProp) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
targetBoundDirectivePropNames.add(boundProp.name);
if (!isEmptyExpression(boundProp.expression)) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
}
}
});
}
@@ -622,23 +639,15 @@ class TemplateParseVisitor implements html.Visitor {
private _createElementPropertyAsts(
elementName: string, props: BoundProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] {
boundDirectivePropNames: Set<string>): BoundElementPropertyAst[] {
const boundElementProps: BoundElementPropertyAst[] = [];
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
directives.forEach((directive: DirectiveAst) => {
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
boundDirectivePropsIndex.set(prop.templateName, prop);
});
});
props.forEach((prop: BoundProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
if (!prop.isLiteral && !boundDirectivePropNames.has(prop.name)) {
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
}
});
this._checkPropertiesInSchema(elementName, boundElementProps);
return boundElementProps;
return this._checkPropertiesInSchema(elementName, boundElementProps);
}
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
@@ -723,8 +732,11 @@ class TemplateParseVisitor implements html.Visitor {
});
}
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) {
boundProps.forEach((boundProp) => {
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]):
BoundElementPropertyAst[] {
// Note: We can't filter out empty expressions before this method,
// as we still want to validate them!
return boundProps.filter((boundProp) => {
if (boundProp.type === PropertyBindingType.Property &&
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
let errorMsg =
@@ -741,6 +753,7 @@ class TemplateParseVisitor implements html.Visitor {
}
this._reportError(errorMsg, boundProp.sourceSpan);
}
return !isEmptyExpression(boundProp.value);
});
}
@@ -870,3 +883,10 @@ export function removeSummaryDuplicates<T extends{type: CompileTypeMetadata}>(it
return Array.from(map.values());
}
function isEmptyExpression(ast: AST): boolean {
if (ast instanceof ASTWithSource) {
ast = ast.ast;
}
return ast instanceof EmptyExpr;
}