feat(ivy): ngtsc compiles @Component, @Directive, @NgModule (#24427)

This change supports compilation of components, directives, and modules
within ngtsc. Support is not complete, but is enough to compile and test
//packages/core/test/bundling/todo in full AOT mode. Code size benefits
are not yet achieved as //packages/core itself does not get compiled, and
some decorators (e.g. @Input) are not stripped, leading to unwanted code
being retained by the tree-shaker. This will be improved in future commits.

PR Close #24427
This commit is contained in:
Alex Rickabaugh 2018-05-31 15:50:02 -07:00 committed by Miško Hevery
parent 0f7e4fae20
commit 27bc7dcb43
69 changed files with 1884 additions and 607 deletions

View File

@ -7,7 +7,7 @@
*/
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
import {TableCell, buildTable, emptyTable} from '../util';
@ -15,7 +15,7 @@ export class LargeTableComponent {
data: TableCell[][] = emptyTable;
/** @nocollapse */
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
static ngComponentDef: ComponentDefInternal<LargeTableComponent> = defineComponent({
type: LargeTableComponent,
selectors: [['largetable']],
template: function(rf: RenderFlags, ctx: LargeTableComponent) {

View File

@ -7,7 +7,7 @@
*/
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
import {TreeNode, buildTree, emptyTree} from '../util';
@ -35,7 +35,7 @@ export class TreeComponent {
data: TreeNode = emptyTree;
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
static ngComponentDef: ComponentDefInternal<TreeComponent> = defineComponent({
type: TreeComponent,
selectors: [['tree']],
template: function(rf: RenderFlags, ctx: TreeComponent) {
@ -95,7 +95,7 @@ export class TreeFunction {
data: TreeNode = emptyTree;
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
static ngComponentDef: ComponentDefInternal<TreeFunction> = defineComponent({
type: TreeFunction,
selectors: [['tree']],
template: function(rf: RenderFlags, ctx: TreeFunction) {

View File

@ -25,6 +25,7 @@ ts_library(
tsconfig = ":tsconfig",
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/transform",
],
)

View File

@ -0,0 +1,17 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "annotations",
srcs = glob([
"index.ts",
"src/**/*.ts",
]),
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/transform",
],
)

View File

@ -0,0 +1,13 @@
/**
* @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
*/
export {ComponentDecoratorHandler} from './src/component';
export {DirectiveDecoratorHandler} from './src/directive';
export {InjectableDecoratorHandler} from './src/injectable';
export {NgModuleDecoratorHandler} from './src/ng_module';
export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope';

View File

@ -0,0 +1,117 @@
/**
* @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, Expression, R3ComponentMetadata, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator, reflectNonStaticField, reflectObjectLiteral, staticallyResolve} from '../../metadata';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {extractDirectiveMetadata} from './directive';
import {SelectorScopeRegistry} from './selector_scope';
const EMPTY_MAP = new Map<string, Expression>();
/**
* `DecoratorHandler` which handles the `@Component` annotation.
*/
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(
decorator => decorator.name === 'Component' && decorator.from === '@angular/core');
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3ComponentMetadata> {
const meta = decorator.args[0];
if (!ts.isObjectLiteralExpression(meta)) {
throw new Error(`Decorator argument must be literal.`);
}
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
// on it.
const directiveMetadata = extractDirectiveMetadata(node, decorator, this.checker);
if (directiveMetadata === undefined) {
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
// case, compilation of the decorator is skipped. Returning an empty object signifies
// that no analysis was produced.
return {};
}
// Next, read the `@Component`-specific fields.
const component = reflectObjectLiteral(meta);
// Resolve and parse the template.
if (!component.has('template')) {
throw new Error(`For now, components must directly have a template.`);
}
const templateExpr = component.get('template') !;
const templateStr = staticallyResolve(templateExpr, this.checker);
if (typeof templateStr !== 'string') {
throw new Error(`Template must statically resolve to a string: ${node.name!.text}`);
}
let preserveWhitespaces: boolean = false;
if (component.has('preserveWhitespaces')) {
const value = staticallyResolve(component.get('preserveWhitespaces') !, this.checker);
if (typeof value !== 'boolean') {
throw new Error(`preserveWhitespaces must resolve to a boolean if present`);
}
preserveWhitespaces = value;
}
const template = parseTemplate(
templateStr, `${node.getSourceFile().fileName}#${node.name!.text}/template.html`,
{preserveWhitespaces});
if (template.errors !== undefined) {
throw new Error(
`Errors parsing template: ${template.errors.map(e => e.toString()).join(', ')}`);
}
// If the component has a selector, it should be registered with the `SelectorScopeRegistry` so
// when this component appears in an `@NgModule` scope, its selector can be determined.
if (directiveMetadata.selector !== null) {
this.scopeRegistry.registerSelector(node, directiveMetadata.selector);
}
return {
analysis: {
...directiveMetadata,
template,
viewQueries: [],
// These will be replaced during the compilation step, after all `NgModule`s have been
// analyzed and the full compilation scope for the component can be realized.
pipes: EMPTY_MAP,
directives: EMPTY_MAP,
}
};
}
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata): CompileResult {
const pool = new ConstantPool();
// Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope.
const scope = this.scopeRegistry.lookupCompilationScope(node);
if (scope !== null) {
// Replace the empty components and directives from the analyze() step with a fully expanded
// scope. This is possible now because during compile() the whole compilation unit has been
// fully analyzed.
analysis = {...analysis, ...scope};
}
const res = compileComponentFromMetadata(analysis, pool, makeBindingParser());
return {
field: 'ngComponentDef',
initializer: res.expression,
statements: pool.statements,
type: res.type,
};
}
}

View File

@ -0,0 +1,212 @@
/**
* @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, R3DirectiveMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator, staticallyResolve} from '../../metadata';
import {DecoratedNode, getDecoratedClassElements, reflectNonStaticField, reflectObjectLiteral} from '../../metadata/src/reflector';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {SelectorScopeRegistry} from './selector_scope';
import {getConstructorDependencies} from './util';
const EMPTY_OBJECT: {[key: string]: string} = {};
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata> {
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(
decorator => decorator.name === 'Directive' && decorator.from === '@angular/core');
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3DirectiveMetadata> {
const analysis = extractDirectiveMetadata(node, decorator, this.checker);
// If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so
// when this directive appears in an `@NgModule` scope, its selector can be determined.
if (analysis && analysis.selector !== null) {
this.scopeRegistry.registerSelector(node, analysis.selector);
}
return {analysis};
}
compile(node: ts.ClassDeclaration, analysis: R3DirectiveMetadata): CompileResult {
const pool = new ConstantPool();
const res = compileDirectiveFromMetadata(analysis, pool, makeBindingParser());
return {
field: 'ngDirectiveDef',
initializer: res.expression,
statements: pool.statements,
type: res.type,
};
}
}
/**
* Helper function to extract metadata from a `Directive` or `Component`.
*/
export function extractDirectiveMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker): R3DirectiveMetadata|
undefined {
const meta = decorator.args[0];
if (!ts.isObjectLiteralExpression(meta)) {
throw new Error(`Decorator argument must be literal.`);
}
const directive = reflectObjectLiteral(meta);
if (directive.has('jit')) {
// The only allowed value is true, so there's no need to expand further.
return undefined;
}
// Precompute a list of ts.ClassElements that have decorators. This includes things like @Input,
// @Output, @HostBinding, etc.
const decoratedElements = getDecoratedClassElements(clazz, checker);
// Construct the map of inputs both from the @Directive/@Component decorator, and the decorated
// fields.
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', checker);
const inputsFromFields = parseDecoratedFields(
findDecoratedFields(decoratedElements, '@angular/core', 'Input'), checker);
// And outputs.
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', checker);
const outputsFromFields = parseDecoratedFields(
findDecoratedFields(decoratedElements, '@angular/core', 'Output'), checker);
// Parse the selector.
let selector = '';
if (directive.has('selector')) {
const resolved = staticallyResolve(directive.get('selector') !, checker);
if (typeof resolved !== 'string') {
throw new Error(`Selector must be a string`);
}
selector = resolved;
}
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
const usesOnChanges = reflectNonStaticField(clazz, 'ngOnChanges') !== null;
return {
name: clazz.name !.text,
deps: getConstructorDependencies(clazz, checker),
host: {
attributes: {},
listeners: {},
properties: {},
},
lifecycle: {
usesOnChanges,
},
inputs: {...inputsFromMeta, ...inputsFromFields},
outputs: {...outputsFromMeta, ...outputsFromFields},
queries: [], selector,
type: new WrappedNodeExpr(clazz.name !),
typeSourceSpan: null !,
};
}
function assertIsStringArray(value: any[]): value is string[] {
for (let i = 0; i < value.length; i++) {
if (typeof value[i] !== 'string') {
throw new Error(`Failed to resolve @Directive.inputs[${i}] to a string`);
}
}
return true;
}
type DecoratedProperty = DecoratedNode<ts.PropertyDeclaration|ts.AccessorDeclaration>;
/**
* Find all fields in the array of `DecoratedNode`s that have a decorator of the given type.
*/
function findDecoratedFields(
elements: DecoratedNode<ts.ClassElement>[], decoratorModule: string,
decoratorName: string): DecoratedProperty[] {
return elements
.map(entry => {
const element = entry.element;
// Only consider properties and accessors. Filter out everything else.
if (!ts.isPropertyDeclaration(element) && !ts.isAccessor(element)) {
return null;
}
// Extract the array of matching decorators (there could be more than one).
const decorators = entry.decorators.filter(
decorator => decorator.name === decoratorName && decorator.from === decoratorModule);
if (decorators.length === 0) {
// No matching decorators, don't include this element.
return null;
}
return {element, decorators};
})
// Filter out nulls.
.filter(entry => entry !== null) as DecoratedProperty[];
}
/**
* Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
* correctly shaped metadata object.
*/
function parseFieldToPropertyMapping(
directive: Map<string, ts.Expression>, field: string,
checker: ts.TypeChecker): {[field: string]: string} {
if (!directive.has(field)) {
return EMPTY_OBJECT;
}
// Resolve the field of interest from the directive metadata to a string[].
const metaValues = staticallyResolve(directive.get(field) !, checker);
if (!Array.isArray(metaValues) || !assertIsStringArray(metaValues)) {
throw new Error(`Failed to resolve @Directive.${field}`);
}
return metaValues.reduce(
(results, value) => {
// Either the value is 'field' or 'field: property'. In the first case, `property` will
// be undefined, in which case the field name should also be used as the property name.
const [field, property] = value.split(':', 2).map(str => str.trim());
results[field] = property || field;
return results;
},
{} as{[field: string]: string});
}
/**
* Parse property decorators (e.g. `Input` or `Output`) and return the correctly shaped metadata
* object.
*/
function parseDecoratedFields(
fields: DecoratedProperty[], checker: ts.TypeChecker): {[field: string]: string} {
return fields.reduce(
(results, field) => {
const fieldName = (field.element.name as ts.Identifier).text;
field.decorators.forEach(decorator => {
// The decorator either doesn't have an argument (@Input()) in which case the property
// name is used, or it has one argument (@Output('named')).
if (decorator.args.length === 0) {
results[fieldName] = fieldName;
} else if (decorator.args.length === 1) {
const property = staticallyResolve(decorator.args[0], checker);
if (typeof property !== 'string') {
throw new Error(`Decorator argument must resolve to a string`);
}
results[fieldName] = property;
} else {
// Too many arguments.
throw new Error(
`Decorator must have 0 or 1 arguments, got ${decorator.args.length} argument(s)`);
}
});
return results;
},
{} as{[field: string]: string});
}

View File

@ -11,14 +11,15 @@ import * as ts from 'typescript';
import {Decorator} from '../../metadata';
import {reflectConstructorParameters, reflectImportedIdentifier, reflectObjectLiteral} from '../../metadata/src/reflector';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform/src/api';
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
import {getConstructorDependencies} from './util';
/**
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
*/
export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMetadata> {
export class InjectableDecoratorHandler implements DecoratorHandler<R3InjectableMetadata> {
constructor(private checker: ts.TypeChecker) {}
detect(decorator: Decorator[]): Decorator|undefined {
@ -31,11 +32,12 @@ export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMe
};
}
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction {
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): CompileResult {
const res = compileIvyInjectable(analysis);
return {
field: 'ngInjectableDef',
initializer: res.expression,
statements: [],
type: res.type,
};
}
@ -105,38 +107,7 @@ function extractInjectableMetadata(
}
}
function getConstructorDependencies(
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
const useType: R3DependencyMetadata[] = [];
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
ctorParams.forEach(param => {
let tokenExpr = param.typeValueExpr;
let optional = false, self = false, skipSelf = false;
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
if (dec.name === 'Inject') {
if (dec.args.length !== 1) {
throw new Error(`Unexpected number of arguments to @Inject().`);
}
tokenExpr = dec.args[0];
} else if (dec.name === 'Optional') {
optional = true;
} else if (dec.name === 'SkipSelf') {
skipSelf = true;
} else if (dec.name === 'Self') {
self = true;
} else {
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
}
if (tokenExpr === null) {
throw new Error(`No suitable token for parameter!`);
}
});
const token = new WrappedNodeExpr(tokenExpr);
useType.push(
{token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token});
});
return useType;
}
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
const meta: R3DependencyMetadata = {

View File

@ -0,0 +1,116 @@
/**
* @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, Expression, R3DirectiveMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator, Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {SelectorScopeRegistry} from './selector_scope';
import {referenceToExpression} from './util';
/**
* Compiles @NgModule annotations to ngModuleDef fields.
*
* TODO(alxhub): handle injector side of things as well.
*/
export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMetadata> {
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(
decorator => decorator.name === 'NgModule' && decorator.from === '@angular/core');
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3NgModuleMetadata> {
const meta = decorator.args[0];
if (!ts.isObjectLiteralExpression(meta)) {
throw new Error(`Decorator argument must be literal.`);
}
const ngModule = reflectObjectLiteral(meta);
if (ngModule.has('jit')) {
// The only allowed value is true, so there's no need to expand further.
return {};
}
// Extract the module declarations, imports, and exports.
let declarations: Reference[] = [];
if (ngModule.has('declarations')) {
const declarationMeta = staticallyResolve(ngModule.get('declarations') !, this.checker);
declarations = resolveTypeList(declarationMeta, 'declarations');
}
let imports: Reference[] = [];
if (ngModule.has('imports')) {
const importsMeta = staticallyResolve(ngModule.get('imports') !, this.checker);
imports = resolveTypeList(importsMeta, 'imports');
}
let exports: Reference[] = [];
if (ngModule.has('exports')) {
const exportsMeta = staticallyResolve(ngModule.get('exports') !, this.checker);
exports = resolveTypeList(exportsMeta, 'exports');
}
// Register this module's information with the SelectorScopeRegistry. This ensures that during
// the compile() phase, the module's metadata is available for selector scope computation.
this.scopeRegistry.registerModule(node, {declarations, imports, exports});
const context = node.getSourceFile();
return {
analysis: {
type: new WrappedNodeExpr(node.name !),
bootstrap: [],
declarations: declarations.map(decl => referenceToExpression(decl, context)),
exports: exports.map(exp => referenceToExpression(exp, context)),
imports: imports.map(imp => referenceToExpression(imp, context)),
emitInline: false,
},
};
}
compile(node: ts.ClassDeclaration, analysis: R3NgModuleMetadata): CompileResult {
const res = compileNgModule(analysis);
return {
field: 'ngModuleDef',
initializer: res.expression,
statements: [],
type: res.type,
};
}
}
/**
* Compute a list of `Reference`s from a resolved metadata value.
*/
function resolveTypeList(resolvedList: ResolvedValue, name: string): Reference[] {
const refList: Reference[] = [];
if (!Array.isArray(resolvedList)) {
throw new Error(`Expected array when reading property ${name}`);
}
resolvedList.forEach((entry, idx) => {
if (Array.isArray(entry)) {
// Recurse into nested arrays.
refList.push(...resolveTypeList(entry, name));
} else if (entry instanceof Reference) {
if (!entry.expressable) {
throw new Error(`Value at position ${idx} in ${name} array is not expressable`);
} else if (!ts.isClassDeclaration(entry.node)) {
throw new Error(`Value at position ${idx} in ${name} array is not a class declaration`);
}
refList.push(entry);
} else {
// TODO(alxhub): expand ModuleWithProviders.
throw new Error(`Value at position ${idx} in ${name} array is not a reference`);
}
});
return refList;
}

View File

@ -0,0 +1,353 @@
/**
* @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 {Expression, ExternalExpr, ExternalReference} from '@angular/compiler';
import * as ts from 'typescript';
import {AbsoluteReference, Reference, reflectStaticField, reflectTypeEntityToDeclaration} from '../../metadata';
import {referenceToExpression} from './util';
/**
* Metadata extracted for a given NgModule that can be used to compute selector scopes.
*/
export interface ModuleData {
declarations: Reference[];
imports: Reference[];
exports: Reference[];
}
/**
* Transitively expanded maps of directives and pipes visible to a component being compiled in the
* context of some module.
*/
export interface CompilationScope<T> {
directives: Map<string, T>;
pipes: Map<string, T>;
}
/**
* Both transitively expanded scopes for a given NgModule.
*/
interface SelectorScopes {
/**
* Set of components, directives, and pipes visible to all components being compiled in the
* context of some module.
*/
compilation: Reference[];
/**
* Set of components, directives, and pipes added to the compilation scope of any module importing
* some module.
*/
exported: Reference[];
}
/**
* Registry which records and correlates static analysis information of Angular types.
*
* Once a compilation unit's information is fed into the SelectorScopeRegistry, it can be asked to
* produce transitive `CompilationScope`s for components.
*/
export class SelectorScopeRegistry {
/**
* Map of modules declared in the current compilation unit to their (local) metadata.
*/
private _moduleToData = new Map<ts.ClassDeclaration, ModuleData>();
/**
* Map of modules to their cached `CompilationScope`s.
*/
private _compilationScopeCache = new Map<ts.ClassDeclaration, CompilationScope<Reference>>();
/**
* Map of components/directives to their selector.
*/
private _directiveToSelector = new Map<ts.ClassDeclaration, string>();
/**
* Map of pipes to their name.
*/
private _pipeToName = new Map<ts.ClassDeclaration, string>();
/**
* Map of components/directives/pipes to their module.
*/
private _declararedTypeToModule = new Map<ts.ClassDeclaration, ts.ClassDeclaration>();
constructor(private checker: ts.TypeChecker) {}
/**
* Register a module's metadata with the registry.
*/
registerModule(node: ts.ClassDeclaration, data: ModuleData): void {
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
if (this._moduleToData.has(node)) {
throw new Error(`Module already registered: ${node.name!.text}`);
}
this._moduleToData.set(node, data);
// Register all of the module's declarations in the context map as belonging to this module.
data.declarations.forEach(decl => {
this._declararedTypeToModule.set(ts.getOriginalNode(decl.node) as ts.ClassDeclaration, node);
});
}
/**
* Register the selector of a component or directive with the registry.
*/
registerSelector(node: ts.ClassDeclaration, selector: string): void {
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
if (this._directiveToSelector.has(node)) {
throw new Error(`Selector already registered: ${node.name!.text} ${selector}`);
}
this._directiveToSelector.set(node, selector);
}
/**
* Register the name of a pipe with the registry.
*/
registerPipe(node: ts.ClassDeclaration, name: string): void { this._pipeToName.set(node, name); }
/**
* Produce the compilation scope of a component, which is determined by the module that declares
* it.
*/
lookupCompilationScope(node: ts.ClassDeclaration): CompilationScope<Expression>|null {
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
// If the component has no associated module, then it has no compilation scope.
if (!this._declararedTypeToModule.has(node)) {
return null;
}
const module = this._declararedTypeToModule.get(node) !;
// Compilation scope computation is somewhat expensive, so it's cached. Check the cache for
// the module.
if (this._compilationScopeCache.has(module)) {
// The compilation scope was cached.
const scope = this._compilationScopeCache.get(module) !;
// The scope as cached is in terms of References, not Expressions. Converting between them
// requires knowledge of the context file (in this case, the component node's source file).
return convertScopeToExpressions(scope, node.getSourceFile());
}
// This is the first time the scope for this module is being computed.
const directives = new Map<string, Reference>();
const pipes = new Map<string, Reference>();
// Process the declaration scope of the module, and lookup the selector of every declared type.
// The initial value of ngModuleImportedFrom is 'null' which signifies that the NgModule
// was not imported from a .d.ts source.
this.lookupScopes(module !, /* ngModuleImportedFrom */ null).compilation.forEach(ref => {
const selector =
this.lookupDirectiveSelector(ts.getOriginalNode(ref.node) as ts.ClassDeclaration);
// Only directives/components with selectors get added to the scope.
if (selector != null) {
directives.set(selector, ref);
}
});
const scope: CompilationScope<Reference> = {directives, pipes};
// Many components may be compiled in the same scope, so cache it.
this._compilationScopeCache.set(node, scope);
// Convert References to Expressions in the context of the component's source file.
return convertScopeToExpressions(scope, node.getSourceFile());
}
/**
* Lookup `SelectorScopes` for a given module.
*
* This function assumes that if the given module was imported from an absolute path
* (`ngModuleImportedFrom`) then all of its declarations are exported at that same path, as well
* as imports and exports from other modules that are relatively imported.
*/
private lookupScopes(node: ts.ClassDeclaration, ngModuleImportedFrom: string|null):
SelectorScopes {
let data: ModuleData|null = null;
// Either this module was analyzed directly, or has a precompiled ngModuleDef.
if (this._moduleToData.has(node)) {
// The module was analyzed before, and thus its data is available.
data = this._moduleToData.get(node) !;
} else {
// The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type
// annotation that specifies the needed metadata.
if (ngModuleImportedFrom === null) {
// TODO(alxhub): handle hand-compiled ngModuleDef in the current Program.
throw new Error(`Need to read .d.ts module but ngModuleImportedFrom is unspecified`);
}
data = this._readMetadataFromCompiledClass(node, ngModuleImportedFrom);
// Note that data here could still be null, if the class didn't have a precompiled
// ngModuleDef.
}
if (data === null) {
throw new Error(`Module not registered: ${node.name!.text}`);
}
return {
compilation: [
...data.declarations,
// Expand imports to the exported scope of those imports.
...flatten(data.imports.map(
ref => this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
.exported)),
// And include the compilation scope of exported modules.
...flatten(
data.exports.filter(ref => this._moduleToData.has(ref.node as ts.ClassDeclaration))
.map(
ref =>
this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
.exported))
],
exported: flatten(data.exports.map(ref => {
if (this._moduleToData.has(ref.node as ts.ClassDeclaration)) {
return this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
.exported;
} else {
return [ref];
}
})),
};
}
/**
* Lookup the selector of a component or directive class.
*
* Potentially this class is declared in a .d.ts file or otherwise has a manually created
* ngComponentDef/ngDirectiveDef. In this case, the type metadata of that definition is read
* to determine the selector.
*/
private lookupDirectiveSelector(node: ts.ClassDeclaration): string|null {
if (this._directiveToSelector.has(node)) {
return this._directiveToSelector.get(node) !;
} else {
return this._readSelectorFromCompiledClass(node);
}
}
private lookupPipeName(node: ts.ClassDeclaration): string|undefined {
return this._pipeToName.get(node);
}
/**
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
* file, or in a .ts file with a handwritten definition).
*
* @param clazz the class of interest
* @param ngModuleImportedFrom module specifier of the import path to assume for all declarations
* stemming from this module.
*/
private _readMetadataFromCompiledClass(clazz: ts.ClassDeclaration, ngModuleImportedFrom: string):
ModuleData|null {
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`.
// TODO(alxhub): investigate caching of .d.ts module metadata.
const ngModuleDef = reflectStaticField(clazz, 'ngModuleDef');
if (ngModuleDef === null) {
return null;
} else if (
// Validate that the shape of the ngModuleDef type is correct.
ngModuleDef.type === undefined || !ts.isTypeReferenceNode(ngModuleDef.type) ||
ngModuleDef.type.typeArguments === undefined ||
ngModuleDef.type.typeArguments.length !== 4) {
return null;
}
// Read the ModuleData out of the type arguments.
const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
return {
declarations: this._extractReferencesFromType(declarationMetadata, ngModuleImportedFrom),
exports: this._extractReferencesFromType(exportMetadata, ngModuleImportedFrom),
imports: this._extractReferencesFromType(importMetadata, ngModuleImportedFrom),
};
}
/**
* Get the selector from type metadata for a class with a precompiled ngComponentDef or
* ngDirectiveDef.
*/
private _readSelectorFromCompiledClass(clazz: ts.ClassDeclaration): string|null {
const def =
reflectStaticField(clazz, 'ngComponentDef') || reflectStaticField(clazz, 'ngDirectiveDef');
if (def === null) {
// No definition could be found.
return null;
} else if (
def.type === undefined || !ts.isTypeReferenceNode(def.type) ||
def.type.typeArguments === undefined || def.type.typeArguments.length !== 2) {
// The type metadata was the wrong shape.
return null;
}
const type = def.type.typeArguments[1];
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
// The type metadata was the wrong type.
return null;
}
return type.literal.text;
}
/**
* Process a `TypeNode` which is a tuple of references to other types, and return `Reference`s to
* them.
*
* This operation assumes that these types should be imported from `ngModuleImportedFrom` unless
* they themselves were imported from another absolute path.
*/
private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string): Reference[] {
if (!ts.isTupleTypeNode(def)) {
return [];
}
return def.elementTypes.map(element => {
if (!ts.isTypeReferenceNode(element)) {
throw new Error(`Expected TypeReferenceNode`);
}
const type = element.typeName;
const {node, from} = reflectTypeEntityToDeclaration(type, this.checker);
const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
const clazz = node as ts.ClassDeclaration;
return new AbsoluteReference(node, clazz.name !, moduleName, clazz.name !.text);
});
}
}
function flatten<T>(array: T[][]): T[] {
return array.reduce((accum, subArray) => {
accum.push(...subArray);
return accum;
}, [] as T[]);
}
function absoluteModuleName(ref: Reference): string|null {
const name = (ref.node as ts.ClassDeclaration).name !.text;
if (!(ref instanceof AbsoluteReference)) {
return null;
}
return ref.moduleName;
}
function convertReferenceMap(
map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> {
return new Map<string, Expression>(Array.from(map.entries()).map(([selector, ref]): [
string, Expression
] => [selector, referenceToExpression(ref, context)]));
}
function convertScopeToExpressions(
scope: CompilationScope<Reference>, context: ts.SourceFile): CompilationScope<Expression> {
const directives = convertReferenceMap(scope.directives, context);
const pipes = convertReferenceMap(scope.pipes, context);
return {directives, pipes};
}

View File

@ -0,0 +1,84 @@
/**
* @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 {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {Reference, reflectConstructorParameters} from '../../metadata';
import {reflectImportedIdentifier} from '../../metadata/src/reflector';
export function getConstructorDependencies(
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
const useType: R3DependencyMetadata[] = [];
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
ctorParams.forEach(param => {
let tokenExpr = param.typeValueExpr;
let optional = false, self = false, skipSelf = false, host = false;
let resolved = R3ResolvedDependencyType.Token;
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
if (dec.name === 'Inject') {
if (dec.args.length !== 1) {
throw new Error(`Unexpected number of arguments to @Inject().`);
}
tokenExpr = dec.args[0];
} else if (dec.name === 'Optional') {
optional = true;
} else if (dec.name === 'SkipSelf') {
skipSelf = true;
} else if (dec.name === 'Self') {
self = true;
} else if (dec.name === 'Host') {
host = true;
} else if (dec.name === 'Attribute') {
if (dec.args.length !== 1) {
throw new Error(`Unexpected number of arguments to @Attribute().`);
}
tokenExpr = dec.args[0];
resolved = R3ResolvedDependencyType.Attribute;
} else {
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
}
});
if (tokenExpr === null) {
throw new Error(
`No suitable token for parameter ${(param.name as ts.Identifier).text} of class ${clazz.name!.text} with decorators ${param.decorators.map(dec => dec.from + '#' + dec.name).join(',')}`);
}
if (ts.isIdentifier(tokenExpr)) {
const importedSymbol = reflectImportedIdentifier(tokenExpr, checker);
if (importedSymbol !== null && importedSymbol.from === '@angular/core') {
switch (importedSymbol.name) {
case 'ElementRef':
resolved = R3ResolvedDependencyType.ElementRef;
break;
case 'Injector':
resolved = R3ResolvedDependencyType.Injector;
break;
case 'TemplateRef':
resolved = R3ResolvedDependencyType.TemplateRef;
break;
case 'ViewContainerRef':
resolved = R3ResolvedDependencyType.ViewContainerRef;
break;
default:
// Leave as a Token or Attribute.
}
}
}
const token = new WrappedNodeExpr(tokenExpr);
useType.push({token, optional, self, skipSelf, host, resolved});
});
return useType;
}
export function referenceToExpression(ref: Reference, context: ts.SourceFile): Expression {
const exp = ref.toExpression(context);
if (exp === null) {
throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`);
}
return exp;
}

View File

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob([
"**/*.ts",
]),
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/testing",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
deps = [
":test_lib",
"//tools/testing:node_no_angular",
],
)

View File

@ -0,0 +1,79 @@
/**
* @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 ts from 'typescript';
import {AbsoluteReference, ResolvedReference} from '../../metadata/src/resolver';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {NgModuleDecoratorHandler} from '../src/ng_module';
import {SelectorScopeRegistry} from '../src/selector_scope';
describe('SelectorScopeRegistry', () => {
it('absolute imports work', () => {
const {program} = makeProgram([
{
name: 'node_modules/@angular/core/index.d.ts',
contents: `
export interface NgComponentDef<A, B> {}
export interface NgModuleDef<A, B, C, D> {}
`
},
{
name: 'node_modules/some_library/index.d.ts',
contents: `
import {NgComponentDef, NgModuleDef} from '@angular/core';
import * as i0 from './component';
export declare class SomeModule {
static ngModuleDef: NgModuleDef<SomeModule, [i0.SomeCmp], any, [i0.SomeCmp]>;
}
export declare class SomeCmp {
static ngComponentDef: NgComponentDef<SomeCmp, 'some-cmp'>;
}
`
},
{
name: 'node_modules/some_library/component.d.ts',
contents: `
export declare class SomeCmp {}
`
},
{
name: 'entry.ts',
contents: `
export class ProgramCmp {}
export class ProgramModule {}
`
},
]);
const checker = program.getTypeChecker();
const ProgramModule =
getDeclaration(program, 'entry.ts', 'ProgramModule', ts.isClassDeclaration);
const ProgramCmp = getDeclaration(program, 'entry.ts', 'ProgramCmp', ts.isClassDeclaration);
const SomeModule = getDeclaration(
program, 'node_modules/some_library/index.d.ts', 'SomeModule', ts.isClassDeclaration);
expect(ProgramModule).toBeDefined();
expect(SomeModule).toBeDefined();
const registry = new SelectorScopeRegistry(checker);
registry.registerModule(ProgramModule, {
declarations: [new ResolvedReference(ProgramCmp, ProgramCmp.name !)],
exports: [],
imports: [new AbsoluteReference(SomeModule, SomeModule.name !, 'some_library', 'SomeModule')],
});
registry.registerSelector(ProgramCmp, 'program-cmp');
const scope = registry.lookupCompilationScope(ProgramCmp) !;
expect(scope).toBeDefined();
expect(scope.directives).toBeDefined();
expect(scope.directives.size).toBe(1);
});
});

View File

@ -10,6 +10,7 @@ ts_library(
]),
module_name = "@angular/compiler-cli/src/ngtsc/metadata",
deps = [
"//packages:types",
"//packages/compiler",
],
)

View File

@ -6,5 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
export {Decorator, Parameter, reflectConstructorParameters, reflectDecorator} from './src/reflector';
export {Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
export {Decorator, Parameter, reflectConstructorParameters, reflectDecorator, reflectNonStaticField, reflectObjectLiteral, reflectStaticField, reflectTypeEntityToDeclaration,} from './src/reflector';
export {AbsoluteReference, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';

View File

@ -236,3 +236,103 @@ export function reflectImportedIdentifier(
return {from, name};
}
export interface DecoratedNode<T extends ts.Node> {
element: T;
decorators: Decorator[];
}
export function getDecoratedClassElements(
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): DecoratedNode<ts.ClassElement>[] {
const decoratedElements: DecoratedNode<ts.ClassElement>[] = [];
clazz.members.forEach(element => {
if (element.decorators !== undefined) {
const decorators = element.decorators.map(decorator => reflectDecorator(decorator, checker))
.filter(decorator => decorator != null) as Decorator[];
if (decorators.length > 0) {
decoratedElements.push({element, decorators});
}
}
});
return decoratedElements;
}
export function reflectStaticField(
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
// Check if the name matches.
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
return false;
}
// Check if the property is static.
if (member.modifiers === undefined ||
!member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
return false;
}
// Found the field.
return true;
}) ||
null;
}
export function reflectNonStaticField(
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
// Check if the name matches.
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
return false;
}
// Check if the property is static.
if (member.modifiers !== undefined &&
member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
return false;
}
// Found the field.
return true;
}) ||
null;
}
export function reflectTypeEntityToDeclaration(
type: ts.EntityName, checker: ts.TypeChecker): {node: ts.Declaration, from: string | null} {
let realSymbol = checker.getSymbolAtLocation(type);
if (realSymbol === undefined) {
throw new Error(`Cannot resolve type entity to symbol`);
}
while (realSymbol.flags & ts.SymbolFlags.Alias) {
realSymbol = checker.getAliasedSymbol(realSymbol);
}
let node: ts.Declaration|null = null;
if (realSymbol.valueDeclaration !== undefined) {
node = realSymbol.valueDeclaration;
} else if (realSymbol.declarations !== undefined && realSymbol.declarations.length === 1) {
node = realSymbol.declarations[0];
} else {
throw new Error(`Cannot resolve type entity symbol to declaration`);
}
if (ts.isQualifiedName(type)) {
if (!ts.isIdentifier(type.left)) {
throw new Error(`Cannot handle qualified name with non-identifier lhs`);
}
const symbol = checker.getSymbolAtLocation(type.left);
if (symbol === undefined || symbol.declarations === undefined ||
symbol.declarations.length !== 1) {
throw new Error(`Cannot resolve qualified type entity lhs to symbol`);
}
const decl = symbol.declarations[0];
if (ts.isNamespaceImport(decl)) {
const clause = decl.parent !;
const importDecl = clause.parent !;
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
throw new Error(`Module specifier is not a string`);
}
return {node, from: importDecl.moduleSpecifier.text};
} else {
throw new Error(`Unknown import type?`);
}
} else {
return {node, from: null};
}
}

View File

@ -11,8 +11,12 @@
* values where possible and returning a `DynamicValue` signal when not.
*/
import {Expression, ExternalExpr, ExternalReference, WrappedNodeExpr} from '@angular/compiler';
import * as path from 'path';
import * as ts from 'typescript';
const TS_DTS_EXTENSION = /(\.d)?\.ts$/;
/**
* Represents a value which cannot be determined statically.
*
@ -72,24 +76,99 @@ export interface ResolvedValueArray extends Array<ResolvedValue> {}
*/
type Scope = Map<ts.ParameterDeclaration, ResolvedValue>;
/**
* Whether or not to allow references during resolution.
*
* See `StaticInterpreter` for details.
*/
const enum AllowReferences {
No = 0,
Yes = 1,
}
/**
* A reference to a `ts.Node`.
*
* For example, if an expression evaluates to a function or class definition, it will be returned
* as a `Reference` (assuming references are allowed in evaluation).
*/
export class Reference {
export abstract class Reference {
constructor(readonly node: ts.Node) {}
/**
* Whether an `Expression` can be generated which references the node.
*/
readonly expressable: boolean;
/**
* Generate an `Expression` representing this type, in the context of the given SourceFile.
*
* This could be a local variable reference, if the symbol is imported, or it could be a new
* import if needed.
*/
abstract toExpression(context: ts.SourceFile): Expression|null;
}
/**
* A reference to a node only, without any ability to get an `Expression` representing that node.
*
* This is used for returning references to things like method declarations, which are not directly
* referenceable.
*/
export class NodeReference extends Reference {
toExpression(context: ts.SourceFile): null { return null; }
}
/**
* A reference to a node which has a `ts.Identifier` and can be resolved to an `Expression`.
*
* Imports generated by `ResolvedReference`s are always relative.
*/
export class ResolvedReference extends Reference {
constructor(node: ts.Node, protected identifier: ts.Identifier) { super(node); }
readonly expressable = true;
toExpression(context: ts.SourceFile): Expression {
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.node).getSourceFile()) {
return new WrappedNodeExpr(this.identifier);
} else {
// Relative import from context -> this.node.getSourceFile().
// TODO(alxhub): investigate the impact of multiple source roots here.
// TODO(alxhub): investigate the need to map such paths via the Host for proper g3 support.
let relative =
path.posix.relative(path.dirname(context.fileName), this.node.getSourceFile().fileName)
.replace(TS_DTS_EXTENSION, '');
// path.relative() does not include the leading './'.
if (!relative.startsWith('.')) {
relative = `./${relative}`;
}
// path.relative() returns the empty string (converted to './' above) if the two paths are the
// same.
if (relative === './') {
// Same file after all.
return new WrappedNodeExpr(this.identifier);
} else {
return new ExternalExpr(new ExternalReference(relative, this.identifier.text));
}
}
}
}
/**
* A reference to a node which has a `ts.Identifer` and an expected absolute module name.
*
* An `AbsoluteReference` can be resolved to an `Expression`, and if that expression is an import
* the module specifier will be an absolute module name, not a relative path.
*/
export class AbsoluteReference extends Reference {
constructor(
node: ts.Node, private identifier: ts.Identifier, readonly moduleName: string,
private symbolName: string) {
super(node);
}
readonly expressable = true;
toExpression(context: ts.SourceFile): Expression {
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.node.getSourceFile())) {
return new WrappedNodeExpr(this.identifier);
} else {
return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName));
}
}
}
/**
@ -100,9 +179,8 @@ export class Reference {
* @returns a `ResolvedValue` representing the resolved value
*/
export function staticallyResolve(node: ts.Expression, checker: ts.TypeChecker): ResolvedValue {
return new StaticInterpreter(
checker, new Map<ts.ParameterDeclaration, ResolvedValue>(), AllowReferences.No)
.visit(node);
return new StaticInterpreter(checker).visit(
node, {absoluteModuleName: null, scope: new Map<ts.ParameterDeclaration, ResolvedValue>()});
}
interface BinaryOperatorDef {
@ -144,59 +222,67 @@ const UNARY_OPERATORS = new Map<ts.SyntaxKind, (a: any) => any>([
[ts.SyntaxKind.PlusToken, a => +a], [ts.SyntaxKind.ExclamationToken, a => !a]
]);
interface Context {
absoluteModuleName: string|null;
scope: Scope;
}
class StaticInterpreter {
constructor(
private checker: ts.TypeChecker, private scope: Scope,
private allowReferences: AllowReferences) {}
constructor(private checker: ts.TypeChecker) {}
visit(node: ts.Expression): ResolvedValue { return this.visitExpression(node); }
visit(node: ts.Expression, context: Context): ResolvedValue {
return this.visitExpression(node, context);
}
private visitExpression(node: ts.Expression): ResolvedValue {
private visitExpression(node: ts.Expression, context: Context): ResolvedValue {
if (node.kind === ts.SyntaxKind.TrueKeyword) {
return true;
} else if (node.kind === ts.SyntaxKind.FalseKeyword) {
return false;
} else if (ts.isStringLiteral(node)) {
return node.text;
} else if (ts.isNoSubstitutionTemplateLiteral(node)) {
return node.text;
} else if (ts.isNumericLiteral(node)) {
return parseFloat(node.text);
} else if (ts.isObjectLiteralExpression(node)) {
return this.visitObjectLiteralExpression(node);
return this.visitObjectLiteralExpression(node, context);
} else if (ts.isIdentifier(node)) {
return this.visitIdentifier(node);
return this.visitIdentifier(node, context);
} else if (ts.isPropertyAccessExpression(node)) {
return this.visitPropertyAccessExpression(node);
return this.visitPropertyAccessExpression(node, context);
} else if (ts.isCallExpression(node)) {
return this.visitCallExpression(node);
return this.visitCallExpression(node, context);
} else if (ts.isConditionalExpression(node)) {
return this.visitConditionalExpression(node);
return this.visitConditionalExpression(node, context);
} else if (ts.isPrefixUnaryExpression(node)) {
return this.visitPrefixUnaryExpression(node);
return this.visitPrefixUnaryExpression(node, context);
} else if (ts.isBinaryExpression(node)) {
return this.visitBinaryExpression(node);
return this.visitBinaryExpression(node, context);
} else if (ts.isArrayLiteralExpression(node)) {
return this.visitArrayLiteralExpression(node);
return this.visitArrayLiteralExpression(node, context);
} else if (ts.isParenthesizedExpression(node)) {
return this.visitParenthesizedExpression(node);
return this.visitParenthesizedExpression(node, context);
} else if (ts.isElementAccessExpression(node)) {
return this.visitElementAccessExpression(node);
return this.visitElementAccessExpression(node, context);
} else if (ts.isAsExpression(node)) {
return this.visitExpression(node.expression);
return this.visitExpression(node.expression, context);
} else if (ts.isNonNullExpression(node)) {
return this.visitExpression(node.expression);
return this.visitExpression(node.expression, context);
} else if (ts.isClassDeclaration(node)) {
return this.visitDeclaration(node);
return this.visitDeclaration(node, context);
} else {
return DYNAMIC_VALUE;
}
}
private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): ResolvedValue {
private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression, context: Context):
ResolvedValue {
const array: ResolvedValueArray = [];
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (ts.isSpreadElement(element)) {
const spread = this.visitExpression(element.expression);
const spread = this.visitExpression(element.expression, context);
if (isDynamicValue(spread)) {
return DYNAMIC_VALUE;
}
@ -206,7 +292,7 @@ class StaticInterpreter {
array.push(...spread);
} else {
const result = this.visitExpression(element);
const result = this.visitExpression(element, context);
if (isDynamicValue(result)) {
return DYNAMIC_VALUE;
}
@ -217,27 +303,28 @@ class StaticInterpreter {
return array;
}
private visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): ResolvedValue {
private visitObjectLiteralExpression(node: ts.ObjectLiteralExpression, context: Context):
ResolvedValue {
const map: ResolvedValueMap = new Map<string, ResolvedValue>();
for (let i = 0; i < node.properties.length; i++) {
const property = node.properties[i];
if (ts.isPropertyAssignment(property)) {
const name = this.stringNameFromPropertyName(property.name);
const name = this.stringNameFromPropertyName(property.name, context);
// Check whether the name can be determined statically.
if (name === undefined) {
return DYNAMIC_VALUE;
}
map.set(name, this.visitExpression(property.initializer));
map.set(name, this.visitExpression(property.initializer, context));
} else if (ts.isShorthandPropertyAssignment(property)) {
const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
if (symbol === undefined || symbol.valueDeclaration === undefined) {
return DYNAMIC_VALUE;
}
map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration));
map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration, context));
} else if (ts.isSpreadAssignment(property)) {
const spread = this.visitExpression(property.expression);
const spread = this.visitExpression(property.expression, context);
if (isDynamicValue(spread)) {
return DYNAMIC_VALUE;
}
@ -252,19 +339,34 @@ class StaticInterpreter {
return map;
}
private visitIdentifier(node: ts.Identifier): ResolvedValue {
private visitIdentifier(node: ts.Identifier, context: Context): ResolvedValue {
let symbol: ts.Symbol|undefined = this.checker.getSymbolAtLocation(node);
if (symbol === undefined) {
return DYNAMIC_VALUE;
}
const result = this.visitSymbol(symbol);
if (this.allowReferences === AllowReferences.Yes && isDynamicValue(result)) {
return new Reference(node);
}
return result;
return this.visitSymbol(symbol, context);
}
private visitSymbol(symbol: ts.Symbol): ResolvedValue {
private visitSymbol(symbol: ts.Symbol, context: Context): ResolvedValue {
let absoluteModuleName = context.absoluteModuleName;
if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
for (let i = 0; i < symbol.declarations.length; i++) {
const decl = symbol.declarations[i];
if (ts.isImportSpecifier(decl) && decl.parent !== undefined &&
decl.parent.parent !== undefined && decl.parent.parent.parent !== undefined) {
const importDecl = decl.parent.parent.parent;
if (ts.isStringLiteral(importDecl.moduleSpecifier)) {
const moduleSpecifier = importDecl.moduleSpecifier.text;
if (!moduleSpecifier.startsWith('.')) {
absoluteModuleName = moduleSpecifier;
}
}
}
}
}
const newContext = {...context, absoluteModuleName};
while (symbol.flags & ts.SymbolFlags.Alias) {
symbol = this.checker.getAliasedSymbol(symbol);
}
@ -274,42 +376,44 @@ class StaticInterpreter {
}
if (symbol.valueDeclaration !== undefined) {
return this.visitDeclaration(symbol.valueDeclaration);
return this.visitDeclaration(symbol.valueDeclaration, newContext);
}
return symbol.declarations.reduce<ResolvedValue>((prev, decl) => {
if (!(isDynamicValue(prev) || prev instanceof Reference)) {
return prev;
}
return this.visitDeclaration(decl);
return this.visitDeclaration(decl, newContext);
}, DYNAMIC_VALUE);
}
private visitDeclaration(node: ts.Declaration): ResolvedValue {
private visitDeclaration(node: ts.Declaration, context: Context): ResolvedValue {
if (ts.isVariableDeclaration(node)) {
if (!node.initializer) {
return undefined;
}
return this.visitExpression(node.initializer);
} else if (ts.isParameter(node) && this.scope.has(node)) {
return this.scope.get(node) !;
return this.visitExpression(node.initializer, context);
} else if (ts.isParameter(node) && context.scope.has(node)) {
return context.scope.get(node) !;
} else if (ts.isExportAssignment(node)) {
return this.visitExpression(node.expression);
return this.visitExpression(node.expression, context);
} else if (ts.isSourceFile(node)) {
return this.visitSourceFile(node);
return this.visitSourceFile(node, context);
} else {
return this.getReference(node, context);
}
return this.allowReferences === AllowReferences.Yes ? new Reference(node) : DYNAMIC_VALUE;
}
private visitElementAccessExpression(node: ts.ElementAccessExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
private visitElementAccessExpression(node: ts.ElementAccessExpression, context: Context):
ResolvedValue {
const lhs = this.visitExpression(node.expression, context);
if (node.argumentExpression === undefined) {
throw new Error(`Expected argument in ElementAccessExpression`);
}
if (isDynamicValue(lhs)) {
return DYNAMIC_VALUE;
}
const rhs = this.withNoReferences.visitExpression(node.argumentExpression);
const rhs = this.visitExpression(node.argumentExpression, context);
if (isDynamicValue(rhs)) {
return DYNAMIC_VALUE;
}
@ -318,33 +422,34 @@ class StaticInterpreter {
`ElementAccessExpression index should be string or number, got ${typeof rhs}: ${rhs}`);
}
return this.accessHelper(lhs, rhs);
return this.accessHelper(lhs, rhs, context);
}
private visitPropertyAccessExpression(node: ts.PropertyAccessExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
private visitPropertyAccessExpression(node: ts.PropertyAccessExpression, context: Context):
ResolvedValue {
const lhs = this.visitExpression(node.expression, context);
const rhs = node.name.text;
// TODO: handle reference to class declaration.
if (isDynamicValue(lhs)) {
return DYNAMIC_VALUE;
}
return this.accessHelper(lhs, rhs);
return this.accessHelper(lhs, rhs, context);
}
private visitSourceFile(node: ts.SourceFile): ResolvedValue {
private visitSourceFile(node: ts.SourceFile, context: Context): ResolvedValue {
const map = new Map<string, ResolvedValue>();
const symbol = this.checker.getSymbolAtLocation(node);
if (symbol === undefined) {
return DYNAMIC_VALUE;
}
const exports = this.checker.getExportsOfModule(symbol);
exports.forEach(symbol => map.set(symbol.name, this.visitSymbol(symbol)));
exports.forEach(symbol => map.set(symbol.name, this.visitSymbol(symbol, context)));
return map;
}
private accessHelper(lhs: ResolvedValue, rhs: string|number): ResolvedValue {
private accessHelper(lhs: ResolvedValue, rhs: string|number, context: Context): ResolvedValue {
const strIndex = `${rhs}`;
if (lhs instanceof Map) {
if (lhs.has(strIndex)) {
@ -367,16 +472,16 @@ class StaticInterpreter {
const ref = lhs.node;
if (ts.isClassDeclaration(ref)) {
let value: ResolvedValue = undefined;
const member = ref.members.filter(member => isStatic(member))
.find(
member => member.name !== undefined &&
this.stringNameFromPropertyName(member.name) === strIndex);
const member =
ref.members.filter(member => isStatic(member))
.find(
member => member.name !== undefined &&
this.stringNameFromPropertyName(member.name, context) === strIndex);
if (member !== undefined) {
if (ts.isPropertyDeclaration(member) && member.initializer !== undefined) {
value = this.visitExpression(member.initializer);
value = this.visitExpression(member.initializer, context);
} else if (ts.isMethodDeclaration(member)) {
value = this.allowReferences === AllowReferences.Yes ? new Reference(member) :
DYNAMIC_VALUE;
value = new NodeReference(member);
}
}
return value;
@ -385,8 +490,8 @@ class StaticInterpreter {
throw new Error(`Invalid dot property access: ${lhs} dot ${rhs}`);
}
private visitCallExpression(node: ts.CallExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
private visitCallExpression(node: ts.CallExpression, context: Context): ResolvedValue {
const lhs = this.visitExpression(node.expression, context);
if (!(lhs instanceof Reference)) {
throw new Error(`attempting to call something that is not a function: ${lhs}`);
} else if (!isFunctionOrMethodDeclaration(lhs.node) || !lhs.node.body) {
@ -406,43 +511,46 @@ class StaticInterpreter {
let value: ResolvedValue = undefined;
if (index < node.arguments.length) {
const arg = node.arguments[index];
value = this.visitExpression(arg);
value = this.visitExpression(arg, context);
}
if (value === undefined && param.initializer !== undefined) {
value = this.visitExpression(param.initializer);
value = this.visitExpression(param.initializer, context);
}
newScope.set(param, value);
});
return ret.expression !== undefined ? this.withScope(newScope).visitExpression(ret.expression) :
undefined;
return ret.expression !== undefined ?
this.visitExpression(ret.expression, {...context, scope: newScope}) :
undefined;
}
private visitConditionalExpression(node: ts.ConditionalExpression): ResolvedValue {
const condition = this.withNoReferences.visitExpression(node.condition);
private visitConditionalExpression(node: ts.ConditionalExpression, context: Context):
ResolvedValue {
const condition = this.visitExpression(node.condition, context);
if (isDynamicValue(condition)) {
return condition;
}
if (condition) {
return this.visitExpression(node.whenTrue);
return this.visitExpression(node.whenTrue, context);
} else {
return this.visitExpression(node.whenFalse);
return this.visitExpression(node.whenFalse, context);
}
}
private visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ResolvedValue {
private visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression, context: Context):
ResolvedValue {
const operatorKind = node.operator;
if (!UNARY_OPERATORS.has(operatorKind)) {
throw new Error(`Unsupported prefix unary operator: ${ts.SyntaxKind[operatorKind]}`);
}
const op = UNARY_OPERATORS.get(operatorKind) !;
const value = this.visitExpression(node.operand);
const value = this.visitExpression(node.operand, context);
return isDynamicValue(value) ? DYNAMIC_VALUE : op(value);
}
private visitBinaryExpression(node: ts.BinaryExpression): ResolvedValue {
private visitBinaryExpression(node: ts.BinaryExpression, context: Context): ResolvedValue {
const tokenKind = node.operatorToken.kind;
if (!BINARY_OPERATORS.has(tokenKind)) {
throw new Error(`Unsupported binary operator: ${ts.SyntaxKind[tokenKind]}`);
@ -451,44 +559,42 @@ class StaticInterpreter {
const opRecord = BINARY_OPERATORS.get(tokenKind) !;
let lhs: ResolvedValue, rhs: ResolvedValue;
if (opRecord.literal) {
const withNoReferences = this.withNoReferences;
lhs = literal(withNoReferences.visitExpression(node.left));
rhs = literal(withNoReferences.visitExpression(node.right));
lhs = literal(this.visitExpression(node.left, context));
rhs = literal(this.visitExpression(node.right, context));
} else {
lhs = this.visitExpression(node.left);
rhs = this.visitExpression(node.right);
lhs = this.visitExpression(node.left, context);
rhs = this.visitExpression(node.right, context);
}
return isDynamicValue(lhs) || isDynamicValue(rhs) ? DYNAMIC_VALUE : opRecord.op(lhs, rhs);
}
private visitParenthesizedExpression(node: ts.ParenthesizedExpression): ResolvedValue {
return this.visitExpression(node.expression);
private visitParenthesizedExpression(node: ts.ParenthesizedExpression, context: Context):
ResolvedValue {
return this.visitExpression(node.expression, context);
}
private stringNameFromPropertyName(node: ts.PropertyName): string|undefined {
private stringNameFromPropertyName(node: ts.PropertyName, context: Context): string|undefined {
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
return node.text;
} else { // ts.ComputedPropertyName
const literal = this.withNoReferences.visitExpression(node.expression);
const literal = this.visitExpression(node.expression, context);
return typeof literal === 'string' ? literal : undefined;
}
}
private get withReferences(): StaticInterpreter {
return this.allowReferences === AllowReferences.Yes ?
this :
new StaticInterpreter(this.checker, this.scope, AllowReferences.Yes);
}
private get withNoReferences(): StaticInterpreter {
return this.allowReferences === AllowReferences.No ?
this :
new StaticInterpreter(this.checker, this.scope, AllowReferences.No);
}
private withScope(scope: Scope): StaticInterpreter {
return new StaticInterpreter(this.checker, scope, this.allowReferences);
private getReference(node: ts.Declaration, context: Context): Reference {
const id = identifierOfDeclaration(node);
if (id === undefined) {
throw new Error(`Don't know how to refer to ${ts.SyntaxKind[node.kind]}`);
}
if (context.absoluteModuleName !== null) {
// TODO(alxhub): investigate whether this can get symbol names wrong in the event of
// re-exports under different names.
return new AbsoluteReference(node, id, context.absoluteModuleName, id.text);
} else {
return new ResolvedReference(node, id);
}
}
}
@ -512,3 +618,17 @@ function literal(value: ResolvedValue): any {
}
throw new Error(`Value ${value} is not literal and cannot be used in this context.`);
}
function identifierOfDeclaration(decl: ts.Declaration): ts.Identifier|undefined {
if (ts.isClassDeclaration(decl)) {
return decl.name;
} else if (ts.isFunctionDeclaration(decl)) {
return decl.name;
} else if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) {
return decl.name;
} else if (ts.isShorthandPropertyAssignment(decl)) {
return decl.name;
} else {
return undefined;
}
}

View File

@ -11,6 +11,7 @@ ts_library(
]),
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/testing",
],

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ExternalExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {ResolvedValue, staticallyResolve} from '../src/resolver';
import {Reference, ResolvedValue, staticallyResolve} from '../src/resolver';
function makeSimpleProgram(contents: string): ts.Program {
return makeProgram([{name: 'entry.ts', contents}]).program;
@ -116,6 +117,62 @@ describe('ngtsc metadata', () => {
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
});
it('imports work', () => {
const {program} = makeProgram([
{name: 'second.ts', contents: 'export function foo(bar) { return bar; }'},
{
name: 'entry.ts',
contents: `
import {foo} from './second';
const target$ = foo;
`
},
]);
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const resolved = staticallyResolve(expr, checker);
if (!(resolved instanceof Reference)) {
return fail('Expected expression to resolve to a reference');
}
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
expect(resolved.expressable).toBe(true);
const reference = resolved.toExpression(program.getSourceFile('entry.ts') !);
if (!(reference instanceof ExternalExpr)) {
return fail('Expected expression reference to be an external (import) expression');
}
expect(reference.value.moduleName).toBe('./second');
expect(reference.value.name).toBe('foo');
});
it('absolute imports work', () => {
const {program} = makeProgram([
{name: 'node_modules/some_library/index.d.ts', contents: 'export declare function foo(bar);'},
{
name: 'entry.ts',
contents: `
import {foo} from 'some_library';
const target$ = foo;
`
},
]);
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const resolved = staticallyResolve(expr, checker);
if (!(resolved instanceof Reference)) {
return fail('Expected expression to resolve to a reference');
}
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
expect(resolved.expressable).toBe(true);
const reference = resolved.toExpression(program.getSourceFile('entry.ts') !);
if (!(reference instanceof ExternalExpr)) {
return fail('Expected expression reference to be an external (import) expression');
}
expect(reference.value.moduleName).toBe('some_library');
expect(reference.value.name).toBe('foo');
});
it('reads values from default exports', () => {
const {program} = makeProgram([
{name: 'second.ts', contents: 'export default {property: "test"}'},
@ -130,7 +187,6 @@ describe('ngtsc metadata', () => {
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
debugger;
expect(staticallyResolve(expr, checker)).toEqual('test');
});

View File

@ -12,8 +12,9 @@ import * as ts from 'typescript';
import * as api from '../transformers/api';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, SelectorScopeRegistry} from './annotations';
import {CompilerHost} from './compiler_host';
import {InjectableCompilerAdapter, IvyCompilation, ivyTransformFactory} from './transform';
import {IvyCompilation, ivyTransformFactory} from './transform';
export class NgtscProgram implements api.Program {
private tsProgram: ts.Program;
@ -89,10 +90,16 @@ export class NgtscProgram implements api.Program {
const mergeEmitResultsCallback = opts && opts.mergeEmitResultsCallback || mergeEmitResults;
const checker = this.tsProgram.getTypeChecker();
const scopeRegistry = new SelectorScopeRegistry(checker);
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const adapters = [new InjectableCompilerAdapter(checker)];
const compilation = new IvyCompilation(adapters, checker);
const handlers = [
new ComponentDecoratorHandler(checker, scopeRegistry),
new DirectiveDecoratorHandler(checker, scopeRegistry),
new InjectableDecoratorHandler(checker),
new NgModuleDecoratorHandler(checker, scopeRegistry),
];
const compilation = new IvyCompilation(handlers, checker);
// Analyze every source file in the program.
this.tsProgram.getSourceFiles()

View File

@ -15,7 +15,10 @@ export function makeProgram(files: {name: string, contents: string}[]):
files.forEach(file => host.writeFile(file.name, file.contents));
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
const program = ts.createProgram(rootNames, {noLib: true, experimentalDecorators: true}, host);
const program = ts.createProgram(
rootNames,
{noLib: true, experimentalDecorators: true, moduleResolution: ts.ModuleResolutionKind.NodeJs},
host);
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
if (diags.length > 0) {
throw new Error(

View File

@ -12,5 +12,6 @@ ts_library(
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/util",
],
)

View File

@ -6,6 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
export * from './src/api';
export {IvyCompilation} from './src/compilation';
export {InjectableCompilerAdapter} from './src/injectable';
export {ivyTransformFactory} from './src/transform';

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, Type} from '@angular/compiler';
import {Expression, Statement, Type} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator} from '../../metadata';
@ -15,13 +15,13 @@ import {Decorator} from '../../metadata';
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
* compiler/transform.
*
* The decorator compilers in @angular/compiler do not depend on Typescript. The adapter is
* The decorator compilers in @angular/compiler do not depend on Typescript. The handler is
* responsible for extracting the information required to perform compilation from the decorators
* and Typescript source, invoking the decorator compiler, and returning the result.
*/
export interface CompilerAdapter<A> {
export interface DecoratorHandler<A> {
/**
* Scan a set of reflected decorators and determine if this adapter is responsible for compilation
* Scan a set of reflected decorators and determine if this handler is responsible for compilation
* of one of them.
*/
detect(decorator: Decorator[]): Decorator|undefined;
@ -37,7 +37,7 @@ export interface CompilerAdapter<A> {
* Generate a description of the field which should be added to the class, including any
* initialization code to be generated.
*/
compile(node: ts.ClassDeclaration, analysis: A): AddStaticFieldInstruction;
compile(node: ts.ClassDeclaration, analysis: A): CompileResult;
}
/**
@ -54,8 +54,9 @@ export interface AnalysisOutput<A> {
* A description of the static field to add to a class, including an initialization expression
* and a type for the .d.ts file.
*/
export interface AddStaticFieldInstruction {
export interface CompileResult {
field: string;
initializer: Expression;
statements: Statement[];
type: Type;
}

View File

@ -11,17 +11,18 @@ import * as ts from 'typescript';
import {Decorator, reflectDecorator} from '../../metadata';
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
import {AnalysisOutput, CompileResult, DecoratorHandler} from './api';
import {DtsFileTransformer} from './declaration';
import {ImportManager, translateType} from './translator';
/**
* Record of an adapter which decided to emit a static field, and the analysis it performed to
* prepare for that operation.
*/
interface EmitFieldOperation<T> {
adapter: CompilerAdapter<T>;
adapter: DecoratorHandler<T>;
analysis: AnalysisOutput<T>;
decorator: ts.Decorator;
}
@ -44,7 +45,7 @@ export class IvyCompilation {
*/
private dtsMap = new Map<string, DtsFileTransformer>();
constructor(private adapters: CompilerAdapter<any>[], private checker: ts.TypeChecker) {}
constructor(private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker) {}
/**
* Analyze a source file and produce diagnostics for it (if any).
@ -60,8 +61,8 @@ export class IvyCompilation {
node.decorators.map(decorator => reflectDecorator(decorator, this.checker))
.filter(decorator => decorator !== null) as Decorator[];
// Look through the CompilerAdapters to see if any are relevant.
this.adapters.forEach(adapter => {
// Look through the DecoratorHandlers to see if any are relevant.
this.handlers.forEach(adapter => {
// An adapter is relevant if it matches one of the decorators on the class.
const decorator = adapter.detect(decorators);
if (decorator === undefined) {
@ -101,7 +102,7 @@ export class IvyCompilation {
* Perform a compilation operation on the given class declaration and return instructions to an
* AST transformer if any are available.
*/
compileIvyFieldFor(node: ts.ClassDeclaration): AddStaticFieldInstruction|undefined {
compileIvyFieldFor(node: ts.ClassDeclaration): CompileResult|undefined {
// Look to see whether the original node was analyzed. If not, there's nothing to do.
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
if (!this.analysis.has(original)) {

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript';
import {AddStaticFieldInstruction} from './api';
import {CompileResult} from './api';
import {ImportManager, translateType} from './translator';
@ -17,15 +17,13 @@ import {ImportManager, translateType} from './translator';
* Processes .d.ts file text and adds static field declarations, with types.
*/
export class DtsFileTransformer {
private ivyFields = new Map<string, AddStaticFieldInstruction>();
private ivyFields = new Map<string, CompileResult>();
private imports = new ImportManager();
/**
* Track that a static field was added to the code for a class.
*/
recordStaticField(name: string, decl: AddStaticFieldInstruction): void {
this.ivyFields.set(name, decl);
}
recordStaticField(name: string, decl: CompileResult): void { this.ivyFields.set(name, decl); }
/**
* Process the .d.ts text for a file and add any declarations which were recorded.

View File

@ -9,8 +9,10 @@
import {WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
import {IvyCompilation} from './compilation';
import {ImportManager, translateExpression} from './translator';
import {ImportManager, translateExpression, translateStatement} from './translator';
export function ivyTransformFactory(compilation: IvyCompilation):
ts.TransformerFactory<ts.SourceFile> {
@ -21,6 +23,40 @@ export function ivyTransformFactory(compilation: IvyCompilation):
};
}
class IvyVisitor extends Visitor {
constructor(private compilation: IvyCompilation, private importManager: ImportManager) {
super();
}
visitClassDeclaration(node: ts.ClassDeclaration):
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
// Determine if this class has an Ivy field that needs to be added, and compile the field
// to an expression if so.
const res = this.compilation.compileIvyFieldFor(node);
if (res !== undefined) {
// There is a field to add. Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(res.initializer, this.importManager);
// Create a static property declaration for the new field.
const property = ts.createProperty(
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
exprNode);
// Replace the class declaration with an updated version.
node = ts.updateClassDeclaration(
node,
// Remove the decorator which triggered this compilation, leaving the others alone.
maybeFilterDecorator(node.decorators, this.compilation.ivyDecoratorFor(node) !),
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
[...node.members, property]);
const statements = res.statements.map(stmt => translateStatement(stmt, this.importManager));
return {node, before: statements};
}
return {node};
}
}
/**
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
*/
@ -30,7 +66,7 @@ function transformIvySourceFile(
const importManager = new ImportManager();
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
const sf = visitNode(file);
const sf = visit(file, new IvyVisitor(compilation, importManager), context);
// Generate the import statements to prepend.
const imports = importManager.getAllImports().map(
@ -44,44 +80,8 @@ function transformIvySourceFile(
sf.statements = ts.createNodeArray([...imports, ...sf.statements]);
}
return sf;
// Helper function to process a class declaration.
function visitClassDeclaration(node: ts.ClassDeclaration): ts.ClassDeclaration {
// Determine if this class has an Ivy field that needs to be added, and compile the field
// to an expression if so.
const res = compilation.compileIvyFieldFor(node);
if (res !== undefined) {
// There is a field to add. Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(res.initializer, importManager);
// Create a static property declaration for the new field.
const property = ts.createProperty(
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
exprNode);
// Replace the class declaration with an updated version.
node = ts.updateClassDeclaration(
node,
// Remove the decorator which triggered this compilation, leaving the others alone.
maybeFilterDecorator(node.decorators, compilation.ivyDecoratorFor(node) !),
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
[...node.members, property]);
}
// Recurse into the class declaration in case there are nested class declarations.
return ts.visitEachChild(node, child => visitNode(child), context);
}
// Helper function that recurses through the nodes and processes each one.
function visitNode<T extends ts.Node>(node: T): T;
function visitNode(node: ts.Node): ts.Node {
if (ts.isClassDeclaration(node)) {
return visitClassDeclaration(node);
} else {
return ts.visitEachChild(node, child => visitNode(child), context);
}
}
}
function maybeFilterDecorator(
decorators: ts.NodeArray<ts.Decorator>| undefined,
toRemove: ts.Decorator): ts.NodeArray<ts.Decorator>|undefined {

View File

@ -6,9 +6,28 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ArrayType, AssertNotNull, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import * as ts from 'typescript';
const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
[BinaryOperator.And, ts.SyntaxKind.AmpersandAmpersandToken],
[BinaryOperator.Bigger, ts.SyntaxKind.GreaterThanToken],
[BinaryOperator.BiggerEquals, ts.SyntaxKind.GreaterThanEqualsToken],
[BinaryOperator.BitwiseAnd, ts.SyntaxKind.AmpersandToken],
[BinaryOperator.Divide, ts.SyntaxKind.SlashToken],
[BinaryOperator.Equals, ts.SyntaxKind.EqualsEqualsToken],
[BinaryOperator.Identical, ts.SyntaxKind.EqualsEqualsEqualsToken],
[BinaryOperator.Lower, ts.SyntaxKind.LessThanToken],
[BinaryOperator.LowerEquals, ts.SyntaxKind.LessThanEqualsToken],
[BinaryOperator.Minus, ts.SyntaxKind.MinusToken],
[BinaryOperator.Modulo, ts.SyntaxKind.PercentToken],
[BinaryOperator.Multiply, ts.SyntaxKind.AsteriskToken],
[BinaryOperator.NotEquals, ts.SyntaxKind.ExclamationEqualsToken],
[BinaryOperator.NotIdentical, ts.SyntaxKind.ExclamationEqualsEqualsToken],
[BinaryOperator.Or, ts.SyntaxKind.BarBarToken],
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
]);
export class ImportManager {
private moduleToIndex = new Map<string, string>();
private nextIndex = 0;
@ -32,6 +51,10 @@ export function translateExpression(expression: Expression, imports: ImportManag
return expression.visitExpression(new ExpressionTranslatorVisitor(imports), null);
}
export function translateStatement(statement: Statement, imports: ImportManager): ts.Statement {
return statement.visitStatement(new ExpressionTranslatorVisitor(imports), null);
}
export function translateType(type: Type, imports: ImportManager): string {
return type.visitType(new TypeTranslatorVisitor(imports), null);
}
@ -39,16 +62,23 @@ export function translateType(type: Type, imports: ImportManager): string {
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
constructor(private imports: ImportManager) {}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any) {
throw new Error('Method not implemented.');
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): ts.VariableStatement {
return ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList([ts.createVariableDeclaration(
stmt.name, undefined, stmt.value && stmt.value.visitExpression(this, context))]));
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) {
throw new Error('Method not implemented.');
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): ts.FunctionDeclaration {
return ts.createFunctionDeclaration(
undefined, undefined, undefined, stmt.name, undefined,
stmt.params.map(param => ts.createParameter(undefined, undefined, undefined, param.name)),
undefined,
ts.createBlock(stmt.statements.map(child => child.visitStatement(this, context))));
}
visitExpressionStmt(stmt: ExpressionStatement, context: any) {
throw new Error('Method not implemented.');
visitExpressionStmt(stmt: ExpressionStatement, context: any): ts.ExpressionStatement {
return ts.createStatement(stmt.expr.visitExpression(this, context));
}
visitReturnStmt(stmt: ReturnStatement, context: any): ts.ReturnStatement {
@ -59,7 +89,14 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
throw new Error('Method not implemented.');
}
visitIfStmt(stmt: IfStmt, context: any) { throw new Error('Method not implemented.'); }
visitIfStmt(stmt: IfStmt, context: any): ts.IfStatement {
return ts.createIf(
stmt.condition.visitExpression(this, context),
ts.createBlock(stmt.trueCase.map(child => child.visitStatement(this, context))),
stmt.falseCase.length > 0 ?
ts.createBlock(stmt.falseCase.map(child => child.visitStatement(this, context))) :
undefined);
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any) {
throw new Error('Method not implemented.');
@ -89,12 +126,17 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
throw new Error('Method not implemented.');
}
visitWritePropExpr(expr: WritePropExpr, context: any): never {
throw new Error('Method not implemented.');
visitWritePropExpr(expr: WritePropExpr, context: any): ts.BinaryExpression {
return ts.createBinary(
ts.createPropertyAccess(expr.receiver.visitExpression(this, context), expr.name),
ts.SyntaxKind.EqualsToken, expr.value.visitExpression(this, context));
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
throw new Error('Method not implemented.');
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): ts.CallExpression {
const target = ast.receiver.visitExpression(this, context);
return ts.createCall(
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
ast.args.map(arg => arg.visitExpression(this, context)));
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): ts.CallExpression {
@ -156,20 +198,26 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context))));
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): never {
throw new Error('Method not implemented.');
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): ts.Expression {
if (!BINARY_OPERATORS.has(ast.operator)) {
throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
}
const binEx = ts.createBinary(
ast.lhs.visitExpression(this, context), BINARY_OPERATORS.get(ast.operator) !,
ast.rhs.visitExpression(this, context));
return ast.parens ? ts.createParen(binEx) : binEx;
}
visitReadPropExpr(ast: ReadPropExpr, context: any): never {
throw new Error('Method not implemented.');
visitReadPropExpr(ast: ReadPropExpr, context: any): ts.PropertyAccessExpression {
return ts.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): never {
throw new Error('Method not implemented.');
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): ts.ArrayLiteralExpression {
return ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): ts.ObjectLiteralExpression {
@ -297,8 +345,9 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
visitReadKeyExpr(ast: ReadKeyExpr, context: any) { throw new Error('Method not implemented.'); }
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any) {
throw new Error('Method not implemented.');
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): string {
const values = ast.entries.map(expr => expr.visitExpression(this, context));
return `[${values.join(',')}]`;
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any) {

View File

@ -33,7 +33,7 @@ export abstract class Visitor {
/**
* Maps statements to an array of statements that should be inserted before them.
*/
private _before = new Map<ts.Statement, ts.Statement[]>();
private _before = new Map<ts.Node, ts.Statement[]>();
/**
* Visit a class declaration, returning at least the transformed declaration and optionally other
@ -44,16 +44,15 @@ export abstract class Visitor {
return {node};
}
private _visitClassDeclaration(node: ts.ClassDeclaration, context: ts.TransformationContext):
ts.ClassDeclaration {
const result = this.visitClassDeclaration(node);
const visited = ts.visitEachChild(result.node, child => this._visit(child, context), context);
private _visitListEntryNode<T extends ts.Statement>(
node: T, visitor: (node: T) => VisitListEntryResult<ts.Statement, T>): T {
const result = visitor(node);
if (result.before !== undefined) {
// Record that some nodes should be inserted before the given declaration. The declaration's
// parent's _visit call is responsible for performing this insertion.
this._before.set(visited, result.before);
this._before.set(result.node, result.before);
}
return visited;
return result.node;
}
/**
@ -61,11 +60,6 @@ export abstract class Visitor {
*/
visitOtherNode<T extends ts.Node>(node: T): T { return node; }
private _visitOtherNode<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
return ts.visitEachChild(
this.visitOtherNode(node), child => this._visit(child, context), context);
}
/**
* @internal
*/
@ -73,10 +67,14 @@ export abstract class Visitor {
// First, visit the node. visitedNode starts off as `null` but should be set after visiting
// is completed.
let visitedNode: T|null = null;
node = ts.visitEachChild(node, child => this._visit(child, context), context) as T;
if (ts.isClassDeclaration(node)) {
visitedNode = this._visitClassDeclaration(node, context) as typeof node;
visitedNode = this._visitListEntryNode(
node, (node: ts.ClassDeclaration) => this.visitClassDeclaration(node)) as typeof node;
} else {
visitedNode = this._visitOtherNode(node, context);
visitedNode = this.visitOtherNode(node);
}
// If the visited node has a `statements` array then process them, maybe replacing the visited

View File

@ -16,6 +16,8 @@ function callableParamDecorator(): FnWithArg<(a: any, b: any, c: any) => void> {
return null !;
}
export const Component = callableClassDecorator();
export const Directive = callableClassDecorator();
export const Injectable = callableClassDecorator();
export const NgModule = callableClassDecorator();

View File

@ -95,7 +95,7 @@ describe('ngtsc behavioral tests', () => {
}`);
});
it('should compile without errors', () => {
it('should compile Injectables without errors', () => {
writeConfig();
write('test.ts', `
import {Injectable} from '@angular/core';
@ -122,4 +122,62 @@ describe('ngtsc behavioral tests', () => {
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Dep>;');
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Service>;');
});
it('should compile Components without errors', () => {
writeConfig();
write('test.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: 'this is a test',
})
export class TestCmp {}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
const jsContents = getContents('test.js');
expect(jsContents).toContain('TestCmp.ngComponentDef = i0.ɵdefineComponent');
expect(jsContents).not.toContain('__decorate');
const dtsContents = getContents('test.d.ts');
expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef<TestCmp, \'test-cmp\'>');
});
it('should compile NgModules without errors', () => {
writeConfig();
write('test.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'test-cmp',
template: 'this is a test',
})
export class TestCmp {}
@NgModule({
declarations: [TestCmp],
})
export class TestModule {}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
const jsContents = getContents('test.js');
expect(jsContents)
.toContain(
'i0.ɵdefineNgModule({ type: TestModule, bootstrap: [], ' +
'declarations: [TestCmp], imports: [], exports: [] })');
const dtsContents = getContents('test.d.ts');
expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef<TestCmp, \'test-cmp\'>');
expect(dtsContents)
.toContain('static ngModuleDef: i0.NgModuleDef<TestModule, [TestCmp], [], []>');
expect(dtsContents).not.toContain('__decorate');
});
});

View File

@ -119,6 +119,11 @@ export class Identifiers {
moduleName: CORE,
};
static NgModuleDef: o.ExternalReference = {
name: 'NgModuleDef',
moduleName: CORE,
};
static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};

View File

@ -72,8 +72,11 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
exports: o.literalArr(exports),
})]);
// TODO(alxhub): write a proper type reference when AOT compilation of @NgModule is implemented.
const type = new o.ExpressionType(o.NULL_EXPR);
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDef, [
new o.ExpressionType(moduleType), new o.ExpressionType(o.literalArr(declarations)),
new o.ExpressionType(o.literalArr(imports)), new o.ExpressionType(o.literalArr(exports))
]));
const additionalStatements: o.Statement[] = [];
return {expression, type, additionalStatements};
}

View File

@ -85,8 +85,9 @@ export function compileDirectiveFromMetadata(
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)]));
const type = new o.ExpressionType(o.importExpr(
R3.DirectiveDef,
[new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(meta.selector || ''))]));
return {expression, type};
}
@ -154,8 +155,9 @@ export function compileComponentFromMetadata(
}
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
const type =
new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)]));
const type = new o.ExpressionType(o.importExpr(
R3.ComponentDef,
[new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(meta.selector || ''))]));
return {expression, type};
}
@ -243,15 +245,15 @@ function directiveMetadataFromGlobalMetadata(
selector: directive.selector,
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
lifecycle: {
usesOnChanges:
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
},
host: {
attributes: directive.hostAttributes,
listeners: summary.hostListeners,
properties: summary.hostProperties,
},
lifecycle: {
usesOnChanges:
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
},
inputs: directive.inputs,
outputs: directive.outputs,
};

View File

@ -849,7 +849,7 @@ function interpolate(args: o.Expression[]): o.Expression {
* @param templateUrl URL to use for source mapping of the parsed template
*/
export function parseTemplate(
template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}):
template: string, templateUrl: string, options: {preserveWhitespaces?: boolean} = {}):
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
const bindingParser = makeBindingParser();
const htmlParser = new HtmlParser();
@ -860,7 +860,7 @@ export function parseTemplate(
}
let rootNodes: html.Node[] = parseResult.rootNodes;
if (!options.preserveWhitespace) {
if (!options.preserveWhitespaces) {
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
}

View File

@ -11,6 +11,7 @@ export {
defineComponent as ɵdefineComponent,
defineDirective as ɵdefineDirective,
definePipe as ɵdefinePipe,
defineNgModule as ɵdefineNgModule,
detectChanges as ɵdetectChanges,
renderComponent as ɵrenderComponent,
ComponentType as ɵComponentType,
@ -82,8 +83,8 @@ export {
st as ɵst,
ld as ɵld,
Pp as ɵPp,
ComponentDef as ɵComponentDef,
DirectiveDef as ɵDirectiveDef,
ComponentDefInternal as ɵComponentDef,
DirectiveDefInternal as ɵDirectiveDef,
PipeDef as ɵPipeDef,
whenRendered as ɵwhenRendered,
} from './render3/index';

View File

@ -13,7 +13,7 @@
import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
import {ModuleWithProviders, NgModule, NgModuleDef, SchemaMetadata, defineNgModule} from './metadata/ng_module';
import {ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
import {ViewEncapsulation} from './metadata/view';
export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';

View File

@ -388,6 +388,14 @@ export interface Directive {
* ```
*/
queries?: {[key: string]: any};
/**
* If true, this directive/component will be skipped by the AOT compiler and so will always be
* compiled using JIT.
*
* This exists to support future Ivy work and has no effect currently.
*/
jit?: true;
}
/**

View File

@ -27,11 +27,39 @@ export interface NgModuleTransitiveScopes {
exported: {directives: Set<any>; pipes: Set<any>;};
}
export interface NgModuleDef<T> {
/**
* A version of {@link NgModuleDef} that represents the runtime type shape only, and excludes
* metadata parameters.
*/
export type NgModuleDefInternal<T> = NgModuleDef<T, any, any, any>;
/**
* Runtime link information for NgModules.
*
* This is the internal data structure used by the runtime to assemble components, directives,
* pipes, and injectors.
*
* NOTE: Always use `defineNgModule` function to create this object,
* never create the object directly since the shape of this object
* can change between versions.
*/
export interface NgModuleDef<T, Declarations, Imports, Exports> {
/** Token representing the module. Used by DI. */
type: T;
/** List of components to bootstrap. */
bootstrap: Type<any>[];
/** List of components, directives, and pipes declared by this module. */
declarations: Type<any>[];
/** List of modules or `ModuleWithProviders` imported by this module. */
imports: Type<any>[];
/**
* List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this
* module.
*/
exports: Type<any>[];
/**
@ -42,18 +70,6 @@ export interface NgModuleDef<T> {
transitiveCompileScopes: NgModuleTransitiveScopes|null;
}
export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): never {
const res: NgModuleDef<T> = {
type: def.type,
bootstrap: def.bootstrap || [],
declarations: def.declarations || [],
imports: def.imports || [],
exports: def.exports || [],
transitiveCompileScopes: null,
};
return res as never;
}
/**
* A wrapper around a module that also includes the providers.
*
@ -226,6 +242,14 @@ export interface NgModule {
* `getModuleFactory`.
*/
id?: string;
/**
* If true, this module will be skipped by the AOT compiler and so will always be compiled
* using JIT.
*
* This exists to support future Ivy work and has no effect currently.
*/
jit?: true;
}
function preR3NgModuleCompile(moduleType: InjectorType<any>, metadata: NgModule): void {

View File

@ -15,7 +15,7 @@ import {Sanitizer} from '../sanitization/security';
import {assertComponentType, assertDefined} from './assert';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings,} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {ComponentDef, ComponentDefInternal, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {LViewData, LViewFlags, RootContext, INJECTOR, CONTEXT, TVIEW} from './interfaces/view';
@ -52,7 +52,7 @@ export interface CreateComponentOptions {
* features list because there's no way of knowing when the component will be used as
* a root component.
*/
hostFeatures?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
hostFeatures?: (<T>(component: T, componentDef: ComponentDef<T, string>) => void)[];
/**
* A function which is used to schedule change detection work in the future.
@ -96,7 +96,8 @@ export function renderComponent<T>(
ngDevMode && assertComponentType(componentType);
const rendererFactory = opts.rendererFactory || domRendererFactory3;
const sanitizer = opts.sanitizer || null;
const componentDef = (componentType as ComponentType<T>).ngComponentDef as ComponentDef<T>;
const componentDef =
(componentType as ComponentType<T>).ngComponentDef as ComponentDefInternal<T>;
if (componentDef.type != componentType) componentDef.type = componentType;
let component: T;
// The first index of the first selector is the tag name.
@ -157,7 +158,7 @@ export function createRootContext(scheduler: (workFn: () => void) => void): Root
* renderComponent(AppComponent, {features: [RootLifecycleHooks]});
* ```
*/
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
export function LifecycleHooksFeature(component: any, def: ComponentDefInternal<any>): void {
const elementNode = _getComponentHostLElementNode(component);
// Root component is always created at dir index 0

View File

@ -19,7 +19,7 @@ import {Type} from '../type';
import {assertComponentType, assertDefined} from './assert';
import {createRootContext} from './component';
import {baseDirectiveCreate, createLViewData, createTView, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {ComponentDefInternal, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement} from './interfaces/renderer';
import {INJECTOR, LViewData, LViewFlags, RootContext} from './interfaces/view';
@ -72,7 +72,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
return toRefArray(this.componentDef.outputs);
}
constructor(private componentDef: ComponentDef<any>) {
constructor(private componentDef: ComponentDefInternal<any>) {
super();
this.componentType = componentDef.type;
this.selector = componentDef.selectors[0][0] as string;

View File

@ -11,12 +11,13 @@ import {ChangeDetectionStrategy} from '../change_detection/constants';
import {PipeTransform} from '../change_detection/pipe_transform';
import {Provider} from '../core';
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
import {NgModuleDef, NgModuleDefInternal} from '../metadata/ng_module';
import {RendererType2} from '../render/api';
import {Type} from '../type';
import {resolveRendererType2} from '../view/util';
import {diPublic} from './di';
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {ComponentDefFeature, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
@ -166,7 +167,7 @@ export function defineComponent<T>(componentDefinition: {
const type = componentDefinition.type;
const pipeTypes = componentDefinition.pipes !;
const directiveTypes = componentDefinition.directives !;
const def = <ComponentDef<any>>{
const def: ComponentDefInternal<any> = {
type: type,
diPublic: null,
factory: componentDefinition.factory,
@ -176,7 +177,7 @@ export function defineComponent<T>(componentDefinition: {
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
exportAs: componentDefinition.exportAs,
exportAs: componentDefinition.exportAs || null,
onInit: type.prototype.ngOnInit || null,
doCheck: type.prototype.ngDoCheck || null,
afterContentInit: type.prototype.ngAfterContentInit || null,
@ -200,7 +201,7 @@ export function defineComponent<T>(componentDefinition: {
}
export function extractDirectiveDef(type: DirectiveType<any>& ComponentType<any>):
DirectiveDef<any>|ComponentDef<any> {
DirectiveDefInternal<any>|ComponentDefInternal<any> {
const def = type.ngComponentDef || type.ngDirectiveDef;
if (ngDevMode && !def) {
throw new Error(`'${type.name}' is neither 'ComponentType' or 'DirectiveType'.`);
@ -216,7 +217,17 @@ export function extractPipeDef(type: PipeType<any>): PipeDef<any> {
return def;
}
export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T, any, any, any>>): never {
const res: NgModuleDefInternal<T> = {
type: def.type,
bootstrap: def.bootstrap || [],
declarations: def.declarations || [],
imports: def.imports || [],
exports: def.exports || [],
transitiveCompileScopes: null,
};
return res as never;
}
const PRIVATE_PREFIX = '__ngOnChanges_';
@ -250,7 +261,7 @@ type OnChangesExpando = OnChanges & {
*/
export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string}):
DirectiveDefFeature {
return function(definition: DirectiveDef<any>): void {
return function(definition: DirectiveDefInternal<any>): void {
const inputs = definition.inputs;
const proto = definition.type.prototype;
for (let pubKey in inputs) {
@ -313,7 +324,7 @@ export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string})
}
export function PublicFeature<T>(definition: DirectiveDef<T>) {
export function PublicFeature<T>(definition: DirectiveDefInternal<T>) {
definition.diPublic = diPublic;
}

View File

@ -21,7 +21,7 @@ import {Type} from '../type';
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
import {VIEWS} from './interfaces/container';
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
import {ComponentTemplate, DirectiveDefInternal} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query';
@ -141,7 +141,7 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
* @param di The node injector in which a directive will be added
* @param def The definition of the directive to be made public
*/
export function diPublicInInjector(di: LInjector, def: DirectiveDef<any>): void {
export function diPublicInInjector(di: LInjector, def: DirectiveDefInternal<any>): void {
bloomAdd(di, def.type);
}
@ -150,7 +150,7 @@ export function diPublicInInjector(di: LInjector, def: DirectiveDef<any>): void
*
* @param def The definition of the directive to be made public
*/
export function diPublic(def: DirectiveDef<any>): void {
export function diPublic(def: DirectiveDefInternal<any>): void {
diPublicInInjector(getOrCreateNodeInjector(), def);
}
@ -376,7 +376,7 @@ export function getOrCreateInjectable<T>(
for (let i = start; i < end; i++) {
// Get the definition for the directive at this index and, if it is injectable (diPublic),
// and matches the given token, return the directive instance.
const directiveDef = defs[i] as DirectiveDef<any>;
const directiveDef = defs[i] as DirectiveDefInternal<any>;
if (directiveDef.type === token && directiveDef.diPublic) {
return getDirectiveInstance(node.view[DIRECTIVES] ![i]);
}
@ -409,7 +409,7 @@ function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
const matches = node.view[TVIEW].currentMatches;
if (matches) {
for (let i = 0; i < matches.length; i += 2) {
const def = matches[i] as DirectiveDef<any>;
const def = matches[i] as DirectiveDefInternal<any>;
if (def.type === token) {
return resolveDirective(def, i + 1, matches, node.view[TVIEW]);
}

View File

@ -7,7 +7,7 @@
*/
import {assertEqual} from './assert';
import {DirectiveDef} from './interfaces/definition';
import {DirectiveDefInternal} from './interfaces/definition';
import {TNodeFlags} from './interfaces/node';
import {DIRECTIVES, FLAGS, HookData, LViewData, LViewFlags, TView} from './interfaces/view';
@ -52,7 +52,7 @@ export function queueLifecycleHooks(flags: number, tView: TView): void {
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
// hooks for projected components and directives must be called *before* their hosts.
for (let i = start; i < end; i++) {
const def: DirectiveDef<any> = tView.directives ![i];
const def: DirectiveDefInternal<any> = tView.directives ![i];
queueContentHooks(def, tView, i);
queueViewHooks(def, tView, i);
queueDestroyHooks(def, tView, i);
@ -61,7 +61,7 @@ export function queueLifecycleHooks(flags: number, tView: TView): void {
}
/** Queues afterContentInit and afterContentChecked hooks on TView */
function queueContentHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
function queueContentHooks(def: DirectiveDefInternal<any>, tView: TView, i: number): void {
if (def.afterContentInit) {
(tView.contentHooks || (tView.contentHooks = [])).push(i, def.afterContentInit);
}
@ -73,7 +73,7 @@ function queueContentHooks(def: DirectiveDef<any>, tView: TView, i: number): voi
}
/** Queues afterViewInit and afterViewChecked hooks on TView */
function queueViewHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
function queueViewHooks(def: DirectiveDefInternal<any>, tView: TView, i: number): void {
if (def.afterViewInit) {
(tView.viewHooks || (tView.viewHooks = [])).push(i, def.afterViewInit);
}
@ -85,7 +85,7 @@ function queueViewHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
}
/** Queues onDestroy hooks on TView */
function queueDestroyHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
function queueDestroyHooks(def: DirectiveDefInternal<any>, tView: TView, i: number): void {
if (def.onDestroy != null) {
(tView.destroyHooks || (tView.destroyHooks = [])).push(i, def.onDestroy);
}

View File

@ -7,8 +7,8 @@
*/
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType, PipeDef} from './interfaces/definition';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
@ -16,6 +16,7 @@ export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener)
@ -116,10 +117,10 @@ export {
// clang-format on
export {
ComponentDef,
ComponentDefInternal,
ComponentTemplate,
ComponentType,
DirectiveDef,
DirectiveDefInternal,
DirectiveDefFlags,
DirectiveType,
NgOnChangesFeature,
@ -128,6 +129,7 @@ export {
LifecycleHooksFeature,
defineComponent,
defineDirective,
defineNgModule,
definePipe,
getHostElement,
getRenderedText,

View File

@ -23,7 +23,7 @@ import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNode
import {assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {ComponentDefInternal, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {isDifferent, stringify} from './util';
import {ViewRef} from './view_ref';
@ -258,7 +258,7 @@ export function setHostBindings(bindings: number[] | null): void {
const defs = tView.directives !;
for (let i = 0; i < bindings.length; i += 2) {
const dirIndex = bindings[i];
const def = defs[dirIndex] as DirectiveDef<any>;
const def = defs[dirIndex] as DirectiveDefInternal<any>;
def.hostBindings && def.hostBindings(dirIndex, bindings[i + 1]);
}
}
@ -659,7 +659,7 @@ function cacheMatchingDirectivesForNode(
const matches = tView.currentMatches = findDirectiveMatches(tNode);
if (matches) {
for (let i = 0; i < matches.length; i += 2) {
const def = matches[i] as DirectiveDef<any>;
const def = matches[i] as DirectiveDefInternal<any>;
const valueIndex = i + 1;
resolveDirective(def, valueIndex, matches, tView);
saveNameToExportMap(matches[valueIndex] as number, def, exportsMap);
@ -676,7 +676,7 @@ function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null {
for (let i = 0; i < registry.length; i++) {
const def = registry[i];
if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
if ((def as ComponentDef<any>).template) {
if ((def as ComponentDefInternal<any>).template) {
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
tNode.flags = TNodeFlags.isComponent;
}
@ -689,7 +689,8 @@ function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null {
}
export function resolveDirective(
def: DirectiveDef<any>, valueIndex: number, matches: CurrentMatchesList, tView: TView): any {
def: DirectiveDefInternal<any>, valueIndex: number, matches: CurrentMatchesList,
tView: TView): any {
if (matches[valueIndex] === null) {
matches[valueIndex] = CIRCULAR;
const instance = def.factory();
@ -745,7 +746,7 @@ function instantiateDirectivesDirectly() {
const tDirectives = tView.directives !;
for (let i = start; i < end; i++) {
const def: DirectiveDef<any> = tDirectives[i];
const def: DirectiveDefInternal<any> = tDirectives[i];
directiveCreate(i, def.factory(), def);
}
}
@ -773,11 +774,11 @@ function cacheMatchingLocalNames(
* to their directive instances.
*/
function saveNameToExportMap(
index: number, def: DirectiveDef<any>| ComponentDef<any>,
index: number, def: DirectiveDefInternal<any>| ComponentDefInternal<any>,
exportsMap: {[key: string]: number} | null) {
if (exportsMap) {
if (def.exportAs) exportsMap[def.exportAs] = index;
if ((def as ComponentDef<any>).template) exportsMap[''] = index;
if ((def as ComponentDefInternal<any>).template) exportsMap[''] = index;
}
}
@ -929,7 +930,7 @@ export function locateHostElement(
* @returns LElementNode created
*/
export function hostElement(
tag: string, rNode: RElement | null, def: ComponentDef<any>,
tag: string, rNode: RElement | null, def: ComponentDefInternal<any>,
sanitizer?: Sanitizer | null): LElementNode {
resetApplicationState();
const node = createLNode(
@ -1187,7 +1188,7 @@ function generatePropertyAliases(
const defs = tView.directives !;
for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>;
const directiveDef = defs[i] as DirectiveDefInternal<any>;
const propertyAliasMap: {[publicName: string]: string} =
isInput ? directiveDef.inputs : directiveDef.outputs;
for (let publicName in propertyAliasMap) {
@ -1387,15 +1388,16 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
* @param directiveDef DirectiveDef object which contains information about the template.
*/
export function directiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
index: number, directive: T,
directiveDef: DirectiveDefInternal<T>| ComponentDefInternal<T>): T {
const instance = baseDirectiveCreate(index, directive, directiveDef);
ngDevMode && assertDefined(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
const tNode = previousOrParentNode.tNode;
const isComponent = (directiveDef as ComponentDef<T>).template;
const isComponent = (directiveDef as ComponentDefInternal<T>).template;
if (isComponent) {
addComponentLogic(index, directive, directiveDef as ComponentDef<T>);
addComponentLogic(index, directive, directiveDef as ComponentDefInternal<T>);
}
if (firstTemplatePass) {
@ -1413,7 +1415,8 @@ export function directiveCreate<T>(
return instance;
}
function addComponentLogic<T>(directiveIndex: number, instance: T, def: ComponentDef<T>): void {
function addComponentLogic<T>(
directiveIndex: number, instance: T, def: ComponentDefInternal<T>): void {
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs);
// Only component views should be added to the view tree directly. Embedded views are
@ -1442,7 +1445,8 @@ function addComponentLogic<T>(directiveIndex: number, instance: T, def: Componen
* current Angular. Example: local refs and inputs on root component.
*/
export function baseDirectiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
index: number, directive: T,
directiveDef: DirectiveDefInternal<T>| ComponentDefInternal<T>): T {
ngDevMode &&
assertEqual(viewData[BINDING_INDEX], -1, 'directives should be created before any bindings');
ngDevMode && assertPreviousIsParent();

View File

@ -54,6 +54,12 @@ export const enum DirectiveDefFlags {ContentQuery = 0b10}
*/
export interface PipeType<T> extends Type<T> { ngPipeDef: never; }
/**
* A version of {@link DirectiveDef} that represents the runtime type shape only, and excludes
* metadata parameters.
*/
export type DirectiveDefInternal<T> = DirectiveDef<T, string>;
/**
* Runtime link information for Directives.
*
@ -64,14 +70,16 @@ export interface PipeType<T> extends Type<T> { ngPipeDef: never; }
* never create the object directly since the shape of this object
* can change between versions.
*
* @param Selector type metadata specifying the selector of the directive or component
*
* See: {@link defineDirective}
*/
export interface DirectiveDef<T> {
export interface DirectiveDef<T, Selector extends string> {
/** Token representing the directive. Used by DI. */
type: Type<T>;
/** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<any>) => void)|null;
diPublic: ((def: DirectiveDef<any, string>) => void)|null;
/** The selectors that will be used to match nodes to this directive. */
selectors: CssSelectorList;
@ -124,6 +132,12 @@ export interface DirectiveDef<T> {
onDestroy: (() => void)|null;
}
/**
* A version of {@link ComponentDef} that represents the runtime type shape only, and excludes
* metadata parameters.
*/
export type ComponentDefInternal<T> = ComponentDef<T, string>;
/**
* Runtime link information for Components.
*
@ -136,7 +150,7 @@ export interface DirectiveDef<T> {
*
* See: {@link defineComponent}
*/
export interface ComponentDef<T> extends DirectiveDef<T> {
export interface ComponentDef<T, Selector extends string> extends DirectiveDef<T, Selector> {
/**
* The View template of the component.
*
@ -220,8 +234,8 @@ export interface PipeDef<T> {
onDestroy: (() => void)|null;
}
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
export type ComponentDefFeature = <T>(componentDef: ComponentDef<T>) => void;
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T, string>) => void;
export type ComponentDefFeature = <T>(componentDef: ComponentDef<T, string>) => void;
/**
* Type used for directiveDefs on component definition.
@ -230,12 +244,12 @@ export type ComponentDefFeature = <T>(componentDef: ComponentDef<T>) => void;
*/
export type DirectiveDefListOrFactory = (() => DirectiveDefList) | DirectiveDefList;
export type DirectiveDefList = (DirectiveDef<any>| ComponentDef<any>)[];
export type DirectiveDefList = (DirectiveDef<any, string>| ComponentDef<any, string>)[];
export type DirectiveTypesOrFactory = (() => DirectiveTypeList) | DirectiveTypeList;
export type DirectiveTypeList =
(DirectiveDef<any>| ComponentDef<any>|
(DirectiveDef<any, string>| ComponentDef<any, string>|
Type<any>/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[];
/**

View File

@ -10,7 +10,7 @@ import {Injector} from '../../di/injector';
import {Sanitizer} from '../../sanitization/security';
import {LContainer} from './container';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
import {ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition';
import {LElementNode, LViewNode, TNode} from './node';
import {LQueries} from './query';
import {Renderer3} from './renderer';
@ -443,7 +443,7 @@ export type HookData = (number | (() => void))[];
export type TData = (TNode | PipeDef<any>| null)[];
/** Type for TView.currentMatches */
export type CurrentMatchesList = [DirectiveDef<any>, (string | number | null)];
export type CurrentMatchesList = [DirectiveDefInternal<any>, (string | number | null)];
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.

View File

@ -47,7 +47,9 @@ export function compileComponent(type: Type<any>, metadata: Component): Promise<
const constantPool = new ConstantPool();
// Parse the template and check for errors.
const template = parseTemplate(templateStr, `ng://${type.name}/template.html`);
const template = parseTemplate(templateStr, `ng://${type.name}/template.html`, {
preserveWhitespaces: metadata.preserveWhitespaces || false,
});
if (template.errors !== undefined) {
const errors = template.errors.map(err => err.toString()).join(', ');
throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`);

View File

@ -8,7 +8,6 @@
import {defineInjectable, defineInjector,} from '../../di/defs';
import {inject} from '../../di/injector';
import {defineNgModule} from '../../metadata/ng_module';
import * as r3 from '../index';
@ -22,7 +21,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵdefineDirective': r3.defineDirective,
'defineInjectable': defineInjectable,
'defineInjector': defineInjector,
'ɵdefineNgModule': defineNgModule,
'ɵdefineNgModule': r3.defineNgModule,
'ɵdefinePipe': r3.definePipe,
'ɵdirectiveInject': r3.directiveInject,
'inject': inject,

View File

@ -8,9 +8,9 @@
import {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileR3NgModule, jitExpression} from '@angular/compiler';
import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module';
import {ModuleWithProviders, NgModule, NgModuleDefInternal, NgModuleTransitiveScopes} from '../../metadata/ng_module';
import {Type} from '../../type';
import {ComponentDef} from '../interfaces/definition';
import {ComponentDefInternal} from '../interfaces/definition';
import {angularCoreEnv} from './environment';
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
@ -48,7 +48,8 @@ export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
if (declaration.hasOwnProperty(NG_COMPONENT_DEF)) {
// An `ngComponentDef` field exists - go ahead and patch the component directly.
patchComponentDefWithScope(
(declaration as Type<any>& {ngComponentDef: ComponentDef<any>}).ngComponentDef, type);
(declaration as Type<any>& {ngComponentDef: ComponentDefInternal<any>}).ngComponentDef,
type);
} else if (
!declaration.hasOwnProperty(NG_DIRECTIVE_DEF) && !declaration.hasOwnProperty(NG_PIPE_DEF)) {
// Set `ngSelectorScope` for future reference when the component compilation finishes.
@ -61,7 +62,8 @@ export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
* Patch the definition of a component with directives and pipes from the compilation scope of
* a given module.
*/
export function patchComponentDefWithScope<C, M>(componentDef: ComponentDef<C>, module: Type<M>) {
export function patchComponentDefWithScope<C, M>(
componentDef: ComponentDefInternal<C>, module: Type<M>) {
componentDef.directiveDefs = () => Array.from(transitiveScopesFor(module).compilation.directives)
.map(dir => dir.ngDirectiveDef || dir.ngComponentDef)
.filter(def => !!def);
@ -113,7 +115,7 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
def.imports.forEach(<I>(imported: Type<I>) => {
let importedTyped = imported as Type<I>& {
// If imported is an @NgModule:
ngModuleDef?: NgModuleDef<I>;
ngModuleDef?: NgModuleDefInternal<I>;
};
if (!isNgModule<I>(importedTyped)) {
@ -132,7 +134,7 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
// Components, Directives, NgModules, and Pipes can all be exported.
ngComponentDef?: any;
ngDirectiveDef?: any;
ngModuleDef?: NgModuleDef<E>;
ngModuleDef?: NgModuleDefInternal<E>;
ngPipeDef?: any;
};
@ -188,6 +190,6 @@ function isModuleWithProviders(value: any): value is ModuleWithProviders {
return (value as{ngModule?: any}).ngModule !== undefined;
}
function isNgModule<T>(value: Type<T>): value is Type<T>&{ngModuleDef: NgModuleDef<T>} {
return (value as{ngModuleDef?: NgModuleDef<T>}).ngModuleDef !== undefined;
function isNgModule<T>(value: Type<T>): value is Type<T>&{ngModuleDef: NgModuleDefInternal<T>} {
return (value as{ngModuleDef?: NgModuleDefInternal<T>}).ngModuleDef !== undefined;
}

View File

@ -18,7 +18,7 @@ import {getSymbolIterator} from '../util';
import {assertDefined, assertEqual} from './assert';
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
import {assertPreviousIsParent, getCurrentQueries, store, storeCleanupWithContext} from './instructions';
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
import {DirectiveDefInternal, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, TNode, TNodeFlags, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
@ -232,7 +232,7 @@ function getIdxOfMatchingDirective(node: LNode, type: Type<any>): number|null {
const start = flags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + count;
for (let i = start; i < end; i++) {
const def = defs[i] as DirectiveDef<any>;
const def = defs[i] as DirectiveDefInternal<any>;
if (def.type === type && def.diPublic) {
return i;
}

View File

@ -49,6 +49,7 @@ jasmine_node_test(
],
tags = [
"ivy-jit",
"ivy-local",
],
deps = [":test_lib"],
)

View File

@ -51,7 +51,11 @@ jasmine_node_test(
":bundle.min.js",
":bundle.min_debug.js",
],
tags = ["ivy-jit"],
tags = [
"ivy-jit",
"ivy-local",
"ivy-only",
],
deps = [":test_lib"],
)

View File

@ -26,24 +26,12 @@
{
"name": "DIRECTIVES"
},
{
"name": "DefaultIterableDiffer"
},
{
"name": "DefaultIterableDifferFactory"
},
{
"name": "EMPTY$2"
},
{
"name": "EMPTY_RENDERER_TYPE_ID"
},
{
"name": "ElementRef$1"
},
{
"name": "EmbeddedViewRef$1"
},
{
"name": "FLAGS"
},
@ -59,12 +47,6 @@
{
"name": "INJECTOR$1"
},
{
"name": "IterableChangeRecord_"
},
{
"name": "IterableDiffers"
},
{
"name": "NEXT"
},
@ -92,12 +74,6 @@
{
"name": "NgIfContext"
},
{
"name": "Optional"
},
{
"name": "PARAMETERS"
},
{
"name": "PARENT"
},
@ -107,9 +83,6 @@
{
"name": "RENDERER"
},
{
"name": "RENDER_PARENT"
},
{
"name": "ROOT_DIRECTIVE_INDICES"
},
@ -119,18 +92,12 @@
{
"name": "SANITIZER"
},
{
"name": "SkipSelf"
},
{
"name": "TAIL"
},
{
"name": "TVIEW"
},
{
"name": "TemplateRef$1"
},
{
"name": "ToDoAppComponent"
},
@ -149,36 +116,12 @@
{
"name": "VIEWS"
},
{
"name": "ViewContainerRef$1"
},
{
"name": "_CLEAN_PROMISE"
},
{
"name": "_DuplicateItemRecordList"
},
{
"name": "_DuplicateMap"
},
{
"name": "_ROOT_DIRECTIVE_INDICES"
},
{
"name": "__extends"
},
{
"name": "__read"
},
{
"name": "__self"
},
{
"name": "__spread"
},
{
"name": "__window"
},
{
"name": "_c0"
},
@ -242,21 +185,12 @@
{
"name": "_getComponentHostLElementNode"
},
{
"name": "_global"
},
{
"name": "_renderCompCount"
},
{
"name": "_symbolIterator"
},
{
"name": "addComponentLogic"
},
{
"name": "addRemoveViewFromContainer"
},
{
"name": "addToViewTree"
},
@ -293,15 +227,9 @@
{
"name": "canInsertNativeNode"
},
{
"name": "checkNoChanges"
},
{
"name": "checkNoChangesMode"
},
{
"name": "cleanUpView"
},
{
"name": "componentRefresh"
},
@ -341,27 +269,12 @@
{
"name": "defineComponent"
},
{
"name": "defineDirective"
},
{
"name": "defineInjectable"
},
{
"name": "defineInjector"
},
{
"name": "destroyLView"
},
{
"name": "destroyViewTree"
},
{
"name": "detachView"
},
{
"name": "detectChanges"
},
{
"name": "detectChangesInternal"
},
@ -398,18 +311,6 @@
{
"name": "executeInitHooks"
},
{
"name": "executeNodeAction"
},
{
"name": "executeOnDestroys"
},
{
"name": "executePipeOnDestroys"
},
{
"name": "extendStatics"
},
{
"name": "extractDirectiveDef"
},
@ -431,9 +332,6 @@
{
"name": "generatePropertyAliases"
},
{
"name": "getChildLNode"
},
{
"name": "getCleanup"
},
@ -446,18 +344,6 @@
{
"name": "getLViewChild"
},
{
"name": "getNextLNode"
},
{
"name": "getNextLNodeWithProjection"
},
{
"name": "getOrCreateContainerRef"
},
{
"name": "getOrCreateElementRef"
},
{
"name": "getOrCreateInjectable"
},
@ -470,39 +356,21 @@
{
"name": "getOrCreateTView"
},
{
"name": "getOrCreateTemplateRef"
},
{
"name": "getParentLNode"
},
{
"name": "getParentState"
},
{
"name": "getPreviousIndex"
},
{
"name": "getPreviousOrParentNode"
},
{
"name": "getRenderFlags"
},
{
"name": "getRenderer"
},
{
"name": "getRootView"
},
{
"name": "getSymbolIterator"
},
{
"name": "getTViewCleanup"
},
{
"name": "getTypeNameForDebugging"
},
{
"name": "getTypeNameForDebugging$1"
},
@ -518,15 +386,6 @@
{
"name": "inject"
},
{
"name": "injectTemplateRef"
},
{
"name": "injectViewContainerRef"
},
{
"name": "insertView"
},
{
"name": "instantiateDirectivesDirectly"
},
@ -545,12 +404,6 @@
{
"name": "isDifferent"
},
{
"name": "isJsObject"
},
{
"name": "isListLikeIterable"
},
{
"name": "isNodeMatchingSelector"
},
@ -563,9 +416,6 @@
{
"name": "isProceduralRenderer"
},
{
"name": "iterateListLike"
},
{
"name": "leaveView"
},
@ -581,15 +431,6 @@
{
"name": "locateHostElement"
},
{
"name": "looseIdentical"
},
{
"name": "makeMetadataCtor"
},
{
"name": "makeParamDecorator"
},
{
"name": "markDirtyIfOnPush"
},
@ -599,9 +440,6 @@
{
"name": "namespaceHTML"
},
{
"name": "notImplemented"
},
{
"name": "queueComponentIndexForCheck"
},
@ -632,12 +470,6 @@
{
"name": "refreshView"
},
{
"name": "removeListeners"
},
{
"name": "removeView"
},
{
"name": "renderComponent"
},
@ -716,15 +548,9 @@
{
"name": "tickRootContext"
},
{
"name": "trackByIdentity"
},
{
"name": "viewAttached"
},
{
"name": "walkLNodeTree"
},
{
"name": "wrapListenerWithDirtyAndDefault"
},

View File

@ -147,36 +147,7 @@ export class ToDoAppComponent {
}
}
// In JIT mode the @Directive decorators in //packages/common will compile the Ivy fields. When
// running under --define=compile=legacy, //packages/common is not compiled with Ivy fields, so they
// must be monkey-patched on.
if (!(NgIf as any).ngDirectiveDef) {
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
(CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule});
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
(NgForOf as any).ngDirectiveDef = defineDirective({
type: NgForOf,
selectors: [['', 'ngFor', '', 'ngForOf', '']],
factory: () => new NgForOf(
injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)),
inputs: {
ngForOf: 'ngForOf',
ngForTrackBy: 'ngForTrackBy',
ngForTemplate: 'ngForTemplate',
}
});
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
(NgIf as any).ngDirectiveDef = defineDirective({
type: NgIf,
selectors: [['', 'ngIf', '']],
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()),
inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'}
});
}
@NgModule({declarations: [ToDoAppComponent, ToDoAppComponent], imports: [CommonModule]})
@NgModule({declarations: [ToDoAppComponent], imports: [CommonModule]})
export class ToDoAppModule {
}

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {renderComponent, toHtml} from '../render_util';
@ -78,8 +78,9 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyComponent.ngComponentDef as ComponentDef<any>).directiveDefs =
[(ChildComponent.ngComponentDef as ComponentDef<any>), SomeDirective.ngDirectiveDef];
(MyComponent.ngComponentDef as ComponentDefInternal<any>).directiveDefs = [
(ChildComponent.ngComponentDef as ComponentDefInternal<any>), SomeDirective.ngDirectiveDef
];
// /NON-NORMATIVE
expect(renderComp(MyComponent)).toEqual('<child some-directive="">child-view</child>!');
@ -127,7 +128,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs = [HostBindingDir.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[HostBindingDir.ngDirectiveDef];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<div hostbindingdir="" id="some id"></div>`);
@ -178,7 +180,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs = [HostListenerDir.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[HostListenerDir.ngDirectiveDef];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<button hostlistenerdir="">Click</button>`);
@ -222,7 +225,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs = [HostAttributeDir.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[HostAttributeDir.ngDirectiveDef];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<div hostattributedir="" role="listbox"></div>`);
@ -269,7 +273,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs = [HostBindingDir.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[HostBindingDir.ngDirectiveDef];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<div aria-label="some label" hostbindingdir=""></div>`);
@ -331,8 +336,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-comp>some name</my-comp>`);
@ -461,8 +466,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-array-comp>Nancy Bess</my-array-comp>`);
@ -506,8 +511,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-array-comp>NANCY Bess</my-array-comp>`);
@ -571,8 +576,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-comp>3</my-comp>`);
@ -614,8 +619,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyArrayComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-array-comp>Nancy Bess</my-array-comp>`);
@ -727,8 +732,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(MyComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<my-comp>start-abcde-middle-fghi-end</my-comp>`);
@ -802,8 +807,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(ObjectComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(ObjectComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp)).toEqual(`<object-comp><p>500</p><p>slide</p></object-comp>`);
@ -890,8 +895,8 @@ describe('components & directives', () => {
}
// NON-NORMATIVE (done by defineNgModule)
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
[(NestedComp.ngComponentDef as ComponentDef<any>)];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[(NestedComp.ngComponentDef as ComponentDefInternal<any>)];
// /NON-NORMATIVE
expect(renderComp(MyApp))

View File

@ -11,7 +11,7 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {AttributeMarker} from '../../../src/render3';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {ComponentFixture, renderComponent, toHtml} from '../render_util';
@ -107,7 +107,8 @@ describe('elements', () => {
}
// NON-NORMATIVE
(LocalRefComp.ngComponentDef as ComponentDef<any>).directiveDefs = () => [Dir.ngDirectiveDef];
(LocalRefComp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
() => [Dir.ngDirectiveDef];
// /NON-NORMATIVE
const fixture = new ComponentFixture(LocalRefComp);

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {renderComponent, toHtml} from '../render_util';
@ -83,7 +83,8 @@ describe('lifecycle hooks', () => {
}
// NON-NORMATIVE
(SimpleLayout.ngComponentDef as ComponentDef<any>).directiveDefs = [LifecycleComp.ngComponentDef];
(SimpleLayout.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[LifecycleComp.ngComponentDef];
// /NON-NORMATIVE
it('should gen hooks with a few simple components', () => {

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {containerEl, renderComponent, toHtml} from '../render_util';
@ -101,7 +101,7 @@ describe('pipes', () => {
}
// NON-NORMATIVE
(MyApp.ngComponentDef as ComponentDef<any>).pipeDefs =
(MyApp.ngComponentDef as ComponentDefInternal<any>).pipeDefs =
() => [MyPurePipe.ngPipeDef, MyPipe.ngPipeDef];
// /NON-NORMATIVE
@ -197,8 +197,8 @@ describe('pipes', () => {
}
// NON-NORMATIVE
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs = [OneTimeIf.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDef<any>).pipeDefs = [MyPurePipe.ngPipeDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs = [OneTimeIf.ngDirectiveDef];
(MyApp.ngComponentDef as ComponentDefInternal<any>).pipeDefs = [MyPurePipe.ngPipeDef];
// /NON-NORMATIVE
let myApp: MyApp = renderComponent(MyApp);

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {renderComponent, toHtml} from '../render_util';
@ -71,7 +71,7 @@ describe('queries', () => {
}
// NON-NORMATIVE
(ViewQueryComponent.ngComponentDef as ComponentDef<any>).directiveDefs =
(ViewQueryComponent.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[SomeDirective.ngDirectiveDef];
// /NON-NORMATIVE
@ -155,7 +155,7 @@ describe('queries', () => {
}
// NON-NORMATIVE
(MyApp.ngComponentDef as ComponentDef<any>).directiveDefs =
(MyApp.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[ContentQueryComponent.ngComponentDef, SomeDirective.ngDirectiveDef];
// /NON-NORMATIVE

View File

@ -96,7 +96,7 @@ class ToDoAppComponent {
}
// NON-NORMATIVE
(ToDoAppComponent.ngComponentDef as r3.ComponentDef<any>).directiveDefs = () =>
(ToDoAppComponent.ngComponentDef as r3.ComponentDefInternal<any>).directiveDefs = () =>
[ToDoItemComponent.ngComponentDef, (NgForOf as r3.DirectiveType<NgForOf<any>>).ngDirectiveDef];
// /NON-NORMATIVE

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {ComponentDef} from '../../../src/render3/interfaces/definition';
import {ComponentDefInternal} from '../../../src/render3/interfaces/definition';
import {renderComponent, toHtml} from '../render_util';
@ -125,7 +125,7 @@ describe('template variables', () => {
}
// NON-NORMATIVE
(MyComponent.ngComponentDef as ComponentDef<any>).directiveDefs =
(MyComponent.ngComponentDef as ComponentDefInternal<any>).directiveDefs =
[ForOfDirective.ngDirectiveDef];
// /NON-NORMATIVE

View File

@ -11,7 +11,7 @@ import {DoCheck, ViewEncapsulation, createInjector, defineInjectable, defineInje
import {getRenderedText} from '../../src/render3/component';
import {ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding, tick} from '../../src/render3/instructions';
import {ComponentDef, DirectiveDef, RenderFlags} from '../../src/render3/interfaces/definition';
import {ComponentDefInternal, DirectiveDefInternal, RenderFlags} from '../../src/render3/interfaces/definition';
import {createRendererType2} from '../../src/view/index';
import {getRendererFactory2} from './imported_renderer2';
@ -346,7 +346,7 @@ describe('recursive components', () => {
});
}
(TreeComponent.ngComponentDef as ComponentDef<TreeComponent>).directiveDefs =
(TreeComponent.ngComponentDef as ComponentDefInternal<TreeComponent>).directiveDefs =
() => [TreeComponent.ngComponentDef];
function _buildTree(currDepth: number): TreeNode {

View File

@ -7,7 +7,7 @@
*/
import {DoCheck, OnChanges, SimpleChange, SimpleChanges} from '../../src/core';
import {DirectiveDef, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
import {DirectiveDefInternal, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
describe('define', () => {
describe('component', () => {
@ -36,15 +36,15 @@ describe('define', () => {
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory() as MyDirective;
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
.factory() as MyDirective;
myDir.valA = 'first';
expect(myDir.valA).toEqual('first');
myDir.valB = 'second';
expect(myDir.log).toEqual(['second']);
expect(myDir.valB).toEqual('works');
myDir.log.length = 0;
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
expect(myDir.log).toEqual(['ngOnChanges', 'valA', changeA, 'valB', changeB, 'ngDoCheck']);
@ -70,18 +70,18 @@ describe('define', () => {
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory() as MyDirective;
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
.factory() as MyDirective;
myDir.valA = 'first';
myDir.valB = 'second';
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
const changeA1 = new SimpleChange(undefined, 'first', true);
const changeB1 = new SimpleChange(undefined, 'second', true);
expect(myDir.log).toEqual(['valA', changeA1, 'valB', changeB1]);
myDir.log.length = 0;
myDir.valA = 'third';
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
const changeA2 = new SimpleChange('first', 'third', false);
expect(myDir.log).toEqual(['valA', changeA2, 'valB', undefined]);
});
@ -106,11 +106,11 @@ describe('define', () => {
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory() as MyDirective;
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
.factory() as MyDirective;
myDir.onlySetter = 'someValue';
expect(myDir.onlySetter).toBeUndefined();
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
const changeSetter = new SimpleChange(undefined, 'someValue', true);
expect(myDir.log).toEqual(['someValue', 'ngOnChanges', 'onlySetter', changeSetter]);
});

View File

@ -10,8 +10,8 @@ import {Injectable} from '@angular/core/src/di/injectable';
import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
import {ivyEnabled} from '@angular/core/src/ivy_switch';
import {Component, HostBinding, HostListener} from '@angular/core/src/metadata/directives';
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {NgModule, NgModuleDefInternal} from '@angular/core/src/metadata/ng_module';
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
ivyEnabled && describe('render3 jit', () => {
let injector: any;
@ -134,7 +134,7 @@ ivyEnabled && describe('render3 jit', () => {
class Module {
}
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
const moduleDef: NgModuleDefInternal<Module> = (Module as any).ngModuleDef;
expect(moduleDef).toBeDefined();
expect(moduleDef.declarations.length).toBe(1);
expect(moduleDef.declarations[0]).toBe(Cmp);
@ -147,7 +147,7 @@ ivyEnabled && describe('render3 jit', () => {
})
class Cmp {
}
const cmpDef: ComponentDef<Cmp> = (Cmp as any).ngComponentDef;
const cmpDef: ComponentDefInternal<Cmp> = (Cmp as any).ngComponentDef;
expect(cmpDef.directiveDefs).toBeNull();
@ -157,7 +157,7 @@ ivyEnabled && describe('render3 jit', () => {
class Module {
}
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
const moduleDef: NgModuleDefInternal<Module> = (Module as any).ngModuleDef;
expect(cmpDef.directiveDefs instanceof Function).toBe(true);
expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]);
});
@ -179,7 +179,7 @@ ivyEnabled && describe('render3 jit', () => {
onChange(event: any): void {}
}
const cmpDef = (Cmp as any).ngComponentDef as ComponentDef<Cmp>;
const cmpDef = (Cmp as any).ngComponentDef as ComponentDefInternal<Cmp>;
expect(cmpDef.hostBindings).toBeDefined();
expect(cmpDef.hostBindings !.length).toBe(2);

View File

@ -14,6 +14,7 @@ import {angularCoreEnv} from '../../src/render3/jit/environment';
const INTERFACE_EXCEPTIONS = new Set<string>([
'ComponentDef',
'DirectiveDef',
'NgModuleDef',
]);
describe('r3 jit environment', () => {

View File

@ -11,7 +11,7 @@ import {stringifyElement} from '@angular/platform-browser/testing/src/browser_ut
import {Injector} from '../../src/di/injector';
import {CreateComponentOptions} from '../../src/render3/component';
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefInternal, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions';
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
import {LElementNode} from '../../src/render3/interfaces/node';
@ -180,13 +180,13 @@ export function renderToHtml(
function toDefs(
types: DirectiveTypesOrFactory | undefined | null,
mapFn: (type: Type<any>) => DirectiveDef<any>): DirectiveDefList|null;
mapFn: (type: Type<any>) => DirectiveDefInternal<any>): DirectiveDefList|null;
function toDefs(
types: PipeTypesOrFactory | undefined | null,
mapFn: (type: Type<any>) => PipeDef<any>): PipeDefList|null;
function toDefs(
types: PipeTypesOrFactory | DirectiveTypesOrFactory | undefined | null,
mapFn: (type: Type<any>) => PipeDef<any>| DirectiveDef<any>): any {
mapFn: (type: Type<any>) => PipeDef<any>| DirectiveDefInternal<any>): any {
if (!types) return null;
if (typeof types == 'function') {
types = types();

View File

@ -389,6 +389,7 @@ export class TestBed implements Injector {
providers: [
...rootProviderOverrides,
],
jit: true,
})
class RootScopeModule {
}
@ -399,7 +400,7 @@ export class TestBed implements Injector {
const imports = [rootScopeImports, this.ngModule, this._imports];
const schemas = this._schemas;
@NgModule({providers, declarations, imports, schemas})
@NgModule({providers, declarations, imports, schemas, jit: true})
class DynamicTestModule {
}
@ -540,7 +541,7 @@ export class TestBed implements Injector {
overrideTemplateUsingTestingModule(component: Type<any>, template: string) {
this._assertNotInstantiated('overrideTemplateUsingTestingModule', 'override template');
@Component({selector: 'empty', template})
@Component({selector: 'empty', template, jit: true})
class OverrideComponent {
}