fix(ivy): generate type references to a default import (#29146)

This commit refactors and expands ngtsc's support for generating imports of
values from imports of types (this is used for example when importing a
class referenced in a type annotation in a constructor).

Previously, this logic handled "import {Foo} from" and "import * as foo
from" style imports, but failed on imports of default values ("import
Foo from"). This commit moves the type-to-value logic to a separate file and
expands it to cover the default import case. Doing this also required
augmenting the ImportManager to track default as well as non-default import
generation. The APIs were made a little cleaner at the same time.

PR Close #29146
This commit is contained in:
Alex Rickabaugh
2019-03-06 16:35:08 -08:00
committed by Kara Erickson
parent 37c5a26421
commit b6f6b1178f
14 changed files with 349 additions and 156 deletions

View File

@ -9,6 +9,7 @@ ts_library(
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],

View File

@ -10,6 +10,7 @@ import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinTyp
import * as ts from 'typescript';
import {ImportRewriter, NoopImportRewriter} from '../../imports';
import {DEFAULT_EXPORT_NAME} from '../../reflection';
export class Context {
constructor(readonly isStatement: boolean) {}
@ -38,11 +39,9 @@ const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
]);
export class ImportManager {
private moduleToIndex = new Map<string, string>();
private importedModules = new Set<string>();
private nonDefaultImports = new Map<string, string>();
private defaultImports = new Map<string, string>();
private nextIndex = 0;
constructor(protected rewriter: ImportRewriter = new NoopImportRewriter(), private prefix = 'i') {
@ -61,20 +60,39 @@ export class ImportManager {
}
// 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, symbol};
const isDefault = symbol === DEFAULT_EXPORT_NAME;
// Use a different map for non-default vs default imports. This allows the same module to be
// imported in both ways simultaneously.
const trackingMap = !isDefault ? this.nonDefaultImports : this.defaultImports;
if (!trackingMap.has(moduleName)) {
trackingMap.set(moduleName, `${this.prefix}${this.nextIndex++}`);
}
const moduleImport = trackingMap.get(moduleName) !;
if (isDefault) {
// For an import of a module's default symbol, the moduleImport *is* the name to use to refer
// to the import.
return {moduleImport: null, symbol: moduleImport};
} else {
// Non-default imports have a qualifier and the symbol name to import.
return {moduleImport, symbol};
}
}
getAllImports(contextPath: string): {name: string, as: string}[] {
return Array.from(this.moduleToIndex.keys()).map(name => {
const as = this.moduleToIndex.get(name) !;
name = this.rewriter.rewriteSpecifier(name, contextPath);
return {name, as};
getAllImports(contextPath: string): {specifier: string, qualifier: string, isDefault: boolean}[] {
const imports: {specifier: string, qualifier: string, isDefault: boolean}[] = [];
this.nonDefaultImports.forEach((qualifier, specifier) => {
specifier = this.rewriter.rewriteSpecifier(specifier, contextPath);
imports.push({specifier, qualifier, isDefault: false});
});
this.defaultImports.forEach((qualifier, specifier) => {
specifier = this.rewriter.rewriteSpecifier(specifier, contextPath);
imports.push({specifier, qualifier, isDefault: true});
});
return imports;
}
}