refactor(ivy): extract import rewriting into a separate interface (#27998)
Currently the ImportManager class handles various rewriting actions of imports when compiling @angular/core. This is required as code compiled within @angular/core cannot import from '@angular/core'. To work around this, imports are rewritten to get core symbols from a particular file, r3_symbols.ts. In this refactoring, this rewriting logic is moved out of the ImportManager and put behind an interface, ImportRewriter. There are three implementers of the interface: * NoopImportRewriter, used for compiling all non-core packages. * R3SymbolsImportRewriter, used when ngtsc compiles @angular/core. * NgccFlatImportRewriter, used when ngcc compiles @angular/core (special logic is needed because ngcc has to rewrite imports in flat bundles differently than in non-flat bundles). This is a precursor to using this rewriting logic in other contexts besides the ImportManager. PR Close #27998
This commit is contained in:
parent
5a0deb8d69
commit
3cf1b62722
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -13,9 +13,10 @@ import {basename, dirname, relative, resolve} from 'canonical-path';
|
|||||||
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||||
import * as ts from 'typescript';
|
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 {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||||
import {translateStatement, translateType, ImportManager} from '../../../ngtsc/translator';
|
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 {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||||
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
||||||
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
@ -135,7 +136,8 @@ export abstract class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (compiledFile) {
|
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
|
// TODO: remove constructor param metadata and property decorators (we need info from the
|
||||||
// handlers to do this)
|
// handlers to do this)
|
||||||
@ -152,9 +154,7 @@ export abstract class Renderer {
|
|||||||
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
||||||
compiledFile.sourceFile);
|
compiledFile.sourceFile);
|
||||||
|
|
||||||
this.addImports(
|
this.addImports(outputText, importManager.getAllImports(compiledFile.sourceFile.fileName));
|
||||||
outputText, importManager.getAllImports(
|
|
||||||
compiledFile.sourceFile.fileName, this.bundle.src.r3SymbolsFile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add exports to the entry-point file
|
// Add exports to the entry-point file
|
||||||
@ -169,7 +169,8 @@ export abstract class Renderer {
|
|||||||
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
||||||
const input = this.extractSourceMap(dtsFile);
|
const input = this.extractSourceMap(dtsFile);
|
||||||
const outputText = new MagicString(input.source);
|
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 => {
|
renderInfo.classInfo.forEach(dtsClass => {
|
||||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||||
@ -181,8 +182,7 @@ export abstract class Renderer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||||
this.addImports(
|
this.addImports(outputText, importManager.getAllImports(dtsFile.fileName));
|
||||||
outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile));
|
|
||||||
|
|
||||||
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
|
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ export abstract class Renderer {
|
|||||||
*/
|
*/
|
||||||
protected addModuleWithProvidersParams(
|
protected addModuleWithProvidersParams(
|
||||||
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||||
importManager: NgccImportManager): void {
|
importManager: ImportManager): void {
|
||||||
moduleWithProviders.forEach(info => {
|
moduleWithProviders.forEach(info => {
|
||||||
const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text;
|
const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text;
|
||||||
const declarationFile = info.declaration.getSourceFile().fileName;
|
const declarationFile = info.declaration.getSourceFile().fileName;
|
||||||
@ -417,6 +417,16 @@ export abstract class Renderer {
|
|||||||
return (
|
return (
|
||||||
id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core'));
|
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.
|
* Render the constant pool as source code for the given class.
|
||||||
*/
|
*/
|
||||||
export function renderConstantPool(
|
export function renderConstantPool(
|
||||||
sourceFile: ts.SourceFile, constantPool: ConstantPool, imports: NgccImportManager): string {
|
sourceFile: ts.SourceFile, constantPool: ConstantPool, imports: ImportManager): string {
|
||||||
const printer = ts.createPrinter();
|
const printer = ts.createPrinter();
|
||||||
return constantPool.statements.map(stmt => translateStatement(stmt, imports))
|
return constantPool.statements.map(stmt => translateStatement(stmt, imports))
|
||||||
.map(stmt => printer.printNode(ts.EmitHint.Unspecified, stmt, sourceFile))
|
.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.
|
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
||||||
*/
|
*/
|
||||||
export function renderDefinitions(
|
export function renderDefinitions(
|
||||||
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: NgccImportManager): string {
|
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
|
||||||
const printer = ts.createPrinter();
|
const printer = ts.createPrinter();
|
||||||
const name = (compiledClass.declaration as ts.NamedDeclaration).name !;
|
const name = (compiledClass.declaration as ts.NamedDeclaration).name !;
|
||||||
const definitions =
|
const definitions =
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {NoopImportRewriter} from '../../imports';
|
||||||
import {TypeScriptReflectionHost} from '../../reflection';
|
import {TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||||
import {ImportManager, translateStatement} from '../../translator';
|
import {ImportManager, translateStatement} from '../../translator';
|
||||||
@ -87,7 +88,7 @@ function compileAndPrint(contents: string): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const sf = program.getSourceFile('index.ts') !;
|
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 tsStatement = translateStatement(call, im);
|
||||||
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
|
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
|
||||||
return res.replace(/\s+/g, ' ');
|
return res.replace(/\s+/g, ' ');
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {AbsoluteReference, ImportMode, NodeReference, Reference, ResolvedReference} from './src/references';
|
||||||
export {ReferenceResolver, TsReferenceResolver} from './src/resolver';
|
export {ReferenceResolver, TsReferenceResolver} from './src/resolver';
|
||||||
|
103
packages/compiler-cli/src/ngtsc/imports/src/core.ts
Normal file
103
packages/compiler-cli/src/ngtsc/imports/src/core.ts
Normal file
@ -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<string, string>([
|
||||||
|
['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) !;
|
||||||
|
}
|
@ -16,7 +16,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato
|
|||||||
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
||||||
import {ErrorCode, ngErrorCode} from './diagnostics';
|
import {ErrorCode, ngErrorCode} from './diagnostics';
|
||||||
import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point';
|
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 {PartialEvaluator} from './partial_evaluator';
|
||||||
import {TypeScriptReflectionHost} from './reflection';
|
import {TypeScriptReflectionHost} from './reflection';
|
||||||
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
|
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
|
||||||
@ -34,6 +34,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
private sourceToFactorySymbols: Map<string, Set<string>>|null = null;
|
private sourceToFactorySymbols: Map<string, Set<string>>|null = null;
|
||||||
private host: ts.CompilerHost;
|
private host: ts.CompilerHost;
|
||||||
private _coreImportsFrom: ts.SourceFile|null|undefined = undefined;
|
private _coreImportsFrom: ts.SourceFile|null|undefined = undefined;
|
||||||
|
private _importRewriter: ImportRewriter|undefined = undefined;
|
||||||
private _reflector: TypeScriptReflectionHost|undefined = undefined;
|
private _reflector: TypeScriptReflectionHost|undefined = undefined;
|
||||||
private _isCore: boolean|undefined = undefined;
|
private _isCore: boolean|undefined = undefined;
|
||||||
private rootDirs: string[];
|
private rootDirs: string[];
|
||||||
@ -234,7 +235,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
|
|
||||||
const customTransforms = opts && opts.customTransformers;
|
const customTransforms = opts && opts.customTransformers;
|
||||||
const beforeTransforms =
|
const beforeTransforms =
|
||||||
[ivyTransformFactory(this.compilation !, this.reflector, this.coreImportsFrom)];
|
[ivyTransformFactory(this.compilation !, this.reflector, this.importRewriter, this.isCore)];
|
||||||
|
|
||||||
if (this.factoryToSourceInfo !== null) {
|
if (this.factoryToSourceInfo !== null) {
|
||||||
beforeTransforms.push(
|
beforeTransforms.push(
|
||||||
@ -303,7 +304,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return new IvyCompilation(
|
return new IvyCompilation(
|
||||||
handlers, checker, this.reflector, this.coreImportsFrom, this.sourceToFactorySymbols);
|
handlers, checker, this.reflector, this.importRewriter, this.sourceToFactorySymbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get reflector(): TypeScriptReflectionHost {
|
private get reflector(): TypeScriptReflectionHost {
|
||||||
@ -326,6 +327,16 @@ export class NgtscProgram implements api.Program {
|
|||||||
}
|
}
|
||||||
return this._isCore;
|
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 =
|
const defaultEmitCallback: api.TsEmitCallback =
|
||||||
|
@ -12,6 +12,7 @@ ts_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/translator",
|
"//packages/compiler-cli/src/ngtsc/translator",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
|
@ -10,6 +10,7 @@ import {ConstantPool} from '@angular/compiler';
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {FatalDiagnosticError} from '../../diagnostics';
|
import {FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
import {ImportRewriter} from '../../imports';
|
||||||
import {Decorator, ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
|
import {Decorator, ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
|
||||||
import {TypeCheckContext} from '../../typecheck';
|
import {TypeCheckContext} from '../../typecheck';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler} from './api';
|
|||||||
import {DtsFileTransformer} from './declaration';
|
import {DtsFileTransformer} from './declaration';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record of an adapter which decided to emit a static field, and the analysis it performed to
|
* Record of an adapter which decided to emit a static field, and the analysis it performed to
|
||||||
* prepare for that operation.
|
* prepare for that operation.
|
||||||
@ -63,7 +65,7 @@ export class IvyCompilation {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private handlers: DecoratorHandler<any, any>[], private checker: ts.TypeChecker,
|
private handlers: DecoratorHandler<any, any>[], private checker: ts.TypeChecker,
|
||||||
private reflector: ReflectionHost, private coreImportsFrom: ts.SourceFile|null,
|
private reflector: ReflectionHost, private importRewriter: ImportRewriter,
|
||||||
private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
|
private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
|
||||||
|
|
||||||
|
|
||||||
@ -227,7 +229,7 @@ export class IvyCompilation {
|
|||||||
|
|
||||||
private getDtsTransformer(tsFileName: string): DtsFileTransformer {
|
private getDtsTransformer(tsFileName: string): DtsFileTransformer {
|
||||||
if (!this.dtsMap.has(tsFileName)) {
|
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) !;
|
return this.dtsMap.get(tsFileName) !;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ImportRewriter} from '../../imports';
|
||||||
import {ImportManager, translateType} from '../../translator';
|
import {ImportManager, translateType} from '../../translator';
|
||||||
|
|
||||||
import {CompileResult} from './api';
|
import {CompileResult} from './api';
|
||||||
@ -21,8 +22,8 @@ export class DtsFileTransformer {
|
|||||||
private ivyFields = new Map<string, CompileResult[]>();
|
private ivyFields = new Map<string, CompileResult[]>();
|
||||||
private imports: ImportManager;
|
private imports: ImportManager;
|
||||||
|
|
||||||
constructor(private coreImportsFrom: ts.SourceFile|null, importPrefix?: string) {
|
constructor(private importRewriter: ImportRewriter, importPrefix?: string) {
|
||||||
this.imports = new ImportManager(coreImportsFrom !== null, importPrefix);
|
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) {
|
if (imports.length !== 0) {
|
||||||
dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join('') + dts;
|
dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join('') + dts;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import {ConstantPool} from '@angular/compiler';
|
import {ConstantPool} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ImportRewriter} from '../../imports';
|
||||||
import {Decorator, ReflectionHost} from '../../reflection';
|
import {Decorator, ReflectionHost} from '../../reflection';
|
||||||
import {ImportManager, translateExpression, translateStatement} from '../../translator';
|
import {ImportManager, translateExpression, translateStatement} from '../../translator';
|
||||||
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
||||||
@ -19,11 +20,11 @@ import {IvyCompilation} from './compilation';
|
|||||||
const NO_DECORATORS = new Set<ts.Decorator>();
|
const NO_DECORATORS = new Set<ts.Decorator>();
|
||||||
|
|
||||||
export function ivyTransformFactory(
|
export function ivyTransformFactory(
|
||||||
compilation: IvyCompilation, reflector: ReflectionHost,
|
compilation: IvyCompilation, reflector: ReflectionHost, importRewriter: ImportRewriter,
|
||||||
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
isCore: boolean): ts.TransformerFactory<ts.SourceFile> {
|
||||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||||
return (file: ts.SourceFile): ts.SourceFile => {
|
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(
|
function transformIvySourceFile(
|
||||||
compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost,
|
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 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.
|
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
||||||
const visitor =
|
const visitor = new IvyVisitor(compilation, reflector, importManager, isCore, constantPool);
|
||||||
new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null, constantPool);
|
|
||||||
const sf = visit(file, visitor, context);
|
const sf = visit(file, visitor, context);
|
||||||
|
|
||||||
// Generate the constant statements first, as they may involve adding additional imports
|
// 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));
|
const constants = constantPool.statements.map(stmt => translateStatement(stmt, importManager));
|
||||||
|
|
||||||
// Generate the import statements to prepend.
|
// 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(
|
return ts.createImportDeclaration(
|
||||||
undefined, undefined,
|
undefined, undefined,
|
||||||
ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))),
|
ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))),
|
||||||
|
@ -9,6 +9,7 @@ ts_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
"@ngdeps//typescript",
|
"@ngdeps//typescript",
|
||||||
],
|
],
|
||||||
|
@ -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 {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 * as ts from 'typescript';
|
||||||
|
|
||||||
import {relativePathBetween} from '../../util/src/path';
|
import {ImportRewriter, NoopImportRewriter} from '../../imports';
|
||||||
|
|
||||||
export class Context {
|
export class Context {
|
||||||
constructor(readonly isStatement: boolean) {}
|
constructor(readonly isStatement: boolean) {}
|
||||||
@ -38,60 +38,41 @@ const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
|
|||||||
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const CORE_SUPPORTED_SYMBOLS = new Map<string, string>([
|
|
||||||
['defineInjectable', 'defineInjectable'],
|
|
||||||
['defineInjector', 'defineInjector'],
|
|
||||||
['ɵdefineNgModule', 'defineNgModule'],
|
|
||||||
['inject', 'inject'],
|
|
||||||
['ɵsetClassMetadata', 'setClassMetadata'],
|
|
||||||
['ɵInjectableDef', 'InjectableDef'],
|
|
||||||
['ɵInjectorDef', 'InjectorDef'],
|
|
||||||
['ɵNgModuleDefWithMeta', 'NgModuleDefWithMeta'],
|
|
||||||
['ɵNgModuleFactory', 'NgModuleFactory'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export class ImportManager {
|
export class ImportManager {
|
||||||
private moduleToIndex = new Map<string, string>();
|
private moduleToIndex = new Map<string, string>();
|
||||||
|
private importedModules = new Set<string>();
|
||||||
private nextIndex = 0;
|
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} {
|
{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)) {
|
if (!this.moduleToIndex.has(moduleName)) {
|
||||||
this.moduleToIndex.set(moduleName, `${this.prefix}${this.nextIndex++}`);
|
this.moduleToIndex.set(moduleName, `${this.prefix}${this.nextIndex++}`);
|
||||||
}
|
}
|
||||||
|
const moduleImport = this.moduleToIndex.get(moduleName) !;
|
||||||
|
|
||||||
return {
|
return {moduleImport, symbol};
|
||||||
moduleImport: this.moduleToIndex.get(moduleName) !,
|
|
||||||
symbol: this.rewriteSymbol(moduleName, symbol)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected rewriteSymbol(moduleName: string, symbol: string): string {
|
getAllImports(contextPath: string): {name: string, as: 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}[] {
|
|
||||||
return Array.from(this.moduleToIndex.keys()).map(name => {
|
return Array.from(this.moduleToIndex.keys()).map(name => {
|
||||||
const as: string|null = this.moduleToIndex.get(name) !;
|
const as = this.moduleToIndex.get(name) !;
|
||||||
if (rewriteCoreImportsTo !== null && name === '@angular/core') {
|
name = this.rewriter.rewriteSpecifier(name, contextPath);
|
||||||
const relative = relativePathBetween(contextPath, rewriteCoreImportsTo.fileName);
|
|
||||||
if (relative === null) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to rewrite import inside core: ${contextPath} -> ${rewriteCoreImportsTo.fileName}`);
|
|
||||||
}
|
|
||||||
name = relative;
|
|
||||||
}
|
|
||||||
return {name, as};
|
return {name, as};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import {R3TargetBinder, SelectorMatcher, TmplAstNode} from '@angular/compiler';
|
import {R3TargetBinder, SelectorMatcher, TmplAstNode} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {NoopImportRewriter} from '../../imports';
|
||||||
import {ImportManager} from '../../translator';
|
import {ImportManager} from '../../translator';
|
||||||
|
|
||||||
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
|
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
|
||||||
@ -16,6 +17,7 @@ import {generateTypeCheckBlock} from './type_check_block';
|
|||||||
import {generateTypeCtor} from './type_constructor';
|
import {generateTypeCtor} from './type_constructor';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A template type checking context for a program.
|
* 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
|
// Imports may need to be added to the file to support type-checking of directives used in the
|
||||||
// template within it.
|
// 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
|
// 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
|
// 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.
|
// 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}';`)
|
.map(i => `import * as ${i.as} from '${i.name}';`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
code = imports + '\n' + code;
|
code = imports + '\n' + code;
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
* compiler writes imports to this file.
|
* compiler writes imports to this file.
|
||||||
*
|
*
|
||||||
* Only a subset of such imports are supported - core is not allowed to declare components or pipes.
|
* 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
|
* A check in ngtsc's `R3SymbolsImportRewriter` validates this condition. The rewriter is only used
|
||||||
* translating an external name (prefixed with ɵ) to the internal symbol name as exported below.
|
* 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.
|
* The below symbols are used for @Injectable and @NgModule compilation.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user