Skip to content

Commit eb71cdd

Browse files
authored
Fix progress reporter type, auto-import/symbol changes, worker thread updates, improve auto-import tooltips (#977)
1 parent 8c5415f commit eb71cdd

31 files changed

+1422
-637
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* aliasDeclarationUtils.ts
3+
* Copyright (c) Microsoft Corporation.
4+
* Licensed under the MIT license.
5+
*
6+
* Helper functions around alias declarations.
7+
*/
8+
9+
import { ImportLookup, ImportLookupResult } from './analyzerFileInfo';
10+
import { Declaration, DeclarationType } from './declaration';
11+
import { Symbol } from './symbol';
12+
13+
// If the specified declaration is an alias declaration that points to a symbol,
14+
// it resolves the alias and looks up the symbol, then returns the first declaration
15+
// associated with that symbol. It does this recursively if necessary. If a symbol
16+
// lookup fails, undefined is returned. If resolveLocalNames is true, the method
17+
// resolves aliases through local renames ("as" clauses found in import statements).
18+
export function resolveAliasDeclaration(
19+
importLookup: ImportLookup,
20+
declaration: Declaration,
21+
resolveLocalNames: boolean
22+
): Declaration | undefined {
23+
let curDeclaration: Declaration | undefined = declaration;
24+
const alreadyVisited: Declaration[] = [];
25+
26+
while (true) {
27+
if (curDeclaration.type !== DeclarationType.Alias) {
28+
return curDeclaration;
29+
}
30+
31+
if (!curDeclaration.symbolName) {
32+
return curDeclaration;
33+
}
34+
35+
// If we are not supposed to follow local alias names and this
36+
// is a local name, don't continue to follow the alias.
37+
if (!resolveLocalNames && curDeclaration.usesLocalName) {
38+
return curDeclaration;
39+
}
40+
41+
let lookupResult: ImportLookupResult | undefined;
42+
if (curDeclaration.path) {
43+
lookupResult = importLookup(curDeclaration.path);
44+
if (!lookupResult) {
45+
return undefined;
46+
}
47+
}
48+
49+
const symbol: Symbol | undefined = lookupResult
50+
? lookupResult.symbolTable.get(curDeclaration.symbolName)
51+
: undefined;
52+
if (!symbol) {
53+
if (curDeclaration.submoduleFallback) {
54+
return resolveAliasDeclaration(importLookup, curDeclaration.submoduleFallback, resolveLocalNames);
55+
}
56+
return undefined;
57+
}
58+
59+
// Prefer declarations with specified types. If we don't have any of those,
60+
// fall back on declarations with inferred types.
61+
let declarations = symbol.getTypedDeclarations();
62+
if (declarations.length === 0) {
63+
declarations = symbol.getDeclarations();
64+
65+
if (declarations.length === 0) {
66+
return undefined;
67+
}
68+
}
69+
70+
// Prefer the last declaration in the list. This ensures that
71+
// we use all of the overloads if it's an overloaded function.
72+
curDeclaration = declarations[declarations.length - 1];
73+
74+
// Make sure we don't follow a circular list indefinitely.
75+
if (alreadyVisited.find((decl) => decl === curDeclaration)) {
76+
return declaration;
77+
}
78+
alreadyVisited.push(curDeclaration);
79+
}
80+
}

server/src/analyzer/analysis.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ export function analyzeProgram(
7474
return false;
7575
}
7676

77-
const message: string =
78-
(e.stack ? e.stack.toString() : undefined) ||
79-
(typeof e.message === 'string' ? e.message : undefined) ||
80-
JSON.stringify(e);
77+
const message = debug.getErrorString(e);
8178
console.error('Error performing analysis: ' + message);
8279

8380
callback({

server/src/analyzer/backgroundAnalysisProgram.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ import { Diagnostic } from '../common/diagnostic';
1616
import { FileDiagnostics } from '../common/diagnosticSink';
1717
import { LanguageServiceExtension } from '../common/extensibility';
1818
import { Range } from '../common/textRange';
19+
import { IndexResults } from '../languageService/documentSymbolProvider';
1920
import { AnalysisCompleteCallback, analyzeProgram } from './analysis';
2021
import { ImportResolver } from './importResolver';
21-
import { MaxAnalysisTime, Program } from './program';
22+
import { Indices, MaxAnalysisTime, Program } from './program';
2223

2324
export class BackgroundAnalysisProgram {
2425
private _program: Program;
2526
private _backgroundAnalysis?: BackgroundAnalysisBase;
2627
private _onAnalysisCompletion?: AnalysisCompleteCallback;
2728
private _maxAnalysisTime?: MaxAnalysisTime;
29+
private _indices: Indices | undefined;
2830

2931
constructor(
3032
private _console: ConsoleInterface,
@@ -115,7 +117,7 @@ export class BackgroundAnalysisProgram {
115117

116118
startAnalysis(token: CancellationToken): boolean {
117119
if (this._backgroundAnalysis) {
118-
this._backgroundAnalysis.startAnalysis(token);
120+
this._backgroundAnalysis.startAnalysis(this._indices, token);
119121
return false;
120122
}
121123

@@ -129,6 +131,38 @@ export class BackgroundAnalysisProgram {
129131
);
130132
}
131133

134+
test_setIndexing(
135+
workspaceIndices: Map<string, IndexResults>,
136+
libraryIndices: Map<string, Map<string, IndexResults>>
137+
) {
138+
const indices = this._getIndices();
139+
for (const [filePath, indexResults] of workspaceIndices) {
140+
indices.setWorkspaceIndex(filePath, indexResults);
141+
}
142+
143+
for (const [execEnvRoot, map] of libraryIndices) {
144+
for (const [libraryPath, indexResults] of map) {
145+
indices.setIndex(execEnvRoot, libraryPath, indexResults);
146+
}
147+
}
148+
}
149+
150+
startIndexing() {
151+
this._backgroundAnalysis?.startIndexing(this._configOptions, this._getIndices());
152+
}
153+
154+
refreshIndexing() {
155+
this._backgroundAnalysis?.refreshIndexing(this._configOptions, this._indices);
156+
}
157+
158+
cancelIndexing() {
159+
this._backgroundAnalysis?.cancelIndexing(this._configOptions);
160+
}
161+
162+
getIndexing(filePath: string) {
163+
return this._indices?.getIndex(this._configOptions.findExecEnvironment(filePath).root);
164+
}
165+
132166
async getDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
133167
if (this._backgroundAnalysis) {
134168
return this._backgroundAnalysis.getDiagnosticsForRange(filePath, range, token);
@@ -152,6 +186,8 @@ export class BackgroundAnalysisProgram {
152186
}
153187

154188
invalidateAndForceReanalysis() {
189+
this.refreshIndexing();
190+
155191
this._backgroundAnalysis?.invalidateAndForceReanalysis();
156192

157193
// Make sure the import resolver doesn't have invalid
@@ -173,6 +209,42 @@ export class BackgroundAnalysisProgram {
173209
this._backgroundAnalysis?.restart();
174210
}
175211

212+
private _getIndices(): Indices {
213+
if (!this._indices) {
214+
const program = this._program;
215+
216+
// The map holds onto index results of library files per execution root.
217+
// The map will be refreshed together when library files are re-scanned.
218+
// It can't be cached by sourceFile since some of library files won't have
219+
// corresponding sourceFile created.
220+
const map = new Map<string, Map<string, IndexResults>>();
221+
this._indices = {
222+
setWorkspaceIndex(path: string, indexResults: IndexResults): void {
223+
// Index result of workspace file will be cached by each sourceFile
224+
// and it will go away when the source file goes away.
225+
program.getSourceFile(path)?.cacheIndexResults(indexResults);
226+
},
227+
getIndex(execEnv: string): Map<string, IndexResults> | undefined {
228+
return map.get(execEnv);
229+
},
230+
setIndex(execEnv: string, path: string, indexResults: IndexResults): void {
231+
let indicesMap = map.get(execEnv);
232+
if (!indicesMap) {
233+
indicesMap = new Map<string, IndexResults>();
234+
map.set(execEnv, indicesMap);
235+
}
236+
237+
indicesMap.set(path, indexResults);
238+
},
239+
reset(): void {
240+
map.clear();
241+
},
242+
};
243+
}
244+
245+
return this._indices!;
246+
}
247+
176248
private _reportDiagnosticsForRemovedFiles(fileDiags: FileDiagnostics[]) {
177249
if (fileDiags.length > 0) {
178250
// If analysis is running in the foreground process, report any

server/src/analyzer/binder.ts

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import { Scope, ScopeType } from './scope';
107107
import * as StaticExpressions from './staticExpressions';
108108
import { indeterminateSymbolId, Symbol, SymbolFlags } from './symbol';
109109
import { isConstantName, isPrivateOrProtectedName } from './symbolNameUtils';
110+
import { getNamesInDunderAll } from './symbolUtils';
110111

111112
export const enum NameBindingType {
112113
// With "nonlocal" keyword
@@ -1586,46 +1587,16 @@ export class Binder extends ParseTreeWalker {
15861587
}
15871588

15881589
private _getWildcardImportNames(lookupInfo: ImportLookupResult): string[] {
1589-
const namesToImport: string[] = [];
1590-
1591-
// Is there an __all__ statement? If so, it overrides the normal
1592-
// wildcard logic.
1593-
const allSymbol = lookupInfo.symbolTable.get('__all__');
1594-
if (allSymbol) {
1595-
const decls = allSymbol.getDeclarations();
1596-
1597-
// For now, we handle only the case where __all__ is defined
1598-
// through a simple assignment. Some libraries use more complex
1599-
// logic like __all__.extend(X) or __all__ += X. We'll punt on
1600-
// those for now.
1601-
if (decls.length === 1 && decls[0].type === DeclarationType.Variable) {
1602-
const firstDecl = decls[0];
1603-
if (firstDecl.node.parent && firstDecl.node.parent.nodeType === ParseNodeType.Assignment) {
1604-
const expr = firstDecl.node.parent.rightExpression;
1605-
if (expr.nodeType === ParseNodeType.List) {
1606-
expr.entries.forEach((listEntryNode) => {
1607-
if (
1608-
listEntryNode.nodeType === ParseNodeType.StringList &&
1609-
listEntryNode.strings.length === 1 &&
1610-
listEntryNode.strings[0].nodeType === ParseNodeType.String
1611-
) {
1612-
const entryName = listEntryNode.strings[0].value;
1613-
if (lookupInfo.symbolTable.get(entryName)) {
1614-
namesToImport.push(entryName);
1615-
}
1616-
}
1617-
});
1618-
1619-
return namesToImport;
1620-
}
1621-
}
1622-
}
1590+
let namesToImport = getNamesInDunderAll(lookupInfo.symbolTable);
1591+
if (namesToImport) {
1592+
return namesToImport;
16231593
}
16241594

16251595
// Import all names that don't begin with an underscore.
1596+
namesToImport = [];
16261597
lookupInfo.symbolTable.forEach((_, name) => {
16271598
if (!name.startsWith('_')) {
1628-
namesToImport.push(name);
1599+
namesToImport!.push(name);
16291600
}
16301601
});
16311602

server/src/analyzer/declarationUtils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,26 @@ export function isExplicitTypeAliasDeclaration(decl: Declaration) {
8484
export function isPossibleTypeAliasDeclaration(decl: Declaration) {
8585
return decl.type === DeclarationType.Variable && !!decl.typeAliasName;
8686
}
87+
88+
export function getNameFromDeclaration(declaration: Declaration) {
89+
switch (declaration.type) {
90+
case DeclarationType.Alias:
91+
return declaration.symbolName;
92+
93+
case DeclarationType.Class:
94+
case DeclarationType.Function:
95+
return declaration.node.name.value;
96+
97+
case DeclarationType.Parameter:
98+
return declaration.node.name?.value;
99+
100+
case DeclarationType.Variable:
101+
return declaration.node.nodeType === ParseNodeType.Name ? declaration.node.value : undefined;
102+
103+
case DeclarationType.Intrinsic:
104+
case DeclarationType.SpecialBuiltInClass:
105+
return undefined;
106+
}
107+
108+
throw new Error(`Shouldn't reach here`);
109+
}

0 commit comments

Comments
 (0)