fix(compiler): do not recurse to find static symbols of same module (#35262)

To create the symbols of a module, the static symbol resolver first gets
all the symbols loaded in the module by an export statement. For `export
* from './module'`-like statements, all symbols from `./module` must be
loaded. In cases where the exporting module is actually the same module
that the export statement is in, this causes an unbounded recursive
resolution of the same module.

Exports of the same module are not needed, as their symbols will be
resolved when the symbols in the module metadata's `metadata` key is
explored.

This commit resolves the unbounded recursion by loading exporting
modules only if they differ from the module currently being resolved.

Closes https://github.com/angular/vscode-ng-language-service/issues/593

PR Close #35262
This commit is contained in:
Ayaz Hafiz 2020-02-08 20:24:56 -08:00 committed by Andrew Kushnir
parent 79659ee5aa
commit e179c5827f
2 changed files with 15 additions and 4 deletions

View File

@ -313,9 +313,9 @@ export class StaticSymbolResolver {
} }
}); });
} else { } else {
// handle the symbols via export * directives. // Handle the symbols loaded by 'export *' directives.
const resolvedModule = this.resolveModule(moduleExport.from, filePath); const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) { if (resolvedModule && resolvedModule !== filePath) {
const nestedExports = this.getSymbolsOf(resolvedModule); const nestedExports = this.getSymbolsOf(resolvedModule);
nestedExports.forEach((targetSymbol) => { nestedExports.forEach((targetSymbol) => {
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name); const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);

View File

@ -15,7 +15,6 @@ import * as ts from 'typescript';
const TS_EXT = /(^.|(?!\.d)..)\.ts$/; const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
describe('StaticSymbolResolver', () => { describe('StaticSymbolResolver', () => {
const noContext = new StaticSymbol('', '', []);
let host: StaticSymbolResolverHost; let host: StaticSymbolResolverHost;
let symbolResolver: StaticSymbolResolver; let symbolResolver: StaticSymbolResolver;
let symbolCache: StaticSymbolCache; let symbolCache: StaticSymbolCache;
@ -103,6 +102,19 @@ describe('StaticSymbolResolver', () => {
.toBe(symbolResolver.getStaticSymbol('/tmp/src/export.ts', 'exportedObj', ['someMember'])); .toBe(symbolResolver.getStaticSymbol('/tmp/src/export.ts', 'exportedObj', ['someMember']));
}); });
it('should not explore re-exports of the same module', () => {
init({
'/tmp/src/test.ts': `
export * from './test';
export const testValue = 10;
`,
});
const symbols = symbolResolver.getSymbolsOf('/tmp/src/test.ts');
expect(symbols).toEqual([symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'testValue')]);
});
it('should use summaries in resolveSymbol and prefer them over regular metadata', () => { it('should use summaries in resolveSymbol and prefer them over regular metadata', () => {
const symbolA = symbolCache.get('/test.ts', 'a'); const symbolA = symbolCache.get('/test.ts', 'a');
const symbolB = symbolCache.get('/test.ts', 'b'); const symbolB = symbolCache.get('/test.ts', 'b');
@ -140,7 +152,6 @@ describe('StaticSymbolResolver', () => {
it('should read the exported symbols of a file from the summary and ignore exports in the source', it('should read the exported symbols of a file from the summary and ignore exports in the source',
() => { () => {
const someSymbol = symbolCache.get('/test.ts', 'a');
init( init(
{'/test.ts': 'export var b = 2'}, {'/test.ts': 'export var b = 2'},
[{symbol: symbolCache.get('/test.ts', 'a'), metadata: 1}]); [{symbol: symbolCache.get('/test.ts', 'a'), metadata: 1}]);