feat(ivy): add an IVY local the compiler which avoids analyzeModule (#23441)
closes #23289 Based on a spike by @chukjaz PR Close #23441
This commit is contained in:
parent
a19e018439
commit
9757347e71
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {ConstantPool} from '../constant_pool';
|
import {ConstantPool} from '../constant_pool';
|
||||||
import {ViewEncapsulation} from '../core';
|
import {ViewEncapsulation} from '../core';
|
||||||
|
@ -14,7 +14,9 @@ import {MessageBundle} from '../i18n/message_bundle';
|
||||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||||
import {InjectableCompiler} from '../injectable_compiler';
|
import {InjectableCompiler} from '../injectable_compiler';
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
import * as html from '../ml_parser/ast';
|
||||||
import {HtmlParser} from '../ml_parser/html_parser';
|
import {HtmlParser} from '../ml_parser/html_parser';
|
||||||
|
import {removeWhitespaces} from '../ml_parser/html_whitespaces';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
|
@ -22,7 +24,9 @@ import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
||||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
import {HtmlToTemplateTransform} from '../render3/r3_template_transform';
|
||||||
|
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler_local';
|
||||||
|
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
import {BindingParser} from '../template_parser/binding_parser';
|
import {BindingParser} from '../template_parser/binding_parser';
|
||||||
|
@ -39,15 +43,11 @@ import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
|
||||||
import {PartialModule} from './partial_module';
|
import {PartialModule} from './partial_module';
|
||||||
import {StaticReflector} from './static_reflector';
|
import {StaticReflector} from './static_reflector';
|
||||||
import {StaticSymbol} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
import {createForJitStub, serializeSummaries} from './summary_serializer';
|
import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName} from './util';
|
||||||
|
|
||||||
enum StubEmitFlags {
|
const enum StubEmitFlags { Basic = 1 << 0, TypeCheck = 1 << 1, All = TypeCheck | Basic }
|
||||||
Basic = 1 << 0,
|
|
||||||
TypeCheck = 1 << 1,
|
|
||||||
All = TypeCheck | Basic
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AotCompiler {
|
export class AotCompiler {
|
||||||
private _templateAstCache =
|
private _templateAstCache =
|
||||||
|
@ -369,11 +369,12 @@ export class AotCompiler {
|
||||||
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||||
injectables: CompileInjectableMetadata[], context: OutputContext): void {
|
injectables: CompileInjectableMetadata[], context: OutputContext): void {
|
||||||
const classes: o.ClassStmt[] = [];
|
|
||||||
const errors: ParseError[] = [];
|
const errors: ParseError[] = [];
|
||||||
|
|
||||||
|
const schemaRegistry = new DomElementSchemaRegistry();
|
||||||
const hostBindingParser = new BindingParser(
|
const hostBindingParser = new BindingParser(
|
||||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
|
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [],
|
||||||
|
errors);
|
||||||
|
|
||||||
// Process all components and directives
|
// Process all components and directives
|
||||||
directives.forEach(directiveType => {
|
directives.forEach(directiveType => {
|
||||||
|
@ -384,11 +385,40 @@ export class AotCompiler {
|
||||||
error(
|
error(
|
||||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||||
|
|
||||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
let htmlAst = directiveMetadata.template !.htmlAst !;
|
||||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
const preserveWhitespaces = directiveMetadata !.template !.preserveWhitespaces;
|
||||||
|
|
||||||
|
if (!preserveWhitespaces) {
|
||||||
|
htmlAst = removeWhitespaces(htmlAst);
|
||||||
|
}
|
||||||
|
const transform = new HtmlToTemplateTransform(hostBindingParser);
|
||||||
|
const nodes = html.visitAll(transform, htmlAst.rootNodes, null);
|
||||||
|
const hasNgContent = transform.hasNgContent;
|
||||||
|
const ngContentSelectors = transform.ngContentSelectors;
|
||||||
|
|
||||||
|
// Map of StaticType by directive selectors
|
||||||
|
const directiveTypeBySel = new Map<string, any>();
|
||||||
|
|
||||||
|
const directives = module.transitiveModule.directives.map(
|
||||||
|
dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||||
|
|
||||||
|
directives.forEach(directive => {
|
||||||
|
if (directive.selector) {
|
||||||
|
directiveTypeBySel.set(directive.selector, directive.type.reference);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Map of StaticType by pipe names
|
||||||
|
const pipeTypeByName = new Map<string, any>();
|
||||||
|
|
||||||
|
const pipes = module.transitiveModule.pipes.map(
|
||||||
|
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||||
|
|
||||||
|
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||||
|
|
||||||
compileIvyComponent(
|
compileIvyComponent(
|
||||||
context, directiveMetadata, parsedPipes, parsedTemplate, this.reflector,
|
context, directiveMetadata, nodes, hasNgContent, ngContentSelectors, this.reflector,
|
||||||
hostBindingParser);
|
hostBindingParser, directiveTypeBySel, pipeTypeByName);
|
||||||
} else {
|
} else {
|
||||||
compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser);
|
compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser);
|
||||||
}
|
}
|
||||||
|
@ -891,16 +921,13 @@ export function analyzeFileForInjectables(
|
||||||
if (!symbolMeta || symbolMeta.__symbolic === 'error') {
|
if (!symbolMeta || symbolMeta.__symbolic === 'error') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let isNgSymbol = false;
|
|
||||||
if (symbolMeta.__symbolic === 'class') {
|
if (symbolMeta.__symbolic === 'class') {
|
||||||
if (metadataResolver.isInjectable(symbol)) {
|
if (metadataResolver.isInjectable(symbol)) {
|
||||||
isNgSymbol = true;
|
|
||||||
const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
|
const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
|
||||||
if (injectable) {
|
if (injectable) {
|
||||||
injectables.push(injectable);
|
injectables.push(injectable);
|
||||||
}
|
}
|
||||||
} else if (metadataResolver.isNgModule(symbol)) {
|
} else if (metadataResolver.isNgModule(symbol)) {
|
||||||
isNgSymbol = true;
|
|
||||||
const module = metadataResolver.getShallowModuleMetadata(symbol);
|
const module = metadataResolver.getShallowModuleMetadata(symbol);
|
||||||
if (module) {
|
if (module) {
|
||||||
shallowModules.push(module);
|
shallowModules.push(module);
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
/**
|
||||||
|
* @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 {SecurityContext} from '../core';
|
||||||
|
import {AST} from '../expression_parser/ast';
|
||||||
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
|
|
||||||
|
export interface Node {
|
||||||
|
sourceSpan: ParseSourceSpan;
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Text implements Node {
|
||||||
|
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitText(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoundText implements Node {
|
||||||
|
constructor(public value: AST, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundText(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextAttribute implements Node {
|
||||||
|
constructor(
|
||||||
|
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
||||||
|
public valueSpan?: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitAttribute(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of types of property bindings.
|
||||||
|
*/
|
||||||
|
export enum PropertyBindingType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A normal binding to a property (e.g. `[property]="expression"`).
|
||||||
|
*/
|
||||||
|
Property,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||||
|
*/
|
||||||
|
Attribute,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||||
|
*/
|
||||||
|
Class,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||||
|
*/
|
||||||
|
Style,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||||
|
*/
|
||||||
|
Animation
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoundAttribute implements Node {
|
||||||
|
constructor(
|
||||||
|
public name: string, public type: PropertyBindingType,
|
||||||
|
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
||||||
|
public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundAttribute(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoundEvent implements Node {
|
||||||
|
constructor(
|
||||||
|
public name: string, public handler: AST, public target: string|null,
|
||||||
|
public phase: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Element implements Node {
|
||||||
|
constructor(
|
||||||
|
public name: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
|
||||||
|
public outputs: BoundEvent[], public children: Node[], public references: Reference[],
|
||||||
|
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||||
|
public endSourceSpan: ParseSourceSpan|null) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitElement(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Template implements Node {
|
||||||
|
constructor(
|
||||||
|
public attributes: TextAttribute[], public inputs: BoundAttribute[], public children: Node[],
|
||||||
|
public references: Reference[], public variables: Variable[],
|
||||||
|
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||||
|
public endSourceSpan: ParseSourceSpan|null) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTemplate(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Content implements Node {
|
||||||
|
constructor(
|
||||||
|
public selectorIndex: number, public attributes: TextAttribute[],
|
||||||
|
public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitContent(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Variable implements Node {
|
||||||
|
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitVariable(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Reference implements Node {
|
||||||
|
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitReference(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Visitor<Result = any> {
|
||||||
|
// Returning a truthy value from `visit()` will prevent `visitAll()` from the call to the typed
|
||||||
|
// method and result returned will become the result included in `visitAll()`s result array.
|
||||||
|
visit?(node: Node): Result;
|
||||||
|
|
||||||
|
visitElement(element: Element): Result;
|
||||||
|
visitTemplate(template: Template): Result;
|
||||||
|
visitContent(content: Content): Result;
|
||||||
|
visitVariable(variable: Variable): Result;
|
||||||
|
visitReference(reference: Reference): Result;
|
||||||
|
visitAttribute(attribute: TextAttribute): Result;
|
||||||
|
visitBoundAttribute(attribute: BoundAttribute): Result;
|
||||||
|
visitBoundEvent(attribute: BoundEvent): Result;
|
||||||
|
visitText(text: Text): Result;
|
||||||
|
visitBoundText(text: BoundText): Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NullVisitor implements Visitor<void> {
|
||||||
|
visitElement(element: Element): void {}
|
||||||
|
visitTemplate(template: Template): void {}
|
||||||
|
visitContent(content: Content): void {}
|
||||||
|
visitVariable(variable: Variable): void {}
|
||||||
|
visitReference(reference: Reference): void {}
|
||||||
|
visitAttribute(attribute: TextAttribute): void {}
|
||||||
|
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||||
|
visitBoundEvent(attribute: BoundEvent): void {}
|
||||||
|
visitText(text: Text): void {}
|
||||||
|
visitBoundText(text: BoundText): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RecursiveVisitor implements Visitor<void> {
|
||||||
|
visitElement(element: Element): void {
|
||||||
|
visitAll(this, element.attributes);
|
||||||
|
visitAll(this, element.children);
|
||||||
|
visitAll(this, element.references);
|
||||||
|
}
|
||||||
|
visitTemplate(template: Template): void {
|
||||||
|
visitAll(this, template.attributes);
|
||||||
|
visitAll(this, template.children);
|
||||||
|
visitAll(this, template.references);
|
||||||
|
visitAll(this, template.variables);
|
||||||
|
}
|
||||||
|
visitContent(content: Content): void {}
|
||||||
|
visitVariable(variable: Variable): void {}
|
||||||
|
visitReference(reference: Reference): void {}
|
||||||
|
visitAttribute(attribute: TextAttribute): void {}
|
||||||
|
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||||
|
visitBoundEvent(attribute: BoundEvent): void {}
|
||||||
|
visitText(text: Text): void {}
|
||||||
|
visitBoundText(text: BoundText): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TransformVisitor implements Visitor<Node> {
|
||||||
|
visitElement(element: Element): Node {
|
||||||
|
const newAttributes = transformAll(this, element.attributes);
|
||||||
|
const newInputs = transformAll(this, element.inputs);
|
||||||
|
const newOutputs = transformAll(this, element.outputs);
|
||||||
|
const newChildren = transformAll(this, element.children);
|
||||||
|
const newReferences = transformAll(this, element.references);
|
||||||
|
if (newAttributes != element.attributes || newInputs != element.inputs ||
|
||||||
|
newOutputs != element.outputs || newChildren != element.children ||
|
||||||
|
newReferences != element.references) {
|
||||||
|
return new Element(
|
||||||
|
element.name, newAttributes, newInputs, newOutputs, newChildren, newReferences,
|
||||||
|
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitTemplate(template: Template): Node {
|
||||||
|
const newAttributes = transformAll(this, template.attributes);
|
||||||
|
const newInputs = transformAll(this, template.inputs);
|
||||||
|
const newChildren = transformAll(this, template.children);
|
||||||
|
const newReferences = transformAll(this, template.references);
|
||||||
|
const newVariables = transformAll(this, template.variables);
|
||||||
|
if (newAttributes != template.attributes || newInputs != template.inputs ||
|
||||||
|
newChildren != template.children || newVariables != template.variables ||
|
||||||
|
newReferences != template.references) {
|
||||||
|
return new Template(
|
||||||
|
newAttributes, newInputs, newChildren, newReferences, newVariables, template.sourceSpan,
|
||||||
|
template.startSourceSpan, template.endSourceSpan);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitContent(content: Content): Node { return content; }
|
||||||
|
|
||||||
|
visitVariable(variable: Variable): Node { return variable; }
|
||||||
|
visitReference(reference: Reference): Node { return reference; }
|
||||||
|
visitAttribute(attribute: TextAttribute): Node { return attribute; }
|
||||||
|
visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; }
|
||||||
|
visitBoundEvent(attribute: BoundEvent): Node { return attribute; }
|
||||||
|
visitText(text: Text): Node { return text; }
|
||||||
|
visitBoundText(text: BoundText): Node { return text; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function visitAll<Result>(visitor: Visitor<Result>, nodes: Node[]): Result[] {
|
||||||
|
const result: Result[] = [];
|
||||||
|
if (visitor.visit) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const newNode = visitor.visit(node) || node.visit(visitor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const newNode = node.visit(visitor);
|
||||||
|
if (newNode) {
|
||||||
|
result.push(newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformAll<Result extends Node>(
|
||||||
|
visitor: Visitor<Node>, nodes: Result[]): Result[] {
|
||||||
|
const result: Result[] = [];
|
||||||
|
let changed = false;
|
||||||
|
for (const node of nodes) {
|
||||||
|
const newNode = node.visit(visitor);
|
||||||
|
if (newNode) {
|
||||||
|
result.push(newNode as Result);
|
||||||
|
}
|
||||||
|
changed = changed || newNode != node;
|
||||||
|
}
|
||||||
|
return changed ? result : nodes;
|
||||||
|
}
|
|
@ -31,14 +31,8 @@ export class Identifiers {
|
||||||
|
|
||||||
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
||||||
|
|
||||||
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
|
|
||||||
|
|
||||||
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
|
|
||||||
|
|
||||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
||||||
|
|
||||||
static directiveInput: o.ExternalReference = {name: 'ɵi', moduleName: CORE};
|
|
||||||
|
|
||||||
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||||
|
|
||||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||||
|
@ -77,10 +71,6 @@ export class Identifiers {
|
||||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||||
|
|
||||||
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
|
|
||||||
|
|
||||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
|
||||||
|
|
||||||
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
|
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
|
||||||
|
|
||||||
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
|
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
|
||||||
|
|
|
@ -15,18 +15,18 @@ import {OutputContext} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
|
||||||
const EMPTY_ARRAY = o.literalArr([]);
|
|
||||||
|
|
||||||
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||||
if (Array.isArray(meta)) {
|
if (Array.isArray(meta)) {
|
||||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||||
} else if (meta instanceof StaticSymbol) {
|
|
||||||
return ctx.importExpr(meta);
|
|
||||||
} else if (meta == null) {
|
|
||||||
return o.literal(meta);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
|
||||||
}
|
}
|
||||||
|
if (meta instanceof StaticSymbol) {
|
||||||
|
return ctx.importExpr(meta);
|
||||||
|
}
|
||||||
|
if (meta == null) {
|
||||||
|
return o.literal(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileNgModule(
|
export function compileNgModule(
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {StaticReflector} from '../aot/static_reflector';
|
import {CompileNgModuleMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {OutputContext} from '../util';
|
import {OutputContext} from '../util';
|
||||||
|
|
|
@ -6,15 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
import {CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||||
import {CompileReflector} from '../compile_reflector';
|
import {CompileReflector} from '../compile_reflector';
|
||||||
import {DefinitionKind} from '../constant_pool';
|
import {DefinitionKind} from '../constant_pool';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {OutputContext, error} from '../util';
|
import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
import {BUILD_OPTIMIZER_COLOCATE} from './r3_types';
|
import {createFactory} from './r3_view_compiler_local';
|
||||||
import {createFactory} from './r3_view_compiler';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a pipe definition to the output context.
|
* Write a pipe definition to the output context.
|
||||||
|
@ -30,11 +29,11 @@ export function compilePipe(
|
||||||
definitionMapValues.push(
|
definitionMapValues.push(
|
||||||
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||||
|
|
||||||
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
// e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }`
|
||||||
const templateFactory = createFactory(pipe.type, outputCtx, reflector, []);
|
const templateFactory = createFactory(pipe.type, outputCtx, reflector, []);
|
||||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||||
|
|
||||||
// e.g. pure: true
|
// e.g. `pure: true`
|
||||||
if (pipe.pure) {
|
if (pipe.pure) {
|
||||||
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
/**
|
||||||
|
* @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 html from '../ml_parser/ast';
|
||||||
|
import {replaceNgsp} from '../ml_parser/html_whitespaces';
|
||||||
|
import {isNgTemplate} from '../ml_parser/tags';
|
||||||
|
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||||
|
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||||
|
import {BindingParser, BoundProperty} from '../template_parser/binding_parser';
|
||||||
|
// TODO(chuckj): Refactor binding parser to not have a dependency on template_ast.
|
||||||
|
import {BoundEventAst, VariableAst} from '../template_parser/template_ast';
|
||||||
|
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
|
||||||
|
|
||||||
|
import * as t from './r3_ast';
|
||||||
|
|
||||||
|
const BIND_NAME_REGEXP =
|
||||||
|
/^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/;
|
||||||
|
|
||||||
|
// Group 1 = "bind-"
|
||||||
|
const KW_BIND_IDX = 1;
|
||||||
|
// Group 2 = "let-"
|
||||||
|
const KW_LET_IDX = 2;
|
||||||
|
// Group 3 = "ref-/#"
|
||||||
|
const KW_REF_IDX = 3;
|
||||||
|
// Group 4 = "on-"
|
||||||
|
const KW_ON_IDX = 4;
|
||||||
|
// Group 5 = "bindon-"
|
||||||
|
const KW_BINDON_IDX = 5;
|
||||||
|
// Group 6 = "@"
|
||||||
|
const KW_AT_IDX = 6;
|
||||||
|
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
|
||||||
|
const IDENT_KW_IDX = 7;
|
||||||
|
// Group 8 = identifier inside [()]
|
||||||
|
const IDENT_BANANA_BOX_IDX = 8;
|
||||||
|
// Group 9 = identifier inside []
|
||||||
|
const IDENT_PROPERTY_IDX = 9;
|
||||||
|
// Group 10 = identifier inside ()
|
||||||
|
const IDENT_EVENT_IDX = 10;
|
||||||
|
|
||||||
|
const TEMPLATE_ATTR_PREFIX = '*';
|
||||||
|
const CLASS_ATTR = 'class';
|
||||||
|
// Default selector used by `<ng-content>` if none specified
|
||||||
|
const DEFAULT_CONTENT_SELECTOR = '*';
|
||||||
|
|
||||||
|
export class HtmlToTemplateTransform implements html.Visitor {
|
||||||
|
errors: ParseError[];
|
||||||
|
|
||||||
|
// Selectors for the `ng-content` tags. Only non `*` selectors are recorded here
|
||||||
|
ngContentSelectors: string[] = [];
|
||||||
|
// Any `<ng-content>` in the template ?
|
||||||
|
hasNgContent = false;
|
||||||
|
|
||||||
|
constructor(private bindingParser: BindingParser) {}
|
||||||
|
|
||||||
|
// HTML visitor
|
||||||
|
visitElement(element: html.Element): t.Node|null {
|
||||||
|
const preparsedElement = preparseElement(element);
|
||||||
|
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||||
|
preparsedElement.type === PreparsedElementType.STYLE) {
|
||||||
|
// Skipping <script> for security reasons
|
||||||
|
// Skipping <style> as we already processed them
|
||||||
|
// in the StyleCompiler
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
|
||||||
|
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
|
||||||
|
// Skipping stylesheets with either relative urls or package scheme as we already processed
|
||||||
|
// them in the StyleCompiler
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the element is a `<ng-template>`
|
||||||
|
const isTemplateElement = isNgTemplate(element.name);
|
||||||
|
|
||||||
|
const matchableAttributes: [string, string][] = [];
|
||||||
|
const boundProperties: BoundProperty[] = [];
|
||||||
|
const boundEvents: t.BoundEvent[] = [];
|
||||||
|
const variables: t.Variable[] = [];
|
||||||
|
const references: t.Reference[] = [];
|
||||||
|
const attributes: t.TextAttribute[] = [];
|
||||||
|
|
||||||
|
const templateMatchableAttributes: [string, string][] = [];
|
||||||
|
let inlineTemplateSourceSpan: ParseSourceSpan;
|
||||||
|
const templateBoundProperties: BoundProperty[] = [];
|
||||||
|
const templateVariables: t.Variable[] = [];
|
||||||
|
|
||||||
|
// Whether the element has any *-attribute
|
||||||
|
let elementHasInlineTemplate = false;
|
||||||
|
|
||||||
|
for (const attribute of element.attrs) {
|
||||||
|
let hasBinding = false;
|
||||||
|
const normalizedName = normalizeAttributeName(attribute.name);
|
||||||
|
|
||||||
|
// `*attr` defines template bindings
|
||||||
|
let isTemplateBinding = false;
|
||||||
|
|
||||||
|
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||||
|
if (elementHasInlineTemplate) {
|
||||||
|
this.reportError(
|
||||||
|
`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`,
|
||||||
|
attribute.sourceSpan);
|
||||||
|
}
|
||||||
|
isTemplateBinding = true;
|
||||||
|
elementHasInlineTemplate = true;
|
||||||
|
const templateBindingsSource = attribute.value;
|
||||||
|
const prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||||
|
|
||||||
|
const oldVariables: VariableAst[] = [];
|
||||||
|
|
||||||
|
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||||
|
|
||||||
|
this.bindingParser.parseInlineTemplateBinding(
|
||||||
|
prefixToken !, templateBindingsSource !, attribute.sourceSpan,
|
||||||
|
templateMatchableAttributes, templateBoundProperties, oldVariables);
|
||||||
|
|
||||||
|
templateVariables.push(
|
||||||
|
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||||
|
} else {
|
||||||
|
// Check for variables, events, property bindings, interpolation
|
||||||
|
hasBinding = this.parseAttribute(
|
||||||
|
isTemplateElement, attribute, matchableAttributes, boundProperties, boundEvents,
|
||||||
|
variables, references);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasBinding && !isTemplateBinding) {
|
||||||
|
// don't include the bindings as attributes as well in the AST
|
||||||
|
attributes.push(this.visitAttribute(attribute) as t.TextAttribute);
|
||||||
|
matchableAttributes.push([attribute.name, attribute.value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const children: t.Node[] =
|
||||||
|
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
|
||||||
|
|
||||||
|
let parsedElement: t.Node|undefined;
|
||||||
|
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||||
|
// `<ng-content>`
|
||||||
|
this.hasNgContent = true;
|
||||||
|
|
||||||
|
if (element.children && !element.children.every(isEmptyTextNode)) {
|
||||||
|
this.reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selector = preparsedElement.selectAttr;
|
||||||
|
|
||||||
|
let attributes: t.TextAttribute[] = element.attrs.map(attribute => {
|
||||||
|
return new t.TextAttribute(
|
||||||
|
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectorIndex =
|
||||||
|
selector === DEFAULT_CONTENT_SELECTOR ? 0 : this.ngContentSelectors.push(selector);
|
||||||
|
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
|
||||||
|
} else if (isTemplateElement) {
|
||||||
|
// `<ng-template>`
|
||||||
|
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||||
|
parsedElement = new t.Template(
|
||||||
|
attributes, boundAttributes, children, references, variables, element.sourceSpan,
|
||||||
|
element.startSourceSpan, element.endSourceSpan);
|
||||||
|
} else {
|
||||||
|
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||||
|
|
||||||
|
parsedElement = new t.Element(
|
||||||
|
element.name, attributes, boundAttributes, boundEvents, children, references,
|
||||||
|
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementHasInlineTemplate) {
|
||||||
|
const attributes: t.TextAttribute[] = [];
|
||||||
|
|
||||||
|
templateMatchableAttributes.forEach(
|
||||||
|
([name, value]) =>
|
||||||
|
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
|
||||||
|
|
||||||
|
const boundAttributes = this.createBoundAttributes('ng-template', templateBoundProperties);
|
||||||
|
parsedElement = new t.Template(
|
||||||
|
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
|
||||||
|
element.startSourceSpan, element.endSourceSpan);
|
||||||
|
}
|
||||||
|
return parsedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitAttribute(attribute: html.Attribute): t.Node {
|
||||||
|
return new t.TextAttribute(
|
||||||
|
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitText(text: html.Text): t.Node {
|
||||||
|
const valueNoNgsp = replaceNgsp(text.value);
|
||||||
|
const expr = this.bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan);
|
||||||
|
return expr ? new t.BoundText(expr, text.sourceSpan) : new t.Text(valueNoNgsp, text.sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitComment(comment: html.Comment): null { return null; }
|
||||||
|
|
||||||
|
visitExpansion(expansion: html.Expansion): null { return null; }
|
||||||
|
|
||||||
|
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
|
||||||
|
|
||||||
|
private createBoundAttributes(elementName: string, boundProperties: BoundProperty[]):
|
||||||
|
t.BoundAttribute[] {
|
||||||
|
const literalProperties = boundProperties.filter(prop => !prop.isLiteral);
|
||||||
|
|
||||||
|
return literalProperties.map(property => {
|
||||||
|
// TODO(vicb): get ride of the boundProperty (from TemplateAst)
|
||||||
|
const boundProp = this.bindingParser.createElementPropertyAst(elementName, property);
|
||||||
|
return new t.BoundAttribute(
|
||||||
|
boundProp.name, boundProp.type as any as t.PropertyBindingType, boundProp.securityContext,
|
||||||
|
boundProp.value, boundProp.unit, boundProp.sourceSpan);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseAttribute(
|
||||||
|
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
|
||||||
|
boundProperties: BoundProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
|
||||||
|
references: t.Reference[]) {
|
||||||
|
const name = normalizeAttributeName(attribute.name);
|
||||||
|
const value = attribute.value;
|
||||||
|
const srcSpan = attribute.sourceSpan;
|
||||||
|
|
||||||
|
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||||
|
let hasBinding = false;
|
||||||
|
|
||||||
|
if (bindParts) {
|
||||||
|
hasBinding = true;
|
||||||
|
if (bindParts[KW_BIND_IDX] != null) {
|
||||||
|
this.bindingParser.parsePropertyBinding(
|
||||||
|
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||||
|
|
||||||
|
} else if (bindParts[KW_LET_IDX]) {
|
||||||
|
if (isTemplateElement) {
|
||||||
|
const identifier = bindParts[IDENT_KW_IDX];
|
||||||
|
this.parseVariable(identifier, value, srcSpan, variables);
|
||||||
|
} else {
|
||||||
|
this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (bindParts[KW_REF_IDX]) {
|
||||||
|
const identifier = bindParts[IDENT_KW_IDX];
|
||||||
|
this.parseReference(identifier, value, srcSpan, references);
|
||||||
|
|
||||||
|
} else if (bindParts[KW_ON_IDX]) {
|
||||||
|
const events: BoundEventAst[] = [];
|
||||||
|
this.bindingParser.parseEvent(
|
||||||
|
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
|
||||||
|
addEvents(events, boundEvents);
|
||||||
|
} else if (bindParts[KW_BINDON_IDX]) {
|
||||||
|
this.bindingParser.parsePropertyBinding(
|
||||||
|
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||||
|
this.parseAssignmentEvent(
|
||||||
|
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||||
|
} else if (bindParts[KW_AT_IDX]) {
|
||||||
|
this.bindingParser.parseLiteralAttr(
|
||||||
|
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||||
|
|
||||||
|
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||||
|
this.bindingParser.parsePropertyBinding(
|
||||||
|
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
|
||||||
|
boundProperties);
|
||||||
|
this.parseAssignmentEvent(
|
||||||
|
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||||
|
|
||||||
|
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||||
|
this.bindingParser.parsePropertyBinding(
|
||||||
|
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
|
||||||
|
boundProperties);
|
||||||
|
|
||||||
|
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||||
|
const events: BoundEventAst[] = [];
|
||||||
|
this.bindingParser.parseEvent(
|
||||||
|
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
|
||||||
|
addEvents(events, boundEvents);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||||
|
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private parseVariable(
|
||||||
|
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
|
||||||
|
if (identifier.indexOf('-') > -1) {
|
||||||
|
this.reportError(`"-" is not allowed in variable names`, sourceSpan);
|
||||||
|
}
|
||||||
|
variables.push(new t.Variable(identifier, value, sourceSpan));
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseReference(
|
||||||
|
identifier: string, value: string, sourceSpan: ParseSourceSpan, references: t.Reference[]) {
|
||||||
|
if (identifier.indexOf('-') > -1) {
|
||||||
|
this.reportError(`"-" is not allowed in reference names`, sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
references.push(new t.Reference(identifier, value, sourceSpan));
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseAssignmentEvent(
|
||||||
|
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||||
|
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
|
||||||
|
const events: BoundEventAst[] = [];
|
||||||
|
this.bindingParser.parseEvent(
|
||||||
|
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
|
||||||
|
addEvents(events, boundEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private reportError(
|
||||||
|
message: string, sourceSpan: ParseSourceSpan,
|
||||||
|
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
|
||||||
|
this.errors.push(new ParseError(sourceSpan, message, level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonBindableVisitor implements html.Visitor {
|
||||||
|
visitElement(ast: html.Element): t.Element|null {
|
||||||
|
const preparsedElement = preparseElement(ast);
|
||||||
|
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||||
|
preparsedElement.type === PreparsedElementType.STYLE ||
|
||||||
|
preparsedElement.type === PreparsedElementType.STYLESHEET) {
|
||||||
|
// Skipping <script> for security reasons
|
||||||
|
// Skipping <style> and stylesheets as we already processed them
|
||||||
|
// in the StyleCompiler
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children: t.Node[] = html.visitAll(this, ast.children, null);
|
||||||
|
return new t.Element(
|
||||||
|
ast.name, html.visitAll(this, ast.attrs) as t.TextAttribute[],
|
||||||
|
/* inputs */[], /* outputs */[], children, /* references */[], ast.sourceSpan,
|
||||||
|
ast.startSourceSpan, ast.endSourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitComment(comment: html.Comment): any { return null; }
|
||||||
|
|
||||||
|
visitAttribute(attribute: html.Attribute): t.TextAttribute {
|
||||||
|
return new t.TextAttribute(attribute.name, attribute.value, attribute.sourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitText(text: html.Text): t.Text { return new t.Text(text.value, text.sourceSpan); }
|
||||||
|
|
||||||
|
visitExpansion(expansion: html.Expansion): any { return null; }
|
||||||
|
|
||||||
|
visitExpansionCase(expansionCase: html.ExpansionCase): any { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
||||||
|
|
||||||
|
function normalizeAttributeName(attrName: string): string {
|
||||||
|
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEvents(events: BoundEventAst[], boundEvents: t.BoundEvent[]) {
|
||||||
|
boundEvents.push(
|
||||||
|
...events.map(e => new t.BoundEvent(e.name, e.handler, e.target, e.phase, e.sourceSpan)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyTextNode(node: html.Node): boolean {
|
||||||
|
return node instanceof html.Text && node.value.trim().length == 0;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -51,14 +51,20 @@ export class BoundProperty {
|
||||||
* Parses bindings in templates and in the directive host area.
|
* Parses bindings in templates and in the directive host area.
|
||||||
*/
|
*/
|
||||||
export class BindingParser {
|
export class BindingParser {
|
||||||
pipesByName: Map<string, CompilePipeSummary> = new Map();
|
pipesByName: Map<string, CompilePipeSummary>|null = null;
|
||||||
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
|
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
||||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[],
|
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null,
|
||||||
private _targetErrors: ParseError[]) {
|
private _targetErrors: ParseError[]) {
|
||||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
// When the `pipes` parameter is `null`, do not check for used pipes
|
||||||
|
// This is used in IVY when we might not know the available pipes at compile time
|
||||||
|
if (pipes) {
|
||||||
|
const pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||||
|
pipes.forEach(pipe => pipesByName.set(pipe.name, pipe));
|
||||||
|
this.pipesByName = pipesByName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||||
|
@ -390,11 +396,11 @@ export class BindingParser {
|
||||||
|
|
||||||
// Make sure all the used pipes are known in `this.pipesByName`
|
// Make sure all the used pipes are known in `this.pipesByName`
|
||||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void {
|
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void {
|
||||||
if (ast) {
|
if (ast && this.pipesByName) {
|
||||||
const collector = new PipeCollector();
|
const collector = new PipeCollector();
|
||||||
ast.visit(collector);
|
ast.visit(collector);
|
||||||
collector.pipes.forEach((ast, pipeName) => {
|
collector.pipes.forEach((ast, pipeName) => {
|
||||||
const pipeMeta = this.pipesByName.get(pipeName);
|
const pipeMeta = this.pipesByName !.get(pipeName);
|
||||||
if (!pipeMeta) {
|
if (!pipeMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
`The pipe '${pipeName}' could not be found`,
|
`The pipe '${pipeName}' could not be found`,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {BindingParser} from '../../src/template_parser/binding_parser';
|
||||||
import {OutputContext, escapeRegExp} from '../../src/util';
|
import {OutputContext, escapeRegExp} from '../../src/util';
|
||||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util';
|
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util';
|
||||||
|
|
||||||
|
|
||||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||||
const OPERATOR =
|
const OPERATOR =
|
||||||
/!|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
/!|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
||||||
|
@ -235,7 +236,7 @@ function doCompile(
|
||||||
const errors: ParseError[] = [];
|
const errors: ParseError[] = [];
|
||||||
|
|
||||||
const hostBindingParser = new BindingParser(
|
const hostBindingParser = new BindingParser(
|
||||||
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, [], errors);
|
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, null, errors);
|
||||||
|
|
||||||
// Load all directives and pipes
|
// Load all directives and pipes
|
||||||
for (const pipeOrDirective of pipesOrDirectives) {
|
for (const pipeOrDirective of pipesOrDirectives) {
|
||||||
|
@ -278,22 +279,45 @@ export function compile(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resolver.isDirective(pipeOrDirective)) {
|
if (resolver.isDirective(pipeOrDirective)) {
|
||||||
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
const directive = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||||
if (metadata.isComponent) {
|
if (directive.isComponent) {
|
||||||
const fakeUrl = 'ng://fake-template-url.html';
|
const fakeUrl = 'ng://fake-template-url.html';
|
||||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
let htmlAst = htmlParser.parse(directive.template !.template !, fakeUrl);
|
||||||
|
|
||||||
|
// Map of StaticType by directive selectors
|
||||||
|
const directiveTypeBySel = new Map<string, any>();
|
||||||
|
|
||||||
const directives = module.transitiveModule.directives.map(
|
const directives = module.transitiveModule.directives.map(
|
||||||
dir => resolver.getDirectiveSummary(dir.reference));
|
dir => resolver.getDirectiveSummary(dir.reference));
|
||||||
|
|
||||||
|
directives.forEach(directive => {
|
||||||
|
if (directive.selector) {
|
||||||
|
directiveTypeBySel.set(directive.selector, directive.type.reference);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Map of StaticType by pipe names
|
||||||
|
const pipeTypeByName = new Map<string, any>();
|
||||||
|
|
||||||
const pipes = module.transitiveModule.pipes.map(
|
const pipes = module.transitiveModule.pipes.map(
|
||||||
pipe => resolver.getPipeSummary(pipe.reference));
|
pipe => resolver.getPipeSummary(pipe.reference));
|
||||||
const parsedTemplate = templateParser.parse(
|
|
||||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||||
|
|
||||||
|
const preserveWhitespaces = directive.template !.preserveWhitespaces;
|
||||||
|
if (!preserveWhitespaces) {
|
||||||
|
htmlAst = removeWhitespaces(htmlAst);
|
||||||
|
}
|
||||||
|
const transform = new HtmlToTemplateTransform(hostBindingParser);
|
||||||
|
const nodes = html.visitAll(transform, htmlAst.rootNodes, null);
|
||||||
|
const hasNgContent = transform.hasNgContent;
|
||||||
|
const ngContentSelectors = transform.ngContentSelectors;
|
||||||
|
|
||||||
compileComponent(
|
compileComponent(
|
||||||
outputCtx, metadata, pipes, parsedTemplate.template, reflector, hostBindingParser);
|
outputCtx, directive, nodes, hasNgContent, ngContentSelectors, reflector,
|
||||||
|
hostBindingParser, directiveTypeBySel, pipeTypeByName);
|
||||||
} else {
|
} else {
|
||||||
compileDirective(
|
compileDirective(outputCtx, directive, reflector, hostBindingParser);
|
||||||
outputCtx, metadata, reflector, hostBindingParser);
|
|
||||||
}
|
}
|
||||||
} else if (resolver.isPipe(pipeOrDirective)) {
|
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||||
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||||
|
|
|
@ -344,11 +344,11 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'ul', null, $c1$);
|
$r3$.ɵE(0, 'ul', null, $c1$);
|
||||||
$r3$.ɵC(2, MyComponent_IfDirective_Template_2, null, $c2$);
|
$r3$.ɵC(2, MyComponent_li_Template_2, null, $c2$);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
const $foo$ = $r3$.ɵld(1);
|
const $foo$ = $r3$.ɵld(1);
|
||||||
function MyComponent_IfDirective_Template_2(rf: IDENT, ctx0: IDENT) {
|
function MyComponent_li_Template_2(rf: IDENT, ctx0: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'li');
|
$r3$.ɵE(0, 'li');
|
||||||
$r3$.ɵT(1);
|
$r3$.ɵT(1);
|
||||||
|
@ -359,7 +359,7 @@ describe('compiler compliance', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: [IfDirective]
|
directives:[IfDirective]
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
|
@ -420,7 +420,7 @@ describe('compiler compliance', () => {
|
||||||
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName)));
|
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: [MyComp]
|
directives: [MyComp]
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -1147,7 +1147,7 @@ describe('compiler compliance', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MyComponentDefinition = `
|
const MyComponentDefinition = `
|
||||||
const $_c0$ = ['forOf',''];
|
const $_c0$ = ['for','','forOf',''];
|
||||||
…
|
…
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
type: MyComponent,
|
type: MyComponent,
|
||||||
|
@ -1156,14 +1156,14 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'ul');
|
$r3$.ɵE(0, 'ul');
|
||||||
$r3$.ɵC(1, MyComponent_ForOfDirective_Template_1, null, $_c0$);
|
$r3$.ɵC(1, MyComponent_li_Template_1, null, $_c0$);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_Template_1(rf: IDENT, ctx0: IDENT) {
|
function MyComponent_li_Template_1(rf: IDENT, ctx0: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'li');
|
$r3$.ɵE(0, 'li');
|
||||||
$r3$.ɵT(1);
|
$r3$.ɵT(1);
|
||||||
|
@ -1225,7 +1225,7 @@ describe('compiler compliance', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const MyComponentDefinition = `
|
const MyComponentDefinition = `
|
||||||
const $c1$ = ['forOf', ''];
|
const $c1$ = ['for', '', 'forOf', ''];
|
||||||
…
|
…
|
||||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
type: MyComponent,
|
type: MyComponent,
|
||||||
|
@ -1234,21 +1234,21 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'ul');
|
$r3$.ɵE(0, 'ul');
|
||||||
$r3$.ɵC(1, MyComponent_ForOfDirective_Template_1, null, $c1$);
|
$r3$.ɵC(1, MyComponent_li_Template_1, null, $c1$);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_Template_1(rf: IDENT, ctx0: IDENT) {
|
function MyComponent_li_Template_1(rf: IDENT, ctx0: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'li');
|
$r3$.ɵE(0, 'li');
|
||||||
$r3$.ɵE(1, 'div');
|
$r3$.ɵE(1, 'div');
|
||||||
$r3$.ɵT(2);
|
$r3$.ɵT(2);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
$r3$.ɵE(3, 'ul');
|
$r3$.ɵE(3, 'ul');
|
||||||
$r3$.ɵC(4, MyComponent_ForOfDirective_ForOfDirective_Template_4, null, $c1$);
|
$r3$.ɵC(4, MyComponent_li_li_Template_4, null, $c1$);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
|
@ -1258,8 +1258,7 @@ describe('compiler compliance', () => {
|
||||||
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
||||||
}
|
}
|
||||||
|
|
||||||
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
function MyComponent_li_li_Template_4(rf: IDENT, ctx1: IDENT) {
|
||||||
rf: IDENT, ctx1: IDENT) {
|
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'li');
|
$r3$.ɵE(0, 'li');
|
||||||
$r3$.ɵT(1);
|
$r3$.ɵT(1);
|
||||||
|
|
|
@ -51,8 +51,7 @@ describe('compiler compliance: listen()', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
const componentDef = `
|
||||||
const template = `
|
|
||||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||||
…
|
…
|
||||||
inputs:{
|
inputs:{
|
||||||
|
@ -63,9 +62,10 @@ describe('compiler compliance: listen()', () => {
|
||||||
componentOutput: 'componentOutput',
|
componentOutput: 'componentOutput',
|
||||||
originalComponentOutput: 'renamedComponentOutput'
|
originalComponentOutput: 'renamedComponentOutput'
|
||||||
}
|
}
|
||||||
});
|
});`;
|
||||||
…
|
|
||||||
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
const directiveDef = `
|
||||||
|
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||||
…
|
…
|
||||||
inputs:{
|
inputs:{
|
||||||
directiveInput: 'directiveInput',
|
directiveInput: 'directiveInput',
|
||||||
|
@ -80,7 +80,8 @@ describe('compiler compliance: listen()', () => {
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
|
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
expectEmit(result.source, componentDef, 'Incorrect component definition');
|
||||||
|
expectEmit(result.source, directiveDef, 'Incorrect directive definition');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
|
@ -44,7 +44,7 @@ describe('compiler compliance: listen()', () => {
|
||||||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'div');
|
$r3$.ɵE(0, 'div');
|
||||||
$r3$.ɵL('click', function MyComponent_Template_div_click_listener($event: $any$) {
|
$r3$.ɵL('click', function MyComponent_Template_div_click_listener($event: $any$) {
|
||||||
ctx.onClick($event);
|
ctx.onClick($event);
|
||||||
return (1 == 2);
|
return (1 == 2);
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,6 @@ describe('compiler compliance: listen()', () => {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
|
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('compiler compliance: template', () => {
|
||||||
template: \`
|
template: \`
|
||||||
<ul *ngFor="let outer of items">
|
<ul *ngFor="let outer of items">
|
||||||
<li *ngFor="let middle of outer.items">
|
<li *ngFor="let middle of outer.items">
|
||||||
<div *ngFor="let inner of items"
|
<div *ngFor="let inner of items"
|
||||||
(click)="onClick(outer, middle, inner)"
|
(click)="onClick(outer, middle, inner)"
|
||||||
[title]="format(outer, middle, inner, component)"
|
[title]="format(outer, middle, inner, component)"
|
||||||
>
|
>
|
||||||
|
@ -51,58 +51,59 @@ describe('compiler compliance: template', () => {
|
||||||
|
|
||||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||||
const template = `
|
const template = `
|
||||||
template:function MyComponent_Template(rf: IDENT, ctx: IDENT){
|
const $c0$ = ['ngFor','','ngForOf',''];
|
||||||
if (rf & 1) {
|
// ...
|
||||||
$i0$.ɵC(0, MyComponent_NgForOf_Template_0, null, _c0);
|
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
|
||||||
|
if (rf & 1) {
|
||||||
|
$i0$.ɵC(0, MyComponent_ul_Template_0, null, _c0);
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$i0$.ɵp(0, 'ngForOf', $i0$.ɵb(ctx.items));
|
$i0$.ɵp(0, 'ngForOf', $i0$.ɵb($ctx$.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
function MyComponent_NgForOf_Template_0(rf: IDENT, ctx0: IDENT) {
|
function MyComponent_ul_Template_0(rf: IDENT, $ctx0$: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$i0$.ɵE(0, 'ul');
|
$i0$.ɵE(0, 'ul');
|
||||||
$i0$.ɵC(1, MyComponent_NgForOf_NgForOf_Template_1, null, _c0);
|
$i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0);
|
||||||
$i0$.ɵe();
|
$i0$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
const $outer$ = ctx0.$implicit;
|
const $outer$ = $ctx0$.$implicit;
|
||||||
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb($outer$.items));
|
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb($outer$.items));
|
||||||
}
|
}
|
||||||
function MyComponent_NgForOf_NgForOf_Template_1(rf: IDENT, ctx1: IDENT) {
|
function MyComponent_ul_li_Template_1(rf: IDENT, $ctx1$: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$i0$.ɵE(0, 'li');
|
$i0$.ɵE(0, 'li');
|
||||||
$i0$.ɵC(1, MyComponent_NgForOf_NgForOf_NgForOf_Template_1, null, _c0);
|
$i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0);
|
||||||
$i0$.ɵe();
|
$i0$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb(ctx.items));
|
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb($ctx$.items));
|
||||||
}
|
}
|
||||||
function MyComponent_NgForOf_NgForOf_NgForOf_Template_1(rf: IDENT, ctx2: IDENT) {
|
function MyComponent_ul_li_div_Template_1(rf: IDENT, $ctx2$: IDENT) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$i0$.ɵE(0, 'div');
|
$i0$.ɵE(0, 'div');
|
||||||
$i0$.ɵL('click', function MyComponent_NgForOf_NgForOf_NgForOf_Template_1_div_click_listener($event:any){
|
$i0$.ɵL('click', function MyComponent_ul_li_div_Template_1_div_click_listener($event:any){
|
||||||
const $outer$ = ctx0.$implicit;
|
const $outer$ = $ctx0$.$implicit;
|
||||||
const $middle$ = ctx1.$implicit;
|
const $middle$ = $ctx1$.$implicit;
|
||||||
const $inner$ = ctx2.$implicit;
|
const $inner$ = $ctx2$.$implicit;
|
||||||
return ctx.onClick($outer$, $middle$, $inner$);
|
return ctx.onClick($outer$, $middle$, $inner$);
|
||||||
});
|
});
|
||||||
$i0$.ɵT(1);
|
$i0$.ɵT(1);
|
||||||
$i0$.ɵe();
|
$i0$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
const $outer$ = ctx0.$implicit;
|
const $outer$ = $ctx0$.$implicit;
|
||||||
const $middle$ = ctx1.$implicit;
|
const $middle$ = $ctx1$.$implicit;
|
||||||
const $inner$ = ctx2.$implicit;
|
const $inner$ = $ctx2$.$implicit;
|
||||||
$i0$.ɵp(0, 'title', $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, ctx.component)));
|
$i0$.ɵp(0, 'title', $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, $ctx$.component)));
|
||||||
$i0$.ɵt(1, $i0$.ɵi1(' ', ctx.format($outer$, $middle$, $inner$, ctx.component), ' '));
|
$i0$.ɵt(1, $i0$.ɵi1(' ', ctx.format($outer$, $middle$, $inner$, $ctx$.component), ' '));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
|
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
|
|
@ -17,10 +17,10 @@ import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TDat
|
||||||
|
|
||||||
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
||||||
import {assertNodeType} from './node_assert';
|
import {assertNodeType} from './node_assert';
|
||||||
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation';
|
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation';
|
||||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, DirectiveType, PipeDef, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||||
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
import {isDifferent, stringify} from './util';
|
import {isDifferent, stringify} from './util';
|
||||||
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef} from './view_ref';
|
||||||
|
@ -1721,11 +1721,18 @@ function appendToProjectionNode(
|
||||||
*
|
*
|
||||||
* @param nodeIndex
|
* @param nodeIndex
|
||||||
* @param localIndex - index under which distribution of projected nodes was memorized
|
* @param localIndex - index under which distribution of projected nodes was memorized
|
||||||
* @param selectorIndex - 0 means <ng-content> without any selector
|
* @param selectorIndex:
|
||||||
|
* - 0 when the selector is `*` (or unspecified as this is the default value),
|
||||||
|
* - 1 based index of the selector from the {@link projectionDef}
|
||||||
*/
|
*/
|
||||||
export function projection(nodeIndex: number, localIndex: number, selectorIndex: number = 0): void {
|
export function projection(
|
||||||
|
nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
|
||||||
const node = createLNode(nodeIndex, LNodeType.Projection, null, {head: null, tail: null});
|
const node = createLNode(nodeIndex, LNodeType.Projection, null, {head: null, tail: null});
|
||||||
|
|
||||||
|
if (node.tNode == null) {
|
||||||
|
node.tNode = createTNode(null, attrs || null, null);
|
||||||
|
}
|
||||||
|
|
||||||
// `<ng-content>` has no content
|
// `<ng-content>` has no content
|
||||||
isParent = false;
|
isParent = false;
|
||||||
const currentParent = node.parent;
|
const currentParent = node.parent;
|
||||||
|
|
|
@ -1334,9 +1334,6 @@
|
||||||
{
|
{
|
||||||
"name": "RecursiveAstVisitor$1"
|
"name": "RecursiveAstVisitor$1"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "RecursiveTemplateAstVisitor"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "RefCountOperator$1"
|
"name": "RefCountOperator$1"
|
||||||
},
|
},
|
||||||
|
@ -3593,6 +3590,9 @@
|
||||||
{
|
{
|
||||||
"name": "pipeBinding"
|
"name": "pipeBinding"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pipeBindingIdentifiers"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pipeDef"
|
"name": "pipeDef"
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule, NgForOf, NgIf} from '@angular/common';
|
import {CommonModule, NgForOf, NgIf} from '@angular/common';
|
||||||
import {ChangeDetectionStrategy, Component, EventEmitter, InjectFlags, Injectable, Input, IterableDiffers, NgModule, Output, createInjector, defineInjector, inject, ɵComponentDef as ComponentDef, ɵComponentType as ComponentType, ɵDirectiveDef as DirectiveDef, ɵDirectiveType as DirectiveType, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefaultIterableDiffers as defaultIterableDiffers, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
import {Component, Injectable, IterableDiffers, NgModule, defineInjector, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
export class Todo {
|
export class Todo {
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
|
|
Loading…
Reference in New Issue