diff --git a/.github/workflows/nightly-coverage.yml b/.github/workflows/nightly-coverage.yml index 8712427b9cad..aebef1c42055 100644 --- a/.github/workflows/nightly-coverage.yml +++ b/.github/workflows/nightly-coverage.yml @@ -51,6 +51,7 @@ jobs: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt --no-user # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy + python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/jedilsp --no-cache-dir --implementation py --no-deps --upgrade -r ./jedils_requirements.txt - name: Install test requirements run: python -m pip install --upgrade -r build/test-requirements.txt diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index dad8b3ffdc54..d13e55ced2a5 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -100,6 +100,10 @@ jobs: # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy + - name: Install Jedi LSP requirements + run: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/jedilsp --no-cache-dir --implementation py --no-deps --upgrade -r ./jedils_requirements.txt + if: startsWith(matrix.python, 3.) + - name: Install test requirements run: python -m pip install --upgrade -r build/test-requirements.txt @@ -388,6 +392,7 @@ jobs: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt --no-user # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy + python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/jedilsp --no-cache-dir --implementation py --no-deps --upgrade -r ./jedils_requirements.txt - name: Install test requirements run: python -m pip install --upgrade -r build/test-requirements.txt diff --git a/news/1 Enhancements/11995.md b/news/1 Enhancements/11995.md new file mode 100644 index 000000000000..90c97afc74ff --- /dev/null +++ b/news/1 Enhancements/11995.md @@ -0,0 +1 @@ +Phase out Jedi 0.17, and use Jedi behind a language server protocol as the Jedi option. diff --git a/package.json b/package.json index 663ca559bcfd..3027c8e2b475 100644 --- a/package.json +++ b/package.json @@ -645,7 +645,6 @@ "pythonDeprecatePythonPath", "pythonDiscoveryModule", "pythonDiscoveryModuleWithoutWatcher", - "pythonJediLSP", "pythonSortEnvs", "pythonSurveyNotification", "pythonTensorboardExperiment", @@ -666,7 +665,6 @@ "pythonDeprecatePythonPath", "pythonDiscoveryModule", "pythonDiscoveryModuleWithoutWatcher", - "pythonJediLSP", "pythonSortEnvs", "pythonSurveyNotification", "pythonTensorboardExperiment", @@ -770,13 +768,11 @@ "enum": [ "Default", "Jedi", - "JediLSP", "Pylance", "None" ], "enumDescriptions": [ "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.", - "Use Jedi as a language server.", "Use Jedi behind the Language Server Protocol (LSP) as a language server.", "Use Pylance as a language server.", "Disable language server capabilities." diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 82705898e514..5accd9f1c478 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -3,7 +3,7 @@ import '../common/extensions'; import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode'; +import { ConfigurationChangeEvent, ConfigurationTarget, Disposable, OutputChannel, Uri } from 'vscode'; import { LSNotSupportedDiagnosticServiceId } from '../application/diagnostics/checks/lsNotSupported'; import { IDiagnosticsService } from '../application/diagnostics/types'; @@ -56,6 +56,8 @@ export class LanguageServerExtensionActivationService private readonly workspaceService: IWorkspaceService; + private readonly configurationService: IConfigurationService; + private readonly output: OutputChannel; private readonly interpreterService: IInterpreterService; @@ -69,6 +71,7 @@ export class LanguageServerExtensionActivationService @inject(IPersistentStateFactory) private stateFactory: IPersistentStateFactory, ) { this.workspaceService = this.serviceContainer.get(IWorkspaceService); + this.configurationService = this.serviceContainer.get(IConfigurationService); this.interpreterService = this.serviceContainer.get(IInterpreterService); this.output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); @@ -87,8 +90,8 @@ export class LanguageServerExtensionActivationService this.serviceContainer.get(IExtensions), this.serviceContainer.get(IApplicationShell), this.serviceContainer.get(ICommandManager), - this.serviceContainer.get(IWorkspaceService), - this.serviceContainer.get(IConfigurationService), + this.workspaceService, + this.configurationService, ); disposables.push(this.languageServerChangeHandler); } @@ -228,6 +231,9 @@ export class LanguageServerExtensionActivationService key: string, ): Promise { let serverType = this.getCurrentLanguageServerType(); + + this.updateLanguageServerSetting(resource); + if (serverType === LanguageServerType.Microsoft) { const lsNotSupportedDiagnosticService = this.serviceContainer.get( IDiagnosticsService, @@ -243,22 +249,13 @@ export class LanguageServerExtensionActivationService } } - if (serverType === LanguageServerType.JediLSP && interpreter && interpreter.version) { - if (interpreter.version.major < 3 || (interpreter.version.major === 3 && interpreter.version.minor < 6)) { - sendTelemetryEvent(EventName.JEDI_FALLBACK); - serverType = LanguageServerType.Jedi; - } - } - - // If Pylance was chosen via the default and the interpreter is Python 2, fall back to - // Jedi. If Pylance was explicitly chosen, continue anyway, even if Pylance won't work - // as expected, matching pre-default behavior. - if (this.getCurrentLanguageServerTypeIsDefault()) { - if (serverType === LanguageServerType.Node && interpreter && interpreter.version) { - if (interpreter.version.major < 3) { - sendTelemetryEvent(EventName.JEDI_FALLBACK); - serverType = LanguageServerType.Jedi; - } + // If the interpreter is Python 2 and the LS setting is explicitly set to Jedi, turn it off. + // If set to Default, use Pylance. + if (interpreter && (interpreter.version?.major ?? 0) < 3) { + if (serverType === LanguageServerType.Jedi) { + serverType = LanguageServerType.None; + } else if (this.getCurrentLanguageServerTypeIsDefault()) { + serverType = LanguageServerType.Node; } } @@ -348,4 +345,23 @@ export class LanguageServerExtensionActivationService const values = await Promise.all([...this.cache.values()]); values.forEach((v) => (v.clearAnalysisCache ? v.clearAnalysisCache() : noop())); } + + private updateLanguageServerSetting(resource: Resource): void { + // Update settings.json value to Jedi if it's JediLSP. + const settings = this.workspaceService + .getConfiguration('python', resource) + .inspect('languageServer'); + + let configTarget: ConfigurationTarget; + + if (settings?.workspaceValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Workspace; + } else if (settings?.globalValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Global; + } else { + return; + } + + this.configurationService.updateSetting('languageServer', LanguageServerType.Jedi, resource, configTarget); + } } diff --git a/src/client/activation/common/defaultlanguageServer.ts b/src/client/activation/common/defaultlanguageServer.ts index 1ef129ba2235..d901ed72155a 100644 --- a/src/client/activation/common/defaultlanguageServer.ts +++ b/src/client/activation/common/defaultlanguageServer.ts @@ -3,8 +3,7 @@ import { injectable } from 'inversify'; import { PYLANCE_EXTENSION_ID } from '../../common/constants'; -import { JediLSP } from '../../common/experiments/groups'; -import { IDefaultLanguageServer, IExperimentService, IExtensions, DefaultLSType } from '../../common/types'; +import { IDefaultLanguageServer, IExtensions, DefaultLSType } from '../../common/types'; import { IServiceManager } from '../../ioc/types'; import { ILSExtensionApi } from '../node/languageServerFolderService'; import { LanguageServerType } from '../types'; @@ -19,26 +18,20 @@ class DefaultLanguageServer implements IDefaultLanguageServer { } export async function setDefaultLanguageServer( - experimentService: IExperimentService, extensions: IExtensions, serviceManager: IServiceManager, ): Promise { - const lsType = await getDefaultLanguageServer(experimentService, extensions); + const lsType = await getDefaultLanguageServer(extensions); serviceManager.addSingletonInstance( IDefaultLanguageServer, new DefaultLanguageServer(lsType), ); } -async function getDefaultLanguageServer( - experimentService: IExperimentService, - extensions: IExtensions, -): Promise { +async function getDefaultLanguageServer(extensions: IExtensions): Promise { if (extensions.getExtension(PYLANCE_EXTENSION_ID)) { return LanguageServerType.Node; } - return (await experimentService.inExperiment(JediLSP.experiment)) - ? LanguageServerType.JediLSP - : LanguageServerType.Jedi; + return LanguageServerType.Jedi; } diff --git a/src/client/activation/jedi/manager.ts b/src/client/activation/jedi/manager.ts index 0afd334e62c0..91a6559f99a6 100644 --- a/src/client/activation/jedi/manager.ts +++ b/src/client/activation/jedi/manager.ts @@ -45,7 +45,7 @@ export class JediLanguageServerManager implements ILanguageServerManager { constructor( @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, @inject(ILanguageServerAnalysisOptions) - @named(LanguageServerType.JediLSP) + @named(LanguageServerType.Jedi) private readonly analysisOptions: ILanguageServerAnalysisOptions, @inject(ICommandManager) commandManager: ICommandManager, ) { @@ -144,7 +144,7 @@ export class JediLanguageServerManager implements ILanguageServerManager { const options = await this.analysisOptions.getAnalysisOptions(); this.middleware = new LanguageClientMiddleware( this.serviceContainer, - LanguageServerType.JediLSP, + LanguageServerType.Jedi, () => this.languageServerProxy?.languageClient, this.lsVersion, ); diff --git a/src/client/activation/languageClientMiddleware.ts b/src/client/activation/languageClientMiddleware.ts index aed6def62a7b..b5f4d83e4c0d 100644 --- a/src/client/activation/languageClientMiddleware.ts +++ b/src/client/activation/languageClientMiddleware.ts @@ -24,7 +24,7 @@ export class LanguageClientMiddleware extends LanguageClientMiddlewareBase { ) { super(serviceContainer, serverType, sendTelemetryEvent, serverVersion); - if (serverType === LanguageServerType.None || serverType === LanguageServerType.Jedi) { + if (serverType === LanguageServerType.None) { return; } diff --git a/src/client/activation/languageClientMiddlewareBase.ts b/src/client/activation/languageClientMiddlewareBase.ts index 4074e9689c03..7ef75b2b93bd 100644 --- a/src/client/activation/languageClientMiddlewareBase.ts +++ b/src/client/activation/languageClientMiddlewareBase.ts @@ -133,7 +133,7 @@ export class LanguageClientMiddlewareBase implements Middleware { this.eventName = EventName.PYTHON_LANGUAGE_SERVER_REQUEST; } else if (serverType === LanguageServerType.Node) { this.eventName = EventName.LANGUAGE_SERVER_REQUEST; - } else if (serverType === LanguageServerType.JediLSP) { + } else if (serverType === LanguageServerType.Jedi) { this.eventName = EventName.JEDI_LANGUAGE_SERVER_REQUEST; } } diff --git a/src/client/activation/languageServer/deprecationPrompt.ts b/src/client/activation/languageServer/deprecationPrompt.ts index f657e9a2b75e..598d0d44a797 100644 --- a/src/client/activation/languageServer/deprecationPrompt.ts +++ b/src/client/activation/languageServer/deprecationPrompt.ts @@ -94,10 +94,7 @@ export class MPLSDeprecationPrompt implements IMPLSDeprecationPrompt { } private async switchLanguageServer(lsType: LanguageServerType.Node | LanguageServerType.Jedi): Promise { - let defaultType = this.defaultLanguageServer.defaultLSType; - if (defaultType === LanguageServerType.JediLSP) { - defaultType = LanguageServerType.Jedi; - } + const defaultType = this.defaultLanguageServer.defaultLSType; // If changing to the default, unset the setting instead of explicitly setting it. const changeTo = lsType !== defaultType ? lsType : undefined; diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index e493bf613a4c..404ca0db6edb 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -10,7 +10,6 @@ import { DownloadBetaChannelRule, DownloadDailyChannelRule } from './common/down import { LanguageServerDownloader } from './common/downloader'; import { LanguageServerDownloadChannel } from './common/packageRepository'; import { ExtensionSurveyPrompt } from './extensionSurvey'; -import { JediExtensionActivator } from './jedi'; import { JediLanguageServerAnalysisOptions } from './jedi/analysisOptions'; import { JediLanguageClientFactory } from './jedi/languageClientFactory'; import { JediLanguageServerProxy } from './jedi/languageServerProxy'; @@ -130,34 +129,29 @@ export function registerTypes(serviceManager: IServiceManager, languageServerTyp ILanguageServerFolderService, NodeLanguageServerFolderService, ); - } else if (languageServerType === LanguageServerType.JediLSP) { + } else if (languageServerType === LanguageServerType.Jedi) { serviceManager.add( ILanguageServerActivator, JediLanguageServerActivator, - LanguageServerType.JediLSP, + LanguageServerType.Jedi, ); serviceManager.add( ILanguageServerAnalysisOptions, JediLanguageServerAnalysisOptions, - LanguageServerType.JediLSP, + LanguageServerType.Jedi, ); serviceManager.addSingleton(ILanguageClientFactory, JediLanguageClientFactory); serviceManager.add(ILanguageServerManager, JediLanguageServerManager); serviceManager.add(ILanguageServerProxy, JediLanguageServerProxy); - } else if (languageServerType === LanguageServerType.None) { - serviceManager.add( - ILanguageServerActivator, - NoLanguageServerExtensionActivator, - LanguageServerType.None, - ); } + serviceManager.add( ILanguageServerActivator, - JediExtensionActivator, - LanguageServerType.Jedi, - ); // We fallback to Jedi if for some reason we're unable to use other language servers, hence register this always. + NoLanguageServerExtensionActivator, + LanguageServerType.None, + ); serviceManager.addSingleton( IDownloadChannelRule, diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d68a8c12e2b7..d1909db0cbd5 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -293,8 +293,12 @@ export class PythonSettings implements IPythonSettings { userLS === 'Default' || !Object.values(LanguageServerType).includes(userLS as LanguageServerType) ) { - this.languageServer = this.defaultLS?.defaultLSType ?? LanguageServerType.Jedi; + this.languageServer = this.defaultLS?.defaultLSType ?? LanguageServerType.None; this.languageServerIsDefault = true; + } else if (userLS === 'JediLSP') { + // Switch JediLSP option to Jedi. + this.languageServer = LanguageServerType.Jedi; + this.languageServerIsDefault = false; } else { this.languageServer = userLS as LanguageServerType; this.languageServerIsDefault = false; diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 8f6d9bc44c27..ca24e0496df5 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -10,10 +10,6 @@ export enum DeprecatePythonPath { experiment = 'pythonDeprecatePythonPath', } -// Experiment to switch Jedi to use an LSP instead of direct providers -export enum JediLSP { - experiment = 'pythonJediLSP', -} // Experiment to show a prompt asking users to join python mailing list. export enum JoinMailingListPromptVariants { variant1 = 'pythonJoinMailingListVar1', diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 5cccc64a22c5..883145254425 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -547,7 +547,7 @@ export interface IInterpreterPathProxyService { get(resource: Resource): string; } -export type DefaultLSType = LanguageServerType.Jedi | LanguageServerType.JediLSP | LanguageServerType.Node; +export type DefaultLSType = LanguageServerType.Jedi | LanguageServerType.Node; /** * Interface used to retrieve the default language server. diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index bf2bc72002c5..113353ece830 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -14,13 +14,7 @@ import { IApplicationEnvironment, ICommandManager } from './common/application/t import { Commands, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL, UseProposedApi } from './common/constants'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { IFileSystem } from './common/platform/types'; -import { - IConfigurationService, - IDisposableRegistry, - IExperimentService, - IExtensions, - IOutputChannel, -} from './common/types'; +import { IConfigurationService, IDisposableRegistry, IExtensions, IOutputChannel } from './common/types'; import { noop } from './common/utils/misc'; import { DebuggerTypeName } from './debugger/constants'; import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; @@ -117,10 +111,8 @@ async function activateLegacy(ext: ExtensionState): Promise { debugConfigurationRegisterTypes(serviceManager); tensorBoardRegisterTypes(serviceManager); - const experimentService = serviceContainer.get(IExperimentService); - const extensions = serviceContainer.get(IExtensions); - await setDefaultLanguageServer(experimentService, extensions, serviceManager); + await setDefaultLanguageServer(extensions, serviceManager); const configuration = serviceManager.get(IConfigurationService); // We should start logging using the log level as soon as possible, so set it as soon as we can access the level. diff --git a/src/client/jupyter/jupyterIntegration.ts b/src/client/jupyter/jupyterIntegration.ts index 7cdaca6b4293..1974270bdc88 100644 --- a/src/client/jupyter/jupyterIntegration.ts +++ b/src/client/jupyter/jupyterIntegration.ts @@ -212,7 +212,7 @@ export class JupyterExtensionIntegration { const interpreter = !isResource(r) ? r : undefined; const client = await this.languageServerCache.get(resource, interpreter); - // Some language servers don't support the connection yet. (like Jedi until we switch to LSP) + // Some language servers don't support the connection yet. if (client && client.connection && client.capabilities) { return { connection: client.connection, diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts index f778304cf1d6..26a4f7e0200e 100644 --- a/src/test/activation/activationService.unit.test.ts +++ b/src/test/activation/activationService.unit.test.ts @@ -3,7 +3,14 @@ import { expect } from 'chai'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; -import { ConfigurationChangeEvent, Disposable, EventEmitter, Uri, WorkspaceConfiguration } from 'vscode'; +import { + ConfigurationChangeEvent, + ConfigurationTarget, + Disposable, + EventEmitter, + Uri, + WorkspaceConfiguration, +} from 'vscode'; import { LanguageServerExtensionActivationService } from '../../client/activation/activationService'; import { @@ -16,6 +23,7 @@ import { import { LSNotSupportedDiagnosticServiceId } from '../../client/application/diagnostics/checks/lsNotSupported'; import { IDiagnostic, IDiagnosticsService } from '../../client/application/diagnostics/types'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types'; +import { PythonSettings } from '../../client/common/configSettings'; import { IPlatformService } from '../../client/common/platform/types'; import { IConfigurationService, @@ -28,6 +36,7 @@ import { IPythonSettings, Resource, } from '../../client/common/types'; +import { LanguageService } from '../../client/common/utils/localize'; import { noop } from '../../client/common/utils/misc'; import { Architecture } from '../../client/common/utils/platform'; import { IInterpreterService } from '../../client/interpreter/contracts'; @@ -53,6 +62,8 @@ suite('Language Server Activation - ActivationService', () => { let workspaceConfig: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let interpreterChangedHandler!: Function; + let output: TypeMoq.IMock; + setup(() => { serviceContainer = TypeMoq.Mock.ofType(); appShell = TypeMoq.Mock.ofType(); @@ -99,7 +110,7 @@ suite('Language Server Activation - ActivationService', () => { workspaceService .setup((ws) => ws.getConfiguration('python', TypeMoq.It.isAny())) .returns(() => workspaceConfig.object); - const output = TypeMoq.Mock.ofType(); + output = TypeMoq.Mock.ofType(); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())) .returns(() => output.object); @@ -649,6 +660,321 @@ suite('Language Server Activation - ActivationService', () => { ); }); + suite('Test JediLSP -> Jedi language server swap', () => { + let serviceContainer: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let cmdManager: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + let platformService: TypeMoq.IMock; + let lsNotSupportedDiagnosticService: TypeMoq.IMock; + let stateFactory: TypeMoq.IMock; + let state: TypeMoq.IMock>; + let interpreterService: TypeMoq.IMock; + let configurationService: TypeMoq.IMock; + + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); + cmdManager = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); + stateFactory = TypeMoq.Mock.ofType(); + state = TypeMoq.Mock.ofType>(); + configurationService = TypeMoq.Mock.ofType(); + const extensionsMock = TypeMoq.Mock.ofType(); + lsNotSupportedDiagnosticService = TypeMoq.Mock.ofType(); + workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); + workspaceService.setup((w) => w.workspaceFolders).returns(() => []); + interpreterService = TypeMoq.Mock.ofType(); + state.setup((s) => s.value).returns(() => undefined); + state.setup((s) => s.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); + const output = TypeMoq.Mock.ofType(); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())) + .returns(() => output.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService))) + .returns(() => configurationService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => cmdManager.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))) + .returns(() => platformService.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService))) + .returns(() => interpreterService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IExtensions))).returns(() => extensionsMock.object); + serviceContainer + .setup((s) => + s.get( + TypeMoq.It.isValue(IDiagnosticsService), + TypeMoq.It.isValue(LSNotSupportedDiagnosticServiceId), + ), + ) + .returns(() => lsNotSupportedDiagnosticService.object); + const activator = TypeMoq.Mock.ofType(); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerActivator), TypeMoq.It.isAny())) + .returns(() => activator.object); + }); + + test('If the workspace language server settings is set to JediLSP, update it to Jedi', async () => { + const resource = Uri.parse('one.py'); + const interpreter = { + version: { major: 3, minor: 8, patch: 0 }, + } as PythonEnvironment; + + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns( + () => + ({ + languageServer: LanguageServerType.JediLSP, + languageServerIsDefault: false, + } as PythonSettings), + ); + workspaceService + .setup((ws) => ws.getConfiguration('python', resource)) + .returns( + () => + (({ + inspect: () => ({ + workspaceValue: LanguageServerType.JediLSP, + }), + } as unknown) as WorkspaceConfiguration), + ); + + const activationService = new LanguageServerExtensionActivationService( + serviceContainer.object, + stateFactory.object, + ); + + await activationService.get(resource, interpreter); + + configurationService.verify( + (c) => + c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Workspace), + TypeMoq.Times.once(), + ); + }); + + test('If the global language server settings is set to JediLSP, update it to Jedi', async () => { + const resource = Uri.parse('one.py'); + const interpreter = { + version: { major: 3, minor: 8, patch: 0 }, + } as PythonEnvironment; + + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns( + () => + ({ + languageServer: LanguageServerType.JediLSP, + languageServerIsDefault: false, + } as PythonSettings), + ); + workspaceService + .setup((ws) => ws.getConfiguration('python', resource)) + .returns( + () => + (({ + inspect: () => ({ + globalValue: LanguageServerType.JediLSP, + }), + } as unknown) as WorkspaceConfiguration), + ); + + const activationService = new LanguageServerExtensionActivationService( + serviceContainer.object, + stateFactory.object, + ); + + await activationService.get(resource, interpreter); + + configurationService.verify( + (c) => c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Global), + TypeMoq.Times.once(), + ); + }); + + test('If no language server settings are set to JediLSP, do nothing', async () => { + const resource = Uri.parse('one.py'); + const interpreter = { + version: { major: 3, minor: 8, patch: 0 }, + } as PythonEnvironment; + + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns( + () => + ({ + languageServer: LanguageServerType.JediLSP, + languageServerIsDefault: false, + } as PythonSettings), + ); + workspaceService + .setup((ws) => ws.getConfiguration('python', resource)) + .returns( + () => + (({ + inspect: () => ({ + workspaceValue: LanguageServerType.Node, + }), + } as unknown) as WorkspaceConfiguration), + ); + + const activationService = new LanguageServerExtensionActivationService( + serviceContainer.object, + stateFactory.object, + ); + + await activationService.get(resource, interpreter); + + configurationService.verify( + (c) => + c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Workspace), + TypeMoq.Times.never(), + ); + }); + }); + + suite('Test language server swap when using Python 2.7', () => { + let serviceContainer: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let cmdManager: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + let platformService: TypeMoq.IMock; + let lsNotSupportedDiagnosticService: TypeMoq.IMock; + let stateFactory: TypeMoq.IMock; + let state: TypeMoq.IMock>; + let workspaceConfig: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let output: TypeMoq.IMock; + let configurationService: TypeMoq.IMock; + + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); + cmdManager = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); + stateFactory = TypeMoq.Mock.ofType(); + state = TypeMoq.Mock.ofType>(); + configurationService = TypeMoq.Mock.ofType(); + const extensionsMock = TypeMoq.Mock.ofType(); + lsNotSupportedDiagnosticService = TypeMoq.Mock.ofType(); + workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); + workspaceService.setup((w) => w.workspaceFolders).returns(() => []); + interpreterService = TypeMoq.Mock.ofType(); + state.setup((s) => s.value).returns(() => undefined); + state.setup((s) => s.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); + workspaceConfig = TypeMoq.Mock.ofType(); + workspaceService + .setup((ws) => ws.getConfiguration('python', TypeMoq.It.isAny())) + .returns(() => workspaceConfig.object); + output = TypeMoq.Mock.ofType(); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())) + .returns(() => output.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService))) + .returns(() => configurationService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => cmdManager.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))) + .returns(() => platformService.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService))) + .returns(() => interpreterService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IExtensions))).returns(() => extensionsMock.object); + serviceContainer + .setup((s) => + s.get( + TypeMoq.It.isValue(IDiagnosticsService), + TypeMoq.It.isValue(LSNotSupportedDiagnosticServiceId), + ), + ) + .returns(() => lsNotSupportedDiagnosticService.object); + }); + + const values: { ls: LanguageServerType; expected: LanguageServerType; outputString: string }[] = [ + { + ls: LanguageServerType.Jedi, + expected: LanguageServerType.None, + outputString: LanguageService.startingNone(), + }, + { + ls: LanguageServerType.Node, + expected: LanguageServerType.Node, + outputString: LanguageService.startingPylance(), + }, + { + ls: LanguageServerType.None, + expected: LanguageServerType.None, + outputString: LanguageService.startingNone(), + }, + ]; + + const interpreter = { + version: { major: 2, minor: 7, patch: 10 }, + } as PythonEnvironment; + + values.forEach(({ ls, expected, outputString }) => { + test(`When language server setting explicitly set to ${ls} and using Python 2.7, use a language server of type ${expected}`, async () => { + const resource = Uri.parse('one.py'); + const activator = TypeMoq.Mock.ofType(); + + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerActivator), expected)) + .returns(() => activator.object); + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns(() => ({ languageServer: ls, languageServerIsDefault: false } as PythonSettings)); + + const activationService = new LanguageServerExtensionActivationService( + serviceContainer.object, + stateFactory.object, + ); + + await activationService.get(resource, interpreter); + + output.verify((o) => o.appendLine(outputString), TypeMoq.Times.once()); + activator.verify((a) => a.start(resource, interpreter), TypeMoq.Times.once()); + }); + }); + + test('When default language server setting set to true and using Python 2.7, use Pylance', async () => { + const resource = Uri.parse('one.py'); + const activator = TypeMoq.Mock.ofType(); + + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerActivator), LanguageServerType.Node)) + .returns(() => activator.object); + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns(() => ({ languageServerIsDefault: true } as PythonSettings)); + + const activationService = new LanguageServerExtensionActivationService( + serviceContainer.object, + stateFactory.object, + ); + + await activationService.get(resource, interpreter); + + output.verify((o) => o.appendLine(LanguageService.startingPylance()), TypeMoq.Times.once()); + activator.verify((a) => a.start(resource, interpreter), TypeMoq.Times.once()); + }); + }); + suite('Test sendTelemetryForChosenLanguageServer()', () => { let serviceContainer: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; diff --git a/src/test/activation/defaultLanguageServer.unit.test.ts b/src/test/activation/defaultLanguageServer.unit.test.ts index 4dd8b4eaf88e..a06a146b9e32 100644 --- a/src/test/activation/defaultLanguageServer.unit.test.ts +++ b/src/test/activation/defaultLanguageServer.unit.test.ts @@ -9,94 +9,50 @@ import { Extension } from 'vscode'; import { setDefaultLanguageServer } from '../../client/activation/common/defaultlanguageServer'; import { LanguageServerType } from '../../client/activation/types'; import { PYLANCE_EXTENSION_ID } from '../../client/common/constants'; -import { JediLSP } from '../../client/common/experiments/groups'; -import { ExperimentService } from '../../client/common/experiments/service'; -import { IDefaultLanguageServer, IExperimentService, IExtensions } from '../../client/common/types'; +import { IDefaultLanguageServer, IExtensions } from '../../client/common/types'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceManager } from '../../client/ioc/types'; suite('Activation - setDefaultLanguageServer()', () => { - let experimentService: IExperimentService; let extensions: IExtensions; let extension: Extension; let serviceManager: IServiceManager; setup(() => { - experimentService = mock(ExperimentService); extensions = mock(); extension = mock(); serviceManager = mock(ServiceManager); }); - test('Pylance not installed and NOT in experiment', async () => { + test('Pylance not installed', async () => { let defaultServerType; when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(undefined); - when(experimentService.inExperiment(JediLSP.experiment)).thenResolve(false); when(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).thenCall( (_symbol, value: IDefaultLanguageServer) => { defaultServerType = value.defaultLSType; }, ); - await setDefaultLanguageServer(instance(experimentService), instance(extensions), instance(serviceManager)); + await setDefaultLanguageServer(instance(extensions), instance(serviceManager)); verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).once(); - verify(experimentService.inExperiment(JediLSP.experiment)).once(); verify(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).once(); expect(defaultServerType).to.equal(LanguageServerType.Jedi); }); - test('Pylance not installed and in experiment', async () => { + test('Pylance installed', async () => { let defaultServerType; - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(undefined); - when(experimentService.inExperiment(JediLSP.experiment)).thenResolve(true); - when(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).thenCall( - (_symbol, value: IDefaultLanguageServer) => { - defaultServerType = value.defaultLSType; - }, - ); - - await setDefaultLanguageServer(instance(experimentService), instance(extensions), instance(serviceManager)); - verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).once(); - verify(experimentService.inExperiment(JediLSP.experiment)).once(); - verify(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).once(); - expect(defaultServerType).to.equal(LanguageServerType.JediLSP); - }); - - test('Pylance installed and NOT in experiment', async () => { - let defaultServerType; - - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(extension)); - when(experimentService.inExperiment(JediLSP.experiment)).thenResolve(false); - when(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).thenCall( - (_symbol, value: IDefaultLanguageServer) => { - defaultServerType = value.defaultLSType; - }, - ); - - await setDefaultLanguageServer(instance(experimentService), instance(extensions), instance(serviceManager)); - - verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).once(); - verify(experimentService.inExperiment(JediLSP.experiment)).never(); - verify(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).once(); - expect(defaultServerType).to.equal(LanguageServerType.Node); - }); - - test('Pylance installed and in experiment', async () => { - let defaultServerType; when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(extension)); - when(experimentService.inExperiment(JediLSP.experiment)).thenResolve(true); when(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).thenCall( (_symbol, value: IDefaultLanguageServer) => { defaultServerType = value.defaultLSType; }, ); - await setDefaultLanguageServer(instance(experimentService), instance(extensions), instance(serviceManager)); + await setDefaultLanguageServer(instance(extensions), instance(serviceManager)); verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).once(); - verify(experimentService.inExperiment(JediLSP.experiment)).never(); verify(serviceManager.addSingletonInstance(IDefaultLanguageServer, anything())).once(); expect(defaultServerType).to.equal(LanguageServerType.Node); }); diff --git a/src/test/activation/languageServer/deprecationPrompt.unit.test.ts b/src/test/activation/languageServer/deprecationPrompt.unit.test.ts index e6dc0e20919e..2d3927aef4ee 100644 --- a/src/test/activation/languageServer/deprecationPrompt.unit.test.ts +++ b/src/test/activation/languageServer/deprecationPrompt.unit.test.ts @@ -101,13 +101,6 @@ suite('MPLS deprecation prompt', () => { switchTo: undefined, telemSwitchTo: LanguageServerType.Jedi, }, - { - shownInPreviousSession: false, - defaultLSType: LanguageServerType.JediLSP, - selection: MPLSDeprecation.switchToJedi(), - switchTo: undefined, - telemSwitchTo: LanguageServerType.Jedi, - }, { shownInPreviousSession: false, defaultLSType: LanguageServerType.Jedi, @@ -115,13 +108,6 @@ suite('MPLS deprecation prompt', () => { switchTo: LanguageServerType.Node, telemSwitchTo: LanguageServerType.Node, }, - { - shownInPreviousSession: false, - defaultLSType: LanguageServerType.JediLSP, - selection: MPLSDeprecation.switchToPylance(), - switchTo: LanguageServerType.Node, - telemSwitchTo: LanguageServerType.Node, - }, ]; [ConfigurationTarget.Workspace, ConfigurationTarget.Global].forEach((configLocation) => { diff --git a/src/test/activation/serviceRegistry.unit.test.ts b/src/test/activation/serviceRegistry.unit.test.ts index 9d8f1910e1eb..13183002830b 100644 --- a/src/test/activation/serviceRegistry.unit.test.ts +++ b/src/test/activation/serviceRegistry.unit.test.ts @@ -8,7 +8,6 @@ import { DownloadBetaChannelRule, DownloadDailyChannelRule } from '../../client/ import { LanguageServerDownloader } from '../../client/activation/common/downloader'; import { LanguageServerDownloadChannel } from '../../client/activation/common/packageRepository'; import { ExtensionSurveyPrompt } from '../../client/activation/extensionSurvey'; -import { JediExtensionActivator } from '../../client/activation/jedi'; import { DotNetLanguageServerActivator } from '../../client/activation/languageServer/activator'; import { DotNetLanguageServerAnalysisOptions } from '../../client/activation/languageServer/analysisOptions'; import { MPLSDeprecationPrompt } from '../../client/activation/languageServer/deprecationPrompt'; @@ -26,6 +25,7 @@ import { DotNetLanguageServerProxy } from '../../client/activation/languageServe import { DotNetLanguageServerManager } from '../../client/activation/languageServer/manager'; import { LanguageServerOutputChannel } from '../../client/activation/languageServer/outputChannel'; import { PlatformData } from '../../client/activation/languageServer/platformData'; +import { NoLanguageServerExtensionActivator } from '../../client/activation/none/activator'; import { registerTypes } from '../../client/activation/serviceRegistry'; import { IDownloadChannelRule, @@ -154,13 +154,6 @@ suite('Unit Tests - Language Server Activation Service Registry', () => { LanguageServerType.Microsoft, ), ).once(); - verify( - serviceManager.add( - ILanguageServerActivator, - JediExtensionActivator, - LanguageServerType.Jedi, - ), - ).once(); verify(serviceManager.add(ILanguageServerProxy, DotNetLanguageServerProxy)).once(); verify(serviceManager.add(ILanguageServerManager, DotNetLanguageServerManager)).once(); verify( @@ -178,5 +171,13 @@ suite('Unit Tests - Language Server Activation Service Registry', () => { verify( serviceManager.addSingleton(IMPLSDeprecationPrompt, MPLSDeprecationPrompt), ).once(); + + verify( + serviceManager.add( + ILanguageServerActivator, + NoLanguageServerExtensionActivator, + LanguageServerType.None, + ), + ).once(); }); }); diff --git a/src/test/common/configSettings/configSettings.unit.test.ts b/src/test/common/configSettings/configSettings.unit.test.ts index 20fe922fc90b..98365beac4b3 100644 --- a/src/test/common/configSettings/configSettings.unit.test.ts +++ b/src/test/common/configSettings/configSettings.unit.test.ts @@ -210,11 +210,19 @@ suite('Python Settings', async () => { } suite('languageServer settings', async () => { - Object.values(LanguageServerType).forEach(async (languageServer) => { - testLanguageServer(languageServer, languageServer, false); + const values = [ + { ls: LanguageServerType.Jedi, expected: LanguageServerType.Jedi }, + { ls: LanguageServerType.JediLSP, expected: LanguageServerType.Jedi }, + { ls: LanguageServerType.Microsoft, expected: LanguageServerType.Microsoft }, + { ls: LanguageServerType.Node, expected: LanguageServerType.Node }, + { ls: LanguageServerType.None, expected: LanguageServerType.None }, + ]; + + values.forEach(({ ls, expected }) => { + testLanguageServer(ls, expected, false); }); - testLanguageServer('invalid' as LanguageServerType, LanguageServerType.Jedi, true); + testLanguageServer('invalid' as LanguageServerType, LanguageServerType.None, true); }); function testExperiments(enabled: boolean) { diff --git a/src/test/languageServers/jedi/autocomplete/base.test.ts b/src/test/languageServers/jedi/autocomplete/base.test.ts deleted file mode 100644 index f4b023e2534c..000000000000 --- a/src/test/languageServers/jedi/autocomplete/base.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { isOs, isPythonVersion, OSType } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileImport = path.join(autoCompPath, 'imp.py'); -const fileDoc = path.join(autoCompPath, 'doc.py'); -const fileLambda = path.join(autoCompPath, 'lamb.py'); -const fileDecorator = path.join(autoCompPath, 'deco.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileSuppress = path.join(autoCompPath, 'suppress.py'); - -suite('Language Server: Autocomplete Base Tests', function () { - // Attempt to fix #1301 - - this.timeout(60000); - let ioc: UnitTestIocContainer; - let skipTest: boolean; - - suiteSetup(async function () { - // Attempt to fix #1301 - - this.timeout(60000); - await initialize(); - await initializeDI(); - skipTest = isOs(OSType.Windows) && (await isPythonVersion('3.8', '3.9')); - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - await ioc.dispose(); - }); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - - test('For "sys."', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileOne) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then(() => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 10); - return vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - }) - .then((list) => { - assert.equal( - list!.items.filter((item) => item.label === 'api_version').length, - 1, - 'api_version not found', - ); - }) - .then(done, done); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/975 - test('For "import *" find a specific completion for known lib [fstat]', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileImport); - await vscode.window.showTextDocument(textDocument); - const lineNum = 1; - const colNum = 4; - const position = new vscode.Position(lineNum, colNum); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - - const indexOfFstat = list!.items.findIndex((val: vscode.CompletionItem) => val.label === 'fstat'); - - assert( - indexOfFstat !== -1, - `fstat was not found as a completion in ${fileImport} at line ${lineNum}, col ${colNum}`, - ); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/898 - test('For "f.readlines()"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 27); - await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - // These are not known to work, jedi issue - // assert.equal(list.items.filter(item => item.label === 'capitalize').length, 1, 'capitalize not found (known not to work, Jedi issue)'); - // assert.notEqual(list.items.filter(item => item.label === 'upper').length, 1, 'upper not found'); - // assert.notEqual(list.items.filter(item => item.label === 'lower').length, 1, 'lower not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/265 - test('For "lambda"', async function () { - if (await isPythonVersion('2')) { - return this.skip(); - } - const textDocument = await vscode.workspace.openTextDocument(fileLambda); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(1, 19); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'append').length, 0, 'append not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'clear').length, 0, 'clear not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'count').length, 0, 'cound not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/630 - test('For "abc.decorators"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDecorator); - await vscode.window.showTextDocument(textDocument); - let position = new vscode.Position(3, 9); - let list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual( - list!.items.filter((item) => item.label === 'abstractmethod').length, - 0, - 'abstractmethod not found', - ); - - position = new vscode.Position(4, 9); - list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual( - list!.items.filter((item) => item.label === 'abstractmethod').length, - 0, - 'abstractmethod not found', - ); - - position = new vscode.Position(2, 30); - list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual( - list!.items.filter((item) => item.label === 'abstractmethod').length, - 0, - 'abstractmethod not found', - ); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/727 - // https://github.com/DonJayamanne/pythonVSCode/issues/746 - // https://github.com/davidhalter/jedi/issues/859 - test('For "time.slee"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(10, 9); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - - const items = list!.items.filter((item) => item.label === 'sleep'); - assert.notEqual(items.length, 0, 'sleep not found'); - - checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); - }); - - test('For custom class', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileOne) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 4); - return vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - }) - .then((list) => { - assert.notEqual(list!.items.filter((item) => item.label === 'method1').length, 0, 'method1 not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'method2').length, 0, 'method2 not found'); - }) - .then(done, done); - }); - - test('With Unicode Characters', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileEncoding) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 4); - return vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - }) - .then((list) => { - const items = list!.items.filter((item) => item.label === 'bar'); - assert.equal(items.length, 1, 'bar not found'); - - const expected1 = '说明 - keep this line, it works'; - checkDocumentation(items[0], expected1); - - const expected2 = '如果存在需要等待审批或正在执行的任务,将不刷新页面'; - checkDocumentation(items[0], expected2); - }) - .then(done, done); - }); - - test('Across files With Unicode Characters', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileEncodingUsed) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 5); - return vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - }) - .then((list) => { - let items = list!.items.filter((item) => item.label === 'Foo'); - assert.equal(items.length, 1, 'Foo not found'); - checkDocumentation(items[0], '说明'); - - items = list!.items.filter((item) => item.label === 'showMessage'); - assert.equal(items.length, 1, 'showMessage not found'); - - const expected1 = - 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.'; - checkDocumentation(items[0], expected1); - - const expected2 = 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.'; - checkDocumentation(items[0], expected2); - }) - .then(done, done); - }); - - // https://github.com/Microsoft/vscode-python/issues/110 - test('Suppress in strings/comments', async () => { - const positions = [ - new vscode.Position(0, 1), // false - new vscode.Position(0, 9), // true - new vscode.Position(0, 12), // false - new vscode.Position(1, 1), // false - new vscode.Position(1, 3), // false - new vscode.Position(2, 7), // false - new vscode.Position(3, 0), // false - new vscode.Position(4, 2), // false - new vscode.Position(4, 8), // false - new vscode.Position(5, 4), // false - new vscode.Position(5, 10), // false - ]; - const expected = [false, true, false, false, false, false, false, false, false, false, false]; - const textDocument = await vscode.workspace.openTextDocument(fileSuppress); - await vscode.window.showTextDocument(textDocument); - for (let i = 0; i < positions.length; i += 1) { - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - positions[i], - ); - const result = list!.items.filter((item) => item.label === 'abs').length; - assert.equal( - result > 0, - expected[i], - `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`, - ); - } - }); -}); - -function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { - let isValidType = false; - let documentation: string; - - if (typeof item.documentation === 'string') { - isValidType = true; - documentation = item.documentation; - } else { - documentation = (item.documentation as vscode.MarkdownString).value; - isValidType = documentation !== undefined && documentation !== null; - } - assert.equal(isValidType, true, 'Documentation is neither string nor vscode.MarkdownString'); - - const inDoc = documentation.indexOf(expectedContains) >= 0; - assert.equal(inDoc, true, 'Documentation incorrect'); -} diff --git a/src/test/languageServers/jedi/autocomplete/pep484.test.ts b/src/test/languageServers/jedi/autocomplete/pep484.test.ts deleted file mode 100644 index 401a0d5f5a76..000000000000 --- a/src/test/languageServers/jedi/autocomplete/pep484.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { rootWorkspaceUri } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'autocomp'); -const filePep484 = path.join(autoCompPath, 'pep484.py'); - -suite('Language Server: Autocomplete PEP 484', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - suiteSetup(async function () { - await initialize(); - await initializeDI(); - isPython2 = (await ioc.getPythonMajorVersion(rootWorkspaceUri!)) === 2; - if (isPython2) { - this.skip(); - return; - } - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - await ioc.dispose(); - }); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - ioc.registerInterpreterStorageTypes(); - await ioc.registerMockInterpreterTypes(); - } - - test('argument', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep484); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(2, 27); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'lower').length, 0, 'lower not found'); - }); - - test('return value', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep484); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(8, 6); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'bit_length').length, 0, 'bit_length not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'from_bytes').length, 0, 'from_bytes not found'); - }); -}); diff --git a/src/test/languageServers/jedi/autocomplete/pep526.test.ts b/src/test/languageServers/jedi/autocomplete/pep526.test.ts deleted file mode 100644 index 0d6a8c51027b..000000000000 --- a/src/test/languageServers/jedi/autocomplete/pep526.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { isPythonVersion } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'autocomp'); -const filePep526 = path.join(autoCompPath, 'pep526.py'); - -suite('Language Server: Autocomplete PEP 526', () => { - let ioc: UnitTestIocContainer; - suiteSetup(async function () { - // Pep526 only valid for 3.6+ (#2545) - if (await isPythonVersion('2', '3.4', '3.5')) { - return this.skip(); - } - - await initialize(); - await initializeDI(); - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - await ioc.dispose(); - }); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - test('variable (abc:str)', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep526); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(9, 8); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'lower').length, 0, 'lower not found'); - }); - - test('variable (abc: str = "")', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep526); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(8, 14); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'lower').length, 0, 'lower not found'); - }); - - test('variable (abc = UNKNOWN # type: str)', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep526); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(7, 14); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list!.items.filter((item) => item.label === 'lower').length, 0, 'lower not found'); - }); - - test('class methods', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep526); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - let position = new vscode.Position(20, 4); - let list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'a').length, 0, 'method a not found'); - - position = new vscode.Position(21, 4); - list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'b').length, 0, 'method b not found'); - }); - - test('class method types', async () => { - const textDocument = await vscode.workspace.openTextDocument(filePep526); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(21, 6); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - assert.notEqual(list!.items.filter((item) => item.label === 'bit_length').length, 0, 'bit_length not found'); - }); -}); diff --git a/src/test/languageServers/jedi/completionSource.unit.test.ts b/src/test/languageServers/jedi/completionSource.unit.test.ts deleted file mode 100644 index 78602e61c6aa..000000000000 --- a/src/test/languageServers/jedi/completionSource.unit.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as TypeMoq from 'typemoq'; -import { CancellationTokenSource, CompletionItemKind, Position, SymbolKind, TextDocument, TextLine } from 'vscode'; -import { IAutoCompleteSettings, IConfigurationService, IPythonSettings } from '../../../client/common/types'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { JediFactory } from '../../../client/languageServices/jediProxyFactory'; -import { CompletionSource } from '../../../client/providers/completionSource'; -import { IItemInfoSource } from '../../../client/providers/itemInfoSource'; -import { IAutoCompleteItem, ICompletionResult, JediProxyHandler } from '../../../client/providers/jediProxy'; - -suite('Completion Provider', () => { - let completionSource: CompletionSource; - let jediHandler: TypeMoq.IMock>; - let autoCompleteSettings: TypeMoq.IMock; - let itemInfoSource: TypeMoq.IMock; - setup(() => { - const jediFactory = TypeMoq.Mock.ofType(JediFactory); - jediHandler = TypeMoq.Mock.ofType>(); - const serviceContainer = TypeMoq.Mock.ofType(); - const configService = TypeMoq.Mock.ofType(); - const pythonSettings = TypeMoq.Mock.ofType(); - autoCompleteSettings = TypeMoq.Mock.ofType(); - autoCompleteSettings = TypeMoq.Mock.ofType(); - - jediFactory.setup((j) => j.getJediProxyHandler(TypeMoq.It.isAny())).returns(() => jediHandler.object); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) - .returns(() => configService.object); - configService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - pythonSettings.setup((p) => p.autoComplete).returns(() => autoCompleteSettings.object); - itemInfoSource = TypeMoq.Mock.ofType(); - completionSource = new CompletionSource(jediFactory.object, serviceContainer.object, itemInfoSource.object); - }); - - async function testDocumentation(source: string, addBrackets: boolean) { - const doc = TypeMoq.Mock.ofType(); - const position = new Position(1, 1); - const token = new CancellationTokenSource().token; - const lineText = TypeMoq.Mock.ofType(); - const completionResult = TypeMoq.Mock.ofType(); - - const autoCompleteItems: IAutoCompleteItem[] = [ - { - description: 'description', - kind: SymbolKind.Function, - raw_docstring: 'raw docstring', - rawType: CompletionItemKind.Function, - rightLabel: 'right label', - text: 'some text', - type: CompletionItemKind.Function, - }, - ]; - - autoCompleteSettings.setup((a) => a.addBrackets).returns(() => addBrackets); - doc.setup((d) => d.fileName).returns(() => ''); - doc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => source); - doc.setup((d) => d.lineAt(TypeMoq.It.isAny())).returns(() => lineText.object); - doc.setup((d) => d.offsetAt(TypeMoq.It.isAny())).returns(() => 0); - lineText.setup((l) => l.text).returns(() => source); - completionResult.setup((c) => c.requestId).returns(() => 1); - completionResult.setup((c) => c.items).returns(() => autoCompleteItems); - completionResult.setup((c: any) => c.then).returns(() => undefined); - jediHandler - .setup((j) => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve(completionResult.object); - }); - - const expectedSource = `${source}${autoCompleteItems[0].text}`; - itemInfoSource - .setup((i) => - i.getItemInfoFromText( - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - expectedSource, - TypeMoq.It.isAny(), - ), - ) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.once()); - - const [item] = await completionSource.getVsCodeCompletionItems(doc.object, position, token); - await completionSource.getDocumentation(item, token); - itemInfoSource.verifyAll(); - } - - test("Ensure docs are provided when 'addBrackets' setting is false", async () => { - const source = 'if True:\n print("Hello")\n'; - await testDocumentation(source, false); - }); - test("Ensure docs are provided when 'addBrackets' setting is true", async () => { - const source = 'if True:\n print("Hello")\n'; - await testDocumentation(source, true); - }); -}); diff --git a/src/test/languageServers/jedi/definitions/hover.jedi.test.ts b/src/test/languageServers/jedi/definitions/hover.jedi.test.ts deleted file mode 100644 index 885a6d8d7337..000000000000 --- a/src/test/languageServers/jedi/definitions/hover.jedi.test.ts +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { isOs, isPythonVersion, OSType } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { normalizeMarkedString } from '../../../textUtils'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'autocomp'); -const hoverPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'hover'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileThree = path.join(autoCompPath, 'three.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileHover = path.join(autoCompPath, 'hoverTest.py'); -const fileStringFormat = path.join(hoverPath, 'functionHover.py'); - -suite('Language Server: Hover Definition (Jedi)', () => { - let skipTest: boolean; - suiteSetup(async () => { - await initialize(); - skipTest = isOs(OSType.Windows) && (await isPythonVersion('3.8', '3.9')); - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('Method', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileOne) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 5); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '30,4', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '30,11', - 'End position is incorrect', - ); - assert.equal(def[0].contents.length, 1, 'Invalid content items'); - - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal( - normalizeMarkedString(def[0].contents[0]), - expectedContent, - 'function signature incorrect', - ); - }) - .then(done, done); - }); - - test('Across files', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileThree) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 12); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '1,9', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '1,12', - 'End position is incorrect', - ); - assert.equal( - normalizeMarkedString(def[0].contents[0]), - - '```python' + EOL + 'def fun()' + EOL + '```' + EOL + 'This is fun', - 'Invalid contents', - ); - }) - .then(done, done); - }); - - test('With Unicode Characters', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileEncoding) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 6); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '25,4', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '25,7', - 'End position is incorrect', - ); - assert.equal( - normalizeMarkedString(def[0].contents[0]), - - '```python' + - EOL + - 'def bar()' + - EOL + - '```' + - EOL + - '说明 - keep this line, it works' + - EOL + - 'delete following line, it works' + - EOL + - '如果存在需要等待审批或正在执行的任务,将不刷新页面', - 'Invalid contents', - ); - }) - .then(done, done); - }); - - test('Across files with Unicode Characters', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileEncodingUsed) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 11); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '1,5', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '1,16', - 'End position is incorrect', - ); - assert.equal( - normalizeMarkedString(def[0].contents[0]), - - '```python' + - EOL + - 'def showMessage()' + - EOL + - '```' + - EOL + - 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + - EOL + - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', - 'Invalid contents', - ); - }) - .then(done, done); - }); - - test('Nothing for keywords (class)', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileOne) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(5, 1); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((def) => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }) - .then(done, done); - }); - - test('Nothing for keywords (for)', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 1); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((def) => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }) - .then(done, done); - }); - - test('Highlighting Class', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(11, 15); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '11,12', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '11,18', - 'End position is incorrect', - ); - const documentation = - '```python' + - EOL + - 'class Random(x=None)' + - EOL + - '```' + - EOL + - 'Random number generator base class used by bound module functions.' + - EOL + - '' + - EOL + - "Used to instantiate instances of Random to get generators that don't" + - EOL + - 'share state.' + - EOL + - '' + - EOL + - 'Class Random can also be subclassed if you want to use a different basic' + - EOL + - 'generator of your own devising: in that case, override the following' + - EOL + - 'methods: random(), seed(), getstate(), and setstate().' + - EOL + - 'Optionally, implement a getrandbits() method so that randrange()' + - EOL + - 'can cover arbitrarily large ranges.'; - - assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid contents'); - }) - .then(done, done); - }); - - test('Highlight Method', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(12, 10); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '12,5', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '12,12', - 'End position is incorrect', - ); - assert.equal( - normalizeMarkedString(def[0].contents[0]), - - '```python' + - EOL + - 'def randint(a, b)' + - EOL + - '```' + - EOL + - 'Return random integer in range [a, b], including both end points.', - 'Invalid contents', - ); - }) - .then(done, done); - }); - - test('Highlight Function', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(8, 14); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '8,11', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '8,15', - 'End position is incorrect', - ); - - const minExpectedContent = - '```python' + - EOL + - 'def acos(x: SupportsFloat)' + - EOL + - '```' + - EOL + - 'Return the arc cosine (measured in radians) of x.'; - assert.ok(normalizeMarkedString(def[0].contents[0]).startsWith(minExpectedContent), 'Invalid contents'); - }) - .then(done, done); - }); - - test('Highlight Multiline Method Signature', function (done) { - // TODO: Fix this test. - // See https://github.com/microsoft/vscode-python/issues/10399. - if (skipTest) { - this.skip(); - } - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(14, 14); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal( - `${def[0].range!.start.line},${def[0].range!.start.character}`, - '14,9', - 'Start position is incorrect', - ); - assert.equal( - `${def[0].range!.end.line},${def[0].range!.end.character}`, - '14,15', - 'End position is incorrect', - ); - assert.equal( - normalizeMarkedString(def[0].contents[0]), - - '```python' + - EOL + - 'class Thread(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)' + - EOL + - '```' + - EOL + - 'A class that represents a thread of control.' + - EOL + - '' + - EOL + - 'This class can be safely subclassed in a limited fashion.', - 'Invalid content items', - ); - }) - .then(done, done); - }); - - test('Variable', (done) => { - let textDocument: vscode.TextDocument; - vscode.workspace - .openTextDocument(fileHover) - .then((document) => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }) - .then((_editor) => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(6, 2); - return vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - }) - .then((result) => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('```python') === -1) { - assert.fail(contents, '', 'First line is incorrect', 'compare'); - } - if (contents.indexOf('rnd: Random') === -1) { - assert.fail(contents, '', 'Variable name or type are missing', 'compare'); - } - }) - .then(done, done); - }); - - test('Hover over method shows proper text.', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(8, 4); - const def = (await vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ))!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('def my_func') === -1) { - assert.fail(contents, '', "'def my_func' is missing", 'compare'); - } - if (contents.indexOf('This is a test.') === -1 && contents.indexOf('It also includes this text, too.') === -1) { - assert.fail(contents, '', 'Expected custom function text missing', 'compare'); - } - }); -}); diff --git a/src/test/languageServers/jedi/definitions/navigation.test.ts b/src/test/languageServers/jedi/definitions/navigation.test.ts deleted file mode 100644 index 9d88ef71e0c3..000000000000 --- a/src/test/languageServers/jedi/definitions/navigation.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { rootWorkspaceUri } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const decoratorsPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'definition', 'navigation'); -const fileDefinitions = path.join(decoratorsPath, 'definitions.py'); -const fileUsages = path.join(decoratorsPath, 'usages.py'); - -suite('Language Server: Definition Navigation', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - - suiteSetup(async () => { - await initialize(); - await initializeDI(); - isPython2 = (await ioc.getPythonMajorVersion(rootWorkspaceUri!)) === 2; - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - await ioc.dispose(); - }); - - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - ioc.registerInterpreterStorageTypes(); - await ioc.registerMockInterpreterTypes(); - } - - const assertFile = (expectedLocation: string, location: vscode.Uri) => { - const relLocation = vscode.workspace.asRelativePath(location); - const expectedRelLocation = vscode.workspace.asRelativePath(expectedLocation); - assert.equal(expectedRelLocation, relLocation, 'Position is in wrong file'); - }; - - const formatPosition = (position: vscode.Position) => `${position.line},${position.character}`; - - const assertRange = (expectedRange: vscode.Range, range: vscode.Range) => { - assert.equal(formatPosition(expectedRange.start), formatPosition(range.start), 'Start position is incorrect'); - assert.equal(formatPosition(expectedRange.end), formatPosition(range.end), 'End position is incorrect'); - }; - - const buildTest = ( - startFile: string, - startPosition: vscode.Position, - expectedFiles: string[], - expectedRanges: vscode.Range[], - ) => async () => { - const textDocument = await vscode.workspace.openTextDocument(startFile); - await vscode.window.showTextDocument(textDocument); - assert(vscode.window.activeTextEditor, 'No active editor'); - - const locations = await vscode.commands.executeCommand( - 'vscode.executeDefinitionProvider', - textDocument.uri, - startPosition, - ); - assert.equal(locations!.length, expectedFiles.length, 'Wrong number of results'); - - for (let i = 0; i < locations!.length; i += 1) { - assertFile(expectedFiles[i], locations![i].uri); - assertRange(expectedRanges[i], locations![i].range!); - } - }; - - test( - 'From own definition', - buildTest(fileDefinitions, new vscode.Position(2, 6), [fileDefinitions], [new vscode.Range(2, 0, 11, 17)]), - ); - - test( - 'Nested function', - buildTest(fileDefinitions, new vscode.Position(11, 16), [fileDefinitions], [new vscode.Range(6, 4, 10, 16)]), - ); - - test( - 'Decorator usage', - buildTest(fileDefinitions, new vscode.Position(13, 1), [fileDefinitions], [new vscode.Range(2, 0, 11, 17)]), - ); - - test( - 'Function decorated by stdlib', - buildTest(fileDefinitions, new vscode.Position(29, 6), [fileDefinitions], [new vscode.Range(21, 0, 27, 17)]), - ); - - test( - 'Function decorated by local decorator', - buildTest(fileDefinitions, new vscode.Position(30, 6), [fileDefinitions], [new vscode.Range(14, 0, 18, 7)]), - ); - - test( - 'Module imported decorator usage', - buildTest(fileUsages, new vscode.Position(3, 15), [fileDefinitions], [new vscode.Range(2, 0, 11, 17)]), - ); - - test( - 'Module imported function decorated by stdlib', - buildTest(fileUsages, new vscode.Position(11, 19), [fileDefinitions], [new vscode.Range(21, 0, 27, 17)]), - ); - - test( - 'Module imported function decorated by local decorator', - buildTest(fileUsages, new vscode.Position(12, 19), [fileDefinitions], [new vscode.Range(14, 0, 18, 7)]), - ); - - test('Specifically imported decorator usage', async () => { - const navigationTest = buildTest(fileUsages, new vscode.Position(7, 1), isPython2 ? [] : [fileDefinitions], [ - new vscode.Range(2, 0, 11, 17), - ]); - await navigationTest(); - }); - - test('Specifically imported function decorated by stdlib', async () => { - const navigationTest = buildTest(fileUsages, new vscode.Position(14, 6), isPython2 ? [] : [fileDefinitions], [ - new vscode.Range(21, 0, 27, 17), - ]); - await navigationTest(); - }); - - test('Specifically imported function decorated by local decorator', async () => { - const navigationTest = buildTest(fileUsages, new vscode.Position(15, 6), isPython2 ? [] : [fileDefinitions], [ - new vscode.Range(14, 0, 18, 7), - ]); - await navigationTest(); - }); -}); diff --git a/src/test/languageServers/jedi/definitions/parallel.jedi.test.ts b/src/test/languageServers/jedi/definitions/parallel.jedi.test.ts deleted file mode 100644 index f9ba8950642d..000000000000 --- a/src/test/languageServers/jedi/definitions/parallel.jedi.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { IS_WINDOWS } from '../../../../client/common/platform/constants'; -import { closeActiveWindows, initialize } from '../../../initialize'; -import { normalizeMarkedString } from '../../../textUtils'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); - -suite('Language Server: Code, Hover Definition and Intellisense (Jedi)', () => { - suiteSetup(initialize); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('All three together', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileOne); - - let position = new vscode.Position(30, 5); - const hoverDef = await vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - textDocument.uri, - position, - ); - const codeDef = await vscode.commands.executeCommand( - 'vscode.executeDefinitionProvider', - textDocument.uri, - position, - ); - position = new vscode.Position(3, 10); - const list = await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - textDocument.uri, - position, - ); - - assert.equal(list!.items.filter((item) => item.label === 'api_version').length, 1, 'api_version not found'); - - assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); - const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; - const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; - assert.equal(actualPath, expectedPath, 'Incorrect file'); - assert.equal( - `${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, - '17,4', - 'Start position is incorrect', - ); - assert.equal( - `${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, - '21,11', - 'End position is incorrect', - ); - - assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); - assert.equal( - `${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, - '30,4', - 'Start position is incorrect', - ); - assert.equal( - `${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, - '30,11', - 'End position is incorrect', - ); - assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); - - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); - }); -}); diff --git a/src/test/languageServers/jedi/lspSetup.ts b/src/test/languageServers/jedi/lspSetup.ts deleted file mode 100644 index 451a66baa8ea..000000000000 --- a/src/test/languageServers/jedi/lspSetup.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as fs from 'fs-extra'; -import * as path from 'path'; -import { JediLSP } from '../../../client/common/experiments/groups'; -import { EXTENSION_ROOT_DIR } from '../../../client/constants'; - -// Modify package.json so that it allows workspace based experiment settings -const packageJsonPath = path.join(EXTENSION_ROOT_DIR, 'package.json'); -const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); -packageJson.contributes.configuration.properties['python.experiments.optInto'].scope = 'resource'; -fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, ' ')); - -// Modify settings.json so that it turns on the LSP experiment -const settingsJsonPath = path.join(__dirname, '..', '..', '..', '..', 'src', 'test', '.vscode', 'settings.json'); -const settingsJsonPromise = import('../../.vscode/settings.json'); - -settingsJsonPromise.then((settingsJson) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (settingsJson)['python.experiments.optInto'] = [JediLSP.experiment]; - return fs.writeFile(settingsJsonPath, JSON.stringify(settingsJson, undefined, ' ')); -}); diff --git a/src/test/languageServers/jedi/lspTeardown.ts b/src/test/languageServers/jedi/lspTeardown.ts deleted file mode 100644 index 674d6ea44c61..000000000000 --- a/src/test/languageServers/jedi/lspTeardown.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed under the MIT License. - -import * as fs from 'fs-extra'; -import * as path from 'path'; -import { EXTENSION_ROOT_DIR } from '../../../client/constants'; - -// Put back the package json -const packageJsonPath = path.join(EXTENSION_ROOT_DIR, 'package.json'); -const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); -packageJson.contributes.configuration.properties['python.experiments.optInto'].scope = 'machine'; -fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, ' ')); - -// Rewrite settings json so we're not overriding the experiment values -const settingsJsonPath = path.join(__dirname, '..', '..', '..', '..', 'src', 'test', '.vscode', 'settings.json'); -const settingsJsonPromise = import('../../.vscode/settings.json'); - -settingsJsonPromise.then((settingsJson) => - fs.writeFile(settingsJsonPath, JSON.stringify(settingsJson, undefined, ' ')), -); diff --git a/src/test/languageServers/jedi/pythonSignatureProvider.unit.test.ts b/src/test/languageServers/jedi/pythonSignatureProvider.unit.test.ts deleted file mode 100644 index dec3a1ea7aff..000000000000 --- a/src/test/languageServers/jedi/pythonSignatureProvider.unit.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { assert, expect, use } from 'chai'; -import * as chaipromise from 'chai-as-promised'; -import * as TypeMoq from 'typemoq'; -import { CancellationToken, Position, SignatureHelp, TextDocument, TextLine, Uri } from 'vscode'; -import { JediFactory } from '../../../client/languageServices/jediProxyFactory'; -import { IArgumentsResult, JediProxyHandler } from '../../../client/providers/jediProxy'; -import { isPositionInsideStringOrComment } from '../../../client/providers/providerUtilities'; -import { PythonSignatureProvider } from '../../../client/providers/signatureProvider'; - -use(chaipromise); - -suite('Signature Provider unit tests', () => { - let pySignatureProvider: PythonSignatureProvider; - let jediHandler: TypeMoq.IMock>; - let argResultItems: IArgumentsResult; - setup(() => { - const jediFactory = TypeMoq.Mock.ofType(JediFactory); - jediHandler = TypeMoq.Mock.ofType>(); - jediFactory.setup((j) => j.getJediProxyHandler(TypeMoq.It.isAny())).returns(() => jediHandler.object); - pySignatureProvider = new PythonSignatureProvider(jediFactory.object); - argResultItems = { - definitions: [ - { - description: 'The result', - docstring: 'Some docstring goes here.', - name: 'print', - paramindex: 0, - params: [ - { - description: 'Some parameter', - docstring: 'gimme docs', - name: 'param', - value: 'blah', - }, - ], - }, - ], - requestId: 1, - }; - }); - - function testSignatureReturns(source: string, pos: number): Thenable { - const doc = TypeMoq.Mock.ofType(); - const position = new Position(0, pos); - const lineText = TypeMoq.Mock.ofType(); - const argsResult = TypeMoq.Mock.ofType(); - const cancelToken = TypeMoq.Mock.ofType(); - cancelToken.setup((ct) => ct.isCancellationRequested).returns(() => false); - - doc.setup((d) => d.fileName).returns(() => ''); - doc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => source); - doc.setup((d) => d.lineAt(TypeMoq.It.isAny())).returns(() => lineText.object); - doc.setup((d) => d.offsetAt(TypeMoq.It.isAny())).returns(() => pos - 1); // pos is 1-based - const docUri = TypeMoq.Mock.ofType(); - docUri.setup((u) => u.scheme).returns(() => 'http'); - doc.setup((d) => d.uri).returns(() => docUri.object); - lineText.setup((l) => l.text).returns(() => source); - argsResult.setup((c) => c.requestId).returns(() => 1); - - argsResult.setup((c) => c.definitions).returns(() => (argResultItems as any)[0].definitions); - jediHandler - .setup((j) => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve(argResultItems); - }); - - return pySignatureProvider.provideSignatureHelp(doc.object, position, cancelToken.object); - } - - function testIsInsideStringOrComment(sourceLine: string, sourcePos: number): boolean { - const textLine: TypeMoq.IMock = TypeMoq.Mock.ofType(); - textLine.setup((t) => t.text).returns(() => sourceLine); - const doc: TypeMoq.IMock = TypeMoq.Mock.ofType(); - const pos: Position = new Position(1, sourcePos); - - doc.setup((d) => d.fileName).returns(() => ''); - doc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => sourceLine); - doc.setup((d) => d.lineAt(TypeMoq.It.isAny())).returns(() => textLine.object); - doc.setup((d) => d.offsetAt(TypeMoq.It.isAny())).returns(() => sourcePos); - - return isPositionInsideStringOrComment(doc.object, pos); - } - - test('Ensure no signature is given within a string.', async () => { - const source = " print('Python is awesome,')\n"; - const sigHelp: SignatureHelp = await testSignatureReturns(source, 27); - expect(sigHelp).to.not.be.equal( - undefined, - 'Expected to get a blank signature item back - did the pattern change here?', - ); - expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a string?'); - }); - test('Ensure no signature is given within a line comment.', async () => { - const source = "# print('Python is awesome,')\n"; - const sigHelp: SignatureHelp = await testSignatureReturns(source, 28); - expect(sigHelp).to.not.be.equal( - undefined, - 'Expected to get a blank signature item back - did the pattern change here?', - ); - expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a full-line comment?'); - }); - test('Ensure no signature is given within a comment tailing a command.', async () => { - const source = " print('Python') # print('is awesome,')\n"; - const sigHelp: SignatureHelp = await testSignatureReturns(source, 38); - expect(sigHelp).to.not.be.equal( - undefined, - 'Expected to get a blank signature item back - did the pattern change here?', - ); - expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a trailing comment?'); - }); - test('Ensure signature is given for built-in print command.', async () => { - const source = " print('Python',)\n"; - let sigHelp: SignatureHelp; - try { - sigHelp = await testSignatureReturns(source, 18); - expect(sigHelp).to.not.equal( - undefined, - 'Expected to get a blank signature item back - did the pattern change here?', - ); - expect(sigHelp.signatures.length).to.not.equal( - 0, - 'Expected dummy argresult back from testing our print signature.', - ); - expect(sigHelp.activeParameter).to.be.equal( - 0, - "Parameter for print should be the first member of the test argresult's params object.", - ); - expect(sigHelp.activeSignature).to.be.equal( - 0, - 'The signature for print should be the first member of the test argresult.', - ); - expect(sigHelp.signatures[sigHelp.activeSignature].label).to.be.equal( - 'print(param)', - `Expected arg result calls for specific returned signature of \'print(param)\' but we got ${ - sigHelp.signatures[sigHelp.activeSignature].label - }`, - ); - } catch (error) { - assert(false, `Caught exception ${error}`); - } - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = sourceLine.length - 1; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.not.be.equal( - true, - [`Position set to the end of ${sourceLine} but `, 'is reported as being within a string or comment.'].join( - '', - ), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected at end of source.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = 0; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.not.be.equal( - true, - [`Position set to the end of ${sourceLine} but `, 'is reported as being within a string or comment.'].join( - '', - ), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected at beginning of source.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = 0; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.not.be.equal( - true, - [ - `Position set to the beginning of ${sourceLine} but `, - 'is reported as being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected within a string.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = 16; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within the string in ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected immediately before a string.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = 8; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - false, - [ - `Position set to just before the string in ${sourceLine} (position ${sourcePos}) but `, - 'is reported as being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected immediately in a string.', () => { - const sourceLine: string = " print('Hello world!')\n"; - const sourcePos: number = 9; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set to the start of the string in ${sourceLine} (position ${sourcePos}) but `, - 'is reported as being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected within a comment.', () => { - const sourceLine: string = "# print('Hello world!')\n"; - const sourcePos: number = 16; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a full line comment ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected within a trailing comment.', () => { - const sourceLine: string = " print('Hello world!') # some comment...\n"; - const sourcePos: number = 34; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a trailing line comment ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected at the very end of a trailing comment.', () => { - const sourceLine: string = " print('Hello world!') # some comment...\n"; - const sourcePos: number = sourceLine.length - 1; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a trailing line comment ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected within a multiline string.', () => { - const sourceLine: string = - " stringVal = '''This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!'''\n"; - const sourcePos: number = 48; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected at the very last quote on a multiline string.', () => { - const sourceLine: string = - " stringVal = '''This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!'''\n"; - const sourcePos: number = sourceLine.length - 2; // just at the last ' - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected within a multiline string (double-quoted).', () => { - const sourceLine: string = - ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!"""\n'; - const sourcePos: number = 48; - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected at the very last quote on a multiline string (double-quoted).', () => { - const sourceLine: string = - ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!"""\n'; - const sourcePos: number = sourceLine.length - 2; // just at the last ' - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); - test('Ensure isPositionInsideStringOrComment is behaving as expected during construction of a multiline string (double-quoted).', () => { - const sourceLine: string = ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff'; - const sourcePos: number = sourceLine.length - 1; // just at the last position in the string before it's termination - const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); - - expect(isInsideStrComment).to.be.equal( - true, - [ - `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, - 'is reported as NOT being within a string or comment.', - ].join(''), - ); - }); -}); diff --git a/src/test/languageServers/jedi/signature/signature.jedi.test.ts b/src/test/languageServers/jedi/signature/signature.jedi.test.ts deleted file mode 100644 index f8286201f8b7..000000000000 --- a/src/test/languageServers/jedi/signature/signature.jedi.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; -import { rootWorkspaceUri } from '../../../common'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const autoCompPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'signature'); - -class SignatureHelpResult { - constructor( - public line: number, - public index: number, - public signaturesCount: number, - public activeParameter: number, - public parameterName: string | null, - ) {} -} - -suite('Language Server: Signatures (Jedi)', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - suiteSetup(async () => { - await initialize(); - await initializeDI(); - isPython2 = (await ioc.getPythonMajorVersion(rootWorkspaceUri!)) === 2; - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - await ioc.dispose(); - }); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - ioc.registerInterpreterStorageTypes(); - await ioc.registerMockInterpreterTypes(); - } - - test('For ctor', async () => { - const expected = [ - new SignatureHelpResult(5, 11, 0, 0, null), - new SignatureHelpResult(5, 12, 1, 0, 'name'), - new SignatureHelpResult(5, 13, 0, 0, null), - new SignatureHelpResult(5, 14, 0, 0, null), - new SignatureHelpResult(5, 15, 0, 0, null), - new SignatureHelpResult(5, 16, 0, 0, null), - new SignatureHelpResult(5, 17, 0, 0, null), - new SignatureHelpResult(5, 18, 1, 1, 'age'), - new SignatureHelpResult(5, 19, 1, 1, 'age'), - new SignatureHelpResult(5, 20, 0, 0, null), - ]; - - const document = await openDocument(path.join(autoCompPath, 'classCtor.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For intrinsic', async () => { - let expected: SignatureHelpResult[]; - if (isPython2) { - expected = [ - new SignatureHelpResult(0, 0, 0, 0, null), - new SignatureHelpResult(0, 1, 0, 0, null), - new SignatureHelpResult(0, 2, 0, 0, null), - new SignatureHelpResult(0, 3, 0, 0, null), - new SignatureHelpResult(0, 4, 0, 0, null), - new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 1, 0, 'x'), - new SignatureHelpResult(0, 7, 1, 0, 'x'), - ]; - } else { - expected = [ - new SignatureHelpResult(0, 0, 0, 0, null), - new SignatureHelpResult(0, 1, 0, 0, null), - new SignatureHelpResult(0, 2, 0, 0, null), - new SignatureHelpResult(0, 3, 0, 0, null), - new SignatureHelpResult(0, 4, 0, 0, null), - new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 2, 0, 'stop'), - new SignatureHelpResult(0, 7, 2, 0, 'stop'), - // new SignatureHelpResult(0, 6, 1, 0, 'start'), - // new SignatureHelpResult(0, 7, 1, 0, 'start'), - // new SignatureHelpResult(0, 8, 1, 1, 'stop'), - // new SignatureHelpResult(0, 9, 1, 1, 'stop'), - // new SignatureHelpResult(0, 10, 1, 1, 'stop'), - // new SignatureHelpResult(0, 11, 1, 2, 'step'), - // new SignatureHelpResult(1, 0, 1, 2, 'step') - ]; - } - - const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For ellipsis', async function () { - if (isPython2) { - return this.skip(); - } - const expected = [ - new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 1, 0, 'values'), - new SignatureHelpResult(0, 7, 1, 0, 'values'), - new SignatureHelpResult(0, 8, 1, 0, 'values'), - new SignatureHelpResult(0, 9, 1, 0, 'values'), - new SignatureHelpResult(0, 10, 1, 0, 'values'), - new SignatureHelpResult(0, 11, 1, 0, 'values'), - new SignatureHelpResult(0, 12, 1, 0, 'values'), - ]; - - const document = await openDocument(path.join(autoCompPath, 'ellipsis.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For pow', async () => { - let expected: SignatureHelpResult; - if (isPython2) { - expected = new SignatureHelpResult(0, 4, 4, 0, 'x'); - } else { - expected = new SignatureHelpResult(0, 4, 4, 0, null); - } - - const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py')); - await checkSignature(expected, document!.uri, 0); - }); -}); - -async function openDocument(documentPath: string): Promise { - const document = await vscode.workspace.openTextDocument(documentPath); - await vscode.window.showTextDocument(document!); - return document; -} - -async function checkSignature(expected: SignatureHelpResult, uri: vscode.Uri, caseIndex: number) { - const position = new vscode.Position(expected.line, expected.index); - const actual = await vscode.commands.executeCommand( - 'vscode.executeSignatureHelpProvider', - uri, - position, - ); - assert.equal( - actual!.signatures.length, - expected.signaturesCount, - `Signature count does not match, case ${caseIndex}`, - ); - if (expected.signaturesCount > 0) { - assert.equal( - actual!.activeParameter, - expected.activeParameter, - `Parameter index does not match, case ${caseIndex}`, - ); - if (expected.parameterName) { - const parameter = actual!.signatures[0].parameters[expected.activeParameter]; - assert.equal(parameter.label, expected.parameterName, `Parameter name is incorrect, case ${caseIndex}`); - } - } -} diff --git a/src/test/languageServers/jedi/symbolProvider.unit.test.ts b/src/test/languageServers/jedi/symbolProvider.unit.test.ts deleted file mode 100644 index 094d73ef6151..000000000000 --- a/src/test/languageServers/jedi/symbolProvider.unit.test.ts +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect, use } from 'chai'; -import * as TypeMoq from 'typemoq'; -import { - CancellationToken, - CancellationTokenSource, - CompletionItemKind, - DocumentSymbolProvider, - Location, - Range, - SymbolInformation, - SymbolKind, - TextDocument, - Uri, -} from 'vscode'; -import { LanguageClient } from 'vscode-languageclient/node'; -import { IFileSystem } from '../../../client/common/platform/types'; -import { parseRange } from '../../../client/common/utils/text'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { JediFactory } from '../../../client/languageServices/jediProxyFactory'; -import { IDefinition, ISymbolResult, JediProxyHandler } from '../../../client/providers/jediProxy'; -import { JediSymbolProvider, LanguageServerSymbolProvider } from '../../../client/providers/symbolProvider'; - -const assertArrays = require('chai-arrays'); -use(assertArrays); - -suite('Jedi Symbol Provider', () => { - let serviceContainer: TypeMoq.IMock; - let jediHandler: TypeMoq.IMock>; - let jediFactory: TypeMoq.IMock; - let fileSystem: TypeMoq.IMock; - let provider: DocumentSymbolProvider; - let uri: Uri; - let doc: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - jediFactory = TypeMoq.Mock.ofType(JediFactory); - jediHandler = TypeMoq.Mock.ofType>(); - - fileSystem = TypeMoq.Mock.ofType(); - doc = TypeMoq.Mock.ofType(); - jediFactory.setup((j) => j.getJediProxyHandler(TypeMoq.It.isAny())).returns(() => jediHandler.object); - - serviceContainer.setup((c) => c.get(IFileSystem)).returns(() => fileSystem.object); - }); - - async function testDocumentation( - requestId: number, - fileName: string, - expectedSize: number, - token?: CancellationToken, - isUntitled = false, - ) { - fileSystem.setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => true); - token = token ? token : new CancellationTokenSource().token; - const symbolResult = TypeMoq.Mock.ofType(); - - const definitions: IDefinition[] = [ - { - container: '', - fileName: fileName, - kind: SymbolKind.Array, - range: { endColumn: 0, endLine: 0, startColumn: 0, startLine: 0 }, - rawType: '', - text: '', - type: CompletionItemKind.Class, - }, - ]; - - uri = Uri.file(fileName); - doc.setup((d) => d.uri).returns(() => uri); - doc.setup((d) => d.fileName).returns(() => fileName); - doc.setup((d) => d.isUntitled).returns(() => isUntitled); - doc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => ''); - symbolResult.setup((c) => c.requestId).returns(() => requestId); - symbolResult.setup((c) => c.definitions).returns(() => definitions); - symbolResult.setup((c: any) => c.then).returns(() => undefined); - jediHandler - .setup((j) => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(symbolResult.object)); - - const items = await provider.provideDocumentSymbols(doc.object, token); - expect(items).to.be.array(); - expect(items).to.be.ofSize(expectedSize); - } - - test('Ensure symbols are returned', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await testDocumentation(1, __filename, 1); - }); - test('Ensure symbols are returned (for untitled documents)', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await testDocumentation(1, __filename, 1, undefined, true); - }); - test('Ensure symbols are returned with a debounce of 100ms', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await testDocumentation(1, __filename, 1); - }); - test('Ensure symbols are returned with a debounce of 100ms (for untitled documents)', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await testDocumentation(1, __filename, 1, undefined, true); - }); - test('Ensure symbols are not returned when cancelled', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - const tokenSource = new CancellationTokenSource(); - tokenSource.cancel(); - await testDocumentation(1, __filename, 0, tokenSource.token); - }); - test('Ensure symbols are not returned when cancelled (for untitled documents)', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - const tokenSource = new CancellationTokenSource(); - tokenSource.cancel(); - await testDocumentation(1, __filename, 0, tokenSource.token, true); - }); - test('Ensure symbols are returned only for the last request', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 100); - await Promise.all([ - testDocumentation(1, __filename, 0), - testDocumentation(2, __filename, 0), - testDocumentation(3, __filename, 1), - ]); - }); - test('Ensure symbols are returned for all the requests when the doc is untitled', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 100); - await Promise.all([ - testDocumentation(1, __filename, 1, undefined, true), - testDocumentation(2, __filename, 1, undefined, true), - testDocumentation(3, __filename, 1, undefined, true), - ]); - }); - test('Ensure symbols are returned for multiple documents', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await Promise.all([testDocumentation(1, 'file1', 1), testDocumentation(2, 'file2', 1)]); - }); - test('Ensure symbols are returned for multiple untitled documents ', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await Promise.all([ - testDocumentation(1, 'file1', 1, undefined, true), - testDocumentation(2, 'file2', 1, undefined, true), - ]); - }); - test('Ensure symbols are returned for multiple documents with a debounce of 100ms', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 100); - await Promise.all([testDocumentation(1, 'file1', 1), testDocumentation(2, 'file2', 1)]); - }); - test('Ensure symbols are returned for multiple untitled documents with a debounce of 100ms', async () => { - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 100); - await Promise.all([ - testDocumentation(1, 'file1', 1, undefined, true), - testDocumentation(2, 'file2', 1, undefined, true), - ]); - }); - test('Ensure IFileSystem.arePathsSame is used', async () => { - doc.setup((d) => d.getText()) - .returns(() => '') - .verifiable(TypeMoq.Times.once()); - doc.setup((d) => d.isDirty) - .returns(() => true) - .verifiable(TypeMoq.Times.once()); - doc.setup((d) => d.fileName).returns(() => __filename); - - const symbols = TypeMoq.Mock.ofType(); - symbols.setup((s: any) => s.then).returns(() => undefined); - const definitions: IDefinition[] = []; - for (let counter = 0; counter < 3; counter += 1) { - const def = TypeMoq.Mock.ofType(); - def.setup((d) => d.fileName).returns(() => counter.toString()); - definitions.push(def.object); - - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isValue(counter.toString()), TypeMoq.It.isValue(__filename))) - .returns(() => false) - .verifiable(TypeMoq.Times.exactly(1)); - } - symbols - .setup((s) => s.definitions) - .returns(() => definitions) - .verifiable(TypeMoq.Times.atLeastOnce()); - - jediHandler - .setup((j) => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(symbols.object)) - .verifiable(TypeMoq.Times.once()); - - provider = new JediSymbolProvider(serviceContainer.object, jediFactory.object, 0); - await provider.provideDocumentSymbols(doc.object, new CancellationTokenSource().token); - - doc.verifyAll(); - symbols.verifyAll(); - fileSystem.verifyAll(); - jediHandler.verifyAll(); - }); -}); - -suite('Language Server Symbol Provider', () => { - function createLanguageClient(token: CancellationToken, results: [any, any[]][]): TypeMoq.IMock { - const langClient = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); - for (const [doc, symbols] of results) { - langClient - .setup((l) => - l.sendRequest( - TypeMoq.It.isValue('textDocument/documentSymbol'), - TypeMoq.It.isValue(doc), - TypeMoq.It.isValue(token), - ), - ) - .returns(() => Promise.resolve(symbols)) - .verifiable(TypeMoq.Times.once()); - } - return langClient; - } - - function getRawDoc(uri: Uri) { - return { - textDocument: { - uri: uri.toString(), - }, - }; - } - - test('Ensure symbols are returned - simple', async () => { - const raw = [ - { - name: 'spam', - kind: SymbolKind.Array + 1, - range: { - start: { line: 0, character: 0 }, - end: { line: 0, character: 0 }, - }, - children: [], - }, - ]; - const uri = Uri.file(__filename); - const expected = createSymbols(uri, [['spam', SymbolKind.Array, 0]]); - const doc = createDoc(uri); - const token = new CancellationTokenSource().token; - const langClient = createLanguageClient(token, [[getRawDoc(uri), raw]]); - const provider = new LanguageServerSymbolProvider(langClient.object); - - const items = await provider.provideDocumentSymbols(doc.object, token); - - expect(items).to.deep.equal(expected); - doc.verifyAll(); - langClient.verifyAll(); - }); - test('Ensure symbols are returned - minimal', async () => { - const uri = Uri.file(__filename); - - // The test data is loosely based on the "full" test. - const raw = [ - { - name: 'SpamTests', - kind: 5, - range: { - start: { line: 2, character: 6 }, - end: { line: 2, character: 15 }, - }, - children: [ - { - name: 'test_all', - kind: 12, - range: { - start: { line: 3, character: 8 }, - end: { line: 3, character: 16 }, - }, - children: [ - { - name: 'self', - kind: 13, - range: { - start: { line: 3, character: 17 }, - end: { line: 3, character: 21 }, - }, - children: [], - }, - ], - }, - { - name: 'assertTrue', - kind: 13, - range: { - start: { line: 0, character: 0 }, - end: { line: 0, character: 0 }, - }, - children: [], - }, - ], - }, - ]; - const expected = [ - new SymbolInformation('SpamTests', SymbolKind.Class, '', new Location(uri, new Range(2, 6, 2, 15))), - new SymbolInformation( - 'test_all', - SymbolKind.Function, - 'SpamTests', - new Location(uri, new Range(3, 8, 3, 16)), - ), - new SymbolInformation('self', SymbolKind.Variable, 'test_all', new Location(uri, new Range(3, 17, 3, 21))), - new SymbolInformation( - 'assertTrue', - SymbolKind.Variable, - 'SpamTests', - new Location(uri, new Range(0, 0, 0, 0)), - ), - ]; - - const doc = createDoc(uri); - const token = new CancellationTokenSource().token; - const langClient = createLanguageClient(token, [[getRawDoc(uri), raw]]); - const provider = new LanguageServerSymbolProvider(langClient.object); - - const items = await provider.provideDocumentSymbols(doc.object, token); - - expect(items).to.deep.equal(expected); - }); - test('Ensure symbols are returned - full', async () => { - const uri = Uri.file(__filename); - - // This is the raw symbol data returned by the language server which - // gets converted to SymbolInformation[]. It was captured from an - // actual VS Code session for a file with the following code: - // - // import unittest - // - // class SpamTests(unittest.TestCase): - // def test_all(self): - // self.assertTrue(False) - // - // See: LanguageServerSymbolProvider.provideDocumentSymbols() - - // TODO: Change "raw" once the following issues are resolved: - // * https://github.com/Microsoft/python-language-server/issues/1 - // * https://github.com/Microsoft/python-language-server/issues/2 - const raw = JSON.parse( - '[{"name":"SpamTests","detail":"SpamTests","kind":5,"deprecated":false,"range":{"start":{"line":2,"character":6},"end":{"line":2,"character":15}},"selectionRange":{"start":{"line":2,"character":6},"end":{"line":2,"character":15}},"children":[{"name":"test_all","detail":"test_all","kind":12,"deprecated":false,"range":{"start":{"line":3,"character":4},"end":{"line":4,"character":30}},"selectionRange":{"start":{"line":3,"character":4},"end":{"line":4,"character":30}},"children":[{"name":"self","detail":"self","kind":13,"deprecated":false,"range":{"start":{"line":3,"character":17},"end":{"line":3,"character":21}},"selectionRange":{"start":{"line":3,"character":17},"end":{"line":3,"character":21}},"children":[],"_functionKind":""}],"_functionKind":"function"},{"name":"assertTrue","detail":"assertTrue","kind":13,"deprecated":false,"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":0}},"selectionRange":{"start":{"line":0,"character":0},"end":{"line":0,"character":0}},"children":[],"_functionKind":""}],"_functionKind":"class"}]', - ); - raw[0].children[0].range.start.character = 8; - raw[0].children[0].range.end.line = 3; - raw[0].children[0].range.end.character = 16; - - // This is the data from Jedi corresponding to same Python code - // for which the raw data above was generated. - // See: JediSymbolProvider.provideDocumentSymbols() - const expectedRaw = JSON.parse( - '[{"name":"unittest","kind":1,"location":{"uri":{"$mid":1,"path":"","scheme":"file"},"range":[{"line":0,"character":7},{"line":0,"character":15}]},"containerName":""},{"name":"SpamTests","kind":4,"location":{"uri":{"$mid":1,"path":"","scheme":"file"},"range":[{"line":2,"character":0},{"line":4,"character":29}]},"containerName":""},{"name":"test_all","kind":11,"location":{"uri":{"$mid":1,"path":"","scheme":"file"},"range":[{"line":3,"character":4},{"line":4,"character":29}]},"containerName":"SpamTests"},{"name":"self","kind":12,"location":{"uri":{"$mid":1,"path":"","scheme":"file"},"range":[{"line":3,"character":17},{"line":3,"character":21}]},"containerName":"test_all"}]', - ); - expectedRaw[1].location.range[0].character = 6; - expectedRaw[1].location.range[1].line = 2; - expectedRaw[1].location.range[1].character = 15; - expectedRaw[2].location.range[0].character = 8; - expectedRaw[2].location.range[1].line = 3; - expectedRaw[2].location.range[1].character = 16; - const expected = normalizeSymbols(uri, expectedRaw); - expected.shift(); // For now, drop the "unittest" symbol. - expected.push( - new SymbolInformation( - 'assertTrue', - SymbolKind.Variable, - 'SpamTests', - new Location(uri, new Range(0, 0, 0, 0)), - ), - ); - - const doc = createDoc(uri); - const token = new CancellationTokenSource().token; - const langClient = createLanguageClient(token, [[getRawDoc(uri), raw]]); - const provider = new LanguageServerSymbolProvider(langClient.object); - - const items = await provider.provideDocumentSymbols(doc.object, token); - - expect(items).to.deep.equal(expected); - }); -}); - -//################################ -// helpers - -function createDoc(uri?: Uri, filename?: string, isUntitled?: boolean, text?: string): TypeMoq.IMock { - const doc = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); - if (uri !== undefined) { - doc.setup((d) => d.uri).returns(() => uri); - } - if (filename !== undefined) { - doc.setup((d) => d.fileName).returns(() => filename); - } - if (isUntitled !== undefined) { - doc.setup((d) => d.isUntitled).returns(() => isUntitled); - } - if (text !== undefined) { - doc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => text); - } - return doc; -} - -function createSymbols(uri: Uri, info: [string, SymbolKind, string | number][]): SymbolInformation[] { - const symbols: SymbolInformation[] = []; - for (const [fullName, kind, range] of info) { - const symbol = createSymbol(uri, fullName, kind, range); - symbols.push(symbol); - } - return symbols; -} - -function createSymbol(uri: Uri, fullName: string, kind: SymbolKind, rawRange: string | number = ''): SymbolInformation { - const [containerName, name] = splitParent(fullName); - const range = parseRange(rawRange); - const loc = new Location(uri, range); - return new SymbolInformation(name, kind, containerName, loc); -} - -function normalizeSymbols(uri: Uri, raw: any[]): SymbolInformation[] { - const symbols: SymbolInformation[] = []; - for (const item of raw) { - const symbol = new SymbolInformation( - item.name, - // Type coercion is a bit fuzzy when it comes to enums, so we - // play it safe by explicitly converting. - (SymbolKind as any)[(SymbolKind as any)[item.kind]], - item.containerName, - new Location( - uri, - new Range( - item.location.range[0].line, - item.location.range[0].character, - item.location.range[1].line, - item.location.range[1].character, - ), - ), - ); - symbols.push(symbol); - } - return symbols; -} - -/** - * Return [parent name, name] for the given qualified (dotted) name. - * - * Examples: - * 'x.y' -> ['x', 'y'] - * 'x' -> ['', 'x'] - * 'x.y.z' -> ['x.y', 'z'] - * '' -> ['', ''] - */ -export function splitParent(fullName: string): [string, string] { - if (fullName.length === 0) { - return ['', '']; - } - const pos = fullName.lastIndexOf('.'); - if (pos < 0) { - return ['', fullName]; - } - const parentName = fullName.slice(0, pos); - const name = fullName.slice(pos + 1); - return [parentName, name]; -}