Push pylance changes to pyright (#4783)

Co-authored-by: Bill Schnurr <bschnurr@microsoft.com>
Co-authored-by: HeeJae Chang <hechang@microsoft.com>
Co-authored-by: Erik De Bonte <erikd@microsoft.com>
Co-authored-by: Rich Chiodo <rchiodo@microsoft.com>
This commit is contained in:
Erik De Bonte 2023-03-15 17:27:27 -07:00 committed by GitHub
parent cebfc312e5
commit 6de737544c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2132 additions and 926 deletions

1194
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,19 +23,19 @@
"@types/glob": "^7.2.0",
"@types/node": "^17.0.45",
"@types/yargs": "^16.0.5",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"detect-indent": "^6.1.0",
"eslint": "^8.34.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"glob": "^7.2.3",
"jsonc-parser": "^3.2.0",
"lerna": "^6.5.1",
"npm-check-updates": "^16.7.4",
"npm-check-updates": "^16.7.10",
"p-queue": "^6.6.2",
"prettier": "2.8.4",
"syncpack": "^9.0.2",
"syncpack": "^9.8.4",
"typescript": "~4.4.4",
"yargs": "^16.2.0"
}

View File

@ -20,10 +20,10 @@
"source-map-support": "^0.5.21",
"tmp": "^0.2.1",
"typescript-char": "^0.0.0",
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver": "8.1.0-next.5",
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-textdocument": "^1.0.9",
"vscode-languageserver-types": "3.17.2",
"vscode-languageserver-types": "3.17.3",
"vscode-uri": "^3.0.7"
},
"devDependencies": {
@ -4185,47 +4185,42 @@
}
},
"node_modules/vscode-jsonrpc": {
"version": "8.1.0-next.6",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0-next.6.tgz",
"integrity": "sha512-AahQokGczPwXKo1Qhnn3aqkZgwUJ0rjVwhWWKW5I5LEWRoqfnWkQp7haVIV6GJRX0oyGL2ezVy7IhwgP5u/3xw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
"integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/vscode-languageserver": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0-next.5.tgz",
"integrity": "sha512-VivctbjOca/iPZKXqgqz03MUhnjAlVkf8/AwfndIEVzD8itD7zaoMlqNUynHJVbGcU5PEygC5deUzKHMU8cNhw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz",
"integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==",
"dependencies": {
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
},
"bin": {
"installServerIntoExtension": "bin/installServerIntoExtension"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.17.3-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3-next.5.tgz",
"integrity": "sha512-9HafkatRVhBVpWQrODes4JaoSu7ozHQUOzYiTmfMmxeFOUYgsSqyODp+j/c+SovcsuwABjuqnsQ9RiFkXCbeMA==",
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
"integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
"dependencies": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver-types": "3.17.3-next.2"
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver-types": "3.17.3"
}
},
"node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": {
"version": "3.17.3-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3-next.2.tgz",
"integrity": "sha512-3kkNSCycNKUalSJIrjIptGeY9UTJr1Nk5HT/aT00jjIwiCvIUNbRdK90av2Y3j1Jityot68dBVc3YYdwwH3zOQ=="
},
"node_modules/vscode-languageserver-textdocument": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.9.tgz",
"integrity": "sha512-NPfHVGFW2/fQEWHspr8x3PXhRgtFbuDZdl7p6ifuN3M7nk2Yjf5POr/NfDBuAiQG88DehDyJ7nGOT+p+edEtbw=="
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"node_modules/vscode-uri": {
"version": "3.0.7",
@ -7602,32 +7597,25 @@
}
},
"vscode-jsonrpc": {
"version": "8.1.0-next.6",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0-next.6.tgz",
"integrity": "sha512-AahQokGczPwXKo1Qhnn3aqkZgwUJ0rjVwhWWKW5I5LEWRoqfnWkQp7haVIV6GJRX0oyGL2ezVy7IhwgP5u/3xw=="
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
"integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="
},
"vscode-languageserver": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0-next.5.tgz",
"integrity": "sha512-VivctbjOca/iPZKXqgqz03MUhnjAlVkf8/AwfndIEVzD8itD7zaoMlqNUynHJVbGcU5PEygC5deUzKHMU8cNhw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz",
"integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==",
"requires": {
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
}
},
"vscode-languageserver-protocol": {
"version": "3.17.3-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3-next.5.tgz",
"integrity": "sha512-9HafkatRVhBVpWQrODes4JaoSu7ozHQUOzYiTmfMmxeFOUYgsSqyODp+j/c+SovcsuwABjuqnsQ9RiFkXCbeMA==",
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
"integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
"requires": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver-types": "3.17.3-next.2"
},
"dependencies": {
"vscode-languageserver-types": {
"version": "3.17.3-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3-next.2.tgz",
"integrity": "sha512-3kkNSCycNKUalSJIrjIptGeY9UTJr1Nk5HT/aT00jjIwiCvIUNbRdK90av2Y3j1Jityot68dBVc3YYdwwH3zOQ=="
}
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver-types": "3.17.3"
}
},
"vscode-languageserver-textdocument": {
@ -7636,9 +7624,9 @@
"integrity": "sha512-NPfHVGFW2/fQEWHspr8x3PXhRgtFbuDZdl7p6ifuN3M7nk2Yjf5POr/NfDBuAiQG88DehDyJ7nGOT+p+edEtbw=="
},
"vscode-languageserver-types": {
"version": "3.17.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"vscode-uri": {
"version": "3.0.7",

View File

@ -26,10 +26,10 @@
"source-map-support": "^0.5.21",
"tmp": "^0.2.1",
"typescript-char": "^0.0.0",
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver": "8.1.0-next.5",
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-textdocument": "^1.0.9",
"vscode-languageserver-types": "3.17.2",
"vscode-languageserver-types": "3.17.3",
"vscode-uri": "^3.0.7"
},
"devDependencies": {

View File

@ -22,6 +22,7 @@ export interface CacheOwner {
}
export class CacheManager {
private _pausedCount = 0;
private readonly _cacheOwners: CacheOwner[] = [];
registerCacheOwner(provider: CacheOwner) {
@ -37,7 +38,21 @@ export class CacheManager {
}
}
pauseTracking(): { dispose(): void } {
const local = this;
local._pausedCount++;
return {
dispose() {
local._pausedCount--;
},
};
}
getCacheUsage() {
if (this._pausedCount > 0) {
return -1;
}
let totalUsage = 0;
this._cacheOwners.forEach((p) => {

View File

@ -103,7 +103,7 @@ import {
import { Scope } from './scope';
import { getScopeForNode } from './scopeUtils';
import { IPythonMode, SourceFile } from './sourceFile';
import { isUserCode } from './sourceFileInfoUtils';
import { collectImportedByFiles, isUserCode } from './sourceFileInfoUtils';
import { isStubFile, SourceMapper } from './sourceMapper';
import { Symbol } from './symbol';
import { isPrivateOrProtectedName } from './symbolNameUtils';
@ -1200,15 +1200,23 @@ export class Program {
let dependentFiles: ParseResults[] | undefined = undefined;
if (fileToCheck.sourceFile.getIPythonMode() === IPythonMode.CellDocs) {
dependentFiles = [];
const importedByFiles = new Set<SourceFileInfo>();
this._collectImportedByFiles(fileToCheck, importedByFiles);
const importedByFiles = collectImportedByFiles(fileToCheck);
for (const file of importedByFiles) {
if (!isUserCode(file)) {
continue;
}
// If the file is already analyzed, it will be no op.
this._checkTypes(file, token);
// And make sure we don't dump parse tree and etc while
// recursively calling checker. Otherwise, inner check
// can dump parse tree required by outer check.
const handle = this._cacheManager.pauseTracking();
try {
this._checkTypes(file, token);
} finally {
handle.dispose();
}
const parseResults = file.sourceFile.getParseResults();
if (parseResults) {
dependentFiles.push(parseResults);
@ -1260,18 +1268,6 @@ export class Program {
});
}
private _collectImportedByFiles(file: SourceFileInfo, importedByFiles: Set<SourceFileInfo>) {
file.importedBy.forEach((dep) => {
if (importedByFiles.has(dep)) {
// Already visited.
return;
}
importedByFiles.add(dep);
this._collectImportedByFiles(dep, importedByFiles);
});
}
// Builds a map of files that includes the specified file and all of the files
// it imports (recursively) and ensures that all such files. If any of these files
// have already been checked (they and their recursive imports have completed the
@ -1653,15 +1649,14 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(
this._createSourceMapper(execEnv, token, sourceFileInfo),
const referencesResult = this._getDeclarationForPosition(
sourceFileInfo,
position,
this._evaluator!,
reporter,
DocumentSymbolCollectorUseCase.Reference,
token
this._createSourceMapper(execEnv, token, sourceFileInfo),
token,
reporter
);
if (!referencesResult) {
return;
}
@ -2844,12 +2839,11 @@ export class Program {
token: CancellationToken
) {
const execEnv = this._configOptions.findExecEnvironment(filePath);
const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(
this._createSourceMapper(execEnv, token),
const referencesResult = this._getDeclarationForPosition(
sourceFileInfo,
position,
this._evaluator!,
undefined,
DocumentSymbolCollectorUseCase.Rename,
this._createSourceMapper(execEnv, token),
token
);
@ -2876,6 +2870,25 @@ export class Program {
);
}
private _getDeclarationForPosition(
sourceFileInfo: SourceFileInfo,
position: Position,
useCase: DocumentSymbolCollectorUseCase,
sourceMapper: SourceMapper,
token: CancellationToken,
reporter?: ReferenceCallback
) {
return sourceFileInfo.sourceFile.getDeclarationForPosition(
sourceMapper,
position,
this._evaluator!,
reporter,
useCase,
token,
Array.from(collectImportedByFiles(sourceFileInfo)).map((fileInfo) => fileInfo.sourceFile)
);
}
private _processModuleReferences(
renameModuleProvider: RenameModuleProvider,
filteringText: string,

View File

@ -297,6 +297,10 @@ export class AnalyzerService {
return this._program.getUserFiles().map((i) => i.sourceFile.getFilePath());
}
getOpenFiles() {
return this._program.getOpened().map((i) => i.sourceFile.getFilePath());
}
setFileOpened(
path: string,
version: number | null,
@ -305,8 +309,11 @@ export class AnalyzerService {
chainedFilePath?: string,
realFilePath?: string
) {
// Open the file. Notebook cells are always tracked as they aren't 3rd party library files.
// This is how it's worked in the past since each notebook used to have its own
// workspace and the workspace include setting marked all cells as tracked.
this._backgroundAnalysisProgram.setFileOpened(path, version, contents, {
isTracked: this.isTracked(path),
isTracked: this.isTracked(path) || ipythonMode !== IPythonMode.None,
ipythonMode,
chainedFilePath,
realFilePath,

View File

@ -1064,7 +1064,8 @@ export class SourceFile {
evaluator: TypeEvaluator,
reporter: ReferenceCallback | undefined,
useCase: DocumentSymbolCollectorUseCase,
token: CancellationToken
token: CancellationToken,
implicitlyImportedBy?: SourceFile[]
): ReferencesResult | undefined {
// If we have no completed analysis job, there's nothing to do.
if (!this._parseResults) {
@ -1079,7 +1080,8 @@ export class SourceFile {
evaluator,
reporter,
useCase,
token
token,
implicitlyImportedBy
);
}

View File

@ -11,3 +11,21 @@ import { SourceFileInfo } from './program';
export function isUserCode(fileInfo: SourceFileInfo | undefined) {
return !!fileInfo && fileInfo.isTracked && !fileInfo.isThirdPartyImport && !fileInfo.isTypeshedFile;
}
export function collectImportedByFiles(fileInfo: SourceFileInfo): Set<SourceFileInfo> {
const importedByFiles = new Set<SourceFileInfo>();
_collectImportedByFiles(fileInfo, importedByFiles);
return importedByFiles;
}
function _collectImportedByFiles(fileInfo: SourceFileInfo, importedByFiles: Set<SourceFileInfo>) {
fileInfo.importedBy.forEach((dep) => {
if (importedByFiles.has(dep)) {
// Already visited.
return;
}
importedByFiles.add(dep);
_collectImportedByFiles(dep, importedByFiles);
});
}

View File

@ -136,7 +136,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
const kind = params.arguments[1];
const workspace = await this._ls.getWorkspaceForFile(filePath);
const parseResults = workspace.serviceInstance.getParseResult(filePath);
const parseResults = workspace.service.getParseResult(filePath);
if (!parseResults) {
return [];
}
@ -181,7 +181,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
break;
}
case 'types': {
const evaluator = workspace.serviceInstance.getEvaluator();
const evaluator = workspace.service.getEvaluator();
const start = params.arguments[2] as number;
const end = params.arguments[3] as number;
if (!evaluator || !start || !end) {
@ -193,7 +193,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
break;
}
case 'cachedtypes': {
const evaluator = workspace.serviceInstance.getEvaluator();
const evaluator = workspace.service.getEvaluator();
const start = params.arguments[2] as number;
const end = params.arguments[3] as number;
if (!evaluator || !start || !end) {
@ -208,7 +208,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
}
case 'codeflowgraph': {
const evaluator = workspace.serviceInstance.getEvaluator();
const evaluator = workspace.service.getEvaluator();
const offset = params.arguments[2] as number;
if (!evaluator || offset === undefined) {
return [];

View File

@ -27,17 +27,9 @@ export class QuickActionCommand implements ServerCommand {
return [];
}
const editActions = workspace.serviceInstance.performQuickAction(
filePath,
params.command,
otherArgs,
token
);
const editActions = workspace.service.performQuickAction(filePath, params.command, otherArgs, token);
return convertToWorkspaceEdit(
workspace.serviceInstance.fs,
convertToFileTextEdits(filePath, editActions ?? [])
);
return convertToWorkspaceEdit(workspace.service.fs, convertToFileTextEdits(filePath, editActions ?? []));
}
}
}

View File

@ -199,3 +199,26 @@ export function log(console: ConsoleInterface, logType: LogLevel, msg: string) {
debug.fail(`${logType} is not expected`);
}
}
export function convertLogLevel(logLevelValue?: string): LogLevel {
if (!logLevelValue) {
return LogLevel.Info;
}
switch (logLevelValue.toLowerCase()) {
case 'error':
return LogLevel.Error;
case 'warning':
return LogLevel.Warn;
case 'information':
return LogLevel.Info;
case 'trace':
return LogLevel.Log;
default:
return LogLevel.Info;
}
}

View File

@ -87,7 +87,12 @@ export enum DeclarationUseCase {
}
export interface DeclarationProviderExtension {
tryGetDeclarations(node: ParseNode, useCase: DeclarationUseCase, token: CancellationToken): Declaration[];
tryGetDeclarations(
evaluator: TypeEvaluator,
node: ParseNode,
useCase: DeclarationUseCase,
token: CancellationToken
): Declaration[];
}
export interface TypeProviderExtension {
@ -102,6 +107,7 @@ export interface TypeProviderExtension {
export interface CodeActionExtension {
addCodeActions(
evaluator: TypeEvaluator,
filePath: string,
range: Range,
parseResults: ParseResults,

View File

@ -17,5 +17,7 @@ declare interface Promise<T> {
/* eslint-disable @typescript-eslint/no-empty-function */
// Explicitly tells that promise should be run asynchronously.
Promise.prototype.ignoreErrors = function <T>(this: Promise<T>) {
this.catch(() => {});
this.catch((e) => {
console.log(e);
});
};

View File

@ -70,7 +70,6 @@ import {
WatchKind,
WorkDoneProgressReporter,
WorkspaceEdit,
WorkspaceFolder,
WorkspaceSymbol,
WorkspaceSymbolParams,
} from 'vscode-languageserver';
@ -94,7 +93,6 @@ import {
} from './common/commandLineOptions';
import { ConfigOptions, getDiagLevelDiagnosticRules, SignatureDisplayType } from './common/configOptions';
import { ConsoleInterface, ConsoleWithLogLevel, LogLevel } from './common/console';
import { createDeferred } from './common/deferred';
import {
Diagnostic as AnalyzerDiagnostic,
DiagnosticCategory,
@ -109,6 +107,7 @@ import { Host } from './common/host';
import { fromLSPAny } from './common/lspUtils';
import { convertPathToUri, deduplicateFolders, getDirectoryPath, getFileName, isFile } from './common/pathUtils';
import { ProgressReporter, ProgressReportTracker } from './common/progressReporter';
import { hashString } from './common/stringUtils';
import { DocumentRange, Position, Range } from './common/textRange';
import { UriParser } from './common/uriParser';
import { convertToWorkspaceEdit } from './common/workspaceEditUtils';
@ -121,7 +120,7 @@ import { convertHoverResults } from './languageService/hoverProvider';
import { ReferenceCallback } from './languageService/referencesProvider';
import { Localizer, setLocaleOverride } from './localization/localize';
import { PyrightFileSystem } from './pyrightFileSystem';
import { WorkspaceMap } from './workspaceMap';
import { InitStatus, WellKnownWorkspaceKinds, Workspace, WorkspaceFactory } from './workspaceFactory';
export interface ServerSettings {
venvPath?: string | undefined;
@ -151,75 +150,6 @@ export interface ServerSettings {
functionSignatureDisplay?: SignatureDisplayType | undefined;
}
export enum WellKnownWorkspaceKinds {
Default = 'default',
Regular = 'regular',
Limited = 'limited',
Cloned = 'cloned',
Test = 'test',
}
export function createInitStatus(): InitStatus {
// Due to the way we get `python path`, `include/exclude` from settings to initialize workspace,
// we need to wait for getSettings to finish before letting IDE features to use workspace (`isInitialized` field).
// So most of cases, whenever we create new workspace, we send request to workspace/configuration right way
// except one place which is `initialize` LSP call.
// In `initialize` method where we create `initial workspace`, we can't do that since LSP spec doesn't allow
// LSP server from sending any request to client until `initialized` method is called.
// This flag indicates whether we had our initial updateSetting call or not after `initialized` call.
let called = false;
const deferred = createDeferred<void>();
const self = {
promise: deferred.promise,
resolve: () => {
called = true;
deferred.resolve();
},
markCalled: () => {
called = true;
},
reset: () => {
if (!called) {
return self;
}
return createInitStatus();
},
resolved: () => {
return deferred.resolved;
},
};
return self;
}
export interface InitStatus {
resolve(): void;
reset(): InitStatus;
markCalled(): void;
promise: Promise<void>;
resolved(): boolean;
}
// path and uri will point to a workspace itself. It could be a folder
// if the workspace represents a folder. it could be '' if it is the default workspace.
// But it also could be a file if it is a virtual workspace.
// rootPath will always point to the folder that contains the workspace.
export interface WorkspaceServiceInstance {
workspaceName: string;
rootPath: string;
path: string;
uri: string;
kinds: string[];
serviceInstance: AnalyzerService;
disableLanguageServices: boolean;
disableOrganizeImports: boolean;
disableWorkspaceSymbol: boolean;
isInitialized: InitStatus;
searchPathsToWatch: string[];
}
export interface MessageAction {
title: string;
id: string;
@ -237,8 +167,8 @@ export interface WindowInterface {
}
export interface LanguageServerInterface {
getWorkspaceForFile(filePath: string): Promise<WorkspaceServiceInstance>;
getSettings(workspace: WorkspaceServiceInstance): Promise<ServerSettings>;
getWorkspaceForFile(filePath: string): Promise<Workspace>;
getSettings(workspace: Workspace): Promise<ServerSettings>;
createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
reanalyze(): void;
restart(): void;
@ -254,7 +184,6 @@ export interface ServerOptions {
productName: string;
rootDirectory: string;
version: string;
workspaceMap: WorkspaceMap;
cancellationProvider: CancellationProvider;
fileSystem: FileSystem;
fileWatcherHandler: FileWatcherHandler;
@ -262,6 +191,7 @@ export interface ServerOptions {
disableChecker?: boolean;
supportedCommands?: string[];
supportedCodeActions?: string[];
supportsTelemetry?: boolean;
}
export interface WorkspaceServices {
@ -363,7 +293,7 @@ namespace VSDiagnosticRank {
export abstract class LanguageServerBase implements LanguageServerInterface {
protected _defaultClientConfig: any;
protected _workspaceMap: WorkspaceMap;
protected _workspaceFactory: WorkspaceFactory;
protected _cacheManager: CacheManager;
// We support running only one "find all reference" at a time.
@ -378,6 +308,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
private _lastFileWatcherRegistration: Disposable | undefined;
private _initialized = false;
// Global root path - the basis for all global settings.
rootPath = '';
@ -429,11 +361,18 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
this.console.info(`Server root directory: ${_serverOptions.rootDirectory}`);
this._cacheManager = new CacheManager();
this._workspaceMap = this._serverOptions.workspaceMap;
this._serviceFS = new PyrightFileSystem(this._serverOptions.fileSystem);
this._uriParser = uriParserFactory(this._serviceFS);
this._workspaceFactory = new WorkspaceFactory(
this.console,
this._uriParser,
this.createAnalyzerServiceForWorkspace.bind(this),
this.isPythonPathImmutable.bind(this),
this.onWorkspaceCreated.bind(this)
);
// Set the working directory to a known location within
// the extension directory. Otherwise the execution of
// python can have unintended and surprising results.
@ -470,7 +409,14 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token: CancellationToken
): Promise<(Command | CodeAction)[] | undefined | null>;
abstract getSettings(workspace: WorkspaceServiceInstance): Promise<ServerSettings>;
abstract getSettings(workspace: Workspace): Promise<ServerSettings>;
protected isPythonPathImmutable(filePath: string): boolean {
// This function is called to determine if the file is using
// a special pythonPath separate from a workspace or not.
// The default is no.
return false;
}
protected async getConfiguration(scopeUri: string | undefined, section: string) {
if (this.client.hasConfigurationCapability) {
@ -577,7 +523,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
async test_getWorkspaces() {
const workspaces = [...this._workspaceMap.values()];
const workspaces = [...this._workspaceFactory.items()];
for (const workspace of workspaces) {
await workspace.isInitialized.promise;
}
@ -585,19 +531,19 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return workspaces;
}
async getWorkspaceForFile(filePath: string): Promise<WorkspaceServiceInstance> {
return this._workspaceMap.getWorkspaceForFile(this, filePath);
async getWorkspaceForFile(filePath: string, pythonPath?: string): Promise<Workspace> {
return this._workspaceFactory.getWorkspaceForFile(filePath, pythonPath);
}
reanalyze() {
this._workspaceMap.forEach((workspace) => {
workspace.serviceInstance.invalidateAndForceReanalysis();
this._workspaceFactory.items().forEach((workspace) => {
workspace.service.invalidateAndForceReanalysis();
});
}
restart() {
this._workspaceMap.forEach((workspace) => {
workspace.serviceInstance.restart();
this._workspaceFactory.items().forEach((workspace) => {
workspace.service.restart();
});
}
@ -711,17 +657,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
);
// Create a service instance for each of the workspace folders.
if (params.workspaceFolders) {
params.workspaceFolders.forEach((folder) => {
const path = this._uriParser.decodeTextDocumentUri(folder.uri);
this._workspaceMap.set(path, this.createWorkspaceServiceInstance(folder, path, path));
});
} else if (params.rootPath) {
this._workspaceMap.set(
params.rootPath,
this.createWorkspaceServiceInstance(undefined, params.rootPath, params.rootPath)
);
}
this._workspaceFactory.handleInitialize(params);
const result: InitializeResult = {
capabilities: {
@ -771,6 +707,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
protected onInitialized() {
// Mark as initialized. We need this to make sure to
// not send config updates before this point.
this._initialized = true;
if (!this.client.hasWorkspaceFoldersCapability) {
// If folder capability is not supported, initialize ones given by onInitialize.
this.updateSettingsForAllWorkspaces();
@ -778,18 +718,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
this._connection.workspace.onDidChangeWorkspaceFolders((event) => {
event.removed.forEach((workspaceInfo) => {
const rootPath = this._uriParser.decodeTextDocumentUri(workspaceInfo.uri);
this._workspaceMap.delete(rootPath);
});
event.added.forEach((workspaceInfo) => {
const rootPath = this._uriParser.decodeTextDocumentUri(workspaceInfo.uri);
const newWorkspace = this.createWorkspaceServiceInstance(workspaceInfo, rootPath, rootPath);
this._workspaceMap.set(rootPath, newWorkspace);
this.updateSettingsForWorkspace(newWorkspace, newWorkspace.isInitialized).ignoreErrors();
});
this._workspaceFactory.handleWorkspaceFoldersChanged(event);
this._setupFileWatcher();
});
@ -815,7 +744,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Get rid of any search path under workspace root since it is already watched by
// "**" above.
const foldersToWatch = deduplicateFolders(
this._workspaceMap
this._workspaceFactory
.getNonDefaultWorkspaces()
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootPath)))
);
@ -856,7 +785,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token,
this.client.hasGoToDeclarationCapability ? DefinitionFilter.PreferSource : DefinitionFilter.All,
(workspace, filePath, position, filter, token) =>
workspace.serviceInstance.getDefinitionForPosition(filePath, position, filter, token)
workspace.service.getDefinitionForPosition(filePath, position, filter, token)
);
}
@ -869,7 +798,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token,
this.client.hasGoToDeclarationCapability ? DefinitionFilter.PreferStubs : DefinitionFilter.All,
(workspace, filePath, position, filter, token) =>
workspace.serviceInstance.getDefinitionForPosition(filePath, position, filter, token)
workspace.service.getDefinitionForPosition(filePath, position, filter, token)
);
}
@ -878,7 +807,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token: CancellationToken
): Promise<Definition | DefinitionLink[] | undefined | null> {
return this.getDefinitions(params, token, DefinitionFilter.All, (workspace, filePath, position, _, token) =>
workspace.serviceInstance.getTypeDefinitionForPosition(filePath, position, token)
workspace.service.getTypeDefinitionForPosition(filePath, position, token)
);
}
@ -887,7 +816,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token: CancellationToken,
filter: DefinitionFilter,
getDefinitionsFunc: (
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
position: Position,
filter: DefinitionFilter,
@ -908,8 +837,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return undefined;
}
return locations
.filter((loc) => this.canNavigateToFile(loc.path, workspace.serviceInstance.fs))
.map((loc) => Location.create(convertPathToUri(workspace.serviceInstance.fs, loc.path), loc.range));
.filter((loc) => this.canNavigateToFile(loc.path, workspace.service.fs))
.map((loc) => Location.create(convertPathToUri(workspace.service.fs, loc.path), loc.range));
}
protected async onReferences(
@ -948,8 +877,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
const convert = (locs: DocumentRange[]): Location[] => {
return locs
.filter((loc) => this.canNavigateToFile(loc.path, workspace.serviceInstance.fs))
.map((loc) => Location.create(convertPathToUri(workspace.serviceInstance.fs, loc.path), loc.range));
.filter((loc) => this.canNavigateToFile(loc.path, workspace.service.fs))
.map((loc) => Location.create(convertPathToUri(workspace.service.fs, loc.path), loc.range));
};
const locations: Location[] = [];
@ -957,7 +886,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
? (locs) => resultReporter.report(convert(locs))
: (locs) => appendArray(locations, convert(locs));
workspace.serviceInstance.reportReferencesForPosition(
workspace.service.reportReferencesForPosition(
filePath,
position,
params.context.includeDeclaration,
@ -986,7 +915,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
const symbolList: DocumentSymbol[] = [];
workspace.serviceInstance.addSymbolsForDocument(filePath, symbolList, token);
workspace.service.addSymbolsForDocument(filePath, symbolList, token);
if (this.client.hasHierarchicalDocumentSymbolCapability) {
return symbolList;
}
@ -1005,10 +934,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
? (symbols) => resultReporter.report(symbols)
: (symbols) => appendArray(symbolList, symbols);
for (const workspace of this._workspaceMap.values()) {
for (const workspace of this._workspaceFactory.items()) {
await workspace.isInitialized.promise;
if (!workspace.disableLanguageServices && !workspace.disableWorkspaceSymbol) {
workspace.serviceInstance.reportSymbolsForWorkspace(params.query, reporter, token);
workspace.service.reportSymbolsForWorkspace(params.query, reporter, token);
}
}
@ -1019,13 +948,17 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
const { filePath, position } = this._uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const workspace = await this.getWorkspaceForFile(filePath);
const hoverResults = workspace.serviceInstance.getHoverForPosition(
const hoverResults = workspace.service.getHoverForPosition(
filePath,
position,
this.client.hoverContentFormat,
token
);
return convertHoverResults(this.client.hoverContentFormat, hoverResults);
return convertHoverResults(
this.client.hoverContentFormat,
hoverResults,
!!this._serverOptions.supportsTelemetry
);
}
protected async onDocumentHighlight(
@ -1034,7 +967,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
): Promise<DocumentHighlight[] | null | undefined> {
const { filePath, position } = this._uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const workspace = await this.getWorkspaceForFile(filePath);
return workspace.serviceInstance.getDocumentHighlight(filePath, position, token);
return workspace.service.getDocumentHighlight(filePath, position, token);
}
protected async onSignatureHelp(
@ -1047,7 +980,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
if (workspace.disableLanguageServices) {
return;
}
const signatureHelpResults = workspace.serviceInstance.getSignatureHelpForPosition(
const signatureHelpResults = workspace.service.getSignatureHelpForPosition(
filePath,
position,
this.client.signatureDocFormat,
@ -1177,6 +1110,22 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
completions.completionList.isIncomplete = completionIncomplete;
}
// Add memberAccessInfo.lastKnownModule if we have it. The client side
// will use this to send extra telemetry
if (
completions?.memberAccessInfo &&
completions.completionList &&
completions.completionList.items.length > 0 &&
completions.memberAccessInfo.lastKnownModule &&
this._serverOptions.supportsTelemetry
) {
// Just stick it on the first item. It only checks the first one
completions.completionList.items[0].data = {
...completions.completionList.items[0].data,
moduleHash: hashString(completions.memberAccessInfo.lastKnownModule),
};
}
return completions?.completionList;
}
@ -1206,10 +1155,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return null;
}
const result = workspace.serviceInstance.canRenameSymbolAtPosition(
const result = workspace.service.canRenameSymbolAtPosition(
filePath,
position,
workspace.path === '',
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
this.allowModuleRename,
token
);
@ -1228,11 +1177,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return;
}
const editActions = workspace.serviceInstance.renameSymbolAtPosition(
const editActions = workspace.service.renameSymbolAtPosition(
filePath,
position,
params.newName,
workspace.path === '',
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
this.allowModuleRename,
token
);
@ -1241,7 +1190,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return undefined;
}
return convertToWorkspaceEdit(workspace.serviceInstance.fs, editActions);
return convertToWorkspaceEdit(workspace.service.fs, editActions);
}
protected async onPrepare(
@ -1255,17 +1204,17 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return null;
}
const callItem = workspace.serviceInstance.getCallForPosition(filePath, position, token) || null;
const callItem = workspace.service.getCallForPosition(filePath, position, token) || null;
if (!callItem) {
return null;
}
if (!this.canNavigateToFile(callItem.uri, workspace.serviceInstance.fs)) {
if (!this.canNavigateToFile(callItem.uri, workspace.service.fs)) {
return null;
}
// Convert the file path in the item to proper URI.
callItem.uri = convertPathToUri(workspace.serviceInstance.fs, callItem.uri);
callItem.uri = convertPathToUri(workspace.service.fs, callItem.uri);
return [callItem];
}
@ -1278,16 +1227,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return null;
}
let callItems = workspace.serviceInstance.getIncomingCallsForPosition(filePath, position, token) || null;
let callItems = workspace.service.getIncomingCallsForPosition(filePath, position, token) || null;
if (!callItems || callItems.length === 0) {
return null;
}
callItems = callItems.filter((item) => this.canNavigateToFile(item.from.uri, workspace.serviceInstance.fs));
callItems = callItems.filter((item) => this.canNavigateToFile(item.from.uri, workspace.service.fs));
// Convert the file paths in the items to proper URIs.
callItems.forEach((item) => {
item.from.uri = convertPathToUri(workspace.serviceInstance.fs, item.from.uri);
item.from.uri = convertPathToUri(workspace.service.fs, item.from.uri);
});
return callItems;
@ -1304,16 +1253,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return null;
}
let callItems = workspace.serviceInstance.getOutgoingCallsForPosition(filePath, position, token) || null;
let callItems = workspace.service.getOutgoingCallsForPosition(filePath, position, token) || null;
if (!callItems || callItems.length === 0) {
return null;
}
callItems = callItems.filter((item) => this.canNavigateToFile(item.to.uri, workspace.serviceInstance.fs));
callItems = callItems.filter((item) => this.canNavigateToFile(item.to.uri, workspace.service.fs));
// Convert the file paths in the items to proper URIs.
callItems.forEach((item) => {
item.to.uri = convertPathToUri(workspace.serviceInstance.fs, item.to.uri);
item.to.uri = convertPathToUri(workspace.service.fs, item.to.uri);
});
return callItems;
@ -1328,12 +1277,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
const workspace = await this.getWorkspaceForFile(filePath);
workspace.serviceInstance.setFileOpened(
filePath,
params.textDocument.version,
params.textDocument.text,
ipythonMode
);
workspace.service.setFileOpened(filePath, params.textDocument.version, params.textDocument.text, ipythonMode);
}
protected async onDidChangeTextDocument(params: DidChangeTextDocumentParams, ipythonMode = IPythonMode.None) {
@ -1346,7 +1290,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
const workspace = await this.getWorkspaceForFile(filePath);
workspace.serviceInstance.updateOpenFileContents(
workspace.service.updateOpenFileContents(
filePath,
params.textDocument.version,
params.contentChanges,
@ -1362,7 +1306,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
const workspace = await this.getWorkspaceForFile(filePath);
workspace.serviceInstance.setFileClosed(filePath);
workspace.service.setFileClosed(filePath);
}
protected onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) {
@ -1423,17 +1367,17 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
protected onShutdown(token: CancellationToken) {
// Shutdown remaining workspaces.
this._workspaceMap.forEach((_, key) => this._workspaceMap.delete(key));
this._workspaceFactory.clear();
return Promise.resolve();
}
protected resolveWorkspaceCompletionItem(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
item: CompletionItem,
token: CancellationToken
): void {
workspace.serviceInstance.resolveCompletionItem(
workspace.service.resolveCompletionItem(
filePath,
item,
this.getCompletionOptions(workspace),
@ -1443,16 +1387,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
protected getWorkspaceCompletionsForPosition(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
position: Position,
options: CompletionOptions,
token: CancellationToken
): Promise<CompletionResultsList | undefined> {
return workspace.serviceInstance.getCompletionsForPosition(
return workspace.service.getCompletionsForPosition(
filePath,
position,
workspace.path,
workspace.rootPath,
options,
undefined,
token
@ -1461,7 +1405,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
updateSettingsForAllWorkspaces(): void {
const tasks: Promise<void>[] = [];
this._workspaceMap.forEach((workspace) => {
this._workspaceFactory.items().forEach((workspace) => {
// Updating settings can change workspace's file ownership. Make workspace uninitialized so that
// features can wait until workspace gets new settings.
// the file's ownership can also changed by `pyrightconfig.json` changes, but those are synchronous
@ -1475,7 +1419,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
});
}
protected getCompletionOptions(workspace: WorkspaceServiceInstance, params?: CompletionParams): CompletionOptions {
protected getCompletionOptions(workspace: Workspace, params?: CompletionParams): CompletionOptions {
return {
format: this.client.completionDocFormat,
snippet: this.client.completionSupportsSnippet,
@ -1488,48 +1432,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
};
}
protected createWorkspaceServiceInstance(
workspaceFolder: WorkspaceFolder | undefined,
rootPath: string,
path: string,
kinds: string[] = [WellKnownWorkspaceKinds.Regular],
services?: WorkspaceServices
): WorkspaceServiceInstance {
// 5 seconds default
const defaultBackOffTime = 5 * 1000;
// 10 seconds back off for multi workspace.
const multiWorkspaceBackOffTime = 10 * 1000;
const libraryReanalysisTimeProvider =
kinds.length === 1 && kinds[0] === WellKnownWorkspaceKinds.Regular
? () =>
this._workspaceMap.hasMultipleWorkspaces(kinds[0])
? multiWorkspaceBackOffTime
: defaultBackOffTime
: () => defaultBackOffTime;
const rootUri = workspaceFolder?.uri ?? '';
return {
workspaceName: workspaceFolder?.name ?? '',
rootPath,
path,
uri: rootUri,
kinds,
serviceInstance: this.createAnalyzerService(
workspaceFolder?.name ?? path,
services,
libraryReanalysisTimeProvider
),
disableLanguageServices: false,
disableOrganizeImports: false,
disableWorkspaceSymbol: false,
isInitialized: createInitStatus(),
searchPathsToWatch: [],
};
}
protected convertDiagnostics(fs: FileSystem, fileDiagnostics: FileDiagnostics): PublishDiagnosticsParams[] {
return [
{
@ -1577,7 +1479,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
async updateSettingsForWorkspace(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
status: InitStatus | undefined,
serverSettings?: ServerSettings
): Promise<void> {
@ -1588,7 +1490,12 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Set logging level first.
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
// Apply the new path to the workspace (before restarting the service).
serverSettings.pythonPath = this._workspaceFactory.applyPythonPath(workspace, serverSettings.pythonPath);
// Then use the updated settings to restart the service.
this.updateOptionsAndRestartService(workspace, serverSettings);
workspace.disableLanguageServices = !!serverSettings.disableLanguageServices;
workspace.disableOrganizeImports = !!serverSettings.disableOrganizeImports;
@ -1599,35 +1506,45 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
updateOptionsAndRestartService(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
serverSettings: ServerSettings,
typeStubTargetImportName?: string
) {
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
workspace.searchPathsToWatch = workspace.serviceInstance.librarySearchPathsToWatch ?? [];
workspace.searchPathsToWatch = workspace.service.librarySearchPathsToWatch ?? [];
}
protected convertLogLevel(logLevelValue?: string): LogLevel {
if (!logLevelValue) {
return LogLevel.Info;
protected onWorkspaceCreated(workspace: Workspace) {
// Update settings on this workspace (but only if initialize has happened)
if (this._initialized) {
this.updateSettingsForWorkspace(workspace, workspace.isInitialized).ignoreErrors();
}
switch (logLevelValue.toLowerCase()) {
case 'error':
return LogLevel.Error;
// Otherwise the intiailize completion should cause settings to be updated on all workspaces.
}
case 'warning':
return LogLevel.Warn;
protected createAnalyzerServiceForWorkspace(
name: string,
_rootPath: string,
_uri: string,
kinds: string[],
services?: WorkspaceServices
): AnalyzerService {
// 5 seconds default
const defaultBackOffTime = 5 * 1000;
case 'information':
return LogLevel.Info;
// 10 seconds back off for multi workspace.
const multiWorkspaceBackOffTime = 10 * 1000;
case 'trace':
return LogLevel.Log;
const libraryReanalysisTimeProvider =
kinds.length === 1 && kinds[0] === WellKnownWorkspaceKinds.Regular
? () =>
this._workspaceFactory.hasMultipleWorkspaces(kinds[0])
? multiWorkspaceBackOffTime
: defaultBackOffTime
: () => defaultBackOffTime;
default:
return LogLevel.Info;
}
return this.createAnalyzerService(name, services, libraryReanalysisTimeProvider);
}
private _sendDiagnostics(params: PublishDiagnosticsParams[]) {
@ -1775,8 +1692,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Tell all of the services that the user is actively
// interacting with one or more editors, so they should
// back off from performing any work.
this._workspaceMap.forEach((workspace: { serviceInstance: { recordUserInteractionTime: () => void } }) => {
workspace.serviceInstance.recordUserInteractionTime();
this._workspaceFactory.items().forEach((workspace: { service: { recordUserInteractionTime: () => void } }) => {
workspace.service.recordUserInteractionTime();
});
}

View File

@ -13,13 +13,8 @@ import { CommandLineOptions } from '../common/commandLineOptions';
import { LogLevel } from '../common/console';
import { FileSystem } from '../common/fileSystem';
import { combinePaths } from '../common/pathUtils';
import {
createInitStatus,
LanguageServerInterface,
ServerSettings,
WellKnownWorkspaceKinds,
WorkspaceServiceInstance,
} from '../languageServerBase';
import { LanguageServerInterface, ServerSettings } from '../languageServerBase';
import { createInitStatus, WellKnownWorkspaceKinds, Workspace } from '../workspaceFactory';
export interface CloneOptions {
useBackgroundAnalysis?: boolean;
@ -30,7 +25,7 @@ export interface CloneOptions {
export class AnalyzerServiceExecutor {
static runWithOptions(
languageServiceRootPath: string,
workspace: WorkspaceServiceInstance,
workspace: Workspace,
serverSettings: ServerSettings,
typeStubTargetImportName?: string,
trackFiles = true
@ -44,12 +39,12 @@ export class AnalyzerServiceExecutor {
);
// Setting options causes the analyzer service to re-analyze everything.
workspace.serviceInstance.setOptions(commandLineOptions);
workspace.service.setOptions(commandLineOptions);
}
static async cloneService(
ls: LanguageServerInterface,
workspace: WorkspaceServiceInstance,
workspace: Workspace,
options?: CloneOptions
): Promise<AnalyzerService> {
// Allocate a temporary pseudo-workspace to perform this job.
@ -58,13 +53,15 @@ export class AnalyzerServiceExecutor {
options = options ?? {};
const tempWorkspace: WorkspaceServiceInstance = {
const tempWorkspace: Workspace = {
...workspace,
workspaceName: `temp workspace for cloned service`,
rootPath: workspace.rootPath,
path: workspace.path,
uri: workspace.uri,
pythonPath: workspace.pythonPath,
pythonPathKind: workspace.pythonPathKind,
kinds: [...workspace.kinds, WellKnownWorkspaceKinds.Cloned],
serviceInstance: workspace.serviceInstance.clone(
service: workspace.service.clone(
instanceName,
serviceId,
options.useBackgroundAnalysis ? ls.createBackgroundAnalysis(serviceId) : undefined,
@ -86,7 +83,7 @@ export class AnalyzerServiceExecutor {
/* trackFiles */ false
);
return tempWorkspace.serviceInstance;
return tempWorkspace.service;
}
}

View File

@ -20,12 +20,12 @@ import { FileEditActions } from '../common/editAction';
import { convertPathToUri, getShortenedFileName } from '../common/pathUtils';
import { Range } from '../common/textRange';
import { convertToWorkspaceEdit } from '../common/workspaceEditUtils';
import { WorkspaceServiceInstance } from '../languageServerBase';
import { Localizer } from '../localization/localize';
import { Workspace } from '../workspaceFactory';
export class CodeActionProvider {
static async getCodeActionsForPosition(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
range: Range,
kinds: CodeActionKind[] | undefined,
@ -36,7 +36,7 @@ export class CodeActionProvider {
const codeActions: CodeAction[] = [];
if (!workspace.disableLanguageServices) {
const diags = await workspace.serviceInstance.getDiagnosticsForRange(filePath, range, token);
const diags = await workspace.service.getDiagnosticsForRange(filePath, range, token);
const typeStubDiag = diags.find((d) => {
const actions = d.getActions();
return actions && actions.find((a) => a.action === Commands.createTypeStub);
@ -52,7 +52,7 @@ export class CodeActionProvider {
Command.create(
Localizer.CodeAction.createTypeStub(),
Commands.createTypeStub,
workspace.path,
workspace.rootPath,
action.moduleName,
filePath
),
@ -72,7 +72,7 @@ export class CodeActionProvider {
.getActions()!
.find((a) => a.action === Commands.addMissingOptionalToParam) as AddMissingOptionalToParamAction;
if (action) {
const fs = workspace.serviceInstance.getImportResolver().fileSystem;
const fs = workspace.service.getImportResolver().fileSystem;
const addMissingOptionalAction = CodeAction.create(
Localizer.CodeAction.addOptionalToAnnotation(),
Command.create(
@ -99,7 +99,7 @@ export class CodeActionProvider {
oldFile: getShortenedFileName(action.oldFile),
newFile: getShortenedFileName(action.newFile),
});
const fs = workspace.serviceInstance.getImportResolver().fileSystem;
const fs = workspace.service.getImportResolver().fileSystem;
const editActions: FileEditActions = {
edits: [],
fileOperations: [

View File

@ -1509,10 +1509,8 @@ export class CompletionProvider {
});
}
// If we don't know this type, look for a module we should stub.
if (!leftType || isUnknown(leftType) || isUnbound(leftType)) {
memberAccessInfo = this._getLastKnownModule(leftExprNode, leftType);
}
// Save member access info for every request
memberAccessInfo = this._getLastKnownModule(leftExprNode, leftType);
return { completionMap, memberAccessInfo };
}
@ -1535,7 +1533,7 @@ export class CompletionProvider {
curNode.nodeType === ParseNodeType.MemberAccess ? curNode?.memberName.value ?? '' : '';
}
} else {
curNode = undefined;
break;
}
if (curNode) {

View File

@ -71,6 +71,7 @@ export class DefinitionProvider {
Extensions.getProgramExtensions(node).forEach((e) => {
if (e.declarationProviderExtension) {
const declarations = e.declarationProviderExtension.tryGetDeclarations(
evaluator,
node,
DeclarationUseCase.Definition,
token

View File

@ -27,7 +27,9 @@ import {
import { getModuleNode, getStringNodeValueRange } from '../analyzer/parseTreeUtils';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { ScopeType } from '../analyzer/scope';
import * as ScopeUtils from '../analyzer/scopeUtils';
import { SourceFile } from '../analyzer/sourceFile';
import { isStubFile, SourceMapper } from '../analyzer/sourceMapper';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { isInstantiableClass, TypeCategory } from '../analyzer/types';
@ -103,7 +105,8 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
resolveLocalName: boolean,
useCase: DocumentSymbolCollectorUseCase,
token: CancellationToken,
sourceMapper?: SourceMapper
sourceMapper?: SourceMapper,
implicitlyImportedBy?: SourceFile[]
): Declaration[] {
throwIfCancellationRequested(token);
@ -114,6 +117,30 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
token,
/*skipUnreachableCode*/ false
);
// Add declarations from chained source files
let builtinsScope = AnalyzerNodeInfo.getFileInfo(node).builtinsScope;
while (builtinsScope && builtinsScope.type === ScopeType.Module) {
const symbol = builtinsScope?.lookUpSymbol(node.value);
if (symbol) {
declarations.push(...symbol.getDeclarations());
}
builtinsScope = builtinsScope?.parent;
}
// Add declarations from files that implicitly import the target file.
implicitlyImportedBy?.forEach((implicitImport) => {
const parseTree = implicitImport.getParseResults()?.parseTree;
if (parseTree) {
const scope = AnalyzerNodeInfo.getScope(parseTree);
const symbol = scope?.lookUpSymbol(node.value);
if (symbol) {
declarations.push(...symbol.getDeclarations());
}
}
});
const resolvedDeclarations: Declaration[] = [];
declarations.forEach((decl) => {
const resolvedDecl = evaluator.resolveAliasDeclaration(decl, resolveLocalName);
@ -427,7 +454,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
useCase === DocumentSymbolCollectorUseCase.Rename
? DeclarationUseCase.Rename
: DeclarationUseCase.References;
const extras = e.declarationProviderExtension?.tryGetDeclarations(node, declUseCase, token);
const extras = e.declarationProviderExtension?.tryGetDeclarations(evaluator, node, declUseCase, token);
if (extras && extras.length > 0) {
result.push(...extras);
}

View File

@ -43,6 +43,7 @@ import { SignatureDisplayType } from '../common/configOptions';
import { assertNever, fail } from '../common/debug';
import { DeclarationUseCase, Extensions } from '../common/extensibility';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { hashString } from '../common/stringUtils';
import { Position, Range } from '../common/textRange';
import { TextRange } from '../common/textRange';
import { ExpressionNode, isExpressionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes';
@ -61,6 +62,7 @@ export interface HoverTextPart {
export interface HoverResults {
parts: HoverTextPart[];
lastKnownModule?: string;
range: Range;
}
@ -100,6 +102,7 @@ export class HoverProvider {
.map(
(e) =>
e.declarationProviderExtension?.tryGetDeclarations(
evaluator,
node,
DeclarationUseCase.Definition,
token
@ -140,6 +143,10 @@ export class HoverProvider {
functionSignatureDisplay,
token
);
// Add the lastKnownModule for this declaration. We'll use this
// in telemetry for hover.
results.lastKnownModule = primaryDeclaration.moduleName;
} else if (!node.parent || node.parent.nodeType !== ParseNodeType.ModuleName) {
// If we had no declaration, see if we can provide a minimal tooltip. We'll skip
// this if it's part of a module name, since a module name part with no declaration
@ -631,12 +638,16 @@ export class HoverProvider {
}
}
export function convertHoverResults(format: MarkupKind, hoverResults: HoverResults | undefined): Hover | undefined {
export function convertHoverResults(
format: MarkupKind,
hoverResults: HoverResults | undefined,
includeHash?: boolean
): Hover | undefined {
if (!hoverResults) {
return undefined;
}
const markupString = hoverResults.parts
let markupString = hoverResults.parts
.map((part) => {
if (part.python) {
if (format === MarkupKind.Markdown) {
@ -652,6 +663,12 @@ export function convertHoverResults(format: MarkupKind, hoverResults: HoverResul
.join('')
.trimEnd();
// If we have a lastKnownModule in the hover results, stick in a comment with
// the hashed module name. This is used by the other side to send telemetry.
if (hoverResults.lastKnownModule && format === MarkupKind.Markdown && includeHash) {
markupString += `<!--moduleHash:${hashString(hoverResults.lastKnownModule)}-->`;
}
return {
contents: {
kind: format,

View File

@ -13,6 +13,7 @@ import { CancellationToken } from 'vscode-languageserver';
import { Declaration, DeclarationType, isAliasDeclaration } from '../analyzer/declaration';
import { getNameFromDeclaration } from '../analyzer/declarationUtils';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { SourceFile } from '../analyzer/sourceFile';
import { SourceMapper } from '../analyzer/sourceMapper';
import { Symbol } from '../analyzer/symbol';
import { isVisibleExternally } from '../analyzer/symbolUtils';
@ -144,7 +145,8 @@ export class ReferencesProvider {
evaluator: TypeEvaluator,
reporter: ReferenceCallback | undefined,
useCase: DocumentSymbolCollectorUseCase,
token: CancellationToken
token: CancellationToken,
implicitlyImportedBy?: SourceFile[]
) {
throwIfCancellationRequested(token);
@ -154,7 +156,8 @@ export class ReferencesProvider {
/* resolveLocalNames */ false,
useCase,
token,
sourceMapper
sourceMapper,
implicitlyImportedBy
);
if (declarations.length === 0) {
@ -184,7 +187,8 @@ export class ReferencesProvider {
evaluator: TypeEvaluator,
reporter: ReferenceCallback | undefined,
useCase: DocumentSymbolCollectorUseCase,
token: CancellationToken
token: CancellationToken,
implicitlyImportedBy?: SourceFile[]
): ReferencesResult | undefined {
throwIfCancellationRequested(token);
@ -203,7 +207,16 @@ export class ReferencesProvider {
return undefined;
}
return this.getDeclarationForNode(sourceMapper, filePath, node, evaluator, reporter, useCase, token);
return this.getDeclarationForNode(
sourceMapper,
filePath,
node,
evaluator,
reporter,
useCase,
token,
implicitlyImportedBy
);
}
static addReferences(

View File

@ -23,7 +23,7 @@ import { BackgroundAnalysisBase } from './backgroundAnalysisBase';
import { CommandController } from './commands/commandController';
import { getCancellationFolderName } from './common/cancellationUtils';
import { ConfigOptions, SignatureDisplayType } from './common/configOptions';
import { ConsoleWithLogLevel, LogLevel } from './common/console';
import { ConsoleWithLogLevel, convertLogLevel, LogLevel } from './common/console';
import { isDebugMode, isString } from './common/core';
import { expandPathVariables } from './common/envVarUtils';
import { FileBasedCancellationProvider } from './common/fileBasedCancellationUtils';
@ -33,9 +33,9 @@ import { Host } from './common/host';
import { resolvePaths } from './common/pathUtils';
import { ProgressReporter } from './common/progressReporter';
import { createFromRealFileSystem, WorkspaceFileWatcherProvider } from './common/realFileSystem';
import { LanguageServerBase, ServerSettings, WorkspaceServiceInstance } from './languageServerBase';
import { LanguageServerBase, ServerSettings } from './languageServerBase';
import { CodeActionProvider } from './languageService/codeActionProvider';
import { WorkspaceMap } from './workspaceMap';
import { Workspace } from './workspaceFactory';
const maxAnalysisTimeInForeground = { openFilesTimeInMs: 50, noOpenFilesTimeInMs: 200 };
@ -52,7 +52,6 @@ export class PyrightServer extends LanguageServerBase {
const rootDirectory = (global as any).__rootDirectory || __dirname;
const console = new ConsoleWithLogLevel(connection.console);
const workspaceMap = new WorkspaceMap();
const fileWatcherProvider = new WorkspaceFileWatcherProvider();
const fileSystem = createFromRealFileSystem(console, fileWatcherProvider);
@ -61,7 +60,6 @@ export class PyrightServer extends LanguageServerBase {
productName: 'Pyright',
rootDirectory,
version,
workspaceMap,
fileSystem,
fileWatcherHandler: fileWatcherProvider,
cancellationProvider: new FileBasedCancellationProvider('bg'),
@ -75,7 +73,7 @@ export class PyrightServer extends LanguageServerBase {
this._controller = new CommandController(this);
}
async getSettings(workspace: WorkspaceServiceInstance): Promise<ServerSettings> {
async getSettings(workspace: Workspace): Promise<ServerSettings> {
const serverSettings: ServerSettings = {
watchForSourceChanges: true,
watchForLibraryChanges: true,
@ -154,7 +152,7 @@ export class PyrightServer extends LanguageServerBase {
serverSettings.useLibraryCodeForTypes = !!pythonAnalysisSection.useLibraryCodeForTypes;
}
serverSettings.logLevel = this.convertLogLevel(pythonAnalysisSection.logLevel);
serverSettings.logLevel = convertLogLevel(pythonAnalysisSection.logLevel);
serverSettings.autoSearchPaths = !!pythonAnalysisSection.autoSearchPaths;
const extraPaths = pythonAnalysisSection.extraPaths;

View File

@ -0,0 +1,84 @@
/*
* cacheManager.test.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Unit tests for cache manager
*/
import assert from 'assert';
import { CacheManager, CacheOwner } from '../analyzer/cacheManager';
test('basic', () => {
const manager = new CacheManager();
const mock = new MockCacheOwner(10);
manager.registerCacheOwner(mock);
assert.strictEqual(manager.getCacheUsage(), 10);
manager.unregisterCacheOwner(mock);
assert.strictEqual(manager.getCacheUsage(), 0);
});
test('nested stopTracking', () => {
const manager = new CacheManager();
const mock = new MockCacheOwner(10);
manager.registerCacheOwner(mock);
assert.strictEqual(manager.getCacheUsage(), 10);
const handle1 = manager.pauseTracking();
assert.strictEqual(manager.getCacheUsage(), -1);
// nested
const handle2 = manager.pauseTracking();
assert.strictEqual(manager.getCacheUsage(), -1);
handle2.dispose();
assert.strictEqual(manager.getCacheUsage(), -1);
handle1.dispose();
assert.strictEqual(manager.getCacheUsage(), 10);
manager.unregisterCacheOwner(mock);
assert.strictEqual(manager.getCacheUsage(), 0);
});
test('multiple owners', () => {
const manager = new CacheManager();
const mock1 = new MockCacheOwner(10);
const mock2 = new MockCacheOwner(20);
manager.registerCacheOwner(mock1);
assert.strictEqual(manager.getCacheUsage(), 10);
manager.registerCacheOwner(mock2);
assert.strictEqual(manager.getCacheUsage(), 30);
const handle = manager.pauseTracking();
assert.strictEqual(manager.getCacheUsage(), -1);
manager.unregisterCacheOwner(mock1);
assert.strictEqual(manager.getCacheUsage(), -1);
handle.dispose();
assert.strictEqual(manager.getCacheUsage(), 20);
manager.unregisterCacheOwner(mock2);
assert.strictEqual(manager.getCacheUsage(), 0);
});
class MockCacheOwner implements CacheOwner {
constructor(private _used: number) {
// empty
}
getCacheUsage(): number {
return this._used;
}
emptyCache(): void {
this._used = 0;
}
}

View File

@ -812,10 +812,10 @@ test('completion quote trigger', async () => {
triggerCharacter: '"',
};
const result = await state.workspace.serviceInstance.getCompletionsForPosition(
const result = await state.workspace.service.getCompletionsForPosition(
filePath,
position,
state.workspace.path,
state.workspace.rootPath,
options,
undefined,
CancellationToken.None
@ -854,10 +854,10 @@ test('completion quote trigger - middle', async () => {
triggerCharacter: "'",
};
const result = await state.workspace.serviceInstance.getCompletionsForPosition(
const result = await state.workspace.service.getCompletionsForPosition(
filePath,
position,
state.workspace.path,
state.workspace.rootPath,
options,
undefined,
CancellationToken.None

View File

@ -25,16 +25,14 @@ import * as debug from '../../../common/debug';
import { FileSystem } from '../../../common/fileSystem';
import { Range } from '../../../common/textRange';
import { UriParser } from '../../../common/uriParser';
import { LanguageServerInterface, MessageAction, ServerSettings, WindowInterface } from '../../../languageServerBase';
import { CodeActionProvider } from '../../../languageService/codeActionProvider';
import {
createInitStatus,
LanguageServerInterface,
MessageAction,
ServerSettings,
WellKnownWorkspaceKinds,
WindowInterface,
WorkspaceServiceInstance,
} from '../../../languageServerBase';
import { CodeActionProvider } from '../../../languageService/codeActionProvider';
Workspace,
WorkspacePythonPathKind,
} from '../../../workspaceFactory';
import { TestAccessHost } from '../testAccessHost';
import { HostSpecificFeatures } from './testState';
@ -59,12 +57,12 @@ export class TestFeatures implements HostSpecificFeatures {
cacheManager
);
runIndexer(workspace: WorkspaceServiceInstance, noStdLib: boolean, options?: string): void {
runIndexer(workspace: Workspace, noStdLib: boolean, options?: string): void {
/* empty */
}
getCodeActionsForPosition(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
range: Range,
token: CancellationToken
@ -78,20 +76,21 @@ export class TestFeatures implements HostSpecificFeatures {
}
export class TestLanguageService implements LanguageServerInterface {
private readonly _workspace: WorkspaceServiceInstance;
private readonly _defaultWorkspace: WorkspaceServiceInstance;
private readonly _workspace: Workspace;
private readonly _defaultWorkspace: Workspace;
private readonly _uriParser: UriParser;
constructor(workspace: WorkspaceServiceInstance, readonly console: ConsoleInterface, readonly fs: FileSystem) {
constructor(workspace: Workspace, readonly console: ConsoleInterface, readonly fs: FileSystem) {
this._workspace = workspace;
this._uriParser = new UriParser(this.fs);
this._defaultWorkspace = {
workspaceName: '',
rootPath: '',
path: '',
uri: '',
pythonPath: undefined,
pythonPathKind: WorkspacePythonPathKind.Mutable,
kinds: [WellKnownWorkspaceKinds.Test],
serviceInstance: new AnalyzerService('test service', this.fs, {
service: new AnalyzerService('test service', this.fs, {
console: this.console,
hostFactory: () => new TestAccessHost(),
importResolverFactory: AnalyzerService.createImportResolver,
@ -108,7 +107,7 @@ export class TestLanguageService implements LanguageServerInterface {
return this._uriParser.decodeTextDocumentUri(uriString);
}
getWorkspaceForFile(filePath: string): Promise<WorkspaceServiceInstance> {
getWorkspaceForFile(filePath: string): Promise<Workspace> {
if (filePath.startsWith(this._workspace.rootPath)) {
return Promise.resolve(this._workspace);
}
@ -116,16 +115,16 @@ export class TestLanguageService implements LanguageServerInterface {
return Promise.resolve(this._defaultWorkspace);
}
getSettings(workspace: WorkspaceServiceInstance): Promise<ServerSettings> {
getSettings(_workspace: Workspace): Promise<ServerSettings> {
const settings: ServerSettings = {
venvPath: this._workspace.serviceInstance.getConfigOptions().venvPath,
pythonPath: this._workspace.serviceInstance.getConfigOptions().pythonPath,
typeshedPath: this._workspace.serviceInstance.getConfigOptions().typeshedPath,
openFilesOnly: this._workspace.serviceInstance.getConfigOptions().checkOnlyOpenFiles,
useLibraryCodeForTypes: this._workspace.serviceInstance.getConfigOptions().useLibraryCodeForTypes,
venvPath: this._workspace.service.getConfigOptions().venvPath,
pythonPath: this._workspace.service.getConfigOptions().pythonPath,
typeshedPath: this._workspace.service.getConfigOptions().typeshedPath,
openFilesOnly: this._workspace.service.getConfigOptions().checkOnlyOpenFiles,
useLibraryCodeForTypes: this._workspace.service.getConfigOptions().useLibraryCodeForTypes,
disableLanguageServices: this._workspace.disableLanguageServices,
autoImportCompletions: this._workspace.serviceInstance.getConfigOptions().autoImportCompletions,
functionSignatureDisplay: this._workspace.serviceInstance.getConfigOptions().functionSignatureDisplay,
autoImportCompletions: this._workspace.service.getConfigOptions().autoImportCompletions,
functionSignatureDisplay: this._workspace.service.getConfigOptions().functionSignatureDisplay,
};
return Promise.resolve(settings);

View File

@ -50,12 +50,7 @@ import {
import { convertOffsetToPosition, convertPositionToOffset } from '../../../common/positionUtils';
import { DocumentRange, Position, Range as PositionRange, rangesAreEqual, TextRange } from '../../../common/textRange';
import { TextRangeCollection } from '../../../common/textRangeCollection';
import {
createInitStatus,
LanguageServerInterface,
WellKnownWorkspaceKinds,
WorkspaceServiceInstance,
} from '../../../languageServerBase';
import { LanguageServerInterface } from '../../../languageServerBase';
import { AbbreviationInfo, ImportFormat } from '../../../languageService/autoImporter';
import { CompletionOptions } from '../../../languageService/completionProvider';
import { DefinitionFilter } from '../../../languageService/definitionProvider';
@ -64,6 +59,12 @@ import { ParseNode } from '../../../parser/parseNodes';
import { ParseResults } from '../../../parser/parser';
import { Tokenizer } from '../../../parser/tokenizer';
import { PyrightFileSystem } from '../../../pyrightFileSystem';
import {
createInitStatus,
WellKnownWorkspaceKinds,
Workspace,
WorkspacePythonPathKind,
} from '../../../workspaceFactory';
import { TestAccessHost } from '../testAccessHost';
import * as host from '../testHost';
import { stringify } from '../utils';
@ -93,9 +94,9 @@ export interface HostSpecificFeatures {
importResolverFactory: ImportResolverFactory;
backgroundAnalysisProgramFactory: BackgroundAnalysisProgramFactory;
runIndexer(workspace: WorkspaceServiceInstance, noStdLib: boolean, options?: string): void;
runIndexer(workspace: Workspace, noStdLib: boolean, options?: string): void;
getCodeActionsForPosition(
workspace: WorkspaceServiceInstance,
workspace: Workspace,
filePath: string,
range: PositionRange,
token: CancellationToken
@ -113,7 +114,7 @@ export class TestState {
readonly testFS: vfs.TestFileSystem;
readonly fs: PyrightFileSystem;
readonly workspace: WorkspaceServiceInstance;
readonly workspace: Workspace;
readonly console: ConsoleInterface;
readonly rawConfigJson: any | undefined;
@ -170,10 +171,11 @@ export class TestState {
this.workspace = {
workspaceName: 'test workspace',
rootPath: vfsInfo.projectRoot,
path: vfsInfo.projectRoot,
pythonPath: undefined,
pythonPathKind: WorkspacePythonPathKind.Mutable,
uri: convertPathToUri(this.fs, vfsInfo.projectRoot),
kinds: [WellKnownWorkspaceKinds.Test],
serviceInstance: service,
service: service,
disableLanguageServices: false,
disableOrganizeImports: false,
disableWorkspaceSymbol: false,
@ -203,19 +205,19 @@ export class TestState {
}
get importResolver(): ImportResolver {
return this.workspace.serviceInstance.getImportResolver();
return this.workspace.service.getImportResolver();
}
get configOptions(): ConfigOptions {
return this.workspace.serviceInstance.getConfigOptions();
return this.workspace.service.getConfigOptions();
}
get program(): Program {
return this.workspace.serviceInstance.test_program;
return this.workspace.service.test_program;
}
dispose() {
this.workspace.serviceInstance.dispose();
this.workspace.service.dispose();
}
cwd() {
@ -650,7 +652,7 @@ export class TestState {
verifyCodeActionCount?: boolean
): Promise<any> {
// make sure we don't use cache built from other tests
this.workspace.serviceInstance.invalidateAndForceReanalysis();
this.workspace.service.invalidateAndForceReanalysis();
this.analyze();
for (const range of this.getRanges()) {
@ -932,10 +934,10 @@ export class TestState {
includeUserSymbolsInAutoImport: false,
};
const nameMap = abbrMap ? new Map<string, AbbreviationInfo>(Object.entries(abbrMap)) : undefined;
const result = await this.workspace.serviceInstance.getCompletionsForPosition(
const result = await this.workspace.service.getCompletionsForPosition(
filePath,
completionPosition,
this.workspace.path,
this.workspace.rootPath,
options,
nameMap,
CancellationToken.None
@ -979,7 +981,7 @@ export class TestState {
if (expected.additionalTextEdits !== undefined) {
if (actual.additionalTextEdits === undefined) {
this.workspace.serviceInstance.resolveCompletionItem(
this.workspace.service.resolveCompletionItem(
filePath,
actual,
options,
@ -993,7 +995,7 @@ export class TestState {
if (expected.documentation !== undefined) {
if (actual.documentation === undefined && actual.data) {
this.workspace.serviceInstance.resolveCompletionItem(
this.workspace.service.resolveCompletionItem(
filePath,
actual,
options,

View File

@ -180,7 +180,7 @@ test('symbol must be from user files', () => {
`;
const state = parseAndGetTestState(code).state;
while (state.workspace.serviceInstance.test_program.analyze());
while (state.workspace.service.test_program.analyze());
const actions = state.program.moveSymbolAtPosition(
state.getMarkerByName('marker').fileName,

View File

@ -70,10 +70,10 @@ test('excluded but still part of program', () => {
const state = parseAndGetTestState(code, '/projectRoot').state;
const marker = state.getMarkerByName('marker');
while (state.workspace.serviceInstance.test_program.analyze());
while (state.workspace.service.test_program.analyze());
assert.strictEqual(
state.workspace.serviceInstance.test_shouldHandleSourceFileWatchChanges(marker.fileName, /* isFile */ true),
state.workspace.service.test_shouldHandleSourceFileWatchChanges(marker.fileName, /* isFile */ true),
true
);
});
@ -87,7 +87,7 @@ test('random folder changed', () => {
const state = parseAndGetTestState(code, '/projectRoot').state;
assert.strictEqual(
state.workspace.serviceInstance.test_shouldHandleSourceFileWatchChanges('/randomFolder', /* isFile */ false),
state.workspace.service.test_shouldHandleSourceFileWatchChanges('/randomFolder', /* isFile */ false),
false
);
});
@ -185,5 +185,5 @@ function testSourceFileWatchChange(code: string, expected = true, isFile = true)
const marker = state.getMarkerByName('marker');
const path = isFile ? marker.fileName : getDirectoryPath(marker.fileName);
assert.strictEqual(state.workspace.serviceInstance.test_shouldHandleSourceFileWatchChanges(path, isFile), expected);
assert.strictEqual(state.workspace.service.test_shouldHandleSourceFileWatchChanges(path, isFile), expected);
}

View File

@ -76,10 +76,10 @@ function checkSignatureHelp(code: string, expects: boolean) {
const state = parseAndGetTestState(code).state;
const marker = state.getMarkerByName('marker');
const parseResults = state.workspace.serviceInstance.getParseResult(marker.fileName)!;
const parseResults = state.workspace.service.getParseResult(marker.fileName)!;
const position = convertOffsetToPosition(marker.position, parseResults.tokenizerOutput.lines);
const actual = state.workspace.serviceInstance.getSignatureHelpForPosition(
const actual = state.workspace.service.getSignatureHelpForPosition(
marker.fileName,
position,
MarkupKind.Markdown,

View File

@ -36,13 +36,10 @@ test('Empty Open file', () => {
const marker = state.getMarkerByName('marker');
assert.strictEqual(
state.workspace.serviceInstance.test_program.getSourceFile(marker.fileName)?.getFileContent(),
state.workspace.service.test_program.getSourceFile(marker.fileName)?.getFileContent(),
'# Content'
);
state.workspace.serviceInstance.updateOpenFileContents(marker.fileName, 1, [{ text: '' }]);
assert.strictEqual(
state.workspace.serviceInstance.test_program.getSourceFile(marker.fileName)?.getFileContent(),
''
);
state.workspace.service.updateOpenFileContents(marker.fileName, 1, [{ text: '' }]);
assert.strictEqual(state.workspace.service.test_program.getSourceFile(marker.fileName)?.getFileContent(), '');
});

View File

@ -171,7 +171,7 @@ test('test generateWorkspaceEdits', async () => {
assert.strictEqual(fileChanged.size, 2);
const actualEdits = generateWorkspaceEdit(state.workspace.serviceInstance, cloned, fileChanged);
const actualEdits = generateWorkspaceEdit(state.workspace.service, cloned, fileChanged);
verifyWorkspaceEdit(
{
changes: {
@ -195,7 +195,7 @@ test('test generateWorkspaceEdits', async () => {
async function getClonedService(state: TestState) {
return await AnalyzerServiceExecutor.cloneService(
new TestLanguageService(state.workspace, state.console, state.workspace.serviceInstance.fs),
new TestLanguageService(state.workspace, state.console, state.workspace.service.fs),
state.workspace,
{ useBackgroundAnalysis: false }
);

View File

@ -0,0 +1,542 @@
/*
* workspaceFactory.ts
*
* Workspace management related functionality.
*/
import { InitializeParams, WorkspaceFoldersChangeEvent } from 'vscode-languageserver';
import { AnalyzerService } from './analyzer/service';
import { ConsoleInterface } from './common/console';
import { createDeferred } from './common/deferred';
import { UriParser } from './common/uriParser';
let WorkspaceFactoryIdCounter = 0;
export enum WellKnownWorkspaceKinds {
Default = 'default',
Regular = 'regular',
Limited = 'limited',
Cloned = 'cloned',
Test = 'test',
}
export enum WorkspacePythonPathKind {
Immutable = 'immutable',
Mutable = 'mutable',
}
export interface InitStatus {
resolve(): void;
reset(): InitStatus;
markCalled(): void;
promise: Promise<void>;
resolved(): boolean;
}
export function createInitStatus(): InitStatus {
// Due to the way we get `python path`, `include/exclude` from settings to initialize workspace,
// we need to wait for getSettings to finish before letting IDE features to use workspace (`isInitialized` field).
// So most of cases, whenever we create new workspace, we send request to workspace/configuration right way
// except one place which is `initialize` LSP call.
// In `initialize` method where we create `initial workspace`, we can't do that since LSP spec doesn't allow
// LSP server from sending any request to client until `initialized` method is called.
// This flag indicates whether we had our initial updateSetting call or not after `initialized` call.
let called = false;
const deferred = createDeferred<void>();
const self = {
promise: deferred.promise,
resolve: () => {
called = true;
deferred.resolve();
},
markCalled: () => {
called = true;
},
reset: () => {
if (!called) {
return self;
}
return createInitStatus();
},
resolved: () => {
return deferred.resolved;
},
};
return self;
}
// path and uri will point to a workspace itself. It could be a folder
// if the workspace represents a folder. it could be '' if it is the default workspace.
// But it also could be a file if it is a virtual workspace.
// rootPath will always point to the folder that contains the workspace.
export interface Workspace {
workspaceName: string;
rootPath: string;
uri: string;
kinds: string[];
service: AnalyzerService;
disableLanguageServices: boolean;
disableOrganizeImports: boolean;
disableWorkspaceSymbol: boolean;
isInitialized: InitStatus;
searchPathsToWatch: string[];
pythonPath: string | undefined;
pythonPathKind: WorkspacePythonPathKind;
}
export class WorkspaceFactory {
private _defaultWorkspacePath = '<default>';
private _map = new Map<string, Workspace>();
private _id = WorkspaceFactoryIdCounter++;
constructor(
private readonly _console: ConsoleInterface,
private readonly _uriParser: UriParser,
private readonly _createService: (
name: string,
rootPath: string,
uri: string,
kinds: string[]
) => AnalyzerService,
private readonly _isPythonPathImmutable: (path: string) => boolean,
private readonly _onWorkspaceCreated: (workspace: Workspace) => void
) {
this._console.log(`WorkspaceFactory ${this._id} created`);
}
handleInitialize(params: InitializeParams) {
// Create a service instance for each of the workspace folders.
if (params.workspaceFolders) {
params.workspaceFolders.forEach((folder) => {
const path = this._uriParser.decodeTextDocumentUri(folder.uri);
this._add(folder.uri, path, folder.name, undefined, WorkspacePythonPathKind.Mutable, [
WellKnownWorkspaceKinds.Regular,
]);
});
} else if (params.rootPath) {
this._add(params.rootPath, params.rootPath, params.rootPath, undefined, WorkspacePythonPathKind.Mutable, [
WellKnownWorkspaceKinds.Regular,
]);
}
}
handleWorkspaceFoldersChanged(params: WorkspaceFoldersChangeEvent) {
params.removed.forEach((workspaceInfo) => {
const rootPath = this._uriParser.decodeTextDocumentUri(workspaceInfo.uri);
// Delete all workspaces for this folder. Even the ones generated for notebook kernels.
const workspaces = this.getNonDefaultWorkspaces().filter((w) => w.rootPath === rootPath);
workspaces.forEach((w) => {
this._remove(w);
});
});
params.added.forEach((workspaceInfo) => {
const rootPath = this._uriParser.decodeTextDocumentUri(workspaceInfo.uri);
this._add(workspaceInfo.uri, rootPath, workspaceInfo.name, undefined, WorkspacePythonPathKind.Mutable, [
WellKnownWorkspaceKinds.Regular,
]);
});
}
items() {
return [...this._map.values()];
}
applyPythonPath(workspace: Workspace, newPythonPath: string | undefined): string | undefined {
// See if were allowed to apply the new python path
if (workspace.pythonPathKind === WorkspacePythonPathKind.Mutable && newPythonPath) {
const originalPythonPath = workspace.pythonPath;
workspace.pythonPath = newPythonPath;
// This may not be the workspace in our map. Update the workspace in the map too.
// This can happen during startup were the Initialize creates a workspace and then
// onDidChangeConfiguration is called right away.
const key = this._getWorkspaceKey(workspace);
const workspaceInMap = this._map.get(key);
if (workspaceInMap) {
workspaceInMap.pythonPath = newPythonPath;
}
// If the python path has changed, we may need to move the immutable files to the correct workspace.
if (originalPythonPath && originalPythonPath !== newPythonPath && workspaceInMap) {
// Potentially move immutable files from one workspace to another.
this._moveImmutableFilesToCorrectWorkspace(originalPythonPath, workspaceInMap);
}
}
// Return the python path that should be used (whether hardcoded or configured)
return workspace.pythonPath;
}
clear() {
this._map.forEach((workspace) => {
workspace.isInitialized.resolve();
workspace.service.dispose();
});
this._map.clear();
this._console.log(`WorkspaceFactory ${this._id} clear`);
}
hasMultipleWorkspaces(kind?: string) {
if (this._map.size === 0 || this._map.size === 1) {
return false;
}
let count = 0;
for (const kv of this._map) {
if (!kind || kv[1].kinds.some((k) => k === kind)) {
count++;
}
if (count > 1) {
return true;
}
}
return false;
}
getContainingWorkspace(filePath: string, pythonPath?: string) {
return this._getBestRegularWorkspace(
this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular).filter((w) =>
filePath.startsWith(w.rootPath)
),
pythonPath
);
}
moveFiles(filePaths: string[], fromWorkspace: Workspace, toWorkspace: Workspace) {
if (fromWorkspace === toWorkspace) {
return;
}
filePaths.forEach((f) => {
const fileInfo = fromWorkspace.service.backgroundAnalysisProgram.program.getSourceFileInfo(f);
if (fileInfo) {
toWorkspace.service.setFileOpened(
f,
fileInfo.sourceFile.getClientVersion() || null,
fileInfo.sourceFile.getFileContent() || '',
fileInfo.sourceFile.getIPythonMode(),
fileInfo.chainedSourceFile ? fileInfo.chainedSourceFile.sourceFile.getFilePath() : undefined,
fileInfo.sourceFile.getRealFilePath()
);
fromWorkspace.service.setFileClosed(f, fileInfo.isTracked);
}
});
// If the fromWorkspace has no more files in it (and it's an immutable pythonPath), then remove it.
this.removeUnused(fromWorkspace);
}
getNonDefaultWorkspaces(kind?: string): Workspace[] {
const workspaces: Workspace[] = [];
this._map.forEach((workspace) => {
if (!workspace.rootPath) {
return;
}
if (kind && !workspace.kinds.some((k) => k === kind)) {
return;
}
workspaces.push(workspace);
});
return workspaces;
}
// Returns the best workspace for a file. Waits for the workspace to be finished handling other events before
// returning the appropriate workspace.
async getWorkspaceForFile(filePath: string, pythonPath: string | undefined): Promise<Workspace> {
// Wait for all workspaces to be initialized before attempting to find the best workspace. Otherwise
// the list of files won't be complete and the `contains` check might fail.
await Promise.all([...this._map.values()].map((w) => w.isInitialized.promise));
// Find or create best match.
const workspace = await this._getOrCreateBestWorkspaceForFile(filePath, pythonPath);
// The workspace may have just been created. Wait for it to be initialized before returning it.
await workspace.isInitialized.promise;
return workspace;
}
removeUnused(workspace: Workspace) {
// Only remove this workspace is it's not being used and it's a hardcoded path kind.
if (
workspace.service.getOpenFiles().length === 0 &&
workspace.pythonPathKind === WorkspacePythonPathKind.Immutable
) {
// Destroy the workspace since it only had immutable files in it.
this._remove(workspace);
}
}
private async _moveImmutableFilesToCorrectWorkspace(oldPythonPath: string, mutableWorkspace: Workspace) {
// If the python path changes we may need to move some immutable files around.
// For example, if a notebook had the old python path, we need to create a new workspace
// for the notebook.
// If a notebook has the new python path but is currently in a workspace with the path hardcoded, we need to move it to
// this workspace.
const oldPathFiles = new Set<string>(
mutableWorkspace.service.getOpenFiles().filter((f) => this._isPythonPathImmutable(f))
);
const exitingWorkspaceWithSamePath = this.items().find(
(w) => w.pythonPath === mutableWorkspace.pythonPath && w !== mutableWorkspace
);
const newPathFiles = new Set<string>(
exitingWorkspaceWithSamePath?.service.getOpenFiles().filter((f) => this._isPythonPathImmutable(f))
);
// Immutable files that were in this mutableWorkspace have to be moved
// to a (potentially) new workspace (with the old path).
for (const file of oldPathFiles) {
const workspace = this._getOrCreateBestWorkspaceFileSync(file, oldPythonPath);
if (workspace !== mutableWorkspace) {
this.moveFiles([file], mutableWorkspace, workspace);
}
}
// Immutable files from a different workspace (with the same path as the new path)
// have to be moved to the mutable workspace (which now has the new path)
if (exitingWorkspaceWithSamePath) {
this.moveFiles([...newPathFiles], exitingWorkspaceWithSamePath!, mutableWorkspace);
this.removeUnused(exitingWorkspaceWithSamePath);
}
}
private _add(
rootUri: string,
rootPath: string,
name: string,
pythonPath: string | undefined,
pythonPathKind: WorkspacePythonPathKind,
kinds: string[]
) {
// Update the kind based of the uri is local or not
if (!this._uriParser.isLocal(rootUri)) {
// Web based workspace should be limited.
kinds = [...kinds, WellKnownWorkspaceKinds.Limited];
}
const result: Workspace = {
workspaceName: name,
rootPath,
uri: rootUri,
kinds,
pythonPath,
pythonPathKind,
service: this._createService(name, rootPath, rootUri, kinds),
disableLanguageServices: false,
disableOrganizeImports: false,
disableWorkspaceSymbol: false,
isInitialized: createInitStatus(),
searchPathsToWatch: [],
};
// Tell our owner we added something
this._onWorkspaceCreated(result);
// Stick in our map
const key = this._getWorkspaceKey(result);
// Make sure to delete existing workspaces if there are any.
this._remove(result);
this._console.log(`WorkspaceFactory ${this._id} add ${key}`);
this._map.set(key, result);
return result;
}
private _remove(value: Workspace) {
const key = this._getWorkspaceKey(value);
const workspace = this._map.get(key);
if (workspace) {
workspace.isInitialized.resolve();
workspace.service.dispose();
this._console.log(`WorkspaceFactory ${this._id} remove ${key}`);
this._map.delete(key);
}
}
private _getDefaultWorskpaceKey(pythonPath: string | undefined) {
return `${this._defaultWorkspacePath}:${pythonPath ? pythonPath : WorkspacePythonPathKind.Mutable}`;
}
private _getWorkspaceKey(value: Workspace) {
// Special the root path for the default workspace. It will be created
// without a root path
const rootPath = value.kinds.includes(WellKnownWorkspaceKinds.Default)
? this._defaultWorkspacePath
: value.rootPath;
// Key is defined by the rootPath and the pythonPath. We might include platform in this, but for now
// platform is only used by the import resolver.
return `${rootPath}:${
value.pythonPathKind === WorkspacePythonPathKind.Mutable ? value.pythonPathKind : value.pythonPath
}`;
}
private async _getOrCreateBestWorkspaceForFile(
filePath: string,
pythonPath: string | undefined
): Promise<Workspace> {
// Find the current best workspace (without creating a new one)
let bestInstance = this._getBestWorkspaceForFile(filePath, pythonPath);
// Make sure the best instance is initialized so that it has its pythonPath.
await bestInstance.isInitialized.promise;
// If this best instance doesn't match the pythonPath, then we need to create a new one.
if (pythonPath && bestInstance.pythonPath !== pythonPath) {
bestInstance = this._add(
bestInstance.uri,
bestInstance.rootPath,
bestInstance.workspaceName,
pythonPath,
WorkspacePythonPathKind.Immutable, // This means the pythonPath should never change.
bestInstance.kinds
);
}
return bestInstance;
}
private _getOrCreateBestWorkspaceFileSync(filePath: string, pythonPath: string) {
// Find the current best workspace (without creating a new one)
let bestInstance = this._getBestWorkspaceForFile(filePath, pythonPath);
// If this best instance doesn't match the pythonPath, then we need to create a new one.
if (bestInstance.pythonPath !== pythonPath) {
bestInstance = this._add(
bestInstance.uri,
bestInstance.rootPath,
bestInstance.workspaceName,
pythonPath,
WorkspacePythonPathKind.Immutable, // This means the pythonPath should never change.
bestInstance.kinds
);
}
return bestInstance;
}
private _getBestWorkspaceForFile(filePath: string, pythonPath: string | undefined): Workspace {
let bestRootPath: string | undefined;
let bestInstance: Workspace | undefined;
// The order of how we find the best matching workspace for the given file is
// 1. The given file is the workspace itself (ex, a file being a virtual workspace itself).
// 2. The given file matches the fileSpec of the service under the workspace
// (the file is a user file the workspace provides LSP service for).
// 3. The given file doesn't match anything but we have only 1 regular workspace
// (ex, open a library file from the workspace).
// 4. The given file doesn't match anything and there are multiple workspaces but one of workspaces
// contains the file (ex, open a library file already imported by a workspace).
// 5. If none of the above works, then it matches the default workspace.
this._map.forEach((workspace) => {
if (workspace.rootPath) {
if (workspace.rootPath !== filePath && !workspace.service.isTracked(filePath)) {
return;
}
// Among workspaces that own the file, make sure we return the inner most one which
// we consider as the best workspace.
if (
bestRootPath === undefined ||
(workspace.rootPath.startsWith(bestRootPath) && workspace.rootPath !== bestRootPath)
) {
// Among workspaces with a python path, make sure we return the one that matches the python path
if (pythonPath && workspace.pythonPath === pythonPath) {
bestRootPath = workspace.rootPath;
bestInstance = workspace;
} else if (workspace.pythonPathKind === WorkspacePythonPathKind.Mutable && !pythonPath) {
// If no python path passed, pick the workspace with the configured python path.
bestRootPath = workspace.rootPath;
bestInstance = workspace;
}
}
}
});
// If there were multiple workspaces or we couldn't find any,
// use the default one.
if (bestInstance === undefined) {
const regularWorkspaces = this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular);
// If we have only regular workspaces with the same path, then pick the one that best matches the python path.
if (
regularWorkspaces.length &&
regularWorkspaces.every((w) => w.rootPath === regularWorkspaces[0].rootPath)
) {
bestInstance = pythonPath
? regularWorkspaces.find((w) => w.pythonPath === pythonPath) || regularWorkspaces[0]
: regularWorkspaces[0];
} else {
// If we have multiple workspaces, then pick the containing workspace that best matches the python path.
const containingWorkspace = this._getBestRegularWorkspace(
regularWorkspaces.filter((w) => w.service.contains(filePath)),
pythonPath
);
if (containingWorkspace) {
bestInstance = containingWorkspace;
} else {
// If no workspace contains it, then it belongs to the default workspace.
bestInstance = this._getOrCreateDefaultWorkspace(pythonPath);
}
}
}
return bestInstance;
}
private _getOrCreateDefaultWorkspace(pythonPath: string | undefined): Workspace {
// Default key depends upon the pythonPath
let defaultWorkspace = this._map.get(this._getDefaultWorskpaceKey(pythonPath));
if (!defaultWorkspace) {
// Create a default workspace for files that are outside
// of all workspaces.
defaultWorkspace = this._add(
'',
'',
this._defaultWorkspacePath,
pythonPath,
pythonPath ? WorkspacePythonPathKind.Immutable : WorkspacePythonPathKind.Mutable,
[WellKnownWorkspaceKinds.Default]
);
}
return defaultWorkspace;
}
private _getBestRegularWorkspace(workspaces: Workspace[], pythonPath?: string): Workspace | undefined {
if (workspaces.length === 0) {
return undefined;
}
if (workspaces.length === 1) {
return workspaces[0];
}
// Further filter by longest paths.
const longestPath = workspaces.reduce((previousPath, currentWorkspace) => {
if (!previousPath) {
return currentWorkspace.rootPath;
}
if (currentWorkspace.rootPath.length > previousPath.length) {
return currentWorkspace.rootPath;
}
return previousPath;
}, '');
const longestWorkspaces = workspaces.filter((w) => w.rootPath === longestPath);
// Filter by any that match the current python path.
return longestWorkspaces.find((w) => !pythonPath || w.pythonPath === pythonPath) || longestWorkspaces[0];
}
}

View File

@ -1,211 +0,0 @@
/*
* workspaceMap.ts
*
* Workspace management related functionality.
*/
import {
createInitStatus,
LanguageServerBase,
WellKnownWorkspaceKinds,
WorkspaceServiceInstance,
} from './languageServerBase';
export class WorkspaceMap extends Map<string, WorkspaceServiceInstance> {
private _defaultWorkspacePath = '<default>';
override set(key: string, value: WorkspaceServiceInstance): this {
// Make sure to delete existing workspace if there is one.
this.delete(key);
return super.set(key, value);
}
override delete(key: string): boolean {
const workspace = this.get(key);
if (!workspace) {
return false;
}
// Make sure to unblock if there is someone waiting this workspace.
workspace.isInitialized.resolve();
// Properly dispose of the service instance.
workspace.serviceInstance.dispose();
return super.delete(key);
}
hasMultipleWorkspaces(kind?: string) {
if (this.size === 0 || this.size === 1) {
return false;
}
let count = 0;
for (const kv of this) {
if (!kind || kv[1].kinds.some((k) => k === kind)) {
count++;
}
if (count > 1) {
return true;
}
}
return false;
}
getNonDefaultWorkspaces(kind?: string): WorkspaceServiceInstance[] {
const workspaces: WorkspaceServiceInstance[] = [];
this.forEach((workspace) => {
if (!workspace.path) {
return;
}
if (kind && !workspace.kinds.some((k) => k === kind)) {
return;
}
workspaces.push(workspace);
});
return workspaces;
}
// Returns the best workspace for a file. Waits for the workspace to be finished handling other events before
// returning the appropriate workspace.
async getWorkspaceForFile(ls: LanguageServerBase, filePath: string): Promise<WorkspaceServiceInstance> {
// Make sure we always have a default workspace.
const defaultWorkspace = this._createDefaultWorkspace(ls);
// Wait for all workspaces to be initialized before attempting to find the best workspace. Otherwise
// the list of files won't be complete and the `contains` check might fail.
await Promise.all([...this.values()].map((w) => w.isInitialized.promise));
// Find best match.
const workspace = this._getBestWorkspaceForFile(ls, filePath, defaultWorkspace);
// During the previous await we might have reset to being uninitialized again, wait before returning
await workspace.isInitialized.promise;
return workspace;
}
getContainingWorkspace(filePath: string) {
return this._getBestWorkspace(
this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular).filter((w) => filePath.startsWith(w.path))
);
}
getDefaultWorkspace(): WorkspaceServiceInstance | undefined {
return this.get(this._defaultWorkspacePath);
}
private _getBestWorkspaceForFile(
ls: LanguageServerBase,
filePath: string,
defaultWorkspace: WorkspaceServiceInstance
): WorkspaceServiceInstance {
let bestRootPath: string | undefined;
let bestInstance: WorkspaceServiceInstance | undefined;
// The order of how we find the best matching workspace for the given file is
// 1. The given file is the workspace itself (ex, a file being a virtual workspace itself).
// 2. The given file matches the fileSpec of the service under the workspace
// (the file is a user file the workspace provides LSP service for).
// 3. The given file doesn't match anything but we have only 1 regular workspace
// (ex, open a library file from the workspace).
// 4. The given file doesn't match anything and there are multiple workspaces but one of workspaces
// contains the file (ex, open a library file already imported by a workspace).
// 5. If none of the above works, then it matches the default workspace.
this.forEach((workspace) => {
if (workspace.path) {
if (workspace.path !== filePath && !workspace.serviceInstance.isTracked(filePath)) {
return;
}
// Among workspaces that own the file, make sure we return the inner most one which
// we consider as the best workspace.
if (bestRootPath === undefined || workspace.path.startsWith(bestRootPath)) {
bestRootPath = workspace.path;
bestInstance = workspace;
}
}
});
// If there were multiple workspaces or we couldn't find any,
// use the default one.
if (bestInstance === undefined) {
const regularWorkspaces = this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular);
// If we have only 1 regular workspace, then use that.
if (regularWorkspaces.length === 1) {
bestInstance = regularWorkspaces[0];
} else {
// If we have multiple workspaces, see whether we can at least find one that contains the file.
// the file might not be tracked (user file), but still belongs to a workspace as a library file or as an orphan file to the workspace.
const containingWorkspace = this._getBestWorkspace(
regularWorkspaces.filter((w) => w.serviceInstance.contains(filePath))
);
if (containingWorkspace) {
bestInstance = containingWorkspace;
} else {
// If no workspace contains it, then it belongs to the default workspace.
bestInstance = defaultWorkspace;
}
}
}
return bestInstance;
}
private _createDefaultWorkspace(ls: LanguageServerBase) {
let defaultWorkspace = this.get(this._defaultWorkspacePath);
if (!defaultWorkspace) {
// Create a default workspace for files that are outside
// of all workspaces.
defaultWorkspace = {
workspaceName: '',
rootPath: '',
path: '',
uri: '',
serviceInstance: ls.createAnalyzerService(this._defaultWorkspacePath),
kinds: [WellKnownWorkspaceKinds.Default],
disableLanguageServices: false,
disableOrganizeImports: false,
disableWorkspaceSymbol: false,
isInitialized: createInitStatus(),
searchPathsToWatch: [],
};
this.set(this._defaultWorkspacePath, defaultWorkspace);
// Do not await this. let isInitialized.promise to await. Otherwise, ordering
// will get messed up. The very first call will run last.
ls.updateSettingsForWorkspace(defaultWorkspace, defaultWorkspace.isInitialized).ignoreErrors();
}
return defaultWorkspace;
}
private _getBestWorkspace(workspaces: WorkspaceServiceInstance[]) {
if (workspaces.length === 0) {
return undefined;
}
if (workspaces.length === 1) {
return workspaces[0];
}
// Best workspace is the inner most workspace.
return workspaces.reduce((previousWorkspace, currentWorkspace) => {
if (!previousWorkspace) {
return currentWorkspace;
}
if (currentWorkspace.path.startsWith(previousWorkspace.path)) {
return currentWorkspace;
}
return previousWorkspace;
}, workspaces[0]);
}
}

View File

@ -20,7 +20,7 @@
"shx": "^0.3.4",
"ts-loader": "^9.4.2",
"typescript": "~4.4.4",
"webpack": "^5.75.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.10.0"
},
"engines": {
@ -2308,9 +2308,9 @@
}
},
"node_modules/webpack": {
"version": "5.76.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
"integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
"version": "5.76.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@ -4134,9 +4134,9 @@
}
},
"webpack": {
"version": "5.76.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
"integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
"version": "5.76.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",

View File

@ -30,7 +30,7 @@
"shx": "^0.3.4",
"ts-loader": "^9.4.2",
"typescript": "~4.4.4",
"webpack": "^5.75.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.10.0"
},
"files": [

View File

@ -9,10 +9,10 @@
"version": "1.1.299",
"license": "MIT",
"dependencies": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageclient": "8.1.0-next.5",
"vscode-languageserver": "8.1.0-next.5",
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-jsonrpc": "8.1.0",
"vscode-languageclient": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-protocol": "3.17.3"
},
"devDependencies": {
"@types/copy-webpack-plugin": "^10.1.0",
@ -24,7 +24,7 @@
"ts-loader": "^9.4.2",
"typescript": "~4.4.4",
"vsce": "^2.7.0",
"webpack": "^5.75.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.10.0"
},
"engines": {
@ -1142,9 +1142,9 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@ -1182,9 +1182,9 @@
"dev": true
},
"node_modules/fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@ -1433,9 +1433,9 @@
]
},
"node_modules/ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true,
"engines": {
"node": ">= 4"
@ -3027,21 +3027,21 @@
}
},
"node_modules/vscode-jsonrpc": {
"version": "8.1.0-next.6",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0-next.6.tgz",
"integrity": "sha512-AahQokGczPwXKo1Qhnn3aqkZgwUJ0rjVwhWWKW5I5LEWRoqfnWkQp7haVIV6GJRX0oyGL2ezVy7IhwgP5u/3xw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
"integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/vscode-languageclient": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0-next.5.tgz",
"integrity": "sha512-RbL68ENqp2uPDs1rsPiD8IfhPWzUP8e12ONdtpmSlR6kmj6FLOd8fEaC1pQMGDtfPfiFCpLav8YytH25QOYwRQ==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz",
"integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==",
"dependencies": {
"minimatch": "^5.1.0",
"semver": "^7.3.7",
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
},
"engines": {
"vscode": "^1.67.0"
@ -3067,29 +3067,29 @@
}
},
"node_modules/vscode-languageserver": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0-next.5.tgz",
"integrity": "sha512-VivctbjOca/iPZKXqgqz03MUhnjAlVkf8/AwfndIEVzD8itD7zaoMlqNUynHJVbGcU5PEygC5deUzKHMU8cNhw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz",
"integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==",
"dependencies": {
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
},
"bin": {
"installServerIntoExtension": "bin/installServerIntoExtension"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.17.3-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3-next.5.tgz",
"integrity": "sha512-9HafkatRVhBVpWQrODes4JaoSu7ozHQUOzYiTmfMmxeFOUYgsSqyODp+j/c+SovcsuwABjuqnsQ9RiFkXCbeMA==",
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
"integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
"dependencies": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver-types": "3.17.3-next.2"
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver-types": "3.17.3"
}
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.3-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3-next.2.tgz",
"integrity": "sha512-3kkNSCycNKUalSJIrjIptGeY9UTJr1Nk5HT/aT00jjIwiCvIUNbRdK90av2Y3j1Jityot68dBVc3YYdwwH3zOQ=="
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"node_modules/watchpack": {
"version": "2.4.0",
@ -3105,9 +3105,9 @@
}
},
"node_modules/webpack": {
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
"version": "5.76.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@ -4247,9 +4247,9 @@
"dev": true
},
"fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@ -4283,9 +4283,9 @@
"dev": true
},
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@ -4474,9 +4474,9 @@
"dev": true
},
"ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true
},
"import-local": {
@ -5659,18 +5659,18 @@
}
},
"vscode-jsonrpc": {
"version": "8.1.0-next.6",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0-next.6.tgz",
"integrity": "sha512-AahQokGczPwXKo1Qhnn3aqkZgwUJ0rjVwhWWKW5I5LEWRoqfnWkQp7haVIV6GJRX0oyGL2ezVy7IhwgP5u/3xw=="
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
"integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="
},
"vscode-languageclient": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0-next.5.tgz",
"integrity": "sha512-RbL68ENqp2uPDs1rsPiD8IfhPWzUP8e12ONdtpmSlR6kmj6FLOd8fEaC1pQMGDtfPfiFCpLav8YytH25QOYwRQ==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz",
"integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==",
"requires": {
"minimatch": "^5.1.0",
"semver": "^7.3.7",
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
},
"dependencies": {
"brace-expansion": {
@ -5692,26 +5692,26 @@
}
},
"vscode-languageserver": {
"version": "8.1.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0-next.5.tgz",
"integrity": "sha512-VivctbjOca/iPZKXqgqz03MUhnjAlVkf8/AwfndIEVzD8itD7zaoMlqNUynHJVbGcU5PEygC5deUzKHMU8cNhw==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz",
"integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==",
"requires": {
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-languageserver-protocol": "3.17.3"
}
},
"vscode-languageserver-protocol": {
"version": "3.17.3-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3-next.5.tgz",
"integrity": "sha512-9HafkatRVhBVpWQrODes4JaoSu7ozHQUOzYiTmfMmxeFOUYgsSqyODp+j/c+SovcsuwABjuqnsQ9RiFkXCbeMA==",
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
"integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
"requires": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver-types": "3.17.3-next.2"
"vscode-jsonrpc": "8.1.0",
"vscode-languageserver-types": "3.17.3"
}
},
"vscode-languageserver-types": {
"version": "3.17.3-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3-next.2.tgz",
"integrity": "sha512-3kkNSCycNKUalSJIrjIptGeY9UTJr1Nk5HT/aT00jjIwiCvIUNbRdK90av2Y3j1Jityot68dBVc3YYdwwH3zOQ=="
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"watchpack": {
"version": "2.4.0",
@ -5724,9 +5724,9 @@
}
},
"webpack": {
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
"version": "5.76.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",

View File

@ -161,7 +161,6 @@
},
"python.analysis.diagnosticSeverityOverrides": {
"type": "object",
"default": {},
"description": "Allows a user to override the severity levels for individual diagnostics.",
"scope": "resource",
"properties": {
@ -940,10 +939,10 @@
"webpack-dev": "npm run clean && webpack --mode development --watch --progress"
},
"dependencies": {
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageclient": "8.1.0-next.5",
"vscode-languageserver": "8.1.0-next.5",
"vscode-languageserver-protocol": "3.17.3-next.5"
"vscode-jsonrpc": "8.1.0",
"vscode-languageclient": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-protocol": "3.17.3"
},
"devDependencies": {
"@types/copy-webpack-plugin": "^10.1.0",
@ -955,7 +954,7 @@
"ts-loader": "^9.4.2",
"typescript": "~4.4.4",
"vsce": "^2.7.0",
"webpack": "^5.75.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.10.0"
}
}