fix(ivy): use `ReflectionHost` in `AbsoluteModuleStrategy` (#30200)
The AbsoluteModuleStrategy in ngtsc assumed that the source code is formatted as TypeScript with regards to module exports. In ngcc this is not always the case, so this commit changes `AbsoluteModuleStrategy` so that it relies upon a `ReflectionHost` to compute the exports of a module. PR Close #30200
This commit is contained in:
parent
02523debe5
commit
76391f8999
|
@ -70,7 +70,8 @@ export class DecorationAnalyzer {
|
|||
fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]);
|
||||
refEmitter = new ReferenceEmitter([
|
||||
new LocalIdentifierStrategy(),
|
||||
new AbsoluteModuleStrategy(this.program, this.typeChecker, this.options, this.host),
|
||||
new AbsoluteModuleStrategy(
|
||||
this.program, this.typeChecker, this.options, this.host, this.reflectionHost),
|
||||
// TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc
|
||||
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted import
|
||||
// based on whether a bestGuessOwningModule is present in the Reference.
|
||||
|
|
|
@ -11,11 +11,13 @@ import {ExternalReference} from '@angular/compiler/src/compiler';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {LogicalFileSystem, LogicalProjectPath} from '../../path';
|
||||
import {ReflectionHost} from '../../reflection';
|
||||
import {getSourceFile, isDeclaration, nodeNameForError, resolveModuleName} from '../../util/src/typescript';
|
||||
|
||||
import {findExportedNameOfNode} from './find_export';
|
||||
import {ImportMode, Reference} from './references';
|
||||
|
||||
|
||||
/**
|
||||
* A host which supports an operation to convert a file name into a module name.
|
||||
*
|
||||
|
@ -115,8 +117,9 @@ export class AbsoluteModuleStrategy implements ReferenceEmitStrategy {
|
|||
private moduleExportsCache = new Map<string, Map<ts.Declaration, string>|null>();
|
||||
|
||||
constructor(
|
||||
private program: ts.Program, private checker: ts.TypeChecker,
|
||||
private options: ts.CompilerOptions, private host: ts.CompilerHost) {}
|
||||
protected program: ts.Program, protected checker: ts.TypeChecker,
|
||||
protected options: ts.CompilerOptions, protected host: ts.CompilerHost,
|
||||
private reflectionHost: ReflectionHost) {}
|
||||
|
||||
emit(ref: Reference<ts.Node>, context: ts.SourceFile, importMode: ImportMode): Expression|null {
|
||||
if (ref.bestGuessOwningModule === null) {
|
||||
|
@ -159,7 +162,7 @@ export class AbsoluteModuleStrategy implements ReferenceEmitStrategy {
|
|||
return this.moduleExportsCache.get(moduleName) !;
|
||||
}
|
||||
|
||||
private enumerateExportsOfModule(specifier: string, fromFile: string):
|
||||
protected enumerateExportsOfModule(specifier: string, fromFile: string):
|
||||
Map<ts.Declaration, string>|null {
|
||||
// First, resolve the module specifier to its entry point, and get the ts.Symbol for it.
|
||||
const resolvedModule = resolveModuleName(specifier, fromFile, this.options, this.host);
|
||||
|
@ -172,34 +175,12 @@ export class AbsoluteModuleStrategy implements ReferenceEmitStrategy {
|
|||
return null;
|
||||
}
|
||||
|
||||
const entryPointSymbol = this.checker.getSymbolAtLocation(entryPointFile);
|
||||
if (entryPointSymbol === undefined) {
|
||||
const exports = this.reflectionHost.getExportsOfModule(entryPointFile);
|
||||
if (exports === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Next, build a Map of all the ts.Declarations exported via the specifier and their exported
|
||||
// names.
|
||||
const exportMap = new Map<ts.Declaration, string>();
|
||||
|
||||
const exports = this.checker.getExportsOfModule(entryPointSymbol);
|
||||
for (const expSymbol of exports) {
|
||||
// Resolve export symbols to their actual declarations.
|
||||
const declSymbol = expSymbol.flags & ts.SymbolFlags.Alias ?
|
||||
this.checker.getAliasedSymbol(expSymbol) :
|
||||
expSymbol;
|
||||
|
||||
// At this point the valueDeclaration of the symbol should be defined.
|
||||
const decl = declSymbol.valueDeclaration;
|
||||
if (decl === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prefer importing the symbol via its declared name, but take any export of it otherwise.
|
||||
if (declSymbol.name === expSymbol.name || !exportMap.has(decl)) {
|
||||
exportMap.set(decl, expSymbol.name);
|
||||
}
|
||||
}
|
||||
|
||||
exports.forEach((declaration, name) => { exportMap.set(declaration.node, name); });
|
||||
return exportMap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -438,7 +438,8 @@ export class NgtscProgram implements api.Program {
|
|||
// First, try to use local identifiers if available.
|
||||
new LocalIdentifierStrategy(),
|
||||
// Next, attempt to use an absolute import.
|
||||
new AbsoluteModuleStrategy(this.tsProgram, checker, this.options, this.host),
|
||||
new AbsoluteModuleStrategy(
|
||||
this.tsProgram, checker, this.options, this.host, this.reflector),
|
||||
// Finally, check if the reference is being written into a file within the project's logical
|
||||
// file system, and use a relative import if so. If this fails, ReferenceEmitter will throw
|
||||
// an error.
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {AbsoluteFsPath, LogicalFileSystem} from '../../path';
|
||||
import {isNamedClassDeclaration} from '../../reflection';
|
||||
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
import {TypeCheckingConfig} from '../src/api';
|
||||
|
@ -54,7 +54,8 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
|
||||
const emitter = new ReferenceEmitter([
|
||||
new LocalIdentifierStrategy(),
|
||||
new AbsoluteModuleStrategy(program, checker, options, host),
|
||||
new AbsoluteModuleStrategy(
|
||||
program, checker, options, host, new TypeScriptReflectionHost(checker)),
|
||||
new LogicalProjectStrategy(checker, logicalFs),
|
||||
]);
|
||||
const ctx = new TypeCheckContext(
|
||||
|
@ -84,7 +85,8 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
|
||||
const emitter = new ReferenceEmitter([
|
||||
new LocalIdentifierStrategy(),
|
||||
new AbsoluteModuleStrategy(program, checker, options, host),
|
||||
new AbsoluteModuleStrategy(
|
||||
program, checker, options, host, new TypeScriptReflectionHost(checker)),
|
||||
new LogicalProjectStrategy(checker, logicalFs),
|
||||
]);
|
||||
const ctx = new TypeCheckContext(
|
||||
|
|
Loading…
Reference in New Issue