diff --git a/packages/compiler-cli/linker/src/file_linker/emit_scopes/emit_scope.ts b/packages/compiler-cli/linker/src/file_linker/emit_scopes/emit_scope.ts index 061ed6aa72afe..dd22af73defed 100644 --- a/packages/compiler-cli/linker/src/file_linker/emit_scopes/emit_scope.ts +++ b/packages/compiler-cli/linker/src/file_linker/emit_scopes/emit_scope.ts @@ -36,7 +36,7 @@ export class EmitScope { */ translateDefinition(definition: LinkedDefinition): TExpression { const expression = this.translator.translateExpression( - definition.expression, new LinkerImportGenerator(this.ngImport)); + definition.expression, new LinkerImportGenerator(this.factory, this.ngImport)); if (definition.statements.length > 0) { // Definition statements must be emitted "after" the declaration for which the definition is @@ -44,7 +44,7 @@ export class EmitScope { // insert statements after definitions. To work around this, the linker transforms the // definition into an IIFE which executes the definition statements before returning the // definition expression. - const importGenerator = new LinkerImportGenerator(this.ngImport); + const importGenerator = new LinkerImportGenerator(this.factory, this.ngImport); return this.wrapInIifeWithStatements( expression, definition.statements.map( @@ -59,7 +59,7 @@ export class EmitScope { * Return any constant statements that are shared between all uses of this `EmitScope`. */ getConstantStatements(): TStatement[] { - const importGenerator = new LinkerImportGenerator(this.ngImport); + const importGenerator = new LinkerImportGenerator(this.factory, this.ngImport); return this.constantPool.statements.map( statement => this.translator.translateStatement(statement, importGenerator)); } diff --git a/packages/compiler-cli/linker/src/file_linker/translator.ts b/packages/compiler-cli/linker/src/file_linker/translator.ts index 60fc9a33ebfb8..2ddc078a05bf5 100644 --- a/packages/compiler-cli/linker/src/file_linker/translator.ts +++ b/packages/compiler-cli/linker/src/file_linker/translator.ts @@ -20,10 +20,11 @@ export class Translator { * Translate the given output AST in the context of an expression. */ translateExpression( - expression: o.Expression, imports: ImportGenerator, + expression: o.Expression, imports: ImportGenerator, options: TranslatorOptions = {}): TExpression { return expression.visitExpression( - new ExpressionTranslatorVisitor(this.factory, imports, options), + new ExpressionTranslatorVisitor( + this.factory, imports, null, options), new Context(false)); } @@ -31,10 +32,11 @@ export class Translator { * Translate the given output AST in the context of a statement. */ translateStatement( - statement: o.Statement, imports: ImportGenerator, + statement: o.Statement, imports: ImportGenerator, options: TranslatorOptions = {}): TStatement { return statement.visitStatement( - new ExpressionTranslatorVisitor(this.factory, imports, options), + new ExpressionTranslatorVisitor( + this.factory, imports, null, options), new Context(true)); } } diff --git a/packages/compiler-cli/linker/src/linker_import_generator.ts b/packages/compiler-cli/linker/src/linker_import_generator.ts index af477dc555752..32ef19f546b55 100644 --- a/packages/compiler-cli/linker/src/linker_import_generator.ts +++ b/packages/compiler-cli/linker/src/linker_import_generator.ts @@ -5,7 +5,7 @@ * 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 {ImportGenerator, NamedImport} from '../../src/ngtsc/translator'; +import {AstFactory, ImportGenerator, ImportRequest} from '../../src/ngtsc/translator'; import {FatalLinkerError} from './fatal_linker_error'; @@ -17,17 +17,19 @@ import {FatalLinkerError} from './fatal_linker_error'; * must be achieved by property access on an `ng` namespace identifier, which is passed in via the * constructor. */ -export class LinkerImportGenerator implements ImportGenerator { - constructor(private ngImport: TExpression) {} - - generateNamespaceImport(moduleName: string): TExpression { - this.assertModuleName(moduleName); - return this.ngImport; +export class LinkerImportGenerator implements + ImportGenerator { + constructor(private factory: AstFactory, private ngImport: TExpression) { } - generateNamedImport(moduleName: string, originalSymbol: string): NamedImport { - this.assertModuleName(moduleName); - return {moduleImport: this.ngImport, symbol: originalSymbol}; + addImport(request: ImportRequest): TExpression { + this.assertModuleName(request.exportModuleSpecifier); + + if (request.exportSymbolName === null) { + return this.ngImport; + } + + return this.factory.createPropertyAccess(this.ngImport, request.exportSymbolName); } private assertModuleName(moduleName: string): void { diff --git a/packages/compiler-cli/linker/test/file_linker/translator_spec.ts b/packages/compiler-cli/linker/test/file_linker/translator_spec.ts index 30f35b0c63728..3fd9478722ac8 100644 --- a/packages/compiler-cli/linker/test/file_linker/translator_spec.ts +++ b/packages/compiler-cli/linker/test/file_linker/translator_spec.ts @@ -6,21 +6,30 @@ * found in the LICENSE file at https://angular.io/license */ import * as o from '@angular/compiler'; -import {ImportGenerator, NamedImport, TypeScriptAstFactory} from '@angular/compiler-cli/src/ngtsc/translator'; +import {TypeScriptAstFactory} from '@angular/compiler-cli/src/ngtsc/translator'; import ts from 'typescript'; import {Translator} from '../../src/file_linker/translator'; +import {LinkerImportGenerator} from '../../src/linker_import_generator'; + import {generate} from './helpers'; +const ngImport = ts.factory.createIdentifier('ngImport'); + describe('Translator', () => { let factory: TypeScriptAstFactory; - beforeEach(() => factory = new TypeScriptAstFactory(/* annotateForClosureCompiler */ false)); + let importGenerator: LinkerImportGenerator; + + beforeEach(() => { + factory = new TypeScriptAstFactory(/* annotateForClosureCompiler */ false); + importGenerator = new LinkerImportGenerator(factory, ngImport); + }); describe('translateExpression()', () => { it('should generate expression specific output', () => { const translator = new Translator(factory); const outputAst = new o.WriteVarExpr('foo', new o.LiteralExpr(42)); - const translated = translator.translateExpression(outputAst, new MockImportGenerator()); + const translated = translator.translateExpression(outputAst, importGenerator); expect(generate(translated)).toEqual('(foo = 42)'); }); }); @@ -29,19 +38,8 @@ describe('Translator', () => { it('should generate statement specific output', () => { const translator = new Translator(factory); const outputAst = new o.ExpressionStatement(new o.WriteVarExpr('foo', new o.LiteralExpr(42))); - const translated = translator.translateStatement(outputAst, new MockImportGenerator()); + const translated = translator.translateStatement(outputAst, importGenerator); expect(generate(translated)).toEqual('foo = 42;'); }); }); - class MockImportGenerator implements ImportGenerator { - generateNamespaceImport(moduleName: string): ts.Expression { - return factory.createLiteral(moduleName); - } - generateNamedImport(moduleName: string, originalSymbol: string): NamedImport { - return { - moduleImport: factory.createLiteral(moduleName), - symbol: originalSymbol, - }; - } - } }); diff --git a/packages/compiler-cli/linker/test/linker_import_generator_spec.ts b/packages/compiler-cli/linker/test/linker_import_generator_spec.ts index 3202ada682d09..ac81dd16fee5e 100644 --- a/packages/compiler-cli/linker/test/linker_import_generator_spec.ts +++ b/packages/compiler-cli/linker/test/linker_import_generator_spec.ts @@ -5,37 +5,62 @@ * 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 ts from 'typescript'; + +import {TypeScriptAstFactory} from '../../src/ngtsc/translator'; import {LinkerImportGenerator} from '../src/linker_import_generator'; -const ngImport = { - ngImport: true -}; +const ngImport = ts.factory.createIdentifier('ngImport'); describe('LinkerImportGenerator', () => { describe('generateNamespaceImport()', () => { it('should error if the import is not `@angular/core`', () => { - const generator = new LinkerImportGenerator(ngImport); - expect(() => generator.generateNamespaceImport('other/import')) + const generator = new LinkerImportGenerator( + new TypeScriptAstFactory(false), ngImport); + + expect( + () => generator.addImport( + {exportModuleSpecifier: 'other/import', exportSymbolName: null, requestedFile: null})) .toThrowError(`Unable to import from anything other than '@angular/core'`); }); it('should return the ngImport expression for `@angular/core`', () => { - const generator = new LinkerImportGenerator(ngImport); - expect(generator.generateNamespaceImport('@angular/core')).toBe(ngImport); + const generator = new LinkerImportGenerator( + new TypeScriptAstFactory(false), ngImport); + + expect(generator.addImport({ + exportModuleSpecifier: '@angular/core', + exportSymbolName: null, + requestedFile: null + })).toBe(ngImport); }); }); describe('generateNamedImport()', () => { it('should error if the import is not `@angular/core`', () => { - const generator = new LinkerImportGenerator(ngImport); - expect(() => generator.generateNamedImport('other/import', 'someSymbol')) - .toThrowError(`Unable to import from anything other than '@angular/core'`); + const generator = new LinkerImportGenerator( + new TypeScriptAstFactory(false), ngImport); + + expect(() => generator.addImport({ + exportModuleSpecifier: 'other/import', + exportSymbolName: 'someSymbol', + requestedFile: null, + })).toThrowError(`Unable to import from anything other than '@angular/core'`); }); it('should return a `NamedImport` object containing the ngImport expression', () => { - const generator = new LinkerImportGenerator(ngImport); - expect(generator.generateNamedImport('@angular/core', 'someSymbol')) - .toEqual({moduleImport: ngImport, symbol: 'someSymbol'}); + const generator = new LinkerImportGenerator( + new TypeScriptAstFactory(false), ngImport); + + const result = generator.addImport({ + exportModuleSpecifier: '@angular/core', + exportSymbolName: 'someSymbol', + requestedFile: null, + }); + + expect(ts.isPropertyAccessExpression(result)).toBe(true); + expect((result as ts.PropertyAccessExpression).name.text).toBe('someSymbol'); + expect((result as ts.PropertyAccessExpression).expression).toBe(ngImport); }); }); }); diff --git a/packages/compiler-cli/src/ngtsc/annotations/common/test/metadata_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/common/test/metadata_spec.ts index d36c76b89f2de..2623952c3cdd7 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/common/test/metadata_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/common/test/metadata_spec.ts @@ -135,7 +135,7 @@ runInEachFileSystem(() => { const sf = getSourceFileOrError(program, _('/index.ts')); const im = new ImportManager(new NoopImportRewriter(), 'i'); const stmt = compileClassMetadata(call).toStmt(); - const tsStatement = translateStatement(stmt, im); + const tsStatement = translateStatement(sf, stmt, im); const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf); return res.replace(/\s+/g, ' '); } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 82bd44831b801..fc934d87a09a6 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -118,6 +118,10 @@ class IvyTransformationVisitor extends Visitor { const statements: ts.Statement[] = []; const members = [...node.members]; + // Note: Class may be already transformed by e.g. Tsickle and + // not have a direct reference to the source file. + const sourceFile = ts.getOriginalNode(node).getSourceFile(); + for (const field of this.classCompilationMap.get(node)!) { // Type-only member. if (field.initializer === null) { @@ -125,7 +129,8 @@ class IvyTransformationVisitor extends Visitor { } // Translate the initializer for the field into TS nodes. - const exprNode = translateExpression(field.initializer, this.importManager, translateOptions); + const exprNode = + translateExpression(sourceFile, field.initializer, this.importManager, translateOptions); // Create a static property declaration for the new field. const property = ts.factory.createPropertyDeclaration( @@ -142,7 +147,8 @@ class IvyTransformationVisitor extends Visitor { /* hasTrailingNewLine */ false); } - field.statements.map(stmt => translateStatement(stmt, this.importManager, translateOptions)) + field.statements + .map(stmt => translateStatement(sourceFile, stmt, this.importManager, translateOptions)) .forEach(stmt => statements.push(stmt)); members.push(property); @@ -313,7 +319,7 @@ function transformIvySourceFile( // to the ImportManager. const downlevelTranslatedCode = getLocalizeCompileTarget(context) < ts.ScriptTarget.ES2015; const constants = - constantPool.statements.map(stmt => translateStatement(stmt, importManager, { + constantPool.statements.map(stmt => translateStatement(file, stmt, importManager, { recordWrappedNode, downlevelTaggedTemplates: downlevelTranslatedCode, downlevelVariableDeclarations: downlevelTranslatedCode, diff --git a/packages/compiler-cli/src/ngtsc/translator/src/api/import_generator.ts b/packages/compiler-cli/src/ngtsc/translator/src/api/import_generator.ts index ec45efa763ef2..4714beb192e1f 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/api/import_generator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/api/import_generator.ts @@ -6,17 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -/** - * The symbol name and import namespace of an imported symbol, - * which has been registered through the ImportGenerator. - */ -export interface NamedImport { - /** The import namespace containing this imported symbol. */ - moduleImport: TExpression|null; - /** The (possibly rewritten) name of the imported symbol. */ - symbol: string; -} - /** * A request to import a given symbol from the given module. */ @@ -49,7 +38,6 @@ export interface ImportRequest { * Implementations of these methods return a specific identifier that corresponds to the imported * module. */ -export interface ImportGenerator { - generateNamespaceImport(moduleName: string): TExpression; - generateNamedImport(moduleName: string, originalSymbol: string): NamedImport; +export interface ImportGenerator { + addImport(request: ImportRequest): TExpression; } diff --git a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts index d5bcc4fedf526..f0f5dae9a4c42 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts @@ -46,15 +46,16 @@ export interface TranslatorOptions { annotateForClosureCompiler?: boolean; } -export class ExpressionTranslatorVisitor implements o.ExpressionVisitor, - o.StatementVisitor { +export class ExpressionTranslatorVisitor implements + o.ExpressionVisitor, o.StatementVisitor { private downlevelTaggedTemplates: boolean; private downlevelVariableDeclarations: boolean; private recordWrappedNode: RecordWrappedNodeFn; constructor( private factory: AstFactory, - private imports: ImportGenerator, options: TranslatorOptions) { + private imports: ImportGenerator, private contextFile: TFile, + options: TranslatorOptions) { this.downlevelTaggedTemplates = options.downlevelTaggedTemplates === true; this.downlevelVariableDeclarations = options.downlevelVariableDeclarations === true; this.recordWrappedNode = options.recordWrappedNode || (() => {}); @@ -210,11 +211,11 @@ export class ExpressionTranslatorVisitor implements o.E private createES5TaggedTemplateFunctionCall( tagHandler: TExpression, {elements, expressions}: TemplateLiteral): TExpression { // Ensure that the `__makeTemplateObject()` helper has been imported. - const {moduleImport, symbol} = - this.imports.generateNamedImport('tslib', '__makeTemplateObject'); - const __makeTemplateObjectHelper = (moduleImport === null) ? - this.factory.createIdentifier(symbol) : - this.factory.createPropertyAccess(moduleImport, symbol); + const __makeTemplateObjectHelper = this.imports.addImport({ + exportModuleSpecifier: 'tslib', + exportSymbolName: '__makeTemplateObject', + requestedFile: this.contextFile, + }); // Collect up the cooked and raw strings into two separate arrays. const cooked: TExpression[] = []; @@ -244,20 +245,21 @@ export class ExpressionTranslatorVisitor implements o.E if (ast.value.moduleName === null) { throw new Error('Invalid import without name nor moduleName'); } - return this.imports.generateNamespaceImport(ast.value.moduleName); + return this.imports.addImport({ + exportModuleSpecifier: ast.value.moduleName, + exportSymbolName: null, + requestedFile: this.contextFile, + }); } // If a moduleName is specified, this is a normal import. If there's no module name, it's a // reference to a global/ambient symbol. if (ast.value.moduleName !== null) { // This is a normal import. Find the imported module. - const {moduleImport, symbol} = - this.imports.generateNamedImport(ast.value.moduleName, ast.value.name); - if (moduleImport === null) { - // The symbol was ambient after all. - return this.factory.createIdentifier(symbol); - } else { - return this.factory.createPropertyAccess(moduleImport, symbol); - } + return this.imports.addImport({ + exportModuleSpecifier: ast.value.moduleName, + exportSymbolName: ast.value.name, + requestedFile: this.contextFile, + }); } else { // The symbol is ambient, so just reference it. return this.factory.createIdentifier(ast.value.name); diff --git a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts index 6ae8412b69b5c..4041e101d5bcf 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts @@ -13,21 +13,21 @@ import {assertSuccessfulReferenceEmit, ImportFlags, OwningModule, Reference, Ref import {AmbientImport, ReflectionHost} from '../../reflection'; import {Context} from './context'; -import {ImportManager} from './import_manager'; +import {ImportManagerV2} from './import_manager/import_manager'; import {tsNumericExpression} from './ts_util'; import {TypeEmitter} from './type_emitter'; export function translateType( type: o.Type, contextFile: ts.SourceFile, reflector: ReflectionHost, - refEmitter: ReferenceEmitter, imports: ImportManager): ts.TypeNode { + refEmitter: ReferenceEmitter, imports: ImportManagerV2): ts.TypeNode { return type.visitType( new TypeTranslatorVisitor(imports, contextFile, reflector, refEmitter), new Context(false)); } class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { constructor( - private imports: ImportManager, private contextFile: ts.SourceFile, + private imports: ImportManagerV2, private contextFile: ts.SourceFile, private reflector: ReflectionHost, private refEmitter: ReferenceEmitter) {} visitBuiltinType(type: o.BuiltinType, context: Context): ts.KeywordTypeNode { @@ -148,12 +148,12 @@ class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { if (ast.value.moduleName === null || ast.value.name === null) { throw new Error(`Import unknown module or symbol`); } - const {moduleImport, symbol} = - this.imports.generateNamedImport(ast.value.moduleName, ast.value.name); - const symbolIdentifier = ts.factory.createIdentifier(symbol); - - const typeName = moduleImport ? ts.factory.createQualifiedName(moduleImport, symbolIdentifier) : - symbolIdentifier; + const typeName = this.imports.addImport({ + exportModuleSpecifier: ast.value.moduleName, + exportSymbolName: ast.value.name, + requestedFile: this.contextFile, + asTypeReference: true + }); const typeArguments = ast.typeParams !== null ? ast.typeParams.map(type => this.translateType(type, context)) : diff --git a/packages/compiler-cli/src/ngtsc/translator/src/typescript_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/typescript_translator.ts index 8e8f932bfd302..5e6e4a8f4cef0 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/typescript_translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/typescript_translator.ts @@ -15,19 +15,23 @@ import {ExpressionTranslatorVisitor, TranslatorOptions} from './translator'; import {TypeScriptAstFactory} from './typescript_ast_factory'; export function translateExpression( - expression: o.Expression, imports: ImportGenerator, + contextFile: ts.SourceFile, expression: o.Expression, + imports: ImportGenerator, options: TranslatorOptions = {}): ts.Expression { return expression.visitExpression( - new ExpressionTranslatorVisitor( - new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, options), + new ExpressionTranslatorVisitor( + new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, + contextFile, options), new Context(false)); } export function translateStatement( - statement: o.Statement, imports: ImportGenerator, + contextFile: ts.SourceFile, statement: o.Statement, + imports: ImportGenerator, options: TranslatorOptions = {}): ts.Statement { return statement.visitStatement( - new ExpressionTranslatorVisitor( - new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, options), + new ExpressionTranslatorVisitor( + new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, + contextFile, options), new Context(true)); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts index e77cf955cc756..d1b184d49bcb2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts @@ -123,7 +123,7 @@ export class Environment extends ReferenceEmitEnvironment { assertSuccessfulReferenceEmit(ngExpr, this.contextFile, 'class'); // Use `translateExpression` to convert the `Expression` into a `ts.Expression`. - return translateExpression(ngExpr.expression, this.importManager); + return translateExpression(this.contextFile, ngExpr.expression, this.importManager); } private emitTypeParameters(declaration: ClassDeclaration): diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/reference_emit_environment.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/reference_emit_environment.ts index ea336d57bb711..1a70753c2883c 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/reference_emit_environment.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/reference_emit_environment.ts @@ -22,7 +22,7 @@ import {ImportManager, translateExpression, translateType} from '../../translato export class ReferenceEmitEnvironment { constructor( readonly importManager: ImportManager, protected refEmitter: ReferenceEmitter, - readonly reflector: ReflectionHost, protected contextFile: ts.SourceFile) {} + readonly reflector: ReflectionHost, public contextFile: ts.SourceFile) {} canReferenceType( ref: Reference, @@ -57,7 +57,7 @@ export class ReferenceEmitEnvironment { */ referenceExternalSymbol(moduleName: string, name: string): ts.Expression { const external = new ExternalExpr({moduleName, name}); - return translateExpression(external, this.importManager); + return translateExpression(this.contextFile, external, this.importManager); } /**