diff --git a/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_manager.ts b/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_manager.ts deleted file mode 100644 index 2dbd05f1d8..0000000000 --- a/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_manager.ts +++ /dev/null @@ -1,22 +0,0 @@ - -/** - * @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 {ImportManager} from '../../../ngtsc/translator'; - -export class NgccImportManager extends ImportManager { - constructor(private isFlat: boolean, isCore: boolean, prefix?: string) { super(isCore, prefix); } - - generateNamedImport(moduleName: string, symbol: string): - {moduleImport: string | null, symbol: string} { - if (this.isFlat && this.isCore && moduleName === '@angular/core') { - return {moduleImport: null, symbol: this.rewriteSymbol(moduleName, symbol)}; - } - return super.generateNamedImport(moduleName, symbol); - } -} diff --git a/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_rewriter.ts b/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_rewriter.ts new file mode 100644 index 0000000000..dc63243859 --- /dev/null +++ b/packages/compiler-cli/src/ngcc/src/rendering/ngcc_import_rewriter.ts @@ -0,0 +1,34 @@ + +/** + * @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 {ImportRewriter, validateAndRewriteCoreSymbol} from '../../../ngtsc/imports'; + +export class NgccFlatImportRewriter implements ImportRewriter { + shouldImportSymbol(symbol: string, specifier: string): boolean { + if (specifier === '@angular/core') { + // Don't use imports for @angular/core symbols in a flat bundle, as they'll be visible + // directly. + return false; + } else { + return true; + } + } + + rewriteSymbol(symbol: string, specifier: string): string { + if (specifier === '@angular/core') { + return validateAndRewriteCoreSymbol(symbol); + } else { + return symbol; + } + } + + rewriteSpecifier(originalModulePath: string, inContextOfFile: string): string { + return originalModulePath; + } +} diff --git a/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts index b6bc54b911..1b702c0ade 100644 --- a/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts @@ -13,9 +13,10 @@ import {basename, dirname, relative, resolve} from 'canonical-path'; import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map'; import * as ts from 'typescript'; +import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter} from '@angular/compiler-cli/src/ngtsc/imports'; import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform'; import {translateStatement, translateType, ImportManager} from '../../../ngtsc/translator'; -import {NgccImportManager} from './ngcc_import_manager'; +import {NgccFlatImportRewriter} from './ngcc_import_rewriter'; import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer'; import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer'; import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer'; @@ -135,7 +136,8 @@ export abstract class Renderer { } if (compiledFile) { - const importManager = new NgccImportManager(this.bundle.isFlat, this.isCore, IMPORT_PREFIX); + const importManager = new ImportManager( + this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlat), IMPORT_PREFIX); // TODO: remove constructor param metadata and property decorators (we need info from the // handlers to do this) @@ -152,9 +154,7 @@ export abstract class Renderer { renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager), compiledFile.sourceFile); - this.addImports( - outputText, importManager.getAllImports( - compiledFile.sourceFile.fileName, this.bundle.src.r3SymbolsFile)); + this.addImports(outputText, importManager.getAllImports(compiledFile.sourceFile.fileName)); } // Add exports to the entry-point file @@ -169,7 +169,8 @@ export abstract class Renderer { renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] { const input = this.extractSourceMap(dtsFile); const outputText = new MagicString(input.source); - const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX); + const importManager = new ImportManager( + this.getImportRewriter(this.bundle.dts !.r3SymbolsFile, false), IMPORT_PREFIX); renderInfo.classInfo.forEach(dtsClass => { const endOfClass = dtsClass.dtsDeclaration.getEnd(); @@ -181,8 +182,7 @@ export abstract class Renderer { }); this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager); - this.addImports( - outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile)); + this.addImports(outputText, importManager.getAllImports(dtsFile.fileName)); this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports); @@ -199,7 +199,7 @@ export abstract class Renderer { */ protected addModuleWithProvidersParams( outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], - importManager: NgccImportManager): void { + importManager: ImportManager): void { moduleWithProviders.forEach(info => { const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text; const declarationFile = info.declaration.getSourceFile().fileName; @@ -417,6 +417,16 @@ export abstract class Renderer { return ( id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core')); } + + private getImportRewriter(r3SymbolsFile: ts.SourceFile|null, isFlat: boolean): ImportRewriter { + if (this.isCore && isFlat) { + return new NgccFlatImportRewriter(); + } else if (this.isCore) { + return new R3SymbolsImportRewriter(r3SymbolsFile !.fileName); + } else { + return new NoopImportRewriter(); + } + } } /** @@ -451,7 +461,7 @@ export function mergeSourceMaps( * Render the constant pool as source code for the given class. */ export function renderConstantPool( - sourceFile: ts.SourceFile, constantPool: ConstantPool, imports: NgccImportManager): string { + sourceFile: ts.SourceFile, constantPool: ConstantPool, imports: ImportManager): string { const printer = ts.createPrinter(); return constantPool.statements.map(stmt => translateStatement(stmt, imports)) .map(stmt => printer.printNode(ts.EmitHint.Unspecified, stmt, sourceFile)) @@ -467,7 +477,7 @@ export function renderConstantPool( * @param imports An object that tracks the imports that are needed by the rendered definitions. */ export function renderDefinitions( - sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: NgccImportManager): string { + sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string { const printer = ts.createPrinter(); const name = (compiledClass.declaration as ts.NamedDeclaration).name !; const definitions = diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts index be298b3a55..adbbed46e5 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts @@ -8,6 +8,7 @@ import * as ts from 'typescript'; +import {NoopImportRewriter} from '../../imports'; import {TypeScriptReflectionHost} from '../../reflection'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; import {ImportManager, translateStatement} from '../../translator'; @@ -87,7 +88,7 @@ function compileAndPrint(contents: string): string { return ''; } const sf = program.getSourceFile('index.ts') !; - const im = new ImportManager(false, 'i'); + const im = new ImportManager(new NoopImportRewriter(), 'i'); const tsStatement = translateStatement(call, im); const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf); return res.replace(/\s+/g, ' '); diff --git a/packages/compiler-cli/src/ngtsc/imports/index.ts b/packages/compiler-cli/src/ngtsc/imports/index.ts index e7c07af3a6..275566d131 100644 --- a/packages/compiler-cli/src/ngtsc/imports/index.ts +++ b/packages/compiler-cli/src/ngtsc/imports/index.ts @@ -6,5 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ +export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAndRewriteCoreSymbol} from './src/core'; export {AbsoluteReference, ImportMode, NodeReference, Reference, ResolvedReference} from './src/references'; export {ReferenceResolver, TsReferenceResolver} from './src/resolver'; diff --git a/packages/compiler-cli/src/ngtsc/imports/src/core.ts b/packages/compiler-cli/src/ngtsc/imports/src/core.ts new file mode 100644 index 0000000000..803fae9121 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/imports/src/core.ts @@ -0,0 +1,103 @@ +/** + * @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 {relativePathBetween} from '../../util/src/path'; + +/** + * Rewrites imports of symbols being written into generated code. + */ +export interface ImportRewriter { + /** + * Should the given symbol be imported at all? + * + * If `true`, the symbol should be imported from the given specifier. If `false`, the symbol + * should be referenced directly, without an import. + */ + shouldImportSymbol(symbol: string, specifier: string): boolean; + + /** + * Optionally rewrite a reference to an imported symbol, changing either the binding prefix or the + * symbol name itself. + */ + rewriteSymbol(symbol: string, specifier: string): string; + + /** + * Optionally rewrite the given module specifier in the context of a given file. + */ + rewriteSpecifier(specifier: string, inContextOfFile: string): string; +} + +/** + * `ImportRewriter` that does no rewriting. + */ +export class NoopImportRewriter implements ImportRewriter { + shouldImportSymbol(symbol: string, specifier: string): boolean { return true; } + + rewriteSymbol(symbol: string, specifier: string): string { return symbol; } + + rewriteSpecifier(specifier: string, inContextOfFile: string): string { return specifier; } +} + +/** + * A mapping of supported symbols that can be imported from within @angular/core, and the names by + * which they're exported from r3_symbols. + */ +const CORE_SUPPORTED_SYMBOLS = new Map([ + ['defineInjectable', 'defineInjectable'], + ['defineInjector', 'defineInjector'], + ['ɵdefineNgModule', 'defineNgModule'], + ['inject', 'inject'], + ['ɵsetClassMetadata', 'setClassMetadata'], + ['ɵInjectableDef', 'InjectableDef'], + ['ɵInjectorDef', 'InjectorDef'], + ['ɵNgModuleDefWithMeta', 'NgModuleDefWithMeta'], + ['ɵNgModuleFactory', 'NgModuleFactory'], +]); + +const CORE_MODULE = '@angular/core'; + +/** + * `ImportRewriter` that rewrites imports from '@angular/core' to be imported from the r3_symbols.ts + * file instead. + */ +export class R3SymbolsImportRewriter implements ImportRewriter { + constructor(private r3SymbolsPath: string) {} + + shouldImportSymbol(symbol: string, specifier: string): boolean { return true; } + + rewriteSymbol(symbol: string, specifier: string): string { + if (specifier !== CORE_MODULE) { + // This import isn't from core, so ignore it. + return symbol; + } + + return validateAndRewriteCoreSymbol(symbol); + } + + rewriteSpecifier(specifier: string, inContextOfFile: string): string { + if (specifier !== CORE_MODULE) { + // This module isn't core, so ignore it. + return specifier; + } + + const relativePathToR3Symbols = relativePathBetween(inContextOfFile, this.r3SymbolsPath); + if (relativePathToR3Symbols === null) { + throw new Error( + `Failed to rewrite import inside ${CORE_MODULE}: ${inContextOfFile} -> ${this.r3SymbolsPath}`); + } + + return relativePathToR3Symbols; + } +} + +export function validateAndRewriteCoreSymbol(name: string): string { + if (!CORE_SUPPORTED_SYMBOLS.has(name)) { + throw new Error(`Importing unexpected symbol ${name} while compiling ${CORE_MODULE}`); + } + return CORE_SUPPORTED_SYMBOLS.get(name) !; +} diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index a1b213d535..157e6d111d 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -16,7 +16,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato import {BaseDefDecoratorHandler} from './annotations/src/base_def'; import {ErrorCode, ngErrorCode} from './diagnostics'; import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point'; -import {Reference, TsReferenceResolver} from './imports'; +import {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, Reference, TsReferenceResolver} from './imports'; import {PartialEvaluator} from './partial_evaluator'; import {TypeScriptReflectionHost} from './reflection'; import {FileResourceLoader, HostResourceLoader} from './resource_loader'; @@ -34,6 +34,7 @@ export class NgtscProgram implements api.Program { private sourceToFactorySymbols: Map>|null = null; private host: ts.CompilerHost; private _coreImportsFrom: ts.SourceFile|null|undefined = undefined; + private _importRewriter: ImportRewriter|undefined = undefined; private _reflector: TypeScriptReflectionHost|undefined = undefined; private _isCore: boolean|undefined = undefined; private rootDirs: string[]; @@ -234,7 +235,7 @@ export class NgtscProgram implements api.Program { const customTransforms = opts && opts.customTransformers; const beforeTransforms = - [ivyTransformFactory(this.compilation !, this.reflector, this.coreImportsFrom)]; + [ivyTransformFactory(this.compilation !, this.reflector, this.importRewriter, this.isCore)]; if (this.factoryToSourceInfo !== null) { beforeTransforms.push( @@ -303,7 +304,7 @@ export class NgtscProgram implements api.Program { ]; return new IvyCompilation( - handlers, checker, this.reflector, this.coreImportsFrom, this.sourceToFactorySymbols); + handlers, checker, this.reflector, this.importRewriter, this.sourceToFactorySymbols); } private get reflector(): TypeScriptReflectionHost { @@ -326,6 +327,16 @@ export class NgtscProgram implements api.Program { } return this._isCore; } + + private get importRewriter(): ImportRewriter { + if (this._importRewriter === undefined) { + const coreImportsFrom = this.coreImportsFrom; + this._importRewriter = coreImportsFrom !== null ? + new R3SymbolsImportRewriter(coreImportsFrom.fileName) : + new NoopImportRewriter(); + } + return this._importRewriter; + } } const defaultEmitCallback: api.TsEmitCallback = diff --git a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel index cece39c676..08fc17b114 100644 --- a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel @@ -12,6 +12,7 @@ ts_library( deps = [ "//packages/compiler", "//packages/compiler-cli/src/ngtsc/diagnostics", + "//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/translator", "//packages/compiler-cli/src/ngtsc/typecheck", diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 06e075c4b7..3e65c93479 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -10,6 +10,7 @@ import {ConstantPool} from '@angular/compiler'; import * as ts from 'typescript'; import {FatalDiagnosticError} from '../../diagnostics'; +import {ImportRewriter} from '../../imports'; import {Decorator, ReflectionHost, reflectNameOfDeclaration} from '../../reflection'; import {TypeCheckContext} from '../../typecheck'; @@ -17,6 +18,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler} from './api'; import {DtsFileTransformer} from './declaration'; + /** * Record of an adapter which decided to emit a static field, and the analysis it performed to * prepare for that operation. @@ -63,7 +65,7 @@ export class IvyCompilation { */ constructor( private handlers: DecoratorHandler[], private checker: ts.TypeChecker, - private reflector: ReflectionHost, private coreImportsFrom: ts.SourceFile|null, + private reflector: ReflectionHost, private importRewriter: ImportRewriter, private sourceToFactorySymbols: Map>|null) {} @@ -227,7 +229,7 @@ export class IvyCompilation { private getDtsTransformer(tsFileName: string): DtsFileTransformer { if (!this.dtsMap.has(tsFileName)) { - this.dtsMap.set(tsFileName, new DtsFileTransformer(this.coreImportsFrom)); + this.dtsMap.set(tsFileName, new DtsFileTransformer(this.importRewriter)); } return this.dtsMap.get(tsFileName) !; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts b/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts index 4e009b9969..e053ccda80 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts @@ -8,6 +8,7 @@ import * as ts from 'typescript'; +import {ImportRewriter} from '../../imports'; import {ImportManager, translateType} from '../../translator'; import {CompileResult} from './api'; @@ -21,8 +22,8 @@ export class DtsFileTransformer { private ivyFields = new Map(); private imports: ImportManager; - constructor(private coreImportsFrom: ts.SourceFile|null, importPrefix?: string) { - this.imports = new ImportManager(coreImportsFrom !== null, importPrefix); + constructor(private importRewriter: ImportRewriter, importPrefix?: string) { + this.imports = new ImportManager(importRewriter, importPrefix); } /** @@ -56,7 +57,7 @@ export class DtsFileTransformer { } } - const imports = this.imports.getAllImports(tsPath, this.coreImportsFrom); + const imports = this.imports.getAllImports(tsPath); if (imports.length !== 0) { dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join('') + dts; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 8eba7da50b..4e369d0d23 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -9,6 +9,7 @@ import {ConstantPool} from '@angular/compiler'; import * as ts from 'typescript'; +import {ImportRewriter} from '../../imports'; import {Decorator, ReflectionHost} from '../../reflection'; import {ImportManager, translateExpression, translateStatement} from '../../translator'; import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor'; @@ -19,11 +20,11 @@ import {IvyCompilation} from './compilation'; const NO_DECORATORS = new Set(); export function ivyTransformFactory( - compilation: IvyCompilation, reflector: ReflectionHost, - coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory { + compilation: IvyCompilation, reflector: ReflectionHost, importRewriter: ImportRewriter, + isCore: boolean): ts.TransformerFactory { return (context: ts.TransformationContext): ts.Transformer => { return (file: ts.SourceFile): ts.SourceFile => { - return transformIvySourceFile(compilation, context, reflector, coreImportsFrom, file); + return transformIvySourceFile(compilation, context, reflector, importRewriter, isCore, file); }; }; } @@ -188,13 +189,12 @@ class IvyVisitor extends Visitor { */ function transformIvySourceFile( compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost, - coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile { + importRewriter: ImportRewriter, isCore: boolean, file: ts.SourceFile): ts.SourceFile { const constantPool = new ConstantPool(); - const importManager = new ImportManager(coreImportsFrom !== null); + const importManager = new ImportManager(importRewriter); // Recursively scan through the AST and perform any updates requested by the IvyCompilation. - const visitor = - new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null, constantPool); + const visitor = new IvyVisitor(compilation, reflector, importManager, isCore, constantPool); const sf = visit(file, visitor, context); // Generate the constant statements first, as they may involve adding additional imports @@ -202,7 +202,7 @@ function transformIvySourceFile( const constants = constantPool.statements.map(stmt => translateStatement(stmt, importManager)); // Generate the import statements to prepend. - const addedImports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => { + const addedImports = importManager.getAllImports(file.fileName).map(i => { return ts.createImportDeclaration( undefined, undefined, ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))), diff --git a/packages/compiler-cli/src/ngtsc/translator/BUILD.bazel b/packages/compiler-cli/src/ngtsc/translator/BUILD.bazel index d4a22e7c34..e181b54f57 100644 --- a/packages/compiler-cli/src/ngtsc/translator/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/translator/BUILD.bazel @@ -9,6 +9,7 @@ ts_library( deps = [ "//packages:types", "//packages/compiler", + "//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/util", "@ngdeps//typescript", ], diff --git a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts index 814a56e47a..a3fbc1ac6c 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts @@ -9,7 +9,7 @@ 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, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeVisitor, TypeofExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler'; import * as ts from 'typescript'; -import {relativePathBetween} from '../../util/src/path'; +import {ImportRewriter, NoopImportRewriter} from '../../imports'; export class Context { constructor(readonly isStatement: boolean) {} @@ -38,60 +38,41 @@ const BINARY_OPERATORS = new Map([ [BinaryOperator.Plus, ts.SyntaxKind.PlusToken], ]); -const CORE_SUPPORTED_SYMBOLS = new Map([ - ['defineInjectable', 'defineInjectable'], - ['defineInjector', 'defineInjector'], - ['ɵdefineNgModule', 'defineNgModule'], - ['inject', 'inject'], - ['ɵsetClassMetadata', 'setClassMetadata'], - ['ɵInjectableDef', 'InjectableDef'], - ['ɵInjectorDef', 'InjectorDef'], - ['ɵNgModuleDefWithMeta', 'NgModuleDefWithMeta'], - ['ɵNgModuleFactory', 'NgModuleFactory'], -]); + export class ImportManager { private moduleToIndex = new Map(); + private importedModules = new Set(); private nextIndex = 0; - constructor(protected isCore: boolean, private prefix = 'i') {} + constructor(protected rewriter: ImportRewriter = new NoopImportRewriter(), private prefix = 'i') { + } - generateNamedImport(moduleName: string, symbol: string): + generateNamedImport(moduleName: string, originalSymbol: string): {moduleImport: string | null, symbol: string} { + // First, rewrite the symbol name. + const symbol = this.rewriter.rewriteSymbol(originalSymbol, moduleName); + + // Ask the rewriter if this symbol should be imported at all. If not, it can be referenced + // directly (moduleImport: null). + if (!this.rewriter.shouldImportSymbol(symbol, moduleName)) { + // The symbol should be referenced directly. + return {moduleImport: null, symbol}; + } + + // If not, this symbol will be imported. Allocate a prefix for the imported module if needed. if (!this.moduleToIndex.has(moduleName)) { this.moduleToIndex.set(moduleName, `${this.prefix}${this.nextIndex++}`); } + const moduleImport = this.moduleToIndex.get(moduleName) !; - return { - moduleImport: this.moduleToIndex.get(moduleName) !, - symbol: this.rewriteSymbol(moduleName, symbol) - }; + return {moduleImport, symbol}; } - protected rewriteSymbol(moduleName: string, symbol: string): string { - if (this.isCore && moduleName === '@angular/core') { - if (!CORE_SUPPORTED_SYMBOLS.has(symbol)) { - throw new Error(`Importing unexpected symbol ${symbol} while compiling core`); - } - - symbol = CORE_SUPPORTED_SYMBOLS.get(symbol) !; - } - - return symbol; - } - - getAllImports(contextPath: string, rewriteCoreImportsTo: ts.SourceFile|null): - {name: string, as: string}[] { + getAllImports(contextPath: string): {name: string, as: string}[] { return Array.from(this.moduleToIndex.keys()).map(name => { - const as: string|null = this.moduleToIndex.get(name) !; - if (rewriteCoreImportsTo !== null && name === '@angular/core') { - const relative = relativePathBetween(contextPath, rewriteCoreImportsTo.fileName); - if (relative === null) { - throw new Error( - `Failed to rewrite import inside core: ${contextPath} -> ${rewriteCoreImportsTo.fileName}`); - } - name = relative; - } + const as = this.moduleToIndex.get(name) !; + name = this.rewriter.rewriteSpecifier(name, contextPath); return {name, as}; }); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts index c796c536b7..b76a88f9b2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts @@ -9,6 +9,7 @@ import {R3TargetBinder, SelectorMatcher, TmplAstNode} from '@angular/compiler'; import * as ts from 'typescript'; +import {NoopImportRewriter} from '../../imports'; import {ImportManager} from '../../translator'; import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api'; @@ -16,6 +17,7 @@ import {generateTypeCheckBlock} from './type_check_block'; import {generateTypeCtor} from './type_constructor'; + /** * A template type checking context for a program. * @@ -117,7 +119,7 @@ export class TypeCheckContext { // Imports may need to be added to the file to support type-checking of directives used in the // template within it. - const importManager = new ImportManager(false, '_i'); + const importManager = new ImportManager(new NoopImportRewriter(), '_i'); // Each Op has a splitPoint index into the text where it needs to be inserted. Split the // original source text into chunks at these split points, where code will be inserted between @@ -139,7 +141,7 @@ export class TypeCheckContext { }); // Write out the imports that need to be added to the beginning of the file. - let imports = importManager.getAllImports(sf.fileName, null) + let imports = importManager.getAllImports(sf.fileName) .map(i => `import * as ${i.as} from '${i.name}';`) .join('\n'); code = imports + '\n' + code; diff --git a/packages/core/src/r3_symbols.ts b/packages/core/src/r3_symbols.ts index 2bddd57e02..7b8c72a138 100644 --- a/packages/core/src/r3_symbols.ts +++ b/packages/core/src/r3_symbols.ts @@ -14,8 +14,9 @@ * compiler writes imports to this file. * * Only a subset of such imports are supported - core is not allowed to declare components or pipes. - * A check in ngtsc's translator.ts validates this condition. The translator is responsible for - * translating an external name (prefixed with ɵ) to the internal symbol name as exported below. + * A check in ngtsc's `R3SymbolsImportRewriter` validates this condition. The rewriter is only used + * when compiling @angular/core and is responsible for translating an external name (prefixed with + * ɵ) to the internal symbol name as exported below. * * The below symbols are used for @Injectable and @NgModule compilation. */