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:
committed by
Miško Hevery
parent
1dc9be4b7d
commit
e4e9dbe33d
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user