diff --git a/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts b/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts index 87ea6c8f4d..1aa5cf196f 100644 --- a/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts @@ -15,7 +15,7 @@ import {findAll, getNameText, getOriginalSymbol, isDefined} from '../utils'; import {DecoratedClass} from './decorated_class'; import {DecoratedFile} from './decorated_file'; -import {NgccReflectionHost, PRE_NGCC_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host'; +import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host'; export const DECORATORS = 'decorators' as ts.__String; export const PROP_DECORATORS = 'propDecorators' as ts.__String; @@ -213,13 +213,13 @@ export class Fesm2015ReflectionHost extends TypeScriptReflectionHost implements /** * Search the given module for variable declarations in which the initializer - * is an identifier marked with the `PRE_NGCC_MARKER`. + * is an identifier marked with the `PRE_R3_MARKER`. * @param module the module in which to search for switchable declarations. * @returns an array of variable declarations that match. */ getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[] { // Don't bother to walk the AST if the marker is not found in the text - return module.getText().indexOf(PRE_NGCC_MARKER) >= 0 ? + return module.getText().indexOf(PRE_R3_MARKER) >= 0 ? findAll(module, isSwitchableVariableDeclaration) : []; } diff --git a/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts b/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts index 97c83b483a..483ade6be0 100644 --- a/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts @@ -9,14 +9,14 @@ import * as ts from 'typescript'; import {ReflectionHost} from '../../../ngtsc/host'; import {DecoratedFile} from './decorated_file'; -export const PRE_NGCC_MARKER = '__PRE_R3__'; -export const POST_NGCC_MARKER = '__POST_R3__'; +export const PRE_R3_MARKER = '__PRE_R3__'; +export const POST_R3_MARKER = '__POST_R3__'; export type SwitchableVariableDeclaration = ts.VariableDeclaration & {initializer: ts.Identifier}; export function isSwitchableVariableDeclaration(node: ts.Node): node is SwitchableVariableDeclaration { return ts.isVariableDeclaration(node) && !!node.initializer && - ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_NGCC_MARKER); + ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER); } /** @@ -33,7 +33,7 @@ export interface NgccReflectionHost extends ReflectionHost { /** * Search the given module for variable declarations in which the initializer - * is an identifier marked with the `PRE_NGCC_MARKER`. + * is an identifier marked with the `PRE_R3_MARKER`. * @param module The module in which to search for switchable declarations. * @returns An array of variable declarations that match. */ diff --git a/packages/compiler-cli/src/ngcc/src/rendering/fesm2015_renderer.ts b/packages/compiler-cli/src/ngcc/src/rendering/fesm2015_renderer.ts index a07cfc43da..fe45f08a66 100644 --- a/packages/compiler-cli/src/ngcc/src/rendering/fesm2015_renderer.ts +++ b/packages/compiler-cli/src/ngcc/src/rendering/fesm2015_renderer.ts @@ -7,7 +7,7 @@ */ import * as ts from 'typescript'; import MagicString from 'magic-string'; -import {NgccReflectionHost, POST_NGCC_MARKER, PRE_NGCC_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; +import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; import {AnalyzedClass} from '../analysis/decoration_analyzer'; import {Renderer} from './renderer'; @@ -85,7 +85,7 @@ export class Fesm2015Renderer extends Renderer { declarations.forEach(declaration => { const start = declaration.initializer.getStart(); const end = declaration.initializer.getEnd(); - const replacement = declaration.initializer.text.replace(PRE_NGCC_MARKER, POST_NGCC_MARKER); + const replacement = declaration.initializer.text.replace(PRE_R3_MARKER, POST_R3_MARKER); outputText.overwrite(start, end, replacement); }); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 5482a69e12..23bd83bf31 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, Statement, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler'; +import {Expression, LiteralArrayExpr, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {Decorator, ReflectionHost} from '../../host'; -import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata'; +import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; @@ -100,14 +100,21 @@ export class NgModuleDecoratorHandler implements DecoratorHandler toR3Reference(bootstrap, context)), - declarations: declarations.map(decl => toR3Reference(decl, context)), - exports: exports.map(exp => toR3Reference(exp, context)), - imports: imports.map(imp => toR3Reference(imp, context)), + bootstrap: + bootstrap.map(bootstrap => this._toR3Reference(bootstrap, valueContext, typeContext)), + declarations: declarations.map(decl => this._toR3Reference(decl, valueContext, typeContext)), + exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)), + imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)), emitInline: false, }; @@ -163,6 +170,21 @@ export class NgModuleDecoratorHandler implements DecoratorHandler, valueContext: ts.SourceFile, + typeContext: ts.SourceFile): R3Reference { + if (!(valueRef instanceof ResolvedReference)) { + return toR3Reference(valueRef, valueRef, valueContext, valueContext); + } else { + let typeRef = valueRef; + let typeNode = this.reflector.getDtsDeclarationOfClass(typeRef.node); + if (typeNode !== null) { + typeRef = new ResolvedReference(typeNode, typeNode.name !); + } + return toR3Reference(valueRef, typeRef, valueContext, typeContext); + } + } + /** * Given a `FunctionDeclaration` or `MethodDeclaration`, check if it is typed as a * `ModuleWithProviders` and return an expression referencing the module if available. diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts index 019f4e395d..3a669955cc 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts @@ -14,7 +14,7 @@ import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDecl import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector'; import {TypeCheckableDirectiveMeta} from '../../typecheck'; -import {extractDirectiveGuards, toR3Reference} from './util'; +import {extractDirectiveGuards} from './util'; /** @@ -423,7 +423,11 @@ function convertDirectiveReferenceMap( context: ts.SourceFile): Map> { const newMap = new Map>(); map.forEach((meta, selector) => { - newMap.set(selector, {...meta, directive: toR3Reference(meta.directive, context).value}); + const directive = meta.directive.toExpression(context); + if (directive === null) { + throw new Error(`Could not write expression to reference ${meta.directive.node}`); + } + newMap.set(selector, {...meta, directive}); }); return newMap; } @@ -431,7 +435,13 @@ function convertDirectiveReferenceMap( function convertPipeReferenceMap( map: Map, context: ts.SourceFile): Map { const newMap = new Map(); - map.forEach((meta, selector) => { newMap.set(selector, toR3Reference(meta, context).value); }); + map.forEach((meta, selector) => { + const pipe = meta.toExpression(context); + if (pipe === null) { + throw new Error(`Could not write expression to reference ${meta.node}`); + } + newMap.set(selector, pipe); + }); return newMap; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index b64668b560..bcb7d79117 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -70,11 +70,13 @@ export function getConstructorDependencies( return useType; } -export function toR3Reference(ref: Reference, context: ts.SourceFile): R3Reference { - const value = ref.toExpression(context, ImportMode.UseExistingImport); - const type = ref.toExpression(context, ImportMode.ForceNewImport); +export function toR3Reference( + valueRef: Reference, typeRef: Reference, valueContext: ts.SourceFile, + typeContext: ts.SourceFile): R3Reference { + const value = valueRef.toExpression(valueContext, ImportMode.UseExistingImport); + const type = typeRef.toExpression(typeContext, ImportMode.ForceNewImport); if (value === null || type === null) { - throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`); + throw new Error(`Could not refer to ${ts.SyntaxKind[valueRef.node.kind]}`); } return {value, type}; } diff --git a/packages/compiler-cli/src/ngtsc/host/src/reflection.ts b/packages/compiler-cli/src/ngtsc/host/src/reflection.ts index 49722ed448..79f0d409a5 100644 --- a/packages/compiler-cli/src/ngtsc/host/src/reflection.ts +++ b/packages/compiler-cli/src/ngtsc/host/src/reflection.ts @@ -436,4 +436,18 @@ export interface ReflectionHost { * if the value cannot be computed. */ getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null; + + /** + * Take an exported declaration of a class (maybe downleveled to a variable) and look up the + * declaration of its type in a separate .d.ts tree. + * + * This function is allowed to return `null` if the current compilation unit does not have a + * separate .d.ts tree. When compiling TypeScript code this is always the case, since .d.ts files + * are produced only during the emit of such a compilation. When compiling .js code, however, + * there is frequently a parallel .d.ts tree which this method exposes. + * + * Note that the `ts.ClassDeclaration` returned from this function may not be from the same + * `ts.Program` as the input declaration. + */ + getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null; } diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts b/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts index 48472794ec..69ea1c8d2d 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts @@ -170,6 +170,8 @@ export class TypeScriptReflectionHost implements ReflectionHost { return declaration.initializer || null; } + getDtsDeclarationOfClass(_: ts.Declaration): ts.ClassDeclaration|null { return null; } + /** * Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way. * @@ -312,7 +314,7 @@ 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`); + throw new Error(`Cannot resolve type entity ${type.getText()} to symbol`); } while (realSymbol.flags & ts.SymbolFlags.Alias) { realSymbol = checker.getAliasedSymbol(realSymbol);