Streaming references/symbols, analysis error, indexing changes (#1093)

This commit is contained in:
Jake Bailey 2020-10-07 11:13:32 -07:00 committed by GitHub
parent 089c9d362a
commit 1e360c99b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 415 additions and 216 deletions

View File

@ -26,11 +26,15 @@ jobs:
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
# Don't cache on Windows; the cache ends up being very large and
# the Windows implementation of the cache task uses a much slower archiver.
- name: Get npm cache directory - name: Get npm cache directory
if: runner.os != 'Windows'
id: npm-cache id: npm-cache
run: | run: |
echo "::set-output name=dir::$(npm config get cache)" echo "::set-output name=dir::$(npm config get cache)"
- uses: actions/cache@v2 - uses: actions/cache@v2
if: runner.os != 'Windows'
with: with:
path: ${{ steps.npm-cache.outputs.dir }} path: ${{ steps.npm-cache.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

View File

@ -28,6 +28,7 @@ export interface AnalysisResults {
fatalErrorOccurred: boolean; fatalErrorOccurred: boolean;
configParseErrorOccurred: boolean; configParseErrorOccurred: boolean;
elapsedTime: number; elapsedTime: number;
error?: Error;
} }
export type AnalysisCompleteCallback = (results: AnalysisResults) => void; export type AnalysisCompleteCallback = (results: AnalysisResults) => void;
@ -85,6 +86,7 @@ export function analyzeProgram(
fatalErrorOccurred: true, fatalErrorOccurred: true,
configParseErrorOccurred: false, configParseErrorOccurred: false,
elapsedTime: 0, elapsedTime: 0,
error: debug.getSerializableError(e),
}); });
} }

View File

@ -28,7 +28,7 @@ import {
stripFileExtension, stripFileExtension,
stripTrailingDirectorySeparator, stripTrailingDirectorySeparator,
} from '../common/pathUtils'; } from '../common/pathUtils';
import { versionToString } from '../common/pythonVersion'; import { getPythonVersionStrings } from '../common/pythonVersion';
import { equateStringsCaseInsensitive } from '../common/stringUtils'; import { equateStringsCaseInsensitive } from '../common/stringUtils';
import * as StringUtils from '../common/stringUtils'; import * as StringUtils from '../common/stringUtils';
import { isIdentifierChar, isIdentifierStartChar } from '../parser/characters'; import { isIdentifierChar, isIdentifierStartChar } from '../parser/characters';
@ -396,17 +396,16 @@ export class ImportResolver {
const importFailureInfo: string[] = []; const importFailureInfo: string[] = [];
const roots = []; const roots = [];
const pythonVersion = execEnv.pythonVersion; const versionFolders = getPythonVersionStrings(execEnv.pythonVersion);
const minorVersion = pythonVersion & 0xff;
const versionFolders = ['2and3', '3'];
if (minorVersion > 0) {
versionFolders.push(versionToString(0x300 + minorVersion));
}
const stdTypeshed = this._getTypeshedPath(true, execEnv, importFailureInfo); const stdTypeshed = this._getTypeshedPath(true, execEnv, importFailureInfo);
if (stdTypeshed) { if (stdTypeshed) {
if (useTypeshedVersionedFolders) { if (useTypeshedVersionedFolders) {
roots.push(...versionFolders.map((vf) => combinePaths(stdTypeshed, vf))); for (const version of versionFolders) {
const path = combinePaths(stdTypeshed, version);
if (this.fileSystem.existsSync(path)) {
roots.push(path);
}
}
} else { } else {
roots.push(stdTypeshed); roots.push(stdTypeshed);
} }
@ -422,7 +421,12 @@ export class ImportResolver {
const typeshedPath = this._getTypeshedPath(false, execEnv, importFailureInfo); const typeshedPath = this._getTypeshedPath(false, execEnv, importFailureInfo);
if (typeshedPath) { if (typeshedPath) {
if (useTypeshedVersionedFolders) { if (useTypeshedVersionedFolders) {
roots.push(...versionFolders.map((vf) => combinePaths(typeshedPath, vf))); for (const version of versionFolders) {
const path = combinePaths(typeshedPath, version);
if (this.fileSystem.existsSync(path)) {
roots.push(path);
}
}
} else { } else {
roots.push(typeshedPath); roots.push(typeshedPath);
} }
@ -976,13 +980,7 @@ export class ImportResolver {
return undefined; return undefined;
} }
const pythonVersion = execEnv.pythonVersion; for (const pythonVersionString of getPythonVersionStrings(execEnv.pythonVersion)) {
let minorVersion = pythonVersion & 0xff;
// Search for module starting at "3.x" down to "3.1", then "3", then "2and3".
while (true) {
const pythonVersionString =
minorVersion > 0 ? versionToString(0x300 + minorVersion) : minorVersion === 0 ? '3' : '2and3';
const testPath = combinePaths(typeshedPath, pythonVersionString); const testPath = combinePaths(typeshedPath, pythonVersionString);
if (this.fileSystem.existsSync(testPath)) { if (this.fileSystem.existsSync(testPath)) {
const importInfo = this.resolveAbsoluteImport( const importInfo = this.resolveAbsoluteImport(
@ -996,12 +994,6 @@ export class ImportResolver {
return importInfo; return importInfo;
} }
} }
// We use -1 to indicate "2and3", which is searched after "3.0".
if (minorVersion === -1) {
break;
}
minorVersion--;
} }
importFailureInfo.push(`Typeshed path not found`); importFailureInfo.push(`Typeshed path not found`);
@ -1021,23 +1013,11 @@ export class ImportResolver {
return; return;
} }
const pythonVersion = execEnv.pythonVersion; for (const pythonVersionString of getPythonVersionStrings(execEnv.pythonVersion)) {
let minorVersion = pythonVersion & 0xff;
// Search for module starting at "3.x" down to "3.1", then "3", then "2and3".
while (true) {
const pythonVersionString =
minorVersion > 0 ? versionToString(0x300 + minorVersion) : minorVersion === 0 ? '3' : '2and3';
const testPath = combinePaths(typeshedPath, pythonVersionString); const testPath = combinePaths(typeshedPath, pythonVersionString);
if (this.fileSystem.existsSync(testPath)) { if (this.fileSystem.existsSync(testPath)) {
this._getCompletionSuggestionsAbsolute(testPath, moduleDescriptor, suggestions, similarityLimit); this._getCompletionSuggestionsAbsolute(testPath, moduleDescriptor, suggestions, similarityLimit);
} }
// We use -1 to indicate "2and3", which is searched after "3.0".
if (minorVersion === -1) {
break;
}
minorVersion--;
} }
} }

View File

@ -47,9 +47,9 @@ import {
} from '../languageService/autoImporter'; } from '../languageService/autoImporter';
import { CallHierarchyProvider } from '../languageService/callHierarchyProvider'; import { CallHierarchyProvider } from '../languageService/callHierarchyProvider';
import { CompletionResults } from '../languageService/completionProvider'; import { CompletionResults } from '../languageService/completionProvider';
import { IndexResults } from '../languageService/documentSymbolProvider'; import { IndexOptions, IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
import { HoverResults } from '../languageService/hoverProvider'; import { HoverResults } from '../languageService/hoverProvider';
import { ReferencesResult } from '../languageService/referencesProvider'; import { ReferenceCallback, ReferencesResult } from '../languageService/referencesProvider';
import { SignatureHelpResults } from '../languageService/signatureHelpProvider'; import { SignatureHelpResults } from '../languageService/signatureHelpProvider';
import { ImportLookupResult } from './analyzerFileInfo'; import { ImportLookupResult } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo'; import * as AnalyzerNodeInfo from './analyzerNodeInfo';
@ -142,10 +142,10 @@ export class Program {
initialConfigOptions: ConfigOptions, initialConfigOptions: ConfigOptions,
console?: ConsoleInterface, console?: ConsoleInterface,
private _extension?: LanguageServiceExtension, private _extension?: LanguageServiceExtension,
logPrefix = 'FG' logTracker?: LogTracker
) { ) {
this._console = console || new StandardConsole(); this._console = console || new StandardConsole();
this._logTracker = new LogTracker(console, logPrefix); this._logTracker = logTracker ?? new LogTracker(console, 'FG');
this._importResolver = initialImportResolver; this._importResolver = initialImportResolver;
this._configOptions = initialConfigOptions; this._configOptions = initialConfigOptions;
this._createNewEvaluator(); this._createNewEvaluator();
@ -436,19 +436,24 @@ export class Program {
return; return;
} }
let count = 0;
return this._runEvaluatorWithCancellationToken(token, () => { return this._runEvaluatorWithCancellationToken(token, () => {
// Go through all workspace files to create indexing data. // Go through all workspace files to create indexing data.
// This will cause all files in the workspace to be parsed and bound. We might // This will cause all files in the workspace to be parsed and bound. But
// need to drop some of those parse tree and binding info once indexing is done // _handleMemoryHighUsage will make sure we don't OOM
// if it didn't exist before.
for (const sourceFileInfo of this._sourceFileList) { for (const sourceFileInfo of this._sourceFileList) {
if (!this._isUserCode(sourceFileInfo)) { if (!this._isUserCode(sourceFileInfo)) {
continue; continue;
} }
this._bindFile(sourceFileInfo); this._bindFile(sourceFileInfo);
const results = sourceFileInfo.sourceFile.index(false, token); const results = sourceFileInfo.sourceFile.index({ indexingForAutoImportMode: false }, token);
if (results) { if (results) {
if (++count > 2000) {
this._console.warn(`Workspace indexing has hit its upper limit: 2000 files`);
return;
}
callback(sourceFileInfo.sourceFile.getFilePath(), results); callback(sourceFileInfo.sourceFile.getFilePath(), results);
} }
@ -658,12 +663,12 @@ export class Program {
return this._evaluator; return this._evaluator;
} }
private _parseFile(fileToParse: SourceFileInfo) { private _parseFile(fileToParse: SourceFileInfo, content?: string) {
if (!this._isFileNeeded(fileToParse) || !fileToParse.sourceFile.isParseRequired()) { if (!this._isFileNeeded(fileToParse) || !fileToParse.sourceFile.isParseRequired()) {
return; return;
} }
if (fileToParse.sourceFile.parse(this._configOptions, this._importResolver)) { if (fileToParse.sourceFile.parse(this._configOptions, this._importResolver, content)) {
this._parsedFileCount++; this._parsedFileCount++;
this._updateSourceFileImports(fileToParse, this._configOptions); this._updateSourceFileImports(fileToParse, this._configOptions);
} }
@ -683,12 +688,12 @@ export class Program {
// Binds the specified file and all of its dependencies, recursively. If // Binds the specified file and all of its dependencies, recursively. If
// it runs out of time, it returns true. If it completes, it returns false. // it runs out of time, it returns true. If it completes, it returns false.
private _bindFile(fileToAnalyze: SourceFileInfo): void { private _bindFile(fileToAnalyze: SourceFileInfo, content?: string): void {
if (!this._isFileNeeded(fileToAnalyze) || !fileToAnalyze.sourceFile.isBindingRequired()) { if (!this._isFileNeeded(fileToAnalyze) || !fileToAnalyze.sourceFile.isBindingRequired()) {
return; return;
} }
this._parseFile(fileToAnalyze); this._parseFile(fileToAnalyze, content);
// We need to parse and bind the builtins import first. // We need to parse and bind the builtins import first.
let builtinsScope: Scope | undefined; let builtinsScope: Scope | undefined;
@ -737,9 +742,16 @@ export class Program {
// Build a map of all modules within this program and the module- // Build a map of all modules within this program and the module-
// level scope that contains the symbol table for the module. // level scope that contains the symbol table for the module.
private _buildModuleSymbolsMap(sourceFileToExclude: SourceFileInfo, token: CancellationToken): ModuleSymbolMap { private _buildModuleSymbolsMap(
sourceFileToExclude: SourceFileInfo,
userFileOnly: boolean,
token: CancellationToken
): ModuleSymbolMap {
// If we have library map, always use the map for library symbols.
return buildModuleSymbolsMap( return buildModuleSymbolsMap(
this._sourceFileList.filter((s) => s !== sourceFileToExclude), this._sourceFileList.filter(
(s) => s !== sourceFileToExclude && (userFileOnly ? this._isUserCode(s) : true)
),
token token
); );
} }
@ -972,9 +984,9 @@ export class Program {
} }
const writtenWord = fileContents.substr(textRange.start, textRange.length); const writtenWord = fileContents.substr(textRange.start, textRange.length);
const map = this._buildModuleSymbolsMap(sourceFileInfo, token); const map = this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token);
const autoImporter = new AutoImporter( const autoImporter = new AutoImporter(
this._configOptions, this._configOptions.findExecEnvironment(filePath),
this._importResolver, this._importResolver,
parseTree, parseTree,
range.start, range.start,
@ -1084,16 +1096,17 @@ export class Program {
}); });
} }
getReferencesForPosition( reportReferencesForPosition(
filePath: string, filePath: string,
position: Position, position: Position,
includeDeclaration: boolean, includeDeclaration: boolean,
reporter: ReferenceCallback,
token: CancellationToken token: CancellationToken
): DocumentRange[] | undefined { ) {
return this._runEvaluatorWithCancellationToken(token, () => { this._runEvaluatorWithCancellationToken(token, () => {
const sourceFileInfo = this._getSourceFileInfoFromPath(filePath); const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
if (!sourceFileInfo) { if (!sourceFileInfo) {
return undefined; return;
} }
const invokedFromUserFile = this._isUserCode(sourceFileInfo); const invokedFromUserFile = this._isUserCode(sourceFileInfo);
@ -1104,11 +1117,12 @@ export class Program {
this._createSourceMapper(execEnv), this._createSourceMapper(execEnv),
position, position,
this._evaluator!, this._evaluator!,
reporter,
token token
); );
if (!referencesResult) { if (!referencesResult) {
return undefined; return;
} }
// Do we need to do a global search as well? // Do we need to do a global search as well?
@ -1155,19 +1169,18 @@ export class Program {
continue; continue;
} }
const tempResult: ReferencesResult = { const tempResult = new ReferencesResult(
requiresGlobalSearch: referencesResult.requiresGlobalSearch, referencesResult.requiresGlobalSearch,
nodeAtOffset: referencesResult.nodeAtOffset, referencesResult.nodeAtOffset,
symbolName: referencesResult.symbolName, referencesResult.symbolName,
declarations: referencesResult.declarations, referencesResult.declarations
locations: [], );
};
declFileInfo.sourceFile.addReferences(tempResult, includeDeclaration, this._evaluator!, token); declFileInfo.sourceFile.addReferences(tempResult, includeDeclaration, this._evaluator!, token);
for (const loc of tempResult.locations) { for (const loc of tempResult.locations) {
// Include declarations only. And throw away any references // Include declarations only. And throw away any references
if (loc.path === decl.path && doesRangeContain(decl.range, loc.range)) { if (loc.path === decl.path && doesRangeContain(decl.range, loc.range)) {
referencesResult.locations.push(loc); referencesResult.addLocations(loc);
} }
} }
} }
@ -1175,13 +1188,11 @@ export class Program {
} else { } else {
sourceFileInfo.sourceFile.addReferences(referencesResult, includeDeclaration, this._evaluator!, token); sourceFileInfo.sourceFile.addReferences(referencesResult, includeDeclaration, this._evaluator!, token);
} }
return referencesResult.locations;
}); });
} }
getFileIndex(filePath: string, importSymbolsOnly: boolean, token: CancellationToken): IndexResults | undefined { getFileIndex(filePath: string, options: IndexOptions, token: CancellationToken): IndexResults | undefined {
if (importSymbolsOnly) { if (options.indexingForAutoImportMode) {
// Memory optimization. We only want to hold onto symbols // Memory optimization. We only want to hold onto symbols
// usable outside when importSymbolsOnly is on. // usable outside when importSymbolsOnly is on.
const name = stripFileExtension(getFileName(filePath)); const name = stripFileExtension(getFileName(filePath));
@ -1198,8 +1209,26 @@ export class Program {
return undefined; return undefined;
} }
this._bindFile(sourceFileInfo); let content: string | undefined = undefined;
return sourceFileInfo.sourceFile.index(importSymbolsOnly, token); if (
options.indexingForAutoImportMode &&
!sourceFileInfo.sourceFile.isStubFile() &&
sourceFileInfo.sourceFile.getClientVersion() === null
) {
try {
// Perf optimization. if py file doesn't contain __all__
// No need to parse and bind.
content = this._fs.readFileSync(filePath, 'utf8');
if (content.indexOf('__all__') < 0) {
return undefined;
}
} catch (error) {
content = undefined;
}
}
this._bindFile(sourceFileInfo, content);
return sourceFileInfo.sourceFile.index(options, token);
}); });
} }
@ -1217,8 +1246,8 @@ export class Program {
}); });
} }
addSymbolsForWorkspace(symbolList: SymbolInformation[], query: string, token: CancellationToken) { reportSymbolsForWorkspace(query: string, reporter: WorkspaceSymbolCallback, token: CancellationToken) {
return this._runEvaluatorWithCancellationToken(token, () => { this._runEvaluatorWithCancellationToken(token, () => {
// Don't do a search if the query is empty. We'll return // Don't do a search if the query is empty. We'll return
// too many results in this case. // too many results in this case.
if (!query) { if (!query) {
@ -1236,7 +1265,10 @@ export class Program {
this._bindFile(sourceFileInfo); this._bindFile(sourceFileInfo);
} }
sourceFileInfo.sourceFile.addSymbolsForDocument(symbolList, query, token); const symbolList = sourceFileInfo.sourceFile.getSymbolsForDocument(query, token);
if (symbolList.length > 0) {
reporter(symbolList);
}
// This operation can consume significant memory, so check // This operation can consume significant memory, so check
// for situations where we need to discard the type cache. // for situations where we need to discard the type cache.
@ -1334,7 +1366,7 @@ export class Program {
this._evaluator!, this._evaluator!,
this._createSourceMapper(execEnv, /* mapCompiled */ true), this._createSourceMapper(execEnv, /* mapCompiled */ true),
libraryMap, libraryMap,
() => this._buildModuleSymbolsMap(sourceFileInfo, token), () => this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token),
token token
); );
}); });
@ -1384,7 +1416,7 @@ export class Program {
this._evaluator!, this._evaluator!,
this._createSourceMapper(execEnv, /* mapCompiled */ true), this._createSourceMapper(execEnv, /* mapCompiled */ true),
libraryMap, libraryMap,
() => this._buildModuleSymbolsMap(sourceFileInfo, token), () => this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token),
completionItem, completionItem,
token token
); );
@ -1410,6 +1442,7 @@ export class Program {
this._createSourceMapper(execEnv), this._createSourceMapper(execEnv),
position, position,
this._evaluator!, this._evaluator!,
undefined,
token token
); );
@ -1472,6 +1505,7 @@ export class Program {
this._createSourceMapper(execEnv), this._createSourceMapper(execEnv),
position, position,
this._evaluator!, this._evaluator!,
undefined,
token token
); );
@ -1508,6 +1542,7 @@ export class Program {
this._createSourceMapper(execEnv), this._createSourceMapper(execEnv),
position, position,
this._evaluator!, this._evaluator!,
undefined,
token token
); );
@ -1563,6 +1598,7 @@ export class Program {
this._createSourceMapper(execEnv), this._createSourceMapper(execEnv),
position, position,
this._evaluator!, this._evaluator!,
undefined,
token token
); );

View File

@ -46,8 +46,9 @@ import {
import { DocumentRange, Position, Range } from '../common/textRange'; import { DocumentRange, Position, Range } from '../common/textRange';
import { timingStats } from '../common/timing'; import { timingStats } from '../common/timing';
import { CompletionResults } from '../languageService/completionProvider'; import { CompletionResults } from '../languageService/completionProvider';
import { IndexResults } from '../languageService/documentSymbolProvider'; import { IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
import { HoverResults } from '../languageService/hoverProvider'; import { HoverResults } from '../languageService/hoverProvider';
import { ReferenceCallback } from '../languageService/referencesProvider';
import { SignatureHelpResults } from '../languageService/signatureHelpProvider'; import { SignatureHelpResults } from '../languageService/signatureHelpProvider';
import { AnalysisCompleteCallback } from './analysis'; import { AnalysisCompleteCallback } from './analysis';
import { BackgroundAnalysisProgram, BackgroundAnalysisProgramFactory } from './backgroundAnalysisProgram'; import { BackgroundAnalysisProgram, BackgroundAnalysisProgramFactory } from './backgroundAnalysisProgram';
@ -244,21 +245,22 @@ export class AnalyzerService {
return this._program.getDefinitionsForPosition(filePath, position, token); return this._program.getDefinitionsForPosition(filePath, position, token);
} }
getReferencesForPosition( reportReferencesForPosition(
filePath: string, filePath: string,
position: Position, position: Position,
includeDeclaration: boolean, includeDeclaration: boolean,
reporter: ReferenceCallback,
token: CancellationToken token: CancellationToken
): DocumentRange[] | undefined { ) {
return this._program.getReferencesForPosition(filePath, position, includeDeclaration, token); this._program.reportReferencesForPosition(filePath, position, includeDeclaration, reporter, token);
} }
addSymbolsForDocument(filePath: string, symbolList: DocumentSymbol[], token: CancellationToken) { addSymbolsForDocument(filePath: string, symbolList: DocumentSymbol[], token: CancellationToken) {
this._program.addSymbolsForDocument(filePath, symbolList, token); this._program.addSymbolsForDocument(filePath, symbolList, token);
} }
addSymbolsForWorkspace(symbolList: SymbolInformation[], query: string, token: CancellationToken) { reportSymbolsForWorkspace(query: string, reporter: WorkspaceSymbolCallback, token: CancellationToken) {
this._program.addSymbolsForWorkspace(symbolList, query, token); this._program.reportSymbolsForWorkspace(query, reporter, token);
} }
getHoverForPosition(filePath: string, position: Position, token: CancellationToken): HoverResults | undefined { getHoverForPosition(filePath: string, position: Position, token: CancellationToken): HoverResults | undefined {

View File

@ -36,10 +36,10 @@ import { CompletionResults } from '../languageService/completionProvider';
import { CompletionItemData, CompletionProvider } from '../languageService/completionProvider'; import { CompletionItemData, CompletionProvider } from '../languageService/completionProvider';
import { DefinitionProvider } from '../languageService/definitionProvider'; import { DefinitionProvider } from '../languageService/definitionProvider';
import { DocumentHighlightProvider } from '../languageService/documentHighlightProvider'; import { DocumentHighlightProvider } from '../languageService/documentHighlightProvider';
import { DocumentSymbolProvider, IndexResults } from '../languageService/documentSymbolProvider'; import { DocumentSymbolProvider, IndexOptions, IndexResults } from '../languageService/documentSymbolProvider';
import { HoverProvider, HoverResults } from '../languageService/hoverProvider'; import { HoverProvider, HoverResults } from '../languageService/hoverProvider';
import { performQuickAction } from '../languageService/quickActions'; import { performQuickAction } from '../languageService/quickActions';
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider'; import { ReferenceCallback, ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
import { SignatureHelpProvider, SignatureHelpResults } from '../languageService/signatureHelpProvider'; import { SignatureHelpProvider, SignatureHelpResults } from '../languageService/signatureHelpProvider';
import { Localizer } from '../localization/localize'; import { Localizer } from '../localization/localize';
import { ModuleNode } from '../parser/parseNodes'; import { ModuleNode } from '../parser/parseNodes';
@ -508,7 +508,7 @@ export class SourceFile {
// Parse the file and update the state. Callers should wait for completion // Parse the file and update the state. Callers should wait for completion
// (or at least cancel) prior to calling again. It returns true if a parse // (or at least cancel) prior to calling again. It returns true if a parse
// was required and false if the parse information was up to date already. // was required and false if the parse information was up to date already.
parse(configOptions: ConfigOptions, importResolver: ImportResolver): boolean { parse(configOptions: ConfigOptions, importResolver: ImportResolver, content?: string): boolean {
return this._logTracker.log(`parsing: ${this._filePath}`, (logState) => { return this._logTracker.log(`parsing: ${this._filePath}`, (logState) => {
// If the file is already parsed, we can skip. // If the file is already parsed, we can skip.
if (!this.isParseRequired()) { if (!this.isParseRequired()) {
@ -520,14 +520,15 @@ export class SourceFile {
let fileContents = this._fileContents; let fileContents = this._fileContents;
if (this._clientVersion === null) { if (this._clientVersion === null) {
try { try {
timingStats.readFileTime.timeOperation(() => { const elapsedTime = timingStats.readFileTime.timeOperation(() => {
// Read the file's contents. // Read the file's contents.
fileContents = this.fileSystem.readFileSync(this._filePath, 'utf8'); fileContents = content ?? this.fileSystem.readFileSync(this._filePath, 'utf8');
// Remember the length and hash for comparison purposes. // Remember the length and hash for comparison purposes.
this._lastFileContentLength = fileContents.length; this._lastFileContentLength = fileContents.length;
this._lastFileContentHash = StringUtils.hashString(fileContents); this._lastFileContentHash = StringUtils.hashString(fileContents);
}); });
logState.add(`fs read ${elapsedTime}ms`);
} catch (error) { } catch (error) {
diagSink.addError(`Source file could not be read`, getEmptyRange()); diagSink.addError(`Source file could not be read`, getEmptyRange());
fileContents = ''; fileContents = '';
@ -634,18 +635,22 @@ export class SourceFile {
}); });
} }
index(importSymbolsOnly: boolean, token: CancellationToken): IndexResults | undefined { index(options: IndexOptions, token: CancellationToken): IndexResults | undefined {
return this._logTracker.log(`indexing: ${this._filePath}`, (ls) => {
// If we have no completed analysis job, there's nothing to do. // If we have no completed analysis job, there's nothing to do.
if (!this._parseResults || !this.isIndexingRequired()) { if (!this._parseResults || !this.isIndexingRequired()) {
ls.suppress();
return undefined; return undefined;
} }
this._indexingNeeded = false; this._indexingNeeded = false;
const symbols = DocumentSymbolProvider.indexSymbols(this._parseResults, importSymbolsOnly, token); const symbols = DocumentSymbolProvider.indexSymbols(this._parseResults, options, token);
ls.add(`found ${symbols.length}`);
const name = stripFileExtension(getFileName(this._filePath)); const name = stripFileExtension(getFileName(this._filePath));
const privateOrProtected = SymbolNameUtils.isPrivateOrProtectedName(name); const privateOrProtected = SymbolNameUtils.isPrivateOrProtectedName(name);
return { privateOrProtected, symbols }; return { privateOrProtected, symbols };
});
} }
getDefinitionsForPosition( getDefinitionsForPosition(
@ -672,6 +677,7 @@ export class SourceFile {
sourceMapper: SourceMapper, sourceMapper: SourceMapper,
position: Position, position: Position,
evaluator: TypeEvaluator, evaluator: TypeEvaluator,
reporter: ReferenceCallback | undefined,
token: CancellationToken token: CancellationToken
): ReferencesResult | undefined { ): ReferencesResult | undefined {
// If we have no completed analysis job, there's nothing to do. // If we have no completed analysis job, there's nothing to do.
@ -685,6 +691,7 @@ export class SourceFile {
this._filePath, this._filePath,
position, position,
evaluator, evaluator,
reporter,
token token
); );
} }
@ -724,18 +731,17 @@ export class SourceFile {
); );
} }
addSymbolsForDocument(symbolList: SymbolInformation[], query: string, token: CancellationToken) { getSymbolsForDocument(query: string, token: CancellationToken) {
// If we have no completed analysis job, there's nothing to do. // If we have no completed analysis job, there's nothing to do.
if (!this._parseResults && !this._cachedIndexResults) { if (!this._parseResults && !this._cachedIndexResults) {
return; return [];
} }
DocumentSymbolProvider.addSymbolsForDocument( return DocumentSymbolProvider.getSymbolsForDocument(
this.getCachedIndexResults(), this.getCachedIndexResults(),
this._parseResults, this._parseResults,
this._filePath, this._filePath,
query, query,
symbolList,
token token
); );
} }

View File

@ -8,7 +8,7 @@
* (parse node trees). * (parse node trees).
*/ */
import { ExecutionEnvironment } from '../common/configOptions'; import { ExecutionEnvironment, PythonPlatform } from '../common/configOptions';
import { ExpressionNode, NumberNode, ParseNodeType, TupleNode } from '../parser/parseNodes'; import { ExpressionNode, NumberNode, ParseNodeType, TupleNode } from '../parser/parseNodes';
import { KeywordType, OperatorType } from '../parser/tokenizerTypes'; import { KeywordType, OperatorType } from '../parser/tokenizerTypes';
@ -220,11 +220,11 @@ function _isOsNameInfoExpression(node: ExpressionNode): boolean {
} }
function _getExpectedPlatformNameFromPlatform(execEnv: ExecutionEnvironment): string | undefined { function _getExpectedPlatformNameFromPlatform(execEnv: ExecutionEnvironment): string | undefined {
if (execEnv.pythonPlatform === 'Darwin') { if (execEnv.pythonPlatform === PythonPlatform.Darwin) {
return 'darwin'; return 'darwin';
} else if (execEnv.pythonPlatform === 'Windows') { } else if (execEnv.pythonPlatform === PythonPlatform.Windows) {
return 'win32'; return 'win32';
} else if (execEnv.pythonPlatform === 'Linux') { } else if (execEnv.pythonPlatform === PythonPlatform.Linux) {
return 'linux'; return 'linux';
} }
@ -232,11 +232,11 @@ function _getExpectedPlatformNameFromPlatform(execEnv: ExecutionEnvironment): st
} }
function _getExpectedOsNameFromPlatform(execEnv: ExecutionEnvironment): string | undefined { function _getExpectedOsNameFromPlatform(execEnv: ExecutionEnvironment): string | undefined {
if (execEnv.pythonPlatform === 'Darwin') { if (execEnv.pythonPlatform === PythonPlatform.Darwin) {
return 'posix'; return 'posix';
} else if (execEnv.pythonPlatform === 'Windows') { } else if (execEnv.pythonPlatform === PythonPlatform.Windows) {
return 'nt'; return 'nt';
} else if (execEnv.pythonPlatform === 'Linux') { } else if (execEnv.pythonPlatform === PythonPlatform.Linux) {
return 'posix'; return 'posix';
} }

View File

@ -32,6 +32,7 @@ import { Diagnostic } from './common/diagnostic';
import { FileDiagnostics } from './common/diagnosticSink'; import { FileDiagnostics } from './common/diagnosticSink';
import { LanguageServiceExtension } from './common/extensibility'; import { LanguageServiceExtension } from './common/extensibility';
import { FileSystem } from './common/fileSystem'; import { FileSystem } from './common/fileSystem';
import { LogTracker } from './common/logTracker';
import { Range } from './common/textRange'; import { Range } from './common/textRange';
import { IndexResults } from './languageService/documentSymbolProvider'; import { IndexResults } from './languageService/documentSymbolProvider';
@ -252,12 +253,13 @@ export class BackgroundAnalysisRunnerBase extends BackgroundThreadBase {
this._configOptions = new ConfigOptions(data.rootDirectory); this._configOptions = new ConfigOptions(data.rootDirectory);
this._importResolver = this.createImportResolver(this.fs, this._configOptions); this._importResolver = this.createImportResolver(this.fs, this._configOptions);
const console = this.getConsole();
this._program = new Program( this._program = new Program(
this._importResolver, this._importResolver,
this._configOptions, this._configOptions,
this.getConsole(), console,
this._extension, this._extension,
`BG(${threadId})` new LogTracker(console, `BG(${threadId})`)
); );
} }

View File

@ -31,6 +31,12 @@ import {
versionToString, versionToString,
} from './pythonVersion'; } from './pythonVersion';
export enum PythonPlatform {
Darwin = 'Darwin',
Windows = 'Windows',
Linux = 'Linux',
}
export class ExecutionEnvironment { export class ExecutionEnvironment {
// Default to "." which indicates every file in the project. // Default to "." which indicates every file in the project.
constructor(root: string, defaultPythonVersion?: PythonVersion, defaultPythonPlatform?: string) { constructor(root: string, defaultPythonVersion?: PythonVersion, defaultPythonPlatform?: string) {
@ -1224,11 +1230,11 @@ export class ConfigOptions {
} }
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
this.defaultPythonPlatform = 'Darwin'; this.defaultPythonPlatform = PythonPlatform.Darwin;
} else if (process.platform === 'linux') { } else if (process.platform === 'linux') {
this.defaultPythonPlatform = 'Linux'; this.defaultPythonPlatform = PythonPlatform.Linux;
} else if (process.platform === 'win32') { } else if (process.platform === 'win32') {
this.defaultPythonPlatform = 'Windows'; this.defaultPythonPlatform = PythonPlatform.Windows;
} }
if (this.defaultPythonPlatform !== undefined) { if (this.defaultPythonPlatform !== undefined) {

View File

@ -7,7 +7,7 @@
*/ */
import { stableSort } from './collectionUtils'; import { stableSort } from './collectionUtils';
import { AnyFunction, compareValues, hasProperty } from './core'; import { AnyFunction, compareValues, hasProperty, isString } from './core';
export function assert( export function assert(
expression: boolean, expression: boolean,
@ -106,6 +106,25 @@ export function getErrorString(error: any): string {
); );
} }
export function getSerializableError(error: any): Error | undefined {
if (!error) {
return undefined;
}
const exception = JSON.stringify(error);
if (exception.length > 2) {
// Given error object is JSON.stringify serializable. Use it as it is
// to preserve properties.
return error;
}
// Convert error to JSON.stringify serializable Error shape.
const name = error.name ? (isString(error.name) ? error.name : 'noname') : 'noname';
const message = error.message ? (isString(error.message) ? error.message : 'nomessage') : 'nomessage';
const stack = error.stack ? (isString(error.stack) ? error.stack : undefined) : undefined;
return { name, message, stack };
}
function getEnumMembers(enumObject: any) { function getEnumMembers(enumObject: any) {
const result: [number, string][] = []; const result: [number, string][] = [];
for (const name of Object.keys(enumObject)) { for (const name of Object.keys(enumObject)) {

View File

@ -914,3 +914,14 @@ export function isFileSystemCaseSensitiveInternal(fs: FileSystem) {
} }
} }
} }
export function getLibraryPathWithoutExtension(libraryFilePath: string) {
let filePathWithoutExtension = stripFileExtension(libraryFilePath);
// Strip off the '/__init__' if it's present.
if (filePathWithoutExtension.endsWith('__init__')) {
filePathWithoutExtension = filePathWithoutExtension.substr(0, filePathWithoutExtension.length - 9);
}
return filePathWithoutExtension;
}

View File

@ -71,3 +71,23 @@ export function versionFromMajorMinor(major: number, minor: number): PythonVersi
export function is3x(version: PythonVersion): boolean { export function is3x(version: PythonVersion): boolean {
return version >> 8 === 3; return version >> 8 === 3;
} }
export function getPythonVersionStrings(pythonVersion: PythonVersion) {
const versionStrings: string[] = [];
let minorVersion = pythonVersion & 0xff;
while (true) {
const pythonVersionString =
minorVersion > 0 ? versionToString(0x300 + minorVersion) : minorVersion === 0 ? '3' : '2and3';
versionStrings.push(pythonVersionString);
// We use -1 to indicate "2and3", which is searched after "3.0".
if (minorVersion === -1) {
break;
}
minorVersion--;
}
return versionStrings;
}

View File

@ -39,8 +39,11 @@ export class TimingStat {
this.isTiming = true; this.isTiming = true;
const duration = new Duration(); const duration = new Duration();
callback(); callback();
this.totalTime += duration.getDurationInMilliseconds(); const elapsedTime = duration.getDurationInMilliseconds();
this.totalTime += elapsedTime;
this.isTiming = false; this.isTiming = false;
return elapsedTime;
} }
} }

View File

@ -76,10 +76,12 @@ import {
import { containsPath, convertPathToUri, convertUriToPath } from './common/pathUtils'; import { containsPath, convertPathToUri, convertUriToPath } from './common/pathUtils';
import { ProgressReporter, ProgressReportTracker } from './common/progressReporter'; import { ProgressReporter, ProgressReportTracker } from './common/progressReporter';
import { convertWorkspaceEdits } from './common/textEditUtils'; import { convertWorkspaceEdits } from './common/textEditUtils';
import { Position } from './common/textRange'; import { DocumentRange, Position } from './common/textRange';
import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor'; import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor';
import { CompletionItemData, CompletionResults } from './languageService/completionProvider'; import { CompletionItemData, CompletionResults } from './languageService/completionProvider';
import { WorkspaceSymbolCallback } from './languageService/documentSymbolProvider';
import { convertHoverResults } from './languageService/hoverProvider'; import { convertHoverResults } from './languageService/hoverProvider';
import { ReferenceCallback } from './languageService/referencesProvider';
import { Localizer } from './localization/localize'; import { Localizer } from './localization/localize';
import { WorkspaceMap } from './workspaceMap'; import { WorkspaceMap } from './workspaceMap';
@ -438,7 +440,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return locations.map((loc) => Location.create(convertPathToUri(loc.path), loc.range)); return locations.map((loc) => Location.create(convertPathToUri(loc.path), loc.range));
}); });
this._connection.onReferences(async (params, token, reporter) => { this._connection.onReferences(async (params, token, workDoneReporter, resultReporter) => {
if (this._pendingFindAllRefsCancellationSource) { if (this._pendingFindAllRefsCancellationSource) {
this._pendingFindAllRefsCancellationSource.cancel(); this._pendingFindAllRefsCancellationSource.cancel();
this._pendingFindAllRefsCancellationSource = undefined; this._pendingFindAllRefsCancellationSource = undefined;
@ -449,9 +451,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// any long-running actions. // any long-running actions.
const progress = await this._getProgressReporter( const progress = await this._getProgressReporter(
params.workDoneToken, params.workDoneToken,
reporter, workDoneReporter,
Localizer.CodeAction.findingReferences() Localizer.CodeAction.findingReferences()
); );
const source = CancelAfter(token, progress.token); const source = CancelAfter(token, progress.token);
this._pendingFindAllRefsCancellationSource = source; this._pendingFindAllRefsCancellationSource = source;
@ -467,18 +470,24 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return; return;
} }
const locations = workspace.serviceInstance.getReferencesForPosition( const convert = (locs: DocumentRange[]): Location[] => {
return locs.map((loc) => Location.create(convertPathToUri(loc.path), loc.range));
};
const locations: Location[] = [];
const reporter: ReferenceCallback = resultReporter
? (locs) => resultReporter.report(convert(locs))
: (locs) => locations.push(...convert(locs));
workspace.serviceInstance.reportReferencesForPosition(
filePath, filePath,
position, position,
params.context.includeDeclaration, params.context.includeDeclaration,
reporter,
source.token source.token
); );
if (!locations) { return locations;
return undefined;
}
return locations.map((loc) => Location.create(convertPathToUri(loc.path), loc.range));
} finally { } finally {
progress.reporter.done(); progress.reporter.done();
source.dispose(); source.dispose();
@ -500,13 +509,17 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return symbolList; return symbolList;
}); });
this._connection.onWorkspaceSymbol(async (params, token) => { this._connection.onWorkspaceSymbol(async (params, token, _, resultReporter) => {
const symbolList: SymbolInformation[] = []; const symbolList: SymbolInformation[] = [];
const reporter: WorkspaceSymbolCallback = resultReporter
? (symbols) => resultReporter.report(symbols)
: (symbols) => symbolList.push(...symbols);
for (const workspace of this._workspaceMap.values()) { for (const workspace of this._workspaceMap.values()) {
await workspace.isInitialized.promise; await workspace.isInitialized.promise;
if (!workspace.disableLanguageServices) { if (!workspace.disableLanguageServices) {
workspace.serviceInstance.addSymbolsForWorkspace(symbolList, params.query, token); workspace.serviceInstance.reportSymbolsForWorkspace(params.query, reporter, token);
} }
} }

View File

@ -7,7 +7,6 @@
import { CancellationToken, CompletionItemKind, SymbolKind } from 'vscode-languageserver'; import { CancellationToken, CompletionItemKind, SymbolKind } from 'vscode-languageserver';
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import { DeclarationType } from '../analyzer/declaration'; import { DeclarationType } from '../analyzer/declaration';
import { ImportResolver, ModuleNameAndType } from '../analyzer/importResolver'; import { ImportResolver, ModuleNameAndType } from '../analyzer/importResolver';
import { ImportType } from '../analyzer/importResult'; import { ImportType } from '../analyzer/importResult';
@ -23,19 +22,14 @@ import { SourceFileInfo } from '../analyzer/program';
import { Symbol } from '../analyzer/symbol'; import { Symbol } from '../analyzer/symbol';
import * as SymbolNameUtils from '../analyzer/symbolNameUtils'; import * as SymbolNameUtils from '../analyzer/symbolNameUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ConfigOptions } from '../common/configOptions'; import { ExecutionEnvironment } from '../common/configOptions';
import { TextEditAction } from '../common/editAction'; import { TextEditAction } from '../common/editAction';
import { combinePaths, getDirectoryPath, getFileName, stripFileExtension } from '../common/pathUtils'; import { combinePaths, getDirectoryPath, getFileName, stripFileExtension } from '../common/pathUtils';
import * as StringUtils from '../common/stringUtils'; import * as StringUtils from '../common/stringUtils';
import { Position } from '../common/textRange'; import { Position } from '../common/textRange';
import { ParseNodeType } from '../parser/parseNodes'; import { ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
import { import { IndexAliasData, IndexResults } from './documentSymbolProvider';
getIndexAliasData,
includeAliasDeclarationInIndex,
IndexAliasData,
IndexResults,
} from './documentSymbolProvider';
export interface AutoImportSymbol { export interface AutoImportSymbol {
readonly importAlias?: IndexAliasData; readonly importAlias?: IndexAliasData;
@ -74,7 +68,6 @@ export function buildModuleSymbolsMap(files: SourceFileInfo[], token: Cancellati
return; return;
} }
const fileInfo = AnalyzerNodeInfo.getFileInfo(file.sourceFile.getParseResults()!.parseTree);
moduleSymbolMap.set(filePath, { moduleSymbolMap.set(filePath, {
forEach(callbackfn: (value: AutoImportSymbol, key: string) => void): void { forEach(callbackfn: (value: AutoImportSymbol, key: string) => void): void {
symbolTable.forEach((symbol, name) => { symbolTable.forEach((symbol, name) => {
@ -92,22 +85,19 @@ export function buildModuleSymbolsMap(files: SourceFileInfo[], token: Cancellati
return; return;
} }
let importAlias: IndexAliasData | undefined;
if (declaration.type === DeclarationType.Alias) { if (declaration.type === DeclarationType.Alias) {
if (!includeAliasDeclarationInIndex(declaration)) { // We don't include import alias in auto import
// for workspace files.
return; return;
} }
importAlias = getIndexAliasData(fileInfo?.importLookup, declaration);
}
const variableKind = const variableKind =
declaration.type === DeclarationType.Variable && declaration.type === DeclarationType.Variable &&
!declaration.isConstant && !declaration.isConstant &&
!declaration.isFinal !declaration.isFinal
? CompletionItemKind.Variable ? CompletionItemKind.Variable
: undefined; : undefined;
callbackfn({ importAlias, symbol, kind: variableKind }, name); callbackfn({ symbol, kind: variableKind }, name);
}); });
}, },
}); });
@ -146,14 +136,14 @@ interface ImportAliasData {
importParts: ImportParts; importParts: ImportParts;
importGroup: ImportGroup; importGroup: ImportGroup;
symbol?: Symbol; symbol?: Symbol;
kind?: CompletionItemKind;
} }
export class AutoImporter { export class AutoImporter {
private _importStatements: ImportStatements; private _importStatements: ImportStatements;
private _filePath: string;
constructor( constructor(
private _configOptions: ConfigOptions, private _execEnvironment: ExecutionEnvironment,
private _importResolver: ImportResolver, private _importResolver: ImportResolver,
private _parseResults: ParseResults, private _parseResults: ParseResults,
private _invocationPosition: Position, private _invocationPosition: Position,
@ -161,7 +151,6 @@ export class AutoImporter {
private _moduleSymbolMap: ModuleSymbolMap, private _moduleSymbolMap: ModuleSymbolMap,
private _libraryMap?: Map<string, IndexResults> private _libraryMap?: Map<string, IndexResults>
) { ) {
this._filePath = AnalyzerNodeInfo.getFileInfo(this._parseResults.parseTree)!.filePath;
this._importStatements = getTopLevelImports(this._parseResults.parseTree); this._importStatements = getTopLevelImports(this._parseResults.parseTree);
} }
@ -309,6 +298,7 @@ export class AutoImporter {
}, },
importGroup, importGroup,
symbol: autoImportSymbol.symbol, symbol: autoImportSymbol.symbol,
kind: convertSymbolKindToCompletionItemKind(autoImportSymbol.importAlias.kind),
}, },
importAliasMap importAliasMap
); );
@ -356,7 +346,7 @@ export class AutoImporter {
} }
this._addToImportAliasMap( this._addToImportAliasMap(
{ modulePath: filePath, originalName: importParts.importName }, { modulePath: filePath, originalName: importParts.importName, kind: SymbolKind.Module },
{ importParts, importGroup }, { importParts, importGroup },
importAliasMap importAliasMap
); );
@ -386,6 +376,7 @@ export class AutoImporter {
name: importAliasData.importParts.importName, name: importAliasData.importParts.importName,
alias: aliasName, alias: aliasName,
symbol: importAliasData.symbol, symbol: importAliasData.symbol,
kind: importAliasData.kind,
source: importAliasData.importParts.importFrom, source: importAliasData.importParts.importFrom,
edits: autoImportTextEdits, edits: autoImportTextEdits,
}); });
@ -525,8 +516,7 @@ export class AutoImporter {
// convert to a module name that can be used in an // convert to a module name that can be used in an
// 'import from' statement. // 'import from' statement.
private _getModuleNameAndTypeFromFilePath(filePath: string): ModuleNameAndType { private _getModuleNameAndTypeFromFilePath(filePath: string): ModuleNameAndType {
const execEnvironment = this._configOptions.findExecEnvironment(this._filePath); return this._importResolver.getModuleNameForImport(filePath, this._execEnvironment);
return this._importResolver.getModuleNameForImport(filePath, execEnvironment);
} }
private _getImportGroupFromModuleNameAndType(moduleNameAndType: ModuleNameAndType): ImportGroup { private _getImportGroupFromModuleNameAndType(moduleNameAndType: ModuleNameAndType): ImportGroup {

View File

@ -965,7 +965,7 @@ export class CompletionProvider {
private _getAutoImportCompletions(priorWord: string, completionList: CompletionList) { private _getAutoImportCompletions(priorWord: string, completionList: CompletionList) {
const moduleSymbolMap = this._moduleSymbolsCallback(); const moduleSymbolMap = this._moduleSymbolsCallback();
const autoImporter = new AutoImporter( const autoImporter = new AutoImporter(
this._configOptions, this._configOptions.findExecEnvironment(this._filePath),
this._importResolver, this._importResolver,
this._parseResults, this._parseResults,
this._position, this._position,

View File

@ -20,24 +20,27 @@ import { getLastTypedDeclaredForSymbol } from '../analyzer/symbolUtils';
import { TypeEvaluator } from '../analyzer/typeEvaluator'; import { TypeEvaluator } from '../analyzer/typeEvaluator';
import { isProperty } from '../analyzer/typeUtils'; import { isProperty } from '../analyzer/typeUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { getLibraryPathWithoutExtension } from '../common/pathUtils';
import { convertOffsetsToRange } from '../common/positionUtils'; import { convertOffsetsToRange } from '../common/positionUtils';
import * as StringUtils from '../common/stringUtils'; import * as StringUtils from '../common/stringUtils';
import { Range } from '../common/textRange'; import { Range } from '../common/textRange';
import { ModuleNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
export interface IndexAliasData { export interface IndexAliasData {
readonly originalName: string; readonly originalName: string;
readonly modulePath: string; readonly modulePath: string;
readonly kind: SymbolKind;
} }
export interface IndexSymbolData { export interface IndexSymbolData {
readonly name: string; readonly name: string;
readonly alias: IndexAliasData | undefined;
readonly externallyVisible: boolean; readonly externallyVisible: boolean;
readonly kind: SymbolKind; readonly kind: SymbolKind;
readonly range: Range; readonly alias?: IndexAliasData;
readonly selectionRange: Range; readonly range?: Range;
readonly children: IndexSymbolData[]; readonly selectionRange?: Range;
readonly children?: IndexSymbolData[];
} }
export interface IndexResults { export interface IndexResults {
@ -45,30 +48,62 @@ export interface IndexResults {
readonly symbols: IndexSymbolData[]; readonly symbols: IndexSymbolData[];
} }
export function includeAliasDeclarationInIndex(declaration: AliasDeclaration): boolean { export interface IndexOptions {
return declaration.usesLocalName && !!declaration.symbolName && declaration.path.length > 0; indexingForAutoImportMode: boolean;
}
export type WorkspaceSymbolCallback = (symbols: SymbolInformation[]) => void;
export function includeAliasDeclarationInIndex(
importLookup: ImportLookup,
modulePath: string,
declaration: AliasDeclaration
): boolean {
const aliasUsed = declaration.usesLocalName && !!declaration.symbolName && declaration.path.length > 0;
const wildcardUsed = declaration.node.nodeType === ParseNodeType.ImportFrom && declaration.node.isWildcardImport;
if (!aliasUsed && !wildcardUsed) {
return false;
}
// Make sure imported symbol is a submodule of same package.
if (!getLibraryPathWithoutExtension(declaration.path).startsWith(modulePath)) {
return false;
}
if (wildcardUsed) {
// if "import *" is used, resolve the alias to see whether we should include it.
const resolved = resolveAliasDeclaration(importLookup, declaration, true);
if (!resolved) {
return false;
}
if (!getLibraryPathWithoutExtension(resolved.path).startsWith(modulePath)) {
return false;
}
}
return true;
} }
export function getIndexAliasData( export function getIndexAliasData(
importLookup: ImportLookup | undefined, importLookup: ImportLookup,
declaration: AliasDeclaration declaration: AliasDeclaration
): IndexAliasData | undefined { ): IndexAliasData | undefined {
if (!declaration.symbolName) { if (!declaration.symbolName) {
return undefined; return undefined;
} }
const aliasData = { originalName: declaration.symbolName!, modulePath: declaration.path };
if (!importLookup) {
return aliasData;
}
const resolved = resolveAliasDeclaration(importLookup, declaration, true); const resolved = resolveAliasDeclaration(importLookup, declaration, true);
const nameValue = resolved ? getNameFromDeclaration(resolved) : undefined; const nameValue = resolved ? getNameFromDeclaration(resolved) : undefined;
if (!nameValue || resolved!.path.length <= 0) { if (!nameValue || resolved!.path.length <= 0) {
return aliasData; return undefined;
} }
return { originalName: nameValue, modulePath: resolved!.path }; return {
originalName: nameValue,
modulePath: resolved!.path,
kind: getSymbolKind(nameValue, resolved!) ?? SymbolKind.Module,
};
} }
// We'll use a somewhat-arbitrary cutoff value here to determine // We'll use a somewhat-arbitrary cutoff value here to determine
@ -76,21 +111,25 @@ export function getIndexAliasData(
const similarityLimit = 0.5; const similarityLimit = 0.5;
export class DocumentSymbolProvider { export class DocumentSymbolProvider {
static addSymbolsForDocument( static getSymbolsForDocument(
indexResults: IndexResults | undefined, indexResults: IndexResults | undefined,
parseResults: ParseResults | undefined, parseResults: ParseResults | undefined,
filePath: string, filePath: string,
query: string, query: string,
symbolList: SymbolInformation[],
token: CancellationToken token: CancellationToken
) { ): SymbolInformation[] {
const symbolList: SymbolInformation[] = [];
if (!indexResults && !parseResults) { if (!indexResults && !parseResults) {
return; return symbolList;
} }
const indexSymbolData = const indexSymbolData =
indexResults?.symbols ?? DocumentSymbolProvider.indexSymbols(parseResults!, false, token); (indexResults?.symbols as IndexSymbolData[]) ??
DocumentSymbolProvider.indexSymbols(parseResults!, { indexingForAutoImportMode: false }, token);
appendWorkspaceSymbolsRecursive(indexSymbolData, filePath, query, '', symbolList, token); appendWorkspaceSymbolsRecursive(indexSymbolData, filePath, query, '', symbolList, token);
return symbolList;
} }
static addHierarchicalSymbolsForDocument( static addHierarchicalSymbolsForDocument(
@ -104,17 +143,18 @@ export class DocumentSymbolProvider {
} }
const indexSymbolData = const indexSymbolData =
indexResults?.symbols ?? DocumentSymbolProvider.indexSymbols(parseResults!, false, token); (indexResults?.symbols as IndexSymbolData[]) ??
DocumentSymbolProvider.indexSymbols(parseResults!, { indexingForAutoImportMode: false }, token);
appendDocumentSymbolsRecursive(indexSymbolData, symbolList, token); appendDocumentSymbolsRecursive(indexSymbolData, symbolList, token);
} }
static indexSymbols( static indexSymbols(
parseResults: ParseResults, parseResults: ParseResults,
importSymbolsOnly: boolean, options: IndexOptions,
token: CancellationToken token: CancellationToken
): IndexSymbolData[] { ): IndexSymbolData[] {
const indexSymbolData: IndexSymbolData[] = []; const indexSymbolData: IndexSymbolData[] = [];
collectSymbolIndexData(parseResults, parseResults.parseTree, importSymbolsOnly, indexSymbolData, token); collectSymbolIndexData(parseResults, parseResults.parseTree, options, indexSymbolData, token);
return indexSymbolData; return indexSymbolData;
} }
@ -168,7 +208,7 @@ function getSymbolKind(name: string, declaration: Declaration, evaluator?: TypeE
} }
function appendWorkspaceSymbolsRecursive( function appendWorkspaceSymbolsRecursive(
indexSymbolData: IndexSymbolData[], indexSymbolData: IndexSymbolData[] | undefined,
filePath: string, filePath: string,
query: string, query: string,
container: string, container: string,
@ -177,6 +217,10 @@ function appendWorkspaceSymbolsRecursive(
) { ) {
throwIfCancellationRequested(token); throwIfCancellationRequested(token);
if (!indexSymbolData) {
return;
}
for (const symbolData of indexSymbolData) { for (const symbolData of indexSymbolData) {
if (symbolData.alias) { if (symbolData.alias) {
continue; continue;
@ -186,7 +230,7 @@ function appendWorkspaceSymbolsRecursive(
if (similarity >= similarityLimit) { if (similarity >= similarityLimit) {
const location: Location = { const location: Location = {
uri: URI.file(filePath).toString(), uri: URI.file(filePath).toString(),
range: symbolData.selectionRange, range: symbolData.selectionRange!,
}; };
const symbolInfo: SymbolInformation = { const symbolInfo: SymbolInformation = {
@ -219,12 +263,16 @@ function appendWorkspaceSymbolsRecursive(
} }
function appendDocumentSymbolsRecursive( function appendDocumentSymbolsRecursive(
indexSymbolData: IndexSymbolData[], indexSymbolData: IndexSymbolData[] | undefined,
symbolList: DocumentSymbol[], symbolList: DocumentSymbol[],
token: CancellationToken token: CancellationToken
) { ) {
throwIfCancellationRequested(token); throwIfCancellationRequested(token);
if (!indexSymbolData) {
return;
}
for (const symbolData of indexSymbolData) { for (const symbolData of indexSymbolData) {
if (symbolData.alias) { if (symbolData.alias) {
continue; continue;
@ -236,19 +284,41 @@ function appendDocumentSymbolsRecursive(
const symbolInfo: DocumentSymbol = { const symbolInfo: DocumentSymbol = {
name: symbolData.name, name: symbolData.name,
kind: symbolData.kind, kind: symbolData.kind,
range: symbolData.range, range: symbolData.range!,
selectionRange: symbolData.selectionRange, selectionRange: symbolData.selectionRange!,
children: children, children: children!,
}; };
symbolList.push(symbolInfo); symbolList.push(symbolInfo);
} }
} }
function getAllNameTable(autoImportMode: boolean, root: ModuleNode) {
if (!autoImportMode) {
// We only care about __all__ in auto import mode.
// other cases such as workspace symbols, document symbols, we will collect all symbols
// regardless whether it shows up in __all__ or not.
return undefined;
}
// If __all__ is defined, we only care ones in the __all__
const allNames = AnalyzerNodeInfo.getDunderAllNames(root);
if (allNames) {
return new Set<string>(allNames);
}
const file = AnalyzerNodeInfo.getFileInfo(root);
if (file && file.isStubFile) {
return undefined;
}
return new Set<string>();
}
function collectSymbolIndexData( function collectSymbolIndexData(
parseResults: ParseResults, parseResults: ParseResults,
node: AnalyzerNodeInfo.ScopedNode, node: AnalyzerNodeInfo.ScopedNode,
autoImportMode: boolean, options: IndexOptions,
indexSymbolData: IndexSymbolData[], indexSymbolData: IndexSymbolData[],
token: CancellationToken token: CancellationToken
) { ) {
@ -259,13 +329,9 @@ function collectSymbolIndexData(
return; return;
} }
// Build __all__ map for regular python file to reduce candidate in autoImportMode. const allNameTable = getAllNameTable(options.indexingForAutoImportMode, parseResults.parseTree);
const file = AnalyzerNodeInfo.getFileInfo(parseResults.parseTree);
let allNameTable: Set<string> | undefined;
if (autoImportMode && !file?.isStubFile) {
allNameTable = new Set<string>(AnalyzerNodeInfo.getDunderAllNames(parseResults.parseTree) ?? []);
}
let modulePath: string | undefined = undefined;
const symbolTable = scope.symbolTable; const symbolTable = scope.symbolTable;
symbolTable.forEach((symbol, name) => { symbolTable.forEach((symbol, name) => {
if (symbol.isIgnoredForProtocolMatch()) { if (symbol.isIgnoredForProtocolMatch()) {
@ -290,11 +356,21 @@ function collectSymbolIndexData(
} }
if (DeclarationType.Alias === declaration.type) { if (DeclarationType.Alias === declaration.type) {
if (!options.indexingForAutoImportMode) {
// We don't include import alias for workspace files.
return;
}
if (declaration.path.length <= 0) { if (declaration.path.length <= 0) {
return; return;
} }
if (!allNameTable && !includeAliasDeclarationInIndex(declaration)) { const lookup = AnalyzerNodeInfo.getFileInfo(parseResults.parseTree)!.importLookup;
modulePath =
modulePath ??
getLibraryPathWithoutExtension(AnalyzerNodeInfo.getFileInfo(parseResults.parseTree)!.filePath);
if (!allNameTable && !includeAliasDeclarationInIndex(lookup, modulePath, declaration)) {
// For import alias, we only put the alias in the index if it is the form of // For import alias, we only put the alias in the index if it is the form of
// "from x import y as z" or the alias is explicitly listed in __all__ // "from x import y as z" or the alias is explicitly listed in __all__
return; return;
@ -304,7 +380,7 @@ function collectSymbolIndexData(
collectSymbolIndexDataForName( collectSymbolIndexDataForName(
parseResults, parseResults,
declaration, declaration,
autoImportMode, options,
!symbol.isExternallyHidden(), !symbol.isExternallyHidden(),
name, name,
indexSymbolData, indexSymbolData,
@ -316,13 +392,13 @@ function collectSymbolIndexData(
function collectSymbolIndexDataForName( function collectSymbolIndexDataForName(
parseResults: ParseResults, parseResults: ParseResults,
declaration: Declaration, declaration: Declaration,
autoImportMode: boolean, options: IndexOptions,
externallyVisible: boolean, externallyVisible: boolean,
name: string, name: string,
indexSymbolData: IndexSymbolData[], indexSymbolData: IndexSymbolData[],
token: CancellationToken token: CancellationToken
) { ) {
if (autoImportMode && !externallyVisible) { if (options.indexingForAutoImportMode && !externallyVisible) {
return; return;
} }
@ -336,29 +412,28 @@ function collectSymbolIndexDataForName(
const children: IndexSymbolData[] = []; const children: IndexSymbolData[] = [];
if (declaration.type === DeclarationType.Class || declaration.type === DeclarationType.Function) { if (declaration.type === DeclarationType.Class || declaration.type === DeclarationType.Function) {
if (!autoImportMode) { if (!options.indexingForAutoImportMode) {
collectSymbolIndexData(parseResults, declaration.node, false, children, token); collectSymbolIndexData(parseResults, declaration.node, options, children, token);
} }
const nameRange = convertOffsetsToRange( range = convertOffsetsToRange(
declaration.node.start, declaration.node.start,
declaration.node.name.start + declaration.node.length, declaration.node.name.start + declaration.node.length,
parseResults.tokenizerOutput.lines parseResults.tokenizerOutput.lines
); );
range = nameRange;
} }
const data: IndexSymbolData = { const data: IndexSymbolData = {
name, name,
alias:
DeclarationType.Alias === declaration.type
? getIndexAliasData(AnalyzerNodeInfo.getFileInfo(parseResults.parseTree)?.importLookup, declaration)
: undefined,
externallyVisible, externallyVisible,
kind: symbolKind, kind: symbolKind,
range, alias:
selectionRange, DeclarationType.Alias === declaration.type
children, ? getIndexAliasData(AnalyzerNodeInfo.getFileInfo(parseResults.parseTree)!.importLookup, declaration)
: undefined,
range: options.indexingForAutoImportMode ? undefined : range,
selectionRange: options.indexingForAutoImportMode ? undefined : selectionRange,
children: options.indexingForAutoImportMode ? undefined : children,
}; };
indexSymbolData.push(data); indexSymbolData.push(data);

View File

@ -24,15 +24,39 @@ import { TextRange } from '../common/textRange';
import { ModuleNameNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes'; import { ModuleNameNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
export interface ReferencesResult { export type ReferenceCallback = (locations: DocumentRange[]) => void;
requiresGlobalSearch: boolean;
nodeAtOffset: ParseNode; export class ReferencesResult {
symbolName: string; private readonly _locations: DocumentRange[] = [];
declarations: Declaration[];
locations: DocumentRange[]; constructor(
readonly requiresGlobalSearch: boolean,
readonly nodeAtOffset: ParseNode,
readonly symbolName: string,
readonly declarations: Declaration[],
private readonly _reporter?: ReferenceCallback
) {}
get locations(): readonly DocumentRange[] {
return this._locations;
}
addLocations(...locs: DocumentRange[]) {
if (locs.length === 0) {
return;
}
if (this._reporter) {
this._reporter(locs);
}
this._locations.push(...locs);
}
} }
class FindReferencesTreeWalker extends ParseTreeWalker { class FindReferencesTreeWalker extends ParseTreeWalker {
private readonly _locationsFound: DocumentRange[] = [];
constructor( constructor(
private _parseResults: ParseResults, private _parseResults: ParseResults,
private _filePath: string, private _filePath: string,
@ -46,6 +70,8 @@ class FindReferencesTreeWalker extends ParseTreeWalker {
findReferences() { findReferences() {
this.walk(this._parseResults.parseTree); this.walk(this._parseResults.parseTree);
return this._locationsFound;
} }
walk(node: ParseNode) { walk(node: ParseNode) {
@ -74,7 +100,7 @@ class FindReferencesTreeWalker extends ParseTreeWalker {
if (declarations.some((decl) => this._resultsContainsDeclaration(decl))) { if (declarations.some((decl) => this._resultsContainsDeclaration(decl))) {
// Is it the same symbol? // Is it the same symbol?
if (this._includeDeclaration || node !== this._referencesResult.nodeAtOffset) { if (this._includeDeclaration || node !== this._referencesResult.nodeAtOffset) {
this._referencesResult.locations.push({ this._locationsFound.push({
path: this._filePath, path: this._filePath,
range: { range: {
start: convertOffsetToPosition(node.start, this._parseResults.tokenizerOutput.lines), start: convertOffsetToPosition(node.start, this._parseResults.tokenizerOutput.lines),
@ -129,6 +155,7 @@ export class ReferencesProvider {
filePath: string, filePath: string,
position: Position, position: Position,
evaluator: TypeEvaluator, evaluator: TypeEvaluator,
reporter: ReferenceCallback | undefined,
token: CancellationToken token: CancellationToken
): ReferencesResult | undefined { ): ReferencesResult | undefined {
throwIfCancellationRequested(token); throwIfCancellationRequested(token);
@ -207,13 +234,7 @@ export class ReferencesProvider {
return false; return false;
}); });
return { return new ReferencesResult(requiresGlobalSearch, node, node.value, resolvedDeclarations, reporter);
requiresGlobalSearch,
nodeAtOffset: node,
symbolName: node.value,
declarations: resolvedDeclarations,
locations: [],
};
} }
private static _addIfUnique(declarations: Declaration[], itemToAdd: Declaration) { private static _addIfUnique(declarations: Declaration[], itemToAdd: Declaration) {
@ -242,6 +263,7 @@ export class ReferencesProvider {
evaluator, evaluator,
token token
); );
refTreeWalker.findReferences();
referencesResult.addLocations(...refTreeWalker.findReferences());
} }
} }

View File

@ -1006,7 +1006,15 @@ export class TestState {
const expected = map[name].references; const expected = map[name].references;
const position = this.convertOffsetToPosition(fileName, marker.position); const position = this.convertOffsetToPosition(fileName, marker.position);
const actual = this.program.getReferencesForPosition(fileName, position, true, CancellationToken.None);
const actual: DocumentRange[] = [];
this.program.reportReferencesForPosition(
fileName,
position,
true,
(locs) => actual.push(...locs),
CancellationToken.None
);
assert.equal(actual?.length ?? 0, expected.length); assert.equal(actual?.length ?? 0, expected.length);

View File

@ -19,7 +19,7 @@
"scripts": { "scripts": {
"build": "webpack --mode production --progress", "build": "webpack --mode production --progress",
"clean": "shx rm -rf ./dist ./out README.md", "clean": "shx rm -rf ./dist ./out README.md",
"prepublishOnly": "npm run clean && shx cp ../../README.md . && npm run build", "prepack": "npm run clean && shx cp ../../README.md . && npm run build",
"webpack": "webpack --mode development --progress" "webpack": "webpack --mode development --progress"
}, },
"devDependencies": { "devDependencies": {