fix(compiler): StaticReflect now resolves re-exported symbols (#10453)
Fixes: #10451
This commit is contained in:
@ -43,6 +43,7 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
}
|
||||
|
||||
private resolve(m: string, containingFile: string) {
|
||||
const resolved =
|
||||
ts.resolveModuleName(m, containingFile, this.options, this.context).resolvedModule;
|
||||
@ -72,12 +73,9 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
importedFile = this.resolveAssetUrl(importedFile, containingFile);
|
||||
containingFile = this.resolveAssetUrl(containingFile, '');
|
||||
|
||||
// TODO(tbosch): if a file does not yet exist (because we compile it later),
|
||||
// we still need to create it so that the `resolve` method works!
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists it so that the `resolve` method works!
|
||||
if (!this.compilerHost.fileExists(importedFile)) {
|
||||
if (this.options.trace) {
|
||||
console.log(`Generating empty file ${importedFile} to allow resolution of import`);
|
||||
}
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
|
||||
@ -133,11 +131,10 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
const sf = this.program.getSourceFile(filePath);
|
||||
if (!sf || !(<any>sf).symbol) {
|
||||
// The source file was not needed in the compile but we do need the values from
|
||||
// the corresponding .ts files stored in the .metadata.json file. Just assume the
|
||||
// symbol and file we resolved to be correct as we don't need this to be the
|
||||
// cannonical reference as this reference could have only been generated by a
|
||||
// .metadata.json file resolving values.
|
||||
return this.getStaticSymbol(filePath, symbolName);
|
||||
// the corresponding .ts files stored in the .metadata.json file. Check the file
|
||||
// for exports to see if the file is exported.
|
||||
return this.resolveExportedSymbol(filePath, symbolName) ||
|
||||
this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
||||
let symbol = tc.getExportsOfModule((<any>sf).symbol).find(m => m.name === symbolName);
|
||||
@ -159,6 +156,7 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
}
|
||||
|
||||
private typeCache = new Map<string, StaticSymbol>();
|
||||
private resolverCache = new Map<string, ModuleMetadata>();
|
||||
|
||||
/**
|
||||
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
|
||||
@ -200,13 +198,71 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
|
||||
readMetadata(filePath: string) {
|
||||
try {
|
||||
const result = JSON.parse(this.context.readFile(filePath));
|
||||
return result;
|
||||
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
|
||||
} catch (e) {
|
||||
console.error(`Failed to read JSON file ${filePath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private getResolverMetadata(filePath: string): ModuleMetadata {
|
||||
let metadata = this.resolverCache.get(filePath);
|
||||
if (!metadata) {
|
||||
metadata = this.getMetadataFor(filePath);
|
||||
this.resolverCache.set(filePath, metadata);
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
|
||||
const resolveModule = (moduleName: string): string => {
|
||||
const resolvedModulePath = this.resolve(moduleName, filePath);
|
||||
if (!resolvedModulePath) {
|
||||
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
|
||||
}
|
||||
return resolvedModulePath;
|
||||
};
|
||||
let metadata = this.getResolverMetadata(filePath);
|
||||
if (metadata) {
|
||||
// If we have metadata for the symbol, this is the original exporting location.
|
||||
if (metadata.metadata[symbolName]) {
|
||||
return this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
||||
// If no, try to find the symbol in one of the re-export location
|
||||
if (metadata.exports) {
|
||||
// Try and find the symbol in the list of explicitly re-exported symbols.
|
||||
for (const moduleExport of metadata.exports) {
|
||||
if (moduleExport.export) {
|
||||
const exportSymbol = moduleExport.export.find(symbol => {
|
||||
if (typeof symbol === 'string') {
|
||||
return symbol == symbolName;
|
||||
} else {
|
||||
return symbol.as == symbolName;
|
||||
}
|
||||
});
|
||||
if (exportSymbol) {
|
||||
let symName = symbolName;
|
||||
if (typeof exportSymbol !== 'string') {
|
||||
symName = exportSymbol.name;
|
||||
}
|
||||
return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find the symbol via export * directives.
|
||||
for (const moduleExport of metadata.exports) {
|
||||
if (!moduleExport.export) {
|
||||
const resolvedModule = resolveModule(moduleExport.from);
|
||||
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
|
||||
if (candidateSymbol) return candidateSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeReflectorHostContext implements ReflectorHostContext {
|
||||
|
@ -105,6 +105,34 @@ describe('reflector_host', () => {
|
||||
it('should return undefined for missing modules', () => {
|
||||
expect(reflectorHost.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to trace a named export', () => {
|
||||
const symbol =
|
||||
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
|
||||
expect(symbol.name).toEqual('One');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
||||
});
|
||||
|
||||
it('should be able to trace a renamed export', () => {
|
||||
const symbol =
|
||||
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
|
||||
expect(symbol.name).toEqual('Three');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
||||
});
|
||||
|
||||
it('should be able to trace an export * export', () => {
|
||||
const symbol =
|
||||
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
|
||||
expect(symbol.name).toEqual('Five');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
|
||||
});
|
||||
|
||||
it('should be able to trace a multi-level re-export', () => {
|
||||
const symbol =
|
||||
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
|
||||
expect(symbol.name).toEqual('Thirty');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
|
||||
});
|
||||
});
|
||||
|
||||
const dummyModule = 'export let foo: any[];';
|
||||
@ -124,6 +152,69 @@ const FILES: Entry = {
|
||||
'collections.ts': dummyModule,
|
||||
},
|
||||
'lib2': {'utils2.ts': dummyModule},
|
||||
'reexport': {
|
||||
'reexport.d.ts': `
|
||||
import * as c from '@angular/core';
|
||||
`,
|
||||
'reexport.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
exports: [
|
||||
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
|
||||
{from: './src/origin5'}, {from: './src/reexport2'}
|
||||
]
|
||||
}),
|
||||
'src': {
|
||||
'origin1.d.ts': `
|
||||
export class One {}
|
||||
export class Two {}
|
||||
export class Three {}
|
||||
`,
|
||||
'origin1.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
One: {__symbolic: 'class'},
|
||||
Two: {__symbolic: 'class'},
|
||||
Three: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'origin5.d.ts': `
|
||||
export class Five {}
|
||||
`,
|
||||
'origin5.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
Five: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'origin30.d.ts': `
|
||||
export class Thirty {}
|
||||
`,
|
||||
'origin30.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
Thirty: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'originNone.d.ts': dummyModule,
|
||||
'originNone.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
}),
|
||||
'reexport2.d.ts': dummyModule,
|
||||
'reexport2.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
||||
})
|
||||
}
|
||||
},
|
||||
'node_modules': {
|
||||
'@angular': {
|
||||
'core.d.ts': dummyModule,
|
||||
|
Reference in New Issue
Block a user