From b0eca85e5187fbfe2f2edd02327ce2fc6f0e196b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 24 Apr 2018 11:34:11 -0700 Subject: [PATCH] refactor(compiler): compile{Component,Directive} take only local information (#23545) Previously, the compileComponent() and compileDirective() APIs still required the output of global analysis, even though they only read local information from that output. With this refactor, compileComponent() and compileDirective() now define their inputs explicitly, with the new interfaces R3ComponentMetadata and R3DirectiveMetadata. compileComponentGlobal() and compileDirectiveGlobal() are introduced and convert from global analysis output into the new metadata format. This refactor also splits out the view compiler into separate files as r3_view_compiler_local.ts was getting unwieldy. Finally, this refactor also splits out generation of DI factory functions into a separate r3_factory utility as the logic is utilized between different compilers. PR Close #23545 --- .../src/ngtsc/transform/src/injectable.ts | 3 +- packages/compiler-cli/test/ngc_spec.ts | 1 + packages/compiler/src/aot/compiler.ts | 2 +- packages/compiler/src/core.ts | 17 + .../compiler/src/injectable_compiler_2.ts | 1 + packages/compiler/src/render3/r3_factory.ts | 288 ++++++ .../compiler/src/render3/r3_identifiers.ts | 10 + .../compiler/src/render3/r3_pipe_compiler.ts | 12 +- packages/compiler/src/render3/view/api.ts | 178 ++++ .../compiler/src/render3/view/compiler.ts | 444 ++++++++++ .../template.ts} | 832 ++++-------------- packages/compiler/src/render3/view/util.ts | 122 +++ .../src/template_parser/binding_parser.ts | 2 - .../compiler/test/render3/mock_compile.ts | 6 +- .../render3/r3_compiler_compliance_spec.ts | 2 +- .../r3_view_compiler_input_outputs_spec.ts | 2 + 16 files changed, 1262 insertions(+), 660 deletions(-) create mode 100644 packages/compiler/src/render3/r3_factory.ts create mode 100644 packages/compiler/src/render3/view/api.ts create mode 100644 packages/compiler/src/render3/view/compiler.ts rename packages/compiler/src/render3/{r3_view_compiler_local.ts => view/template.ts} (55%) create mode 100644 packages/compiler/src/render3/view/util.ts 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' } + … });`;