diff --git a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts index 62bebcfd19..a97ffe38a1 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts @@ -131,7 +131,7 @@ function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInj } }); const token = new WrappedNodeExpr(tokenExpr); - useType.push({token, optional, self, skipSelf}); + useType.push({token, optional, self, skipSelf, attribute: false}); }); return useType; } @@ -142,6 +142,7 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep { optional: false, self: false, skipSelf: false, + attribute: false, }; function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void { diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 14845f2c07..c4cbf8a1dd 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -2021,6 +2021,7 @@ describe('ngc transformer command-line', () => { const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); expect(exitCode).toBe(0, 'Compile failed'); expect(emittedFile('hello-world.js')).toContain('ngComponentDef'); + expect(emittedFile('hello-world.js')).toContain('HelloWorldComponent_Factory'); }); it('should emit an injection of a string token', () => { diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 775b8d5d8b..829691e6e8 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -25,7 +25,7 @@ import {ParseError} from '../parse_util'; import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler'; import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler'; import {HtmlToTemplateTransform} from '../render3/r3_template_transform'; -import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler_local'; +import {compileComponentFromRender2 as compileIvyComponent, compileDirectiveFromRender2 as compileIvyDirective} from '../render3/view/compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {SummaryResolver} from '../summary_resolver'; diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 6f6580fd10..1c704baf3f 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -361,3 +361,20 @@ export function parseSelectorToR3Selector(selector: string): R3CssSelectorList { const selectors = CssSelector.parse(selector); return selectors.map(parserSelectorToR3Selector); } + +// Pasted from render3/interfaces/definition since it cannot be referenced directly +/** + * Flags passed into template functions to determine which blocks (i.e. creation, update) + * should be executed. + * + * Typically, a template runs both the creation block and the update block on initialization and + * subsequent runs only execute the update block. However, dynamically created views require that + * the creation block be executed separately from the update block (for backwards compat). + */ +export const enum RenderFlags { + /* Whether to run the creation block (e.g. create elements and directives) */ + Create = 0b01, + + /* Whether to run the update block (e.g. refresh bindings) */ + Update = 0b10 +} diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index a29d18f404..0f01f881b0 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -30,6 +30,7 @@ export interface IvyInjectableDep { optional: boolean; self: boolean; skipSelf: boolean; + attribute: boolean; } export interface IvyInjectableMetadata { diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts new file mode 100644 index 0000000000..78d7e3c75c --- /dev/null +++ b/packages/compiler/src/render3/r3_factory.ts @@ -0,0 +1,288 @@ +/** + * @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 {StaticSymbol} from '../aot/static_symbol'; +import {CompileTypeMetadata, tokenReference} from '../compile_metadata'; +import {CompileReflector} from '../compile_reflector'; +import {InjectFlags} from '../core'; +import {Identifiers} from '../identifiers'; +import * as o from '../output/output_ast'; +import {Identifiers as R3} from '../render3/r3_identifiers'; +import {OutputContext} from '../util'; + +import {unsupported} from './view/util'; + +/** + * Metadata required by the factory generator to generate a `factory` function for a type. + */ +export interface R3FactoryMetadata { + /** + * String name of the type being generated (used to name the factory function). + */ + name: string; + + /** + * An expression representing the function (or constructor) which will instantiate the requested + * type. + * + * This could be a reference to a constructor type, or to a user-defined factory function. The + * `useNew` property determines whether it will be called as a constructor or not. + */ + fnOrClass: o.Expression; + + /** + * Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it + * may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata` + * for those parameters. + */ + deps: R3DependencyMetadata[]; + + /** + * Whether to interpret `fnOrClass` as a constructor function (`useNew: true`) or as a factory + * (`useNew: false`). + */ + useNew: boolean; + + + /** + * An expression for the function which will be used to inject dependencies. The API of this + * function could be different, and other options control how it will be invoked. + */ + injectFn: o.ExternalReference; + + /** + * Whether the `injectFn` given above accepts a 2nd parameter indicating the default value to + * be used to resolve missing @Optional dependencies. + * + * If the optional parameter is used, injectFn for an optional dependency will be invoked as: + * `injectFn(token, null, flags)`. + * + * If it's not used, injectFn for an optional dependency will be invoked as: + * `injectFn(token, flags)`. The Optional flag will indicate that injectFn should select a default + * value if it cannot satisfy the injection request for the token. + */ + useOptionalParam: boolean; + + /** + * If present, the return of the factory function will be an array with the injected value in the + * 0th position and the extra results included in subsequent positions. + * + * Occasionally APIs want to construct additional values when the factory function is called. The + * paradigm there is to have the factory function return an array, with the DI-created value as + * well as other values. Specifying `extraResults` enables this functionality. + */ + extraResults?: o.Expression[]; +} + +/** + * Resolved type of a dependency. + * + * Occasionally, dependencies will have special significance which is known statically. In that + * case the `R3ResolvedDependencyType` informs the factory generator that a particular dependency + * should be generated specially (usually by calling a special injection function instead of the + * standard one). + */ +export enum R3ResolvedDependencyType { + /** + * A normal token dependency. + */ + Token = 0, + + /** + * The dependency is for an attribute. + * + * The token expression is a string representing the attribute name. + */ + Attribute = 1, + + /** + * The dependency is for the `Injector` type itself. + */ + Injector = 2, + + /** + * The dependency is for `ElementRef`. + */ + ElementRef = 3, + + /** + * The dependency is for `TemplateRef`. + */ + TemplateRef = 4, + + /** + * The dependency is for `ViewContainerRef`. + */ + ViewContainerRef = 5, +} + +/** + * Metadata representing a single dependency to be injected into a constructor or function call. + */ +export interface R3DependencyMetadata { + /** + * An expression representing the token or value to be injected. + */ + token: o.Expression; + + /** + * An enum indicating whether this dependency has special meaning to Angular and needs to be + * injected specially. + */ + resolved: R3ResolvedDependencyType; + + /** + * Whether the dependency has an @Host qualifier. + */ + host: boolean; + + /** + * Whether the dependency has an @Optional qualifier. + */ + optional: boolean; + + /** + * Whether the dependency has an @Self qualifier. + */ + self: boolean; + + /** + * Whether the dependency has an @SkipSelf qualifier. + */ + skipSelf: boolean; +} + +/** + * Construct a factory function expression for the given `R3FactoryMetadata`. + */ +export function compileFactoryFunction(meta: R3FactoryMetadata): o.Expression { + // Each dependency becomes an invocation of an inject*() function. + const args = + meta.deps.map(dep => compileInjectDependency(dep, meta.injectFn, meta.useOptionalParam)); + + // The overall result depends on whether this is construction or function invocation. + const expr = meta.useNew ? new o.InstantiateExpr(meta.fnOrClass, args) : + new o.InvokeFunctionExpr(meta.fnOrClass, args); + + // If `extraResults` is specified, then the result is an array consisting of the instantiated + // value plus any extra results. + const retExpr = + meta.extraResults === undefined ? expr : o.literalArr([expr, ...meta.extraResults]); + return o.fn( + [], [new o.ReturnStatement(retExpr)], o.INFERRED_TYPE, undefined, `${meta.name}_Factory`); +} + +function compileInjectDependency( + dep: R3DependencyMetadata, injectFn: o.ExternalReference, + useOptionalParam: boolean): o.Expression { + // Interpret the dependency according to its resolved type. + switch (dep.resolved) { + case R3ResolvedDependencyType.Token: + case R3ResolvedDependencyType.Injector: { + // Build up the injection flags according to the metadata. + const flags = InjectFlags.Default | (dep.self ? InjectFlags.Self : 0) | + (dep.skipSelf ? InjectFlags.SkipSelf : 0) | (dep.host ? InjectFlags.Host : 0) | + (dep.optional ? InjectFlags.Optional : 0); + // Determine the token used for injection. In almost all cases this is the given token, but + // if the dependency is resolved to the `Injector` then the special `INJECTOR` token is used + // instead. + let token: o.Expression = dep.token; + if (dep.resolved === R3ResolvedDependencyType.Injector) { + token = o.importExpr(Identifiers.INJECTOR); + } + + // Build up the arguments to the injectFn call. + const injectArgs = [dep.token]; + // If this dependency is optional or otherwise has non-default flags, then additional + // parameters describing how to inject the dependency must be passed to the inject function + // that's being used. + if (flags !== InjectFlags.Default || dep.optional) { + // Either the dependency is optional, or non-default flags are in use. Either of these cases + // necessitates adding an argument for the default value if such an argument is required + // by the inject function (useOptionalParam === true). + if (useOptionalParam) { + // The inject function requires a default value parameter. + injectArgs.push(dep.optional ? o.NULL_EXPR : o.literal(undefined)); + } + // The last parameter is always the InjectFlags, which only need to be specified if they're + // non-default. + if (flags !== InjectFlags.Default) { + injectArgs.push(o.literal(flags)); + } + } + return o.importExpr(injectFn).callFn(injectArgs); + } + case R3ResolvedDependencyType.Attribute: + // In the case of attributes, the attribute name in question is given as the token. + return o.importExpr(R3.injectAttribute).callFn([dep.token]); + case R3ResolvedDependencyType.ElementRef: + return o.importExpr(R3.injectElementRef).callFn([]); + case R3ResolvedDependencyType.TemplateRef: + return o.importExpr(R3.injectTemplateRef).callFn([]); + case R3ResolvedDependencyType.ViewContainerRef: + return o.importExpr(R3.injectViewContainerRef).callFn([]); + default: + return unsupported( + `Unknown R3ResolvedDependencyType: ${R3ResolvedDependencyType[dep.resolved]}`); + } +} + +/** + * A helper function useful for extracting `R3DependencyMetadata` from a Render2 + * `CompileTypeMetadata` instance. + */ +export function dependenciesFromGlobalMetadata( + type: CompileTypeMetadata, outputCtx: OutputContext, + reflector: CompileReflector): R3DependencyMetadata[] { + // Use the `CompileReflector` to look up references to some well-known Angular types. These will + // be compared with the token to statically determine whether the token has significance to + // Angular, and set the correct `R3ResolvedDependencyType` as a result. + const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef); + const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef); + const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef); + const injectorRef = reflector.resolveExternalReference(Identifiers.Injector); + + // Iterate through the type's DI dependencies and produce `R3DependencyMetadata` for each of them. + const deps: R3DependencyMetadata[] = []; + for (let dependency of type.diDeps) { + if (dependency.token) { + const tokenRef = tokenReference(dependency.token); + let resolved: R3ResolvedDependencyType = R3ResolvedDependencyType.Token; + if (tokenRef === elementRef) { + resolved = R3ResolvedDependencyType.ElementRef; + } else if (tokenRef === templateRef) { + resolved = R3ResolvedDependencyType.TemplateRef; + } else if (tokenRef === viewContainerRef) { + resolved = R3ResolvedDependencyType.ViewContainerRef; + } else if (tokenRef === injectorRef) { + resolved = R3ResolvedDependencyType.Injector; + } else if (dependency.isAttribute) { + resolved = R3ResolvedDependencyType.Attribute; + } + + // In the case of most dependencies, the token will be a reference to a type. Sometimes, + // however, it can be a string, in the case of older Angular code or @Attribute injection. + const token = + tokenRef instanceof StaticSymbol ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef); + + // Construct the dependency. + deps.push({ + token, + resolved, + host: !!dependency.isHost, + optional: !!dependency.isOptional, + self: !!dependency.isSelf, + skipSelf: !!dependency.isSkipSelf, + }); + } else { + unsupported('dependency without a token'); + } + } + + return deps; +} diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index b0a6c9af22..030a2b9b94 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -90,11 +90,21 @@ export class Identifiers { static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE}; + static ComponentDef: o.ExternalReference = { + name: 'ComponentDef', + moduleName: CORE, + }; + static defineDirective: o.ExternalReference = { name: 'ɵdefineDirective', moduleName: CORE, }; + static DirectiveDef: o.ExternalReference = { + name: 'DirectiveDef', + moduleName: CORE, + }; + static defineInjector: o.ExternalReference = { name: 'defineInjector', moduleName: CORE, diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index f1a849c33c..98c75db184 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -12,8 +12,9 @@ import {DefinitionKind} from '../constant_pool'; import * as o from '../output/output_ast'; import {OutputContext, error} from '../util'; +import {compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; -import {createFactory} from './r3_view_compiler_local'; + /** * Write a pipe definition to the output context. @@ -30,7 +31,14 @@ export function compilePipe( {key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false}); // e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }` - const templateFactory = createFactory(pipe.type, outputCtx, reflector, []); + const deps = dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector); + const templateFactory = compileFactoryFunction({ + name: identifierName(pipe.type) !, + fnOrClass: outputCtx.importExpr(pipe.type.reference), deps, + useNew: true, + injectFn: R3.directiveInject, + useOptionalParam: false, + }); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); // e.g. `pure: true` diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts new file mode 100644 index 0000000000..71f1624a69 --- /dev/null +++ b/packages/compiler/src/render3/view/api.ts @@ -0,0 +1,178 @@ +/** + * @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 o from '../../output/output_ast'; +import {ParseSourceSpan} from '../../parse_util'; +import * as t from '../r3_ast'; +import {R3DependencyMetadata} from '../r3_factory'; + +/** + * Information needed to compile a directive for the render3 runtime. + */ +export interface R3DirectiveMetadata { + /** + * Name of the directive type. + */ + name: string; + + /** + * An expression representing a reference to the directive itself. + */ + type: o.Expression; + + /** + * A source span for the directive type. + */ + typeSourceSpan: ParseSourceSpan; + + /** + * Dependencies of the directive's constructor. + */ + deps: R3DependencyMetadata[]; + + /** + * Unparsed selector of the directive, or `null` if there was no selector. + */ + selector: string|null; + + /** + * Information about the content queries made by the directive. + */ + queries: R3QueryMetadata[]; + + /** + * Mappings indicating how the directive interacts with its host element (host bindings, + * listeners, etc). + */ + host: { + /** + * A mapping of attribute binding keys to unparsed expressions. + */ + attributes: {[key: string]: string}; + + /** + * A mapping of event binding keys to unparsed expressions. + */ + listeners: {[key: string]: string}; + + /** + * A mapping of property binding keys to unparsed expressions. + */ + properties: {[key: string]: string}; + }; + + /** + * A mapping of input field names to the property names. + */ + inputs: {[field: string]: string}; + + /** + * A mapping of output field names to the property names. + */ + outputs: {[field: string]: string}; +} + +/** + * Information needed to compile a component for the render3 runtime. + */ +export interface R3ComponentMetadata extends R3DirectiveMetadata { + /** + * Information about the component's template. + */ + template: { + /** + * Parsed nodes of the template. + */ + nodes: t.Node[]; + + /** + * Whether the template includes tags. + */ + hasNgContent: boolean; + + /** + * Selectors found in the tags in the template. + */ + ngContentSelectors: string[]; + }; + + /** + * Information about usage of specific lifecycle events which require special treatment in the + * code generator. + */ + lifecycle: { + /** + * Whether the component uses NgOnChanges. + */ + usesOnChanges: boolean; + }; + + /** + * Information about the view queries made by the component. + */ + viewQueries: R3QueryMetadata[]; + + /** + * A map of pipe names to an expression referencing the pipe type which are in the scope of the + * compilation. + */ + pipes: Map; + + /** + * A map of directive selectors to an expression referencing the directive type which are in the + * scope of the compilation. + */ + directives: Map; +} + +/** + * Information needed to compile a query (view or content). + */ +export interface R3QueryMetadata { + /** + * Name of the property on the class to update with query results. + */ + propertyName: string; + + /** + * Whether to read only the first matching result, or an array of results. + */ + first: boolean; + + /** + * Either an expression representing a type for the query predicate, or a set of string selectors. + */ + predicate: o.Expression|string[]; + + /** + * Whether to include only direct children or all descendants. + */ + descendants: boolean; + + /** + * An expression representing a type to read from each matched node, or null if the node itself + * is to be returned. + */ + read: o.Expression|null; +} + +/** + * Output of render3 directive compilation. + */ +export interface R3DirectiveDef { + expression: o.Expression; + type: o.Type; +} + +/** + * Output of render3 component compilation. + */ +export interface R3ComponentDef { + expression: o.Expression; + type: o.Type; +} diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts new file mode 100644 index 0000000000..cba9e7be1f --- /dev/null +++ b/packages/compiler/src/render3/view/compiler.ts @@ -0,0 +1,444 @@ +/** + * @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 {StaticSymbol} from '../../aot/static_symbol'; +import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../../compile_metadata'; +import {CompileReflector} from '../../compile_reflector'; +import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; +import {ConstantPool, DefinitionKind} from '../../constant_pool'; +import * as core from '../../core'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {Identifiers} from '../../identifiers'; +import {LifecycleHooks} from '../../lifecycle_reflector'; +import * as o from '../../output/output_ast'; +import {ParseSourceSpan, typeSourceSpan} from '../../parse_util'; +import {CssSelector, SelectorMatcher} from '../../selector'; +import {BindingParser} from '../../template_parser/binding_parser'; +import {OutputContext, error} from '../../util'; + +import * as t from './../r3_ast'; +import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from './../r3_factory'; +import {Identifiers as R3} from './../r3_identifiers'; +import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; +import {BindingScope, TemplateDefinitionBuilder} from './template'; +import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; + +function baseDirectiveFields( + meta: R3DirectiveMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): DefinitionMap { + const definitionMap = new DefinitionMap(); + + // e.g. `type: MyDirective` + definitionMap.set('type', meta.type); + + // e.g. `selectors: [['', 'someDir', '']]` + definitionMap.set('selectors', createDirectiveSelector(meta.selector !)); + + const queryDefinitions = createQueryDefinitions(meta.queries, constantPool); + + // e.g. `factory: () => new MyApp(injectElementRef())` + definitionMap.set('factory', compileFactoryFunction({ + name: meta.name, + fnOrClass: meta.type, + deps: meta.deps, + useNew: true, + injectFn: R3.directiveInject, + useOptionalParam: false, + extraResults: queryDefinitions, + })); + + // e.g. `hostBindings: (dirIndex, elIndex) => { ... } + definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser)); + + // e.g. `attributes: ['role', 'listbox']` + definitionMap.set('attributes', createHostAttributesArray(meta)); + + // e.g 'inputs: {a: 'a'}` + definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs)); + + // e.g 'outputs: {a: 'a'}` + definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); + + return definitionMap; +} + +/** + * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`. + */ +export function compileDirective( + meta: R3DirectiveMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): R3DirectiveDef { + const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]); + const type = + new o.ExpressionType(o.importExpr(R3.DirectiveDef, [new o.ExpressionType(meta.type)])); + return {expression, type}; +} + +/** + * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. + */ +export function compileComponent( + meta: R3ComponentMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): R3ComponentDef { + const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + + const selector = meta.selector && CssSelector.parse(meta.selector); + const firstSelector = selector && selector[0]; + + // e.g. `attr: ["class", ".my.app"]` + // This is optional an only included if the first selector of a component specifies attributes. + if (firstSelector) { + const selectorAttributes = firstSelector.getAttrs(); + if (selectorAttributes.length) { + definitionMap.set( + 'attrs', constantPool.getConstLiteral( + o.literalArr(selectorAttributes.map( + value => value != null ? o.literal(value) : o.literal(undefined))), + /* forceShared */ true)); + } + } + + // Generate the CSS matcher that recognize directive + let directiveMatcher: SelectorMatcher|null = null; + + if (meta.directives.size) { + const matcher = new SelectorMatcher(); + meta.directives.forEach((expression, selector: string) => { + matcher.addSelectables(CssSelector.parse(selector), expression); + }); + directiveMatcher = matcher; + } + + // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` + const templateTypeName = meta.name; + const templateName = templateTypeName ? `${templateTypeName}_Template` : null; + + const directivesUsed = new Set(); + const pipesUsed = new Set(); + + const template = meta.template; + const templateFunctionExpression = + new TemplateDefinitionBuilder( + constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName, + meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed) + .buildTemplateFunction( + template.nodes, [], template.hasNgContent, template.ngContentSelectors); + + definitionMap.set('template', templateFunctionExpression); + + // e.g. `directives: [MyDirective]` + if (directivesUsed.size) { + definitionMap.set('directives', o.literalArr(Array.from(directivesUsed))); + } + + // e.g. `pipes: [MyPipe]` + if (pipesUsed.size) { + definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); + } + + // e.g. `features: [NgOnChangesFeature(MyComponent)]` + const features: o.Expression[] = []; + if (meta.lifecycle.usesOnChanges) { + features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); + } + if (features.length) { + definitionMap.set('features', o.literalArr(features)); + } + + const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); + const type = + new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)])); + + return {expression, type}; +} + +/** + * A wrapper around `compileDirective` which depends on render2 global analysis data as its input + * instead of the `R3DirectiveMetadata`. + * + * `R3DirectiveMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected + * information. + */ +export function compileDirectiveFromRender2( + outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector, + bindingParser: BindingParser) { + const name = identifierName(directive.type) !; + name || error(`Cannot resolver the name of ${directive.type}`); + + const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); + + const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector); + const res = compileDirective(meta, outputCtx.constantPool, bindingParser); + + // Create the partial class to be merged with the actual class. + outputCtx.statements.push(new o.ClassStmt( + name, null, + [new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)], + [], new o.ClassMethod(null, [], []), [])); +} + +/** + * A wrapper around `compileComponent` which depends on render2 global analysis data as its input + * instead of the `R3DirectiveMetadata`. + * + * `R3ComponentMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected + * information. + */ +export function compileComponentFromRender2( + outputCtx: OutputContext, component: CompileDirectiveMetadata, nodes: t.Node[], + hasNgContent: boolean, ngContentSelectors: string[], reflector: CompileReflector, + bindingParser: BindingParser, directiveTypeBySel: Map, + pipeTypeByName: Map) { + const name = identifierName(component.type) !; + name || error(`Cannot resolver the name of ${component.type}`); + + const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component); + + const summary = component.toSummary(); + + // Compute the R3ComponentMetadata from the CompileDirectiveMetadata + const meta: R3ComponentMetadata = { + ...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector), + selector: component.selector, + template: { + nodes, hasNgContent, ngContentSelectors, + }, + lifecycle: { + usesOnChanges: + component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), + }, + directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), + pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), + viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), + }; + const res = compileComponent(meta, outputCtx.constantPool, bindingParser); + + // Create the partial class to be merged with the actual class. + outputCtx.statements.push(new o.ClassStmt( + name, null, + [new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)], + [], new o.ClassMethod(null, [], []), [])); +} + +/** + * Compute `R3DirectiveMetadata` given `CompileDirectiveMetadata` and a `CompileReflector`. + */ +function directiveMetadataFromGlobalMetadata( + directive: CompileDirectiveMetadata, outputCtx: OutputContext, + reflector: CompileReflector): R3DirectiveMetadata { + const summary = directive.toSummary(); + const name = identifierName(directive.type) !; + name || error(`Cannot resolver the name of ${directive.type}`); + + return { + name, + type: outputCtx.importExpr(directive.type.reference), + typeSourceSpan: + typeSourceSpan(directive.isComponent ? 'Component' : 'Directive', directive.type), + selector: directive.selector, + deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector), + queries: queriesFromGlobalMetadata(directive.queries, outputCtx), + host: { + attributes: directive.hostAttributes, + listeners: summary.hostListeners, + properties: summary.hostProperties, + }, + inputs: directive.inputs, + outputs: directive.outputs, + }; +} + +/** + * Convert `CompileQueryMetadata` into `R3QueryMetadata`. + */ +function queriesFromGlobalMetadata( + queries: CompileQueryMetadata[], outputCtx: OutputContext): R3QueryMetadata[] { + return queries.map(query => { + let read: o.Expression|null = null; + if (query.read && query.read.identifier) { + read = outputCtx.importExpr(query.read.identifier.reference); + } + return { + propertyName: query.propertyName, + first: query.first, + predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx), + descendants: query.descendants, read, + }; + }); +} + +/** + * Convert `CompileTokenMetadata` for query selectors into either an expression for a predicate + * type, or a list of string predicates. + */ +function selectorsFromGlobalMetadata( + selectors: CompileTokenMetadata[], outputCtx: OutputContext): o.Expression|string[] { + if (selectors.length > 1 || (selectors.length == 1 && selectors[0].value)) { + const selectorStrings = selectors.map(value => value.value as string); + selectorStrings.some(value => !value) && + error('Found a type among the string selectors expected'); + return outputCtx.constantPool.getConstLiteral( + o.literalArr(selectorStrings.map(value => o.literal(value)))); + } + + if (selectors.length == 1) { + const first = selectors[0]; + if (first.identifier) { + return outputCtx.importExpr(first.identifier.reference); + } + } + + error('Unexpected query form'); + return o.NULL_EXPR; +} + +/** + * + * @param meta + * @param constantPool + */ +function createQueryDefinitions( + queries: R3QueryMetadata[], constantPool: ConstantPool): o.Expression[]|undefined { + const queryDefinitions: o.Expression[] = []; + for (let i = 0; i < queries.length; i++) { + const query = queries[i]; + const predicate = getQueryPredicate(query, constantPool); + + // e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false) + const parameters = [ + o.literal(null, o.INFERRED_TYPE), + predicate, + o.literal(query.descendants), + ]; + + if (query.read) { + parameters.push(query.read); + } + + queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); + } + return queryDefinitions.length > 0 ? queryDefinitions : undefined; +} + +// Turn a directive selector into an R3-compatible selector for directive def +function createDirectiveSelector(selector: string): o.Expression { + return asLiteral(core.parseSelectorToR3Selector(selector)); +} + +function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null { + const values: o.Expression[] = []; + const attributes = meta.host.attributes; + for (let key of Object.getOwnPropertyNames(attributes)) { + const value = attributes[key]; + values.push(o.literal(key), o.literal(value)); + } + if (values.length > 0) { + return o.literalArr(values); + } + return null; +} + +// Return a host binding function or null if one is not necessary. +function createHostBindingsFunction( + meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null { + const statements: o.Statement[] = []; + + const temporary = temporaryAllocator(statements, TEMPORARY_NAME); + + const hostBindingSourceSpan = meta.typeSourceSpan; + + // Calculate the queries + for (let index = 0; index < meta.queries.length; index++) { + const query = meta.queries[index]; + + // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); + const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + // The query list is at the query index + 1 because the directive itself is in slot 0. + const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); + const assignToTemporary = temporary().set(getQueryList); + const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]); + const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE)) + .prop(query.propertyName) + .set(query.first ? temporary().prop('first') : temporary()); + const andExpression = callQueryRefresh.and(updateDirective); + statements.push(andExpression.toStmt()); + } + + const directiveSummary = metadataAsSummary(meta); + + // Calculate the host property bindings + const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); + const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + if (bindings) { + for (const binding of bindings) { + const bindingExpr = convertPropertyBinding( + null, bindingContext, binding.expression, 'b', BindingForm.TrySimple, + () => error('Unexpected interpolation')); + statements.push(...bindingExpr.stmts); + statements.push(o.importExpr(R3.elementProperty) + .callFn([ + o.variable('elIndex'), + o.literal(binding.name), + o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), + ]) + .toStmt()); + } + } + + // Calculate host event bindings + const eventBindings = + bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); + if (eventBindings) { + for (const binding of eventBindings) { + const bindingExpr = convertActionBinding( + null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation')); + const bindingName = binding.name && sanitizeIdentifier(binding.name); + const typeName = meta.name; + const functionName = + typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; + const handler = o.fn( + [new o.FnParam('$event', o.DYNAMIC_TYPE)], + [...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE, + null, functionName); + statements.push( + o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt()); + } + } + + if (statements.length > 0) { + const typeName = meta.name; + return o.fn( + [ + new o.FnParam('dirIndex', o.NUMBER_TYPE), + new o.FnParam('elIndex', o.NUMBER_TYPE), + ], + statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null); + } + + return null; +} + +function metadataAsSummary(meta: R3DirectiveMetadata): CompileDirectiveSummary { + // clang-format off + return { + hostAttributes: meta.host.attributes, + hostListeners: meta.host.listeners, + hostProperties: meta.host.properties, + } as CompileDirectiveSummary; + // clang-format on +} + + +function typeMapToExpressionMap( + map: Map, outputCtx: OutputContext): Map { + // Convert each map entry into another entry where the value is an expression importing the type. + const entries = Array.from(map).map( + ([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]); + return new Map(entries); +} \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_view_compiler_local.ts b/packages/compiler/src/render3/view/template.ts similarity index 55% rename from packages/compiler/src/render3/r3_view_compiler_local.ts rename to packages/compiler/src/render3/view/template.ts index 4e1a4f9c1c..a27851d86b 100644 --- a/packages/compiler/src/render3/r3_view_compiler_local.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -6,212 +6,21 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileQueryMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../compile_metadata'; -import {CompileReflector} from '../compile_reflector'; -import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter'; -import {ConstantPool, DefinitionKind} from '../constant_pool'; -import * as core from '../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../expression_parser/ast'; -import {Identifiers} from '../identifiers'; -import {LifecycleHooks} from '../lifecycle_reflector'; -import * as o from '../output/output_ast'; -import {ParseSourceSpan, typeSourceSpan} from '../parse_util'; -import {CssSelector, SelectorMatcher} from '../selector'; -import {BindingParser} from '../template_parser/binding_parser'; -import {OutputContext, error} from '../util'; +import {flatten, sanitizeIdentifier} from '../../compile_metadata'; +import {CompileReflector} from '../../compile_reflector'; +import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; +import {ConstantPool} from '../../constant_pool'; +import * as core from '../../core'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import * as o from '../../output/output_ast'; +import {ParseSourceSpan} from '../../parse_util'; +import {CssSelector, SelectorMatcher} from '../../selector'; +import {OutputContext, error} from '../../util'; +import * as t from '../r3_ast'; +import {Identifiers as R3} from '../r3_identifiers'; -import * as t from './r3_ast'; -import {Identifiers as R3} from './r3_identifiers'; - - - -/** Name of the context parameter passed into a template function */ -const CONTEXT_NAME = 'ctx'; - -/** Name of the RenderFlag passed into a template function */ -const RENDER_FLAGS = 'rf'; - -/** Name of the temporary to use during data binding */ -const TEMPORARY_NAME = '_t'; - -/** The prefix reference variables */ -const REFERENCE_PREFIX = '_r'; - -/** The name of the implicit context reference */ -const IMPLICIT_REFERENCE = '$implicit'; - -/** Name of the i18n attributes **/ -const I18N_ATTR = 'i18n'; -const I18N_ATTR_PREFIX = 'i18n-'; - -/** I18n separators for metadata **/ -const MEANING_SEPARATOR = '|'; -const ID_SEPARATOR = '@@'; - -export function compileDirective( - outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector, - bindingParser: BindingParser) { - const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; - - const field = (key: string, value: o.Expression | null) => { - if (value) { - definitionMapValues.push({key, value, quoted: false}); - } - }; - - // e.g. `type: MyDirective` - field('type', outputCtx.importExpr(directive.type.reference)); - - // e.g. `selectors: [['', 'someDir', '']]` - field('selectors', createDirectiveSelector(directive.selector !)); - - // e.g. `factory: () => new MyApp(injectElementRef())` - field('factory', createFactory(directive.type, outputCtx, reflector, directive.queries)); - - // e.g. `hostBindings: (dirIndex, elIndex) => { ... } - field('hostBindings', createHostBindingsFunction(directive, outputCtx, bindingParser)); - - // e.g. `attributes: ['role', 'listbox']` - field('attributes', createHostAttributesArray(directive, outputCtx)); - - // e.g 'inputs: {a: 'a'}` - field('inputs', conditionallyCreateMapObjectLiteral(directive.inputs)); - - // e.g 'outputs: {a: 'a'}` - field('outputs', conditionallyCreateMapObjectLiteral(directive.outputs)); - - const className = identifierName(directive.type) !; - className || error(`Cannot resolver the name of ${directive.type}`); - - const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); - const definitionFunction = - o.importExpr(R3.defineDirective).callFn([o.literalMap(definitionMapValues)]); - - // Create the partial class to be merged with the actual class. - outputCtx.statements.push(new o.ClassStmt( - className, null, - [new o.ClassField( - definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], definitionFunction)], - [], new o.ClassMethod(null, [], []), [])); -} - -export function compileComponent( - outputCtx: OutputContext, component: CompileDirectiveMetadata, nodes: t.Node[], - hasNgContent: boolean, ngContentSelectors: string[], reflector: CompileReflector, - bindingParser: BindingParser, directiveTypeBySel: Map, - pipeTypeByName: Map) { - const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; - - const field = (key: string, value: o.Expression | null) => { - if (value) { - definitionMapValues.push({key, value, quoted: false}); - } - }; - - // Generate the CSS matcher that recognize directive - let directiveMatcher: SelectorMatcher|null = null; - - if (directiveTypeBySel.size) { - const matcher = new SelectorMatcher(); - directiveTypeBySel.forEach((staticType: any, selector: string) => { - matcher.addSelectables(CssSelector.parse(selector), staticType); - }); - directiveMatcher = matcher; - } - - // Directives and Pipes used from the template - const directives = new Set(); - const pipes = new Set(); - - // e.g. `type: MyApp` - field('type', outputCtx.importExpr(component.type.reference)); - - // e.g. `selectors: [['my-app']]` - field('selectors', createDirectiveSelector(component.selector !)); - - const selector = component.selector && CssSelector.parse(component.selector); - const firstSelector = selector && selector[0]; - - // e.g. `attr: ["class", ".my.app"]` - // This is optional an only included if the first selector of a component specifies attributes. - if (firstSelector) { - const selectorAttributes = firstSelector.getAttrs(); - if (selectorAttributes.length) { - field( - 'attrs', outputCtx.constantPool.getConstLiteral( - o.literalArr(selectorAttributes.map( - value => value != null ? o.literal(value) : o.literal(undefined))), - /* forceShared */ true)); - } - } - - // e.g. `factory: function MyApp_Factory() { return new MyApp(injectElementRef()); }` - field('factory', createFactory(component.type, outputCtx, reflector, component.queries)); - - // e.g `hostBindings: function MyApp_HostBindings { ... } - field('hostBindings', createHostBindingsFunction(component, outputCtx, bindingParser)); - - // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` - const templateTypeName = component.type.reference.name; - const templateName = templateTypeName ? `${templateTypeName}_Template` : null; - - const templateFunctionExpression = - new TemplateDefinitionBuilder( - outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, - templateTypeName, templateName, component.viewQueries, directiveMatcher, directives, - pipeTypeByName, pipes) - .buildTemplateFunction(nodes, [], hasNgContent, ngContentSelectors); - - field('template', templateFunctionExpression); - - // e.g. `directives: [MyDirective]` - if (directives.size) { - const expressions = Array.from(directives).map(d => outputCtx.importExpr(d)); - field('directives', o.literalArr(expressions)); - } - - // e.g. `pipes: [MyPipe]` - if (pipes.size) { - const expressions = Array.from(pipes).map(d => outputCtx.importExpr(d)); - field('pipes', o.literalArr(expressions)); - } - - // e.g `inputs: {a: 'a'}` - field('inputs', conditionallyCreateMapObjectLiteral(component.inputs)); - - // e.g 'outputs: {a: 'a'}` - field('outputs', conditionallyCreateMapObjectLiteral(component.outputs)); - - // e.g. `features: [NgOnChangesFeature(MyComponent)]` - const features: o.Expression[] = []; - if (component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges)) { - features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([outputCtx.importExpr( - component.type.reference)])); - } - if (features.length) { - field('features', o.literalArr(features)); - } - - const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component); - const definitionFunction = - o.importExpr(R3.defineComponent).callFn([o.literalMap(definitionMapValues)]); - const className = identifierName(component.type) !; - className || error(`Cannot resolver the name of ${component.type}`); - - // Create the partial class to be merged with the actual class. - outputCtx.statements.push(new o.ClassStmt( - className, null, - [new o.ClassField( - definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], definitionFunction)], - [], new o.ClassMethod(null, [], []), [])); -} - -function unsupported(feature: string): never { - if (this) { - throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`); - } - throw new Error(`Feature ${feature} is not supported yet`); -} +import {R3QueryMetadata} from './api'; +import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util'; const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { [BoundElementBindingType.Property]: R3.elementProperty, @@ -220,164 +29,7 @@ const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { [BoundElementBindingType.Style]: R3.elementStyleNamed, }; -function interpolate(args: o.Expression[]): o.Expression { - args = args.slice(1); // Ignore the length prefix added for render2 - switch (args.length) { - case 3: - return o.importExpr(R3.interpolation1).callFn(args); - case 5: - return o.importExpr(R3.interpolation2).callFn(args); - case 7: - return o.importExpr(R3.interpolation3).callFn(args); - case 9: - return o.importExpr(R3.interpolation4).callFn(args); - case 11: - return o.importExpr(R3.interpolation5).callFn(args); - case 13: - return o.importExpr(R3.interpolation6).callFn(args); - case 15: - return o.importExpr(R3.interpolation7).callFn(args); - case 17: - return o.importExpr(R3.interpolation8).callFn(args); - } - (args.length >= 19 && args.length % 2 == 1) || - error(`Invalid interpolation argument length ${args.length}`); - return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); -} - -// Pipes always have at least one parameter, the value they operate on -const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; - -function pipeBinding(args: o.Expression[]): o.ExternalReference { - return pipeBindingIdentifiers[args.length] || R3.pipeBindV; -} - -const pureFunctionIdentifiers = [ - R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, - R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 -]; -function getLiteralFactory( - outputContext: OutputContext, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { - const {literalFactory, literalFactoryArguments} = - outputContext.constantPool.getLiteralFactory(literal); - literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); - let pureFunctionIdent = - pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; - - // Literal factories are pure functions that only need to be re-invoked when the parameters - // change. - return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); -} - -function noop() {} - -/** - * Function which is executed whenever a variable is referenced for the first time in a given - * scope. - * - * It is expected that the function creates the `const localName = expression`; statement. - */ -type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void; - -class BindingScope implements LocalResolver { - /** - * Keeps a map from local variables to their expressions. - * - * This is used when one refers to variable such as: 'let abc = a.b.c`. - * - key to the map is the string literal `"abc"`. - * - value `lhs` is the left hand side which is an AST representing `abc`. - * - value `rhs` is the right hand side which is an AST representing `a.b.c`. - * - value `declared` is true if the `declareLocalVarCallback` has been called for this scope - * already. - */ - private map = new Map < string, { - lhs: o.ReadVarExpr; - rhs: o.Expression|undefined; - declared: boolean; - } - > (); - private referenceNameIndex = 0; - - static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); - - private constructor( - private parent: BindingScope|null = null, - private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} - - get(name: string): o.Expression|null { - let current: BindingScope|null = this; - while (current) { - let value = current.map.get(name); - if (value != null) { - if (current !== this) { - // make a local copy and reset the `declared` state. - value = {lhs: value.lhs, rhs: value.rhs, declared: false}; - // Cache the value locally. - this.map.set(name, value); - } - if (value.rhs && !value.declared) { - // if it is first time we are referencing the variable in the scope - // than invoke the callback to insert variable declaration. - this.declareLocalVarCallback(value.lhs, value.rhs); - value.declared = true; - } - return value.lhs; - } - current = current.parent; - } - return null; - } - - /** - * Create a local variable for later reference. - * - * @param name Name of the variable. - * @param lhs AST representing the left hand side of the `let lhs = rhs;`. - * @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be - * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` - * declaration. - */ - set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope { - !this.map.has(name) || - error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`); - this.map.set(name, {lhs: lhs, rhs: rhs, declared: false}); - return this; - } - - getLocal(name: string): (o.Expression|null) { return this.get(name); } - - nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope { - return new BindingScope(this, declareCallback); - } - - freshReferenceName(): string { - let current: BindingScope = this; - // Find the top scope as it maintains the global reference count - while (current.parent) current = current.parent; - const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`; - return ref; - } -} - -// Pasted from render3/interfaces/definition since it cannot be referenced directly -/** - * Flags passed into template functions to determine which blocks (i.e. creation, update) - * should be executed. - * - * Typically, a template runs both the creation block and the update block on initialization and - * subsequent runs only execute the update block. However, dynamically created views require that - * the creation block be executed separately from the update block (for backwards compat). - */ -// TODO(vicb): move to ../core -export const enum RenderFlags { - /* Whether to run the creation block (e.g. create elements and directives) */ - Create = 0b01, - - /* Whether to run the update block (e.g. refresh bindings) */ - Update = 0b10 -} - -class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { +export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _dataIndex = 0; private _bindingContext = 0; private _prefixCode: o.Statement[] = []; @@ -398,19 +50,19 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}]; constructor( - private outputCtx: OutputContext, private constantPool: ConstantPool, - private reflector: CompileReflector, private contextParameter: string, + private constantPool: ConstantPool, private contextParameter: string, parentBindingScope: BindingScope, private level = 0, private contextName: string|null, - private templateName: string|null, private viewQueries: CompileQueryMetadata[], - private directiveMatcher: SelectorMatcher|null, private directives: Set, - private pipeTypeByName: Map, private pipes: Set) { + private templateName: string|null, private viewQueries: R3QueryMetadata[], + private directiveMatcher: SelectorMatcher|null, private directives: Set, + private pipeTypeByName: Map, private pipes: Set) { this._bindingScope = parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => { this._bindingCode.push( lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); }); this._valueConverter = new ValueConverter( - outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value: o.ReadVarExpr) => { + constantPool, () => this.allocateDataSlot(), + (name, localName, slot, value: o.ReadVarExpr) => { const pipeType = pipeTypeByName.get(name); if (pipeType) { this.pipes.add(pipeType); @@ -443,9 +95,8 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { if (ngContentSelectors.length > 1) { const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s)); // `projectionDef` needs both the parsed and raw value of the selectors - const parsed = this.outputCtx.constantPool.getConstLiteral(asLiteral(r3Selectors), true); - const unParsed = - this.outputCtx.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true); + const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true); + const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true); parameters.push(parsed, unParsed); } @@ -456,15 +107,15 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { for (let query of this.viewQueries) { // e.g. r3.Q(0, somePredicate, true); const querySlot = this.allocateDataSlot(); - const predicate = getQueryPredicate(query, this.outputCtx); - const args = [ + const predicate = getQueryPredicate(query, this.constantPool); + const args: o.Expression[] = [ o.literal(querySlot, o.INFERRED_TYPE), predicate, o.literal(query.descendants, o.INFERRED_TYPE), ]; if (query.read) { - args.push(this.outputCtx.importExpr(query.read.identifier !.reference)); + args.push(query.read); } this.instruction(this._creationCode, null, R3.query, ...args); @@ -482,13 +133,13 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { const creationCode = this._creationCode.length > 0 ? [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(RenderFlags.Create), null, false), + o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), this._creationCode)] : []; const updateCode = this._bindingCode.length > 0 ? [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(RenderFlags.Update), null, false), + o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Update), null, false), this._bindingCode)] : []; @@ -624,7 +275,7 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { let attrArg: o.Expression = o.TYPED_NULL_EXPR; if (attributes.length > 0) { - attrArg = hasI18nAttr ? getLiteralFactory(this.outputCtx, o.literalArr(attributes)) : + attrArg = hasI18nAttr ? getLiteralFactory(this.constantPool, o.literalArr(attributes)) : this.constantPool.getConstLiteral(o.literalArr(attributes), true); } @@ -770,9 +421,8 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { // Create the template function const templateVisitor = new TemplateDefinitionBuilder( - this.outputCtx, this.constantPool, this.reflector, templateContext, this._bindingScope, - this.level + 1, contextName, templateName, [], this.directiveMatcher, this.directives, - this.pipeTypeByName, this.pipes); + this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, + templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes); const templateFunctionExpr = templateVisitor.buildTemplateFunction(template.children, template.variables); this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null)); @@ -839,223 +489,9 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { } } -function getQueryPredicate(query: CompileQueryMetadata, outputCtx: OutputContext): o.Expression { - if (query.selectors.length > 1 || (query.selectors.length == 1 && query.selectors[0].value)) { - const selectors = query.selectors.map(value => value.value as string); - selectors.some(value => !value) && error('Found a type among the string selectors expected'); - return outputCtx.constantPool.getConstLiteral( - o.literalArr(selectors.map(value => o.literal(value)))); - } - - if (query.selectors.length == 1) { - const first = query.selectors[0]; - if (first.identifier) { - return outputCtx.importExpr(first.identifier.reference); - } - } - - error('Unexpected query form'); - return o.NULL_EXPR; -} - -export function createFactory( - type: CompileTypeMetadata, outputCtx: OutputContext, reflector: CompileReflector, - queries: CompileQueryMetadata[]): o.Expression { - let args: o.Expression[] = []; - - const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef); - const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef); - const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef); - - for (let dependency of type.diDeps) { - const token = dependency.token; - if (token) { - const tokenRef = tokenReference(token); - if (tokenRef === elementRef) { - args.push(o.importExpr(R3.injectElementRef).callFn([])); - } else if (tokenRef === templateRef) { - args.push(o.importExpr(R3.injectTemplateRef).callFn([])); - } else if (tokenRef === viewContainerRef) { - args.push(o.importExpr(R3.injectViewContainerRef).callFn([])); - } else if (dependency.isAttribute) { - args.push(o.importExpr(R3.injectAttribute).callFn([o.literal(dependency.token !.value)])); - } else { - const tokenValue = - token.identifier != null ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef); - const directiveInjectArgs = [tokenValue]; - const flags = extractFlags(dependency); - if (flags != core.InjectFlags.Default) { - // Append flag information if other than default. - directiveInjectArgs.push(o.literal(flags)); - } - args.push(o.importExpr(R3.directiveInject).callFn(directiveInjectArgs)); - } - } else { - unsupported('dependency without a token'); - } - } - - const queryDefinitions: o.Expression[] = []; - for (let query of queries) { - const predicate = getQueryPredicate(query, outputCtx); - - // e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false) - const parameters = [ - o.literal(null, o.INFERRED_TYPE), - predicate, - o.literal(query.descendants), - ]; - - if (query.read) { - parameters.push(outputCtx.importExpr(query.read.identifier !.reference)); - } - - queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); - } - - const createInstance = new o.InstantiateExpr(outputCtx.importExpr(type.reference), args); - const result = queryDefinitions.length > 0 ? o.literalArr([createInstance, ...queryDefinitions]) : - createInstance; - - return o.fn( - [], [new o.ReturnStatement(result)], o.INFERRED_TYPE, null, - type.reference.name ? `${type.reference.name}_Factory` : null); -} - -function extractFlags(dependency: CompileDiDependencyMetadata): core.InjectFlags { - let flags = core.InjectFlags.Default; - if (dependency.isHost) { - flags |= core.InjectFlags.Host; - } - if (dependency.isOptional) { - flags |= core.InjectFlags.Optional; - } - if (dependency.isSelf) { - flags |= core.InjectFlags.Self; - } - if (dependency.isSkipSelf) { - flags |= core.InjectFlags.SkipSelf; - } - if (dependency.isValue) { - unsupported('value dependencies'); - } - return flags; -} - -/** - * Remove trailing null nodes as they are implied. - */ -function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] { - while (o.isNull(parameters[parameters.length - 1])) { - parameters.pop(); - } - return parameters; -} - -// Turn a directive selector into an R3-compatible selector for directive def -function createDirectiveSelector(selector: string): o.Expression { - return asLiteral(core.parseSelectorToR3Selector(selector)); -} - -function createHostAttributesArray( - directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext): o.Expression|null { - const values: o.Expression[] = []; - const attributes = directiveMetadata.hostAttributes; - for (let key of Object.getOwnPropertyNames(attributes)) { - const value = attributes[key]; - values.push(o.literal(key), o.literal(value)); - } - if (values.length > 0) { - return outputCtx.constantPool.getConstLiteral(o.literalArr(values)); - } - return null; -} - -// Return a host binding function or null if one is not necessary. -function createHostBindingsFunction( - directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext, - bindingParser: BindingParser): o.Expression|null { - const statements: o.Statement[] = []; - - const temporary = temporaryAllocator(statements, TEMPORARY_NAME); - - const hostBindingSourceSpan = typeSourceSpan( - directiveMetadata.isComponent ? 'Component' : 'Directive', directiveMetadata.type); - - // Calculate the queries - for (let index = 0; index < directiveMetadata.queries.length; index++) { - const query = directiveMetadata.queries[index]; - - // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); - const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); - // The query list is at the query index + 1 because the directive itself is in slot 0. - const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); - const assignToTemporary = temporary().set(getQueryList); - const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]); - const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE)) - .prop(query.propertyName) - .set(query.first ? temporary().prop('first') : temporary()); - const andExpression = callQueryRefresh.and(updateDirective); - statements.push(andExpression.toStmt()); - } - - const directiveSummary = directiveMetadata.toSummary(); - - // Calculate the host property bindings - const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); - const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); - if (bindings) { - for (const binding of bindings) { - const bindingExpr = convertPropertyBinding( - null, bindingContext, binding.expression, 'b', BindingForm.TrySimple, - () => error('Unexpected interpolation')); - statements.push(...bindingExpr.stmts); - statements.push(o.importExpr(R3.elementProperty) - .callFn([ - o.variable('elIndex'), - o.literal(binding.name), - o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), - ]) - .toStmt()); - } - } - - // Calculate host event bindings - const eventBindings = - bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); - if (eventBindings) { - for (const binding of eventBindings) { - const bindingExpr = convertActionBinding( - null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation')); - const bindingName = binding.name && sanitizeIdentifier(binding.name); - const typeName = identifierName(directiveMetadata.type); - const functionName = - typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; - const handler = o.fn( - [new o.FnParam('$event', o.DYNAMIC_TYPE)], - [...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE, - null, functionName); - statements.push( - o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt()); - } - } - - if (statements.length > 0) { - const typeName = directiveMetadata.type.reference.name; - return o.fn( - [ - new o.FnParam('dirIndex', o.NUMBER_TYPE), - new o.FnParam('elIndex', o.NUMBER_TYPE), - ], - statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null); - } - - return null; -} - class ValueConverter extends AstMemoryEfficientTransformer { constructor( - private outputCtx: OutputContext, private allocateSlot: () => number, + private constantPool: ConstantPool, private allocateSlot: () => number, private definePipe: (name: string, localName: string, slot: number, value: o.Expression) => void) { super(); @@ -1082,9 +518,8 @@ class ValueConverter extends AstMemoryEfficientTransformer { // calls to literal factories that compose the literal and will cache intermediate // values. Otherwise, just return an literal array that contains the values. const literal = o.literalArr(values); - return values.every(a => a.isConstant()) ? - this.outputCtx.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.outputCtx, literal); + return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal); }); } @@ -1095,51 +530,144 @@ class ValueConverter extends AstMemoryEfficientTransformer { // values. Otherwise, just return an literal array that contains the values. const literal = o.literalMap(values.map( (value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted}))); - return values.every(a => a.isConstant()) ? - this.outputCtx.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.outputCtx, literal); + return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal); }); } } -function invalid(arg: o.Expression | o.Statement | t.Node): never { - throw new Error( - `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); + + +// Pipes always have at least one parameter, the value they operate on +const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; + +function pipeBinding(args: o.Expression[]): o.ExternalReference { + return pipeBindingIdentifiers[args.length] || R3.pipeBindV; } -function asLiteral(value: any): o.Expression { - if (Array.isArray(value)) { - return o.literalArr(value.map(asLiteral)); - } - return o.literal(value, o.INFERRED_TYPE); -} +const pureFunctionIdentifiers = [ + R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, + R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 +]; +function getLiteralFactory( + constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { + const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal); + literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); + let pureFunctionIdent = + pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; -function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|null { - if (Object.getOwnPropertyNames(keys).length > 0) { - return mapToExpression(keys); - } - return null; -} - -function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression { - return o.literalMap( - Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])}))); + // Literal factories are pure functions that only need to be re-invoked when the parameters + // change. + return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); } /** - * Creates an allocator for a temporary variable. + * Function which is executed whenever a variable is referenced for the first time in a given + * scope. * - * A variable declaration is added to the statements the first time the allocator is invoked. + * It is expected that the function creates the `const localName = expression`; statement. */ -function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr { - let temp: o.ReadVarExpr|null = null; - return () => { - if (!temp) { - statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); - temp = o.variable(name); +export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void; + +export class BindingScope implements LocalResolver { + /** + * Keeps a map from local variables to their expressions. + * + * This is used when one refers to variable such as: 'let abc = a.b.c`. + * - key to the map is the string literal `"abc"`. + * - value `lhs` is the left hand side which is an AST representing `abc`. + * - value `rhs` is the right hand side which is an AST representing `a.b.c`. + * - value `declared` is true if the `declareLocalVarCallback` has been called for this scope + * already. + */ + private map = new Map < string, { + lhs: o.ReadVarExpr; + rhs: o.Expression|undefined; + declared: boolean; + } + > (); + private referenceNameIndex = 0; + + static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); + + private constructor( + private parent: BindingScope|null = null, + private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} + + get(name: string): o.Expression|null { + let current: BindingScope|null = this; + while (current) { + let value = current.map.get(name); + if (value != null) { + if (current !== this) { + // make a local copy and reset the `declared` state. + value = {lhs: value.lhs, rhs: value.rhs, declared: false}; + // Cache the value locally. + this.map.set(name, value); + } + if (value.rhs && !value.declared) { + // if it is first time we are referencing the variable in the scope + // than invoke the callback to insert variable declaration. + this.declareLocalVarCallback(value.lhs, value.rhs); + value.declared = true; + } + return value.lhs; + } + current = current.parent; } - return temp; - }; + return null; + } + + /** + * Create a local variable for later reference. + * + * @param name Name of the variable. + * @param lhs AST representing the left hand side of the `let lhs = rhs;`. + * @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be + * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` + * declaration. + */ + set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope { + !this.map.has(name) || + error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`); + this.map.set(name, {lhs: lhs, rhs: rhs, declared: false}); + return this; + } + + getLocal(name: string): (o.Expression|null) { return this.get(name); } + + nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope { + return new BindingScope(this, declareCallback); + } + + freshReferenceName(): string { + let current: BindingScope = this; + // Find the top scope as it maintains the global reference count + while (current.parent) current = current.parent; + const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`; + return ref; + } +} + +/** + * Creates a `CssSelector` given a tag name and a map of attributes + */ +function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector { + const cssSelector = new CssSelector(); + + cssSelector.setElement(tag); + + Object.getOwnPropertyNames(attributes).forEach((name) => { + const value = attributes[name]; + + cssSelector.addAttribute(name, value); + if (name.toLowerCase() === 'class') { + const classes = value.trim().split(/\s+/g); + classes.forEach(className => cssSelector.addClassName(className)); + } + }); + + return cssSelector; } // Parse i18n metas like: @@ -1167,23 +695,27 @@ function parseI18nMeta(i18n?: string): {description?: string, id?: string, meani return {description, id, meaning}; } -/** - * Creates a `CssSelector` given a tag name and a map of attributes - */ -function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector { - const cssSelector = new CssSelector(); - - cssSelector.setElement(tag); - - Object.getOwnPropertyNames(attributes).forEach((name) => { - const value = attributes[name]; - - cssSelector.addAttribute(name, value); - if (name.toLowerCase() === 'class') { - const classes = value.trim().split(/\s+/g); - classes.forEach(className => cssSelector.addClassName(className)); - } - }); - - return cssSelector; +function interpolate(args: o.Expression[]): o.Expression { + args = args.slice(1); // Ignore the length prefix added for render2 + switch (args.length) { + case 3: + return o.importExpr(R3.interpolation1).callFn(args); + case 5: + return o.importExpr(R3.interpolation2).callFn(args); + case 7: + return o.importExpr(R3.interpolation3).callFn(args); + case 9: + return o.importExpr(R3.interpolation4).callFn(args); + case 11: + return o.importExpr(R3.interpolation5).callFn(args); + case 13: + return o.importExpr(R3.interpolation6).callFn(args); + case 15: + return o.importExpr(R3.interpolation7).callFn(args); + case 17: + return o.importExpr(R3.interpolation8).callFn(args); + } + (args.length >= 19 && args.length % 2 == 1) || + error(`Invalid interpolation argument length ${args.length}`); + return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); } diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts new file mode 100644 index 0000000000..e96e4ed2cd --- /dev/null +++ b/packages/compiler/src/render3/view/util.ts @@ -0,0 +1,122 @@ +/** + * @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 {ConstantPool} from '../../constant_pool'; +import * as o from '../../output/output_ast'; +import * as t from '../r3_ast'; + +import {R3QueryMetadata} from './api'; + + +/** Name of the temporary to use during data binding */ +export const TEMPORARY_NAME = '_t'; + + + +/** Name of the context parameter passed into a template function */ +export const CONTEXT_NAME = 'ctx'; + +/** Name of the RenderFlag passed into a template function */ +export const RENDER_FLAGS = 'rf'; + +/** The prefix reference variables */ +export const REFERENCE_PREFIX = '_r'; + +/** The name of the implicit context reference */ +export const IMPLICIT_REFERENCE = '$implicit'; + +/** Name of the i18n attributes **/ +export const I18N_ATTR = 'i18n'; +export const I18N_ATTR_PREFIX = 'i18n-'; + +/** I18n separators for metadata **/ +export const MEANING_SEPARATOR = '|'; +export const ID_SEPARATOR = '@@'; + +/** + * Creates an allocator for a temporary variable. + * + * A variable declaration is added to the statements the first time the allocator is invoked. + */ +export function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr { + let temp: o.ReadVarExpr|null = null; + return () => { + if (!temp) { + statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); + temp = o.variable(name); + } + return temp; + }; +} + + +export function unsupported(feature: string): never { + if (this) { + throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`); + } + throw new Error(`Feature ${feature} is not supported yet`); +} + +export function invalid(arg: o.Expression | o.Statement | t.Node): never { + throw new Error( + `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); +} + +export function asLiteral(value: any): o.Expression { + if (Array.isArray(value)) { + return o.literalArr(value.map(asLiteral)); + } + return o.literal(value, o.INFERRED_TYPE); +} + +export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression| + null { + if (Object.getOwnPropertyNames(keys).length > 0) { + return mapToExpression(keys); + } + return null; +} + +export function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression { + return o.literalMap( + Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])}))); +} + +/** + * Remove trailing null nodes as they are implied. + */ +export function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] { + while (o.isNull(parameters[parameters.length - 1])) { + parameters.pop(); + } + return parameters; +} + +export function getQueryPredicate( + query: R3QueryMetadata, constantPool: ConstantPool): o.Expression { + if (Array.isArray(query.predicate)) { + return constantPool.getConstLiteral( + o.literalArr(query.predicate.map(selector => o.literal(selector) as o.Expression))); + } else { + return query.predicate; + } +} + +export function noop() {} + +export class DefinitionMap { + values: {key: string, quoted: boolean, value: o.Expression}[] = []; + + set(key: string, value: o.Expression|null): void { + if (value) { + this.values.push({key, value, quoted: false}); + } + } + + toLiteralMap(): o.LiteralMapExpr { return o.literalMap(this.values); } +} diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index 7cdcb2f0ee..bd798b81f2 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -17,8 +17,6 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector} from '../selector'; import {splitAtColon, splitAtPeriod} from '../util'; -import {BoundElementPropertyAst, PropertyBindingType} from './template_ast'; - const PROPERTY_PARTS_SEPARATOR = '.'; const ATTRIBUTE_PREFIX = 'attr'; const CLASS_PREFIX = 'class'; diff --git a/packages/compiler/test/render3/mock_compile.ts b/packages/compiler/test/render3/mock_compile.ts index 58c570b377..d74aa6d304 100644 --- a/packages/compiler/test/render3/mock_compile.ts +++ b/packages/compiler/test/render3/mock_compile.ts @@ -17,7 +17,7 @@ import {removeWhitespaces} from '../../src/ml_parser/html_whitespaces'; import * as o from '../../src/output/output_ast'; import {compilePipe} from '../../src/render3/r3_pipe_compiler'; import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform'; -import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler_local'; +import {compileComponentFromRender2, compileDirectiveFromRender2} from '../../src/render3/view/compiler'; import {BindingParser} from '../../src/template_parser/binding_parser'; import {OutputContext, escapeRegExp} from '../../src/util'; import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util'; @@ -313,11 +313,11 @@ export function compile( const hasNgContent = transform.hasNgContent; const ngContentSelectors = transform.ngContentSelectors; - compileComponent( + compileComponentFromRender2( outputCtx, directive, nodes, hasNgContent, ngContentSelectors, reflector, hostBindingParser, directiveTypeBySel, pipeTypeByName); } else { - compileDirective(outputCtx, directive, reflector, hostBindingParser); + compileDirectiveFromRender2(outputCtx, directive, reflector, hostBindingParser); } } else if (resolver.isPipe(pipeOrDirective)) { const metadata = resolver.getPipeMetadata(pipeOrDirective); diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 41c19bc162..fdea13d17a 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -1022,8 +1022,8 @@ describe('compiler compliance', () => { type: LifecycleComp, selectors: [['lifecycle-comp']], factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, - template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, inputs: {nameMin: 'name'}, + template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)] });`; diff --git a/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts b/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts index ad101293d3..bfdbc3ab87 100644 --- a/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts @@ -62,6 +62,7 @@ describe('compiler compliance: listen()', () => { componentOutput: 'componentOutput', originalComponentOutput: 'renamedComponentOutput' } + … });`; const directiveDef = ` @@ -75,6 +76,7 @@ describe('compiler compliance: listen()', () => { directiveOutput: 'directiveOutput', originalDirectiveOutput: 'renamedDirectiveOutput' } + … });`;