diff --git a/gulpfile.js b/gulpfile.js index 2949490abd66..cb901ad9f10c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -82,7 +82,7 @@ async function addExtensionPackDependencies() { // extension dependencies need not be installed during development const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8'); const packageJson = JSON.parse(packageJsonContents); - packageJson.extensionPack = ['ms-toolsai.jupyter', 'ms-python.vscode-pylance'].concat( + packageJson.extensionPack = ['ms-toolsai.jupyter', 'ms-python.vscode-pylance', 'ms-python.isort'].concat( packageJson.extensionPack ? packageJson.extensionPack : [], ); await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8'); diff --git a/package.json b/package.json index badaa2511a89..da42a7fc335e 100644 --- a/package.json +++ b/package.json @@ -1699,7 +1699,7 @@ { "command": "python.sortImports", "group": "Refactor", - "title": "Refactor: Sort Imports", + "title": "%python.command.python.sortImports.title%", "when": "editorLangId == python && !notebookEditorFocused && !virtualWorkspace && shellExecutionSupported" } ], diff --git a/requirements.in b/requirements.in index 4093133f7ec0..0acff6b4efab 100644 --- a/requirements.in +++ b/requirements.in @@ -5,6 +5,3 @@ # Unittest test adapter typing-extensions==4.4.0 - -# Sort Imports -isort==5.10.1 diff --git a/requirements.txt b/requirements.txt index 173d503b3f75..1f26087deb2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,6 @@ # # pip-compile --generate-hashes requirements.in # -isort==5.10.1 \ - --hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \ - --hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951 - # via -r requirements.in typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 8b2c90aa5e70..e1387f55f0e3 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -4,7 +4,6 @@ 'use strict'; import { - CodeActionKind, debug, DebugConfigurationProvider, DebugConfigurationProviderTriggerKind, @@ -38,12 +37,10 @@ import { getLanguageConfiguration } from './language/languageConfiguration'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { setLoggingLevel } from './logging'; -import { PythonCodeActionProvider } from './providers/codeActionProvider/pythonCodeActionProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; import { ReplProvider } from './providers/replProvider'; import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry'; import { TerminalProvider } from './providers/terminalProvider'; -import { ISortImportsEditingProvider } from './providers/types'; import { setExtensionInstallTelemetryProperties } from './telemetry/extensionInstallTelemetry'; import { registerTypes as tensorBoardRegisterTypes } from './tensorBoard/serviceRegistry'; import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; @@ -182,9 +179,6 @@ async function activateLegacy(ext: ExtensionState): Promise { serviceManager.get(ITerminalAutoActivation).register(); const pythonSettings = configuration.getSettings(); - const sortImports = serviceContainer.get(ISortImportsEditingProvider); - sortImports.registerCommands(); - serviceManager.get(ICodeExecutionManager).registerCommands(); disposables.push(new LinterCommands(serviceManager)); @@ -205,12 +199,6 @@ async function activateLegacy(ext: ExtensionState): Promise { terminalProvider.initialize(window.activeTerminal).ignoreErrors(); disposables.push(terminalProvider); - disposables.push( - languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { - providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports], - }), - ); - serviceContainer .getAll(IDebugConfigurationService) .forEach((debugConfigProvider) => { diff --git a/src/client/providers/codeActionProvider/main.ts b/src/client/providers/codeActionProvider/main.ts index 259f42848606..e8126ac2e95c 100644 --- a/src/client/providers/codeActionProvider/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -4,7 +4,9 @@ import { inject, injectable } from 'inversify'; import * as vscodeTypes from 'vscode'; import { IExtensionSingleActivationService } from '../../activation/types'; +import { Commands } from '../../common/constants'; import { IDisposableRegistry } from '../../common/types'; +import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis'; import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() @@ -26,5 +28,8 @@ export class CodeActionProviderService implements IExtensionSingleActivationServ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix], }), ); + this.disposableRegistry.push( + registerCommand(Commands.Sort_Imports, () => executeCommand('editor.action.organizeImports')), + ); } } diff --git a/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts b/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts deleted file mode 100644 index f6e1693e4fe2..000000000000 --- a/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as vscode from 'vscode'; -import { isNotebookCell } from '../../common/utils/misc'; - -export class PythonCodeActionProvider implements vscode.CodeActionProvider { - // eslint-disable-next-line class-methods-use-this - public provideCodeActions( - document: vscode.TextDocument, - _range: vscode.Range, - _context: vscode.CodeActionContext, - _token: vscode.CancellationToken, - ): vscode.ProviderResult { - if (isNotebookCell(document)) { - return []; - } - const sortImports = new vscode.CodeAction('Sort imports', vscode.CodeActionKind.SourceOrganizeImports); - sortImports.command = { - title: 'Sort imports', - command: 'python.sortImports', - }; - - return [sortImports]; - } -} diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts deleted file mode 100644 index 3729af629ceb..000000000000 --- a/src/client/providers/importSortProvider.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { EOL } from 'os'; -import * as path from 'path'; -import { CancellationToken, CancellationTokenSource, TextDocument, Uri, WorkspaceEdit } from 'vscode'; -import * as nls from 'vscode-nls'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../common/application/types'; -import { Commands, PYTHON_LANGUAGE } from '../common/constants'; -import * as internalScripts from '../common/process/internal/scripts'; -import { IProcessServiceFactory, IPythonExecutionFactory, ObservableExecutionResult } from '../common/process/types'; -import { - IConfigurationService, - IDisposableRegistry, - IEditorUtils, - IExtensions, - IPersistentStateFactory, -} from '../common/types'; -import { createDeferred, createDeferredFromPromise, Deferred } from '../common/utils/async'; -import { Common, Diagnostics } from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { IInterpreterService } from '../interpreter/contracts'; -import { IServiceContainer } from '../ioc/types'; -import { traceError } from '../logging'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { ISortImportsEditingProvider } from './types'; - -const localize: nls.LocalizeFunc = nls.loadMessageBundle(); - -const doNotDisplayPromptStateKey = 'ISORT5_UPGRADE_WARNING_KEY'; - -@injectable() -export class SortImportsEditingProvider implements ISortImportsEditingProvider { - private readonly isortPromises = new Map< - string, - { deferred: Deferred; tokenSource: CancellationTokenSource } - >(); - - private readonly processServiceFactory: IProcessServiceFactory; - - private readonly pythonExecutionFactory: IPythonExecutionFactory; - - private readonly shell: IApplicationShell; - - private readonly persistentStateFactory: IPersistentStateFactory; - - private readonly documentManager: IDocumentManager; - - private readonly configurationService: IConfigurationService; - - private readonly editorUtils: IEditorUtils; - - private readonly extensions: IExtensions; - - public constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.shell = serviceContainer.get(IApplicationShell); - this.documentManager = serviceContainer.get(IDocumentManager); - this.configurationService = serviceContainer.get(IConfigurationService); - this.pythonExecutionFactory = serviceContainer.get(IPythonExecutionFactory); - this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); - this.editorUtils = serviceContainer.get(IEditorUtils); - this.persistentStateFactory = serviceContainer.get(IPersistentStateFactory); - this.extensions = serviceContainer.get(IExtensions); - } - - @captureTelemetry(EventName.FORMAT_SORT_IMPORTS) - public async provideDocumentSortImportsEdits(uri: Uri): Promise { - if (this.isortPromises.has(uri.fsPath)) { - const isortPromise = this.isortPromises.get(uri.fsPath)!; - if (!isortPromise.deferred.completed) { - // Cancelling the token will kill the previous isort process & discard its result. - isortPromise.tokenSource.cancel(); - } - } - const tokenSource = new CancellationTokenSource(); - const promise = this._provideDocumentSortImportsEdits(uri, tokenSource.token); - const deferred = createDeferredFromPromise(promise); - this.isortPromises.set(uri.fsPath, { deferred, tokenSource }); - // If token has been cancelled discard the result. - return promise.then((edit) => (tokenSource.token.isCancellationRequested ? undefined : edit)); - } - - public async _provideDocumentSortImportsEdits( - uri: Uri, - token?: CancellationToken, - ): Promise { - const document = await this.documentManager.openTextDocument(uri); - if (!document) { - return undefined; - } - if (document.lineCount <= 1) { - return undefined; - } - - const execIsort = await this.getExecIsort(document, uri, token); - if (token && token.isCancellationRequested) { - return undefined; - } - const diffPatch = await execIsort(document.getText()); - - return diffPatch - ? this.editorUtils.getWorkspaceEditsFromPatch(document.getText(), diffPatch, document.uri) - : undefined; - } - - public registerCommands(): void { - const cmdManager = this.serviceContainer.get(ICommandManager); - const disposable = cmdManager.registerCommand(Commands.Sort_Imports, this.sortImports, this); - this.serviceContainer.get(IDisposableRegistry).push(disposable); - } - - public async sortImports(uri?: Uri): Promise { - const interpreterService = this.serviceContainer.get(IInterpreterService); - const interpreter = await interpreterService.getActiveInterpreter(uri); - if (!interpreter) { - this.serviceContainer - .get(ICommandManager) - .executeCommand(Commands.TriggerEnvironmentSelection, uri) - .then(noop, noop); - return; - } - const extension = this.extensions.getExtension('ms-python.isort'); - if (extension && extension.isActive) { - // Don't run isort from python extension if isort extension is active. - return; - } - - if (!uri) { - const activeEditor = this.documentManager.activeTextEditor; - if (!activeEditor || activeEditor.document.languageId !== PYTHON_LANGUAGE) { - this.shell - .showErrorMessage(localize('sortImportError', 'Please open a Python file to sort the imports.')) - .then(noop, noop); - return; - } - uri = activeEditor.document.uri; - } - - const document = await this.documentManager.openTextDocument(uri); - if (document.lineCount <= 1) { - return; - } - - // Hack, if the document doesn't contain an empty line at the end, then add it - // Else the library strips off the last line - const lastLine = document.lineAt(document.lineCount - 1); - if (lastLine.text.trim().length > 0) { - const edit = new WorkspaceEdit(); - edit.insert(uri, lastLine.range.end, EOL); - await this.documentManager.applyEdit(edit); - } - - try { - const changes = await this.provideDocumentSortImportsEdits(uri); - if (!changes || changes.entries().length === 0) { - return; - } - await this.documentManager.applyEdit(changes); - } catch (error) { - let message = error; - if (typeof error === 'string') { - message = error; - } else if (error instanceof Error) { - message = error.message; - } - traceError(`Failed to format imports for '${uri.fsPath}'.`, error); - this.shell.showErrorMessage(message as string).then(noop, noop); - } - } - - public async _showWarningAndOptionallyShowOutput(): Promise { - const neverShowAgain = this.persistentStateFactory.createGlobalPersistentState( - doNotDisplayPromptStateKey, - false, - ); - if (neverShowAgain.value) { - return; - } - const selection = await this.shell.showWarningMessage( - Diagnostics.checkIsort5UpgradeGuide, - Common.openOutputPanel, - Common.doNotShowAgain, - ); - if (selection === Common.openOutputPanel) { - const cmdManager = this.serviceContainer.get(ICommandManager); - await cmdManager.executeCommand(Commands.ViewOutput); - } else if (selection === Common.doNotShowAgain) { - await neverShowAgain.updateValue(true); - } - } - - private async getExecIsort( - document: TextDocument, - uri: Uri, - token?: CancellationToken, - ): Promise<(documentText: string) => Promise> { - const settings = this.configurationService.getSettings(uri); - const _isort = settings.sortImports.path; - const isort = typeof _isort === 'string' && _isort.length > 0 ? _isort : undefined; - const isortArgs = settings.sortImports.args; - - // We pass the content of the file to be sorted via stdin. This avoids - // saving the file (as well as a potential temporary file), but does - // mean that we need another way to tell `isort` where to look for - // configuration. We do that by setting the working directory to the - // directory which contains the file. - const filename = '-'; - - const spawnOptions = { - token, - cwd: path.dirname(uri.fsPath), - }; - - if (isort) { - const procService = await this.processServiceFactory.create(document.uri); - // Use isort directly instead of the internal script. - return async (documentText: string) => { - const args = getIsortArgs(filename, isortArgs); - const result = procService.execObservable(isort, args, spawnOptions); - return this.communicateWithIsortProcess(result, documentText); - }; - } - const procService = await this.pythonExecutionFactory.create({ resource: document.uri }); - return async (documentText: string) => { - const [args, parse] = internalScripts.sortImports(filename, isortArgs); - const result = procService.execObservable(args, spawnOptions); - return parse(await this.communicateWithIsortProcess(result, documentText)); - }; - } - - private async communicateWithIsortProcess( - observableResult: ObservableExecutionResult, - inputText: string, - ): Promise { - // Configure our listening to the output from isort ... - let outputBuffer = ''; - let isAnyErrorRelatedToUpgradeGuide = false; - const isortOutput = createDeferred(); - observableResult.out.subscribe({ - next: (output) => { - if (output.source === 'stdout') { - outputBuffer += output.out; - } else { - // All the W0500 warning codes point to isort5 upgrade guide: https://pycqa.github.io/isort/docs/warning_and_error_codes/W0500/ - // Do not throw error on these types of stdErrors - isAnyErrorRelatedToUpgradeGuide = isAnyErrorRelatedToUpgradeGuide || output.out.includes('W050'); - traceError(output.out); - if (!output.out.includes('W050')) { - isortOutput.reject(output.out); - } - } - }, - complete: () => { - isortOutput.resolve(outputBuffer); - }, - }); - - // ... then send isort the document content ... - observableResult.proc?.stdin?.write(inputText); - observableResult.proc?.stdin?.end(); - - // .. and finally wait for isort to do its thing - await isortOutput.promise; - - if (isAnyErrorRelatedToUpgradeGuide) { - this._showWarningAndOptionallyShowOutput().ignoreErrors(); - } - return outputBuffer; - } -} - -function getIsortArgs(filename: string, extraArgs?: string[]): string[] { - // We could just adapt internalScripts.sortImports(). However, - // the following is simpler and the alternative doesn't offer - // any signficant benefit. - const args = [filename, '--diff']; - if (extraArgs) { - args.push(...extraArgs); - } - return args; -} diff --git a/src/client/providers/serviceRegistry.ts b/src/client/providers/serviceRegistry.ts index 9561e26cb731..a96ec14ff5e9 100644 --- a/src/client/providers/serviceRegistry.ts +++ b/src/client/providers/serviceRegistry.ts @@ -6,11 +6,8 @@ import { IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; import { CodeActionProviderService } from './codeActionProvider/main'; -import { SortImportsEditingProvider } from './importSortProvider'; -import { ISortImportsEditingProvider } from './types'; export function registerTypes(serviceManager: IServiceManager): void { - serviceManager.addSingleton(ISortImportsEditingProvider, SortImportsEditingProvider); serviceManager.addSingleton( IExtensionSingleActivationService, CodeActionProviderService, diff --git a/src/client/providers/types.ts b/src/client/providers/types.ts deleted file mode 100644 index f2d1bc6eea3a..000000000000 --- a/src/client/providers/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CancellationToken, Uri, WorkspaceEdit } from 'vscode'; - -export const ISortImportsEditingProvider = Symbol('ISortImportsEditingProvider'); -export interface ISortImportsEditingProvider { - provideDocumentSortImportsEdits(uri: Uri, token?: CancellationToken): Promise; - sortImports(uri?: Uri): Promise; - registerCommands(): void; -} diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts deleted file mode 100644 index 0d21060b0225..000000000000 --- a/src/test/format/extension.sort.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import * as assert from 'assert'; -import { expect } from 'chai'; -import * as fs from 'fs'; -import { EOL } from 'os'; -import * as TypeMoq from 'typemoq'; -import * as path from 'path'; -import { instance, mock } from 'ts-mockito'; -import { commands, ConfigurationTarget, Position, Range, Uri, window, workspace } from 'vscode'; -import { Commands } from '../../client/common/constants'; -import { ICondaService, IInterpreterService } from '../../client/interpreter/contracts'; -import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; -import { ISortImportsEditingProvider } from '../../client/providers/types'; -import { CondaService } from '../../client/pythonEnvironments/common/environmentManagers/condaService'; -import { updateSetting } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, TEST_TIMEOUT } from '../initialize'; -import { UnitTestIocContainer } from '../testing/serviceRegistry'; -import { PythonEnvironment } from '../../client/pythonEnvironments/info'; - -const sortingPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'sorting'); -const fileToFormatWithoutConfig = path.join(sortingPath, 'noconfig', 'before.py'); -const originalFileToFormatWithoutConfig = path.join(sortingPath, 'noconfig', 'original.py'); -const fileToFormatWithConfig = path.join(sortingPath, 'withconfig', 'before.py'); -const originalFileToFormatWithConfig = path.join(sortingPath, 'withconfig', 'original.py'); -const fileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'before.1.py'); -const originalFileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'original.1.py'); - -suite('Sorting', () => { - let ioc: UnitTestIocContainer; - let sorter: ISortImportsEditingProvider; - const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; - suiteSetup(async function () { - const pythonVersion = process.env.CI_PYTHON_VERSION ? parseFloat(process.env.CI_PYTHON_VERSION) : undefined; - if (pythonVersion && pythonVersion < 3) { - return this.skip(); - } - await initialize(); - - return undefined; - }); - suiteTeardown(async () => { - fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); - fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); - fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); - await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); - await closeActiveWindows(); - }); - setup(async function () { - this.timeout(TEST_TIMEOUT * 2); - await initializeTest(); - await initializeDI(); - fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); - fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); - fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); - await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); - await closeActiveWindows(); - sorter = new SortImportsEditingProvider(ioc.serviceContainer); - }); - teardown(async () => { - await ioc.dispose(); - await closeActiveWindows(); - }); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - ioc.registerInterpreterStorageTypes(); - await ioc.registerMockInterpreterTypes(); - const interpreterService = TypeMoq.Mock.ofType(); - interpreterService - .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment)); - ioc.serviceManager.rebindInstance(ICondaService, instance(mock(CondaService))); - ioc.serviceManager.rebindInstance(IInterpreterService, interpreterService.object); - } - test('Without Config', async () => { - const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); - await window.showTextDocument(textDocument); - const edit = (await sorter.provideDocumentSortImportsEdits(textDocument.uri))!; - expect(edit.entries()).to.be.lengthOf(1); - const edits = edit.entries()[0][1]; - expect(edits.length).to.equal(4); - assert.strictEqual( - edits.filter((value) => value.newText === EOL && value.range.isEqual(new Range(2, 0, 2, 0))).length, - 1, - 'EOL not found', - ); - assert.strictEqual( - edits.filter((value) => value.newText === '' && value.range.isEqual(new Range(3, 0, 4, 0))).length, - 1, - '"" not found', - ); - assert.strictEqual( - edits.filter( - (value) => - value.newText === `from rope.refactor.extract import ExtractMethod, ExtractVariable${EOL}` && - value.range.isEqual(new Range(15, 0, 15, 0)), - ).length, - 1, - 'Text not found', - ); - assert.strictEqual( - edits.filter((value) => value.newText === '' && value.range.isEqual(new Range(16, 0, 18, 0))).length, - 1, - '"" not found', - ); - }); - - test('Without Config (via Command)', async () => { - sorter.registerCommands(); - const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); - const originalContent = textDocument.getText(); - await window.showTextDocument(textDocument); - await commands.executeCommand(Commands.Sort_Imports); - assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).timeout(TEST_TIMEOUT * 3); - - test('With Config', async () => { - const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); - await window.showTextDocument(textDocument); - const edit = (await sorter.provideDocumentSortImportsEdits(textDocument.uri))!; - expect(edit).not.to.eq(undefined, 'No edit returned'); - expect(edit.entries()).to.be.lengthOf(1); - const edits = edit.entries()[0][1]; - const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.strictEqual( - edits.filter((value) => value.newText === newValue && value.range.isEqual(new Range(0, 0, 3, 0))).length, - 1, - 'New Text not found', - ); - }); - - test('With Config (via Command)', async () => { - sorter.registerCommands(); - const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); - const originalContent = textDocument.getText(); - await window.showTextDocument(textDocument); - await commands.executeCommand(Commands.Sort_Imports); - assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).timeout(TEST_TIMEOUT * 3); - - test('With Changes and Config in Args', async () => { - await updateSetting( - 'sortImports.args', - ['--sp', path.join(sortingPath, 'withconfig')], - Uri.file(sortingPath), - ConfigurationTarget.Workspace, - ); - const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); - const editor = await window.showTextDocument(textDocument); - await editor.edit((builder) => { - builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); - }); - const edit = (await sorter.provideDocumentSortImportsEdits(textDocument.uri))!; - expect(edit.entries()).to.be.lengthOf(1); - const edits = edit.entries()[0][1]; - assert.notStrictEqual(edits.length, 0, 'No edits'); - }); - test('With Changes and Config in Args (via Command)', async () => { - sorter.registerCommands(); - await updateSetting( - 'sortImports.args', - ['--sp', path.join(sortingPath, 'withconfig')], - Uri.file(sortingPath), - configTarget, - ); - const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); - const editor = await window.showTextDocument(textDocument); - await editor.edit((builder) => { - builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); - }); - const originalContent = textDocument.getText(); - await commands.executeCommand(Commands.Sort_Imports); - assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).timeout(TEST_TIMEOUT * 3); - - test('With Changes and Config implicit from cwd', async () => { - const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); - assert.strictEqual(textDocument.isDirty, false, 'Document should initially be unmodified'); - const editor = await window.showTextDocument(textDocument); - await editor.edit((builder) => { - builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); - }); - assert.strictEqual(textDocument.isDirty, true, 'Document should have been modified (pre sort)'); - await sorter.sortImports(textDocument.uri); - assert.strictEqual(textDocument.isDirty, true, 'Document should have been modified by sorting'); - const newValue = `from third_party import lib0${EOL}from third_party import lib1${EOL}from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.strictEqual(textDocument.getText(), newValue); - }); -}); diff --git a/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts b/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts deleted file mode 100644 index 1fdf37d209fe..000000000000 --- a/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { assert, expect } from 'chai'; -import * as TypeMoq from 'typemoq'; -import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument, Uri } from 'vscode'; -import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider'; - -suite('Python CodeAction Provider', () => { - let codeActionsProvider: PythonCodeActionProvider; - let document: TypeMoq.IMock; - let range: TypeMoq.IMock; - let context: TypeMoq.IMock; - let token: TypeMoq.IMock; - - setup(() => { - codeActionsProvider = new PythonCodeActionProvider(); - document = TypeMoq.Mock.ofType(); - range = TypeMoq.Mock.ofType(); - context = TypeMoq.Mock.ofType(); - token = TypeMoq.Mock.ofType(); - }); - - test('Ensure it always returns a source.organizeImports CodeAction', async () => { - document.setup((d) => d.uri).returns(() => Uri.file('hello.ipynb')); - const codeActions = await codeActionsProvider.provideCodeActions( - document.object, - range.object, - context.object, - token.object, - ); - - assert.isArray(codeActions, 'codeActionsProvider.provideCodeActions did not return an array'); - - const organizeImportsCodeAction = (codeActions || []).filter( - (codeAction) => codeAction.kind === CodeActionKind.SourceOrganizeImports, - ); - expect(organizeImportsCodeAction).to.have.length(1); - expect(organizeImportsCodeAction[0].kind).to.eq(CodeActionKind.SourceOrganizeImports); - }); - test('Ensure it does not returns a source.organizeImports CodeAction for Notebook Cells', async () => { - document.setup((d) => d.uri).returns(() => Uri.file('hello.ipynb').with({ scheme: 'vscode-notebook-cell' })); - const codeActions = await codeActionsProvider.provideCodeActions( - document.object, - range.object, - context.object, - token.object, - ); - - assert.isArray(codeActions, 'codeActionsProvider.provideCodeActions did not return an array'); - - const organizeImportsCodeAction = (codeActions || []).filter( - (codeAction) => codeAction.kind === CodeActionKind.SourceOrganizeImports, - ); - expect(organizeImportsCodeAction).to.have.length(0); - }); -}); diff --git a/src/test/providers/importSortProvider.unit.test.ts b/src/test/providers/importSortProvider.unit.test.ts deleted file mode 100644 index ff050efbed83..000000000000 --- a/src/test/providers/importSortProvider.unit.test.ts +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { assert, expect } from 'chai'; -import { ChildProcess } from 'child_process'; -import { EOL } from 'os'; -import * as path from 'path'; -import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; -import * as sinon from 'sinon'; -import { Writable } from 'stream'; -import * as TypeMoq from 'typemoq'; -import { Range, TextDocument, TextEditor, TextLine, Uri, WorkspaceEdit } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../../client/common/application/types'; -import { Commands, EXTENSION_ROOT_DIR } from '../../client/common/constants'; -import { ProcessService } from '../../client/common/process/proc'; -import { - IProcessServiceFactory, - IPythonExecutionFactory, - IPythonExecutionService, - Output, -} from '../../client/common/process/types'; -import { - IConfigurationService, - IDisposableRegistry, - IEditorUtils, - IExtensions, - IPersistentState, - IPersistentStateFactory, - IPythonSettings, - ISortImportSettings, -} from '../../client/common/types'; -import { createDeferred, createDeferredFromPromise } from '../../client/common/utils/async'; -import { Common, Diagnostics } from '../../client/common/utils/localize'; -import { noop } from '../../client/common/utils/misc'; -import { IInterpreterService } from '../../client/interpreter/contracts'; -import { IServiceContainer } from '../../client/ioc/types'; -import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; -import { PythonEnvironment } from '../../client/pythonEnvironments/info'; -import { sleep } from '../core'; - -suite('Import Sort Provider', () => { - let serviceContainer: TypeMoq.IMock; - let shell: TypeMoq.IMock; - let documentManager: TypeMoq.IMock; - let configurationService: TypeMoq.IMock; - let pythonExecFactory: TypeMoq.IMock; - let processServiceFactory: TypeMoq.IMock; - let editorUtils: TypeMoq.IMock; - let commandManager: TypeMoq.IMock; - let pythonSettings: TypeMoq.IMock; - let persistentStateFactory: TypeMoq.IMock; - let extensions: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - let sortProvider: SortImportsEditingProvider; - - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - commandManager = TypeMoq.Mock.ofType(); - documentManager = TypeMoq.Mock.ofType(); - shell = TypeMoq.Mock.ofType(); - configurationService = TypeMoq.Mock.ofType(); - pythonExecFactory = TypeMoq.Mock.ofType(); - processServiceFactory = TypeMoq.Mock.ofType(); - pythonSettings = TypeMoq.Mock.ofType(); - editorUtils = TypeMoq.Mock.ofType(); - persistentStateFactory = TypeMoq.Mock.ofType(); - extensions = TypeMoq.Mock.ofType(); - extensions.setup((e) => e.getExtension(TypeMoq.It.isAny())).returns(() => undefined); - serviceContainer.setup((c) => c.get(IPersistentStateFactory)).returns(() => persistentStateFactory.object); - serviceContainer.setup((c) => c.get(ICommandManager)).returns(() => commandManager.object); - serviceContainer.setup((c) => c.get(IDocumentManager)).returns(() => documentManager.object); - serviceContainer.setup((c) => c.get(IApplicationShell)).returns(() => shell.object); - serviceContainer.setup((c) => c.get(IConfigurationService)).returns(() => configurationService.object); - serviceContainer.setup((c) => c.get(IPythonExecutionFactory)).returns(() => pythonExecFactory.object); - serviceContainer.setup((c) => c.get(IProcessServiceFactory)).returns(() => processServiceFactory.object); - serviceContainer.setup((c) => c.get(IEditorUtils)).returns(() => editorUtils.object); - serviceContainer.setup((c) => c.get(IDisposableRegistry)).returns(() => []); - serviceContainer.setup((c) => c.get(IExtensions)).returns(() => extensions.object); - interpreterService = TypeMoq.Mock.ofType(); - interpreterService - .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment)); - serviceContainer.setup((c) => c.get(IInterpreterService)).returns(() => interpreterService.object); - configurationService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - sortProvider = new SortImportsEditingProvider(serviceContainer.object); - }); - - teardown(() => { - sinon.restore(); - }); - - test('Ensure command is registered', () => { - commandManager - .setup((c) => - c.registerCommand( - TypeMoq.It.isValue(Commands.Sort_Imports), - TypeMoq.It.isAny(), - TypeMoq.It.isValue(sortProvider), - ), - ) - .verifiable(TypeMoq.Times.once()); - - sortProvider.registerCommands(); - commandManager.verifyAll(); - }); - test("Ensure message is displayed when no doc is opened and uri isn't provided", async () => { - documentManager - .setup((d) => d.activeTextEditor) - .returns(() => undefined) - .verifiable(TypeMoq.Times.once()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isValue('Please open a Python file to sort the imports.'))) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.once()); - await sortProvider.sortImports(); - - shell.verifyAll(); - documentManager.verifyAll(); - }); - test("Ensure message is displayed when uri isn't provided and current doc is non-python", async () => { - const mockEditor = TypeMoq.Mock.ofType(); - const mockDoc = TypeMoq.Mock.ofType(); - mockDoc - .setup((d) => d.languageId) - .returns(() => 'xyz') - .verifiable(TypeMoq.Times.atLeastOnce()); - mockEditor - .setup((d) => d.document) - .returns(() => mockDoc.object) - .verifiable(TypeMoq.Times.atLeastOnce()); - - documentManager - .setup((d) => d.activeTextEditor) - .returns(() => mockEditor.object) - .verifiable(TypeMoq.Times.once()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isValue('Please open a Python file to sort the imports.'))) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.once()); - await sortProvider.sortImports(); - - mockEditor.verifyAll(); - mockDoc.verifyAll(); - shell.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure document is opened', async () => { - const uri = Uri.file('TestDoc'); - - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager.setup((d) => d.activeTextEditor).verifiable(TypeMoq.Times.never()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - await sortProvider.sortImports(uri).catch(noop); - - shell.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure no edits are provided when there is only one line', async () => { - const uri = Uri.file('TestDoc'); - const mockDoc = TypeMoq.Mock.ofType(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 1) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - const edit = await sortProvider.sortImports(uri); - - expect(edit).to.be.equal(undefined, 'not undefined'); - shell.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure no edits are provided when there are no lines', async () => { - const uri = Uri.file('TestDoc'); - const mockDoc = TypeMoq.Mock.ofType(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 0) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - const edit = await sortProvider.sortImports(uri); - - expect(edit).to.be.equal(undefined, 'not undefined'); - shell.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure empty line is added when line does not end with an empty line', async () => { - const uri = Uri.file('TestDoc'); - const mockDoc = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 10) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const lastLine = TypeMoq.Mock.ofType(); - let editApplied: WorkspaceEdit | undefined; - lastLine - .setup((l) => l.text) - .returns(() => '1234') - .verifiable(TypeMoq.Times.atLeastOnce()); - lastLine - .setup((l) => l.range) - .returns(() => new Range(1, 0, 10, 1)) - .verifiable(TypeMoq.Times.atLeastOnce()); - mockDoc - .setup((d) => d.lineAt(TypeMoq.It.isValue(9))) - .returns(() => lastLine.object) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.applyEdit(TypeMoq.It.isAny())) - .callback((e) => { - editApplied = e; - }) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - - sortProvider.provideDocumentSortImportsEdits = () => Promise.resolve(undefined); - await sortProvider.sortImports(uri); - - expect(editApplied).not.to.be.equal(undefined, 'Applied edit is undefined'); - expect(editApplied!.entries()).to.be.lengthOf(1); - expect(editApplied!.entries()[0][1]).to.be.lengthOf(1); - expect(editApplied!.entries()[0][1][0].newText).to.be.equal(EOL); - shell.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure no edits are provided when there is only one line (when using provider method)', async () => { - const uri = Uri.file('TestDoc'); - const mockDoc = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 1) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - const edit = await sortProvider.provideDocumentSortImportsEdits(uri); - - expect(edit).to.be.equal(undefined, 'not undefined'); - shell.verifyAll(); - documentManager.verifyAll(); - }); - - test('Ensure no edits are provided when there are no lines (when using provider method)', async () => { - const uri = Uri.file('TestDoc'); - const mockDoc = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 0) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - shell - .setup((s) => s.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(TypeMoq.Times.never()); - const edit = await sortProvider.provideDocumentSortImportsEdits(uri); - - expect(edit).to.be.equal(undefined, 'not undefined'); - shell.verifyAll(); - documentManager.verifyAll(); - }); - - test('Ensure stdin is used for sorting (with custom isort path)', async () => { - const uri = Uri.file('something.py'); - const mockDoc = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 10) - .verifiable(TypeMoq.Times.atLeastOnce()); - mockDoc - .setup((d) => d.getText(TypeMoq.It.isAny())) - .returns(() => 'Hello') - .verifiable(TypeMoq.Times.atLeastOnce()); - mockDoc - .setup((d) => d.isDirty) - .returns(() => true) - .verifiable(TypeMoq.Times.never()); - mockDoc - .setup((d) => d.uri) - .returns(() => uri) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - pythonSettings - .setup((s) => s.sortImports) - .returns(() => ({ path: 'CUSTOM_ISORT', args: ['1', '2'] } as ISortImportSettings)) - .verifiable(TypeMoq.Times.once()); - processServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)) - .verifiable(TypeMoq.Times.once()); - - let actualSubscriber: Subscriber>; - const stdinStream = TypeMoq.Mock.ofType(); - stdinStream.setup((s) => s.write('Hello')).verifiable(TypeMoq.Times.once()); - stdinStream - .setup((s) => s.end()) - .callback(() => { - actualSubscriber.next({ source: 'stdout', out: 'DIFF' }); - actualSubscriber.complete(); - }) - .verifiable(TypeMoq.Times.once()); - const childProcess = TypeMoq.Mock.ofType(); - childProcess.setup((p) => p.stdin).returns(() => stdinStream.object); - const executionResult = { - proc: childProcess.object, - out: new Observable>((subscriber) => { - actualSubscriber = subscriber; - }), - dispose: noop, - }; - const expectedArgs = ['-', '--diff', '1', '2']; - processService - .setup((p) => - p.execObservable( - TypeMoq.It.isValue('CUSTOM_ISORT'), - TypeMoq.It.isValue(expectedArgs), - TypeMoq.It.isValue({ token: undefined, cwd: path.sep }), - ), - ) - .returns(() => executionResult) - .verifiable(TypeMoq.Times.once()); - const expectedEdit = new WorkspaceEdit(); - editorUtils - .setup((e) => - e.getWorkspaceEditsFromPatch( - TypeMoq.It.isValue('Hello'), - TypeMoq.It.isValue('DIFF'), - TypeMoq.It.isAny(), - ), - ) - .returns(() => expectedEdit) - .verifiable(TypeMoq.Times.once()); - - const edit = await sortProvider._provideDocumentSortImportsEdits(uri); - - expect(edit).to.be.equal(expectedEdit); - shell.verifyAll(); - mockDoc.verifyAll(); - documentManager.verifyAll(); - }); - test('Ensure stdin is used for sorting', async () => { - const uri = Uri.file('something.py'); - const mockDoc = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc - .setup((d) => d.lineCount) - .returns(() => 10) - .verifiable(TypeMoq.Times.atLeastOnce()); - mockDoc - .setup((d) => d.getText(TypeMoq.It.isAny())) - .returns(() => 'Hello') - .verifiable(TypeMoq.Times.atLeastOnce()); - mockDoc - .setup((d) => d.isDirty) - .returns(() => true) - .verifiable(TypeMoq.Times.never()); - mockDoc - .setup((d) => d.uri) - .returns(() => uri) - .verifiable(TypeMoq.Times.atLeastOnce()); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)) - .verifiable(TypeMoq.Times.atLeastOnce()); - pythonSettings - .setup((s) => s.sortImports) - .returns(() => ({ args: ['1', '2'] } as ISortImportSettings)) - .verifiable(TypeMoq.Times.once()); - - const processExeService = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processExeService.setup((p: any) => p.then).returns(() => undefined); - pythonExecFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processExeService.object)) - .verifiable(TypeMoq.Times.once()); - - let actualSubscriber: Subscriber>; - const stdinStream = TypeMoq.Mock.ofType(); - stdinStream.setup((s) => s.write('Hello')).verifiable(TypeMoq.Times.once()); - stdinStream - .setup((s) => s.end()) - .callback(() => { - actualSubscriber.next({ source: 'stdout', out: 'DIFF' }); - actualSubscriber.complete(); - }) - .verifiable(TypeMoq.Times.once()); - const childProcess = TypeMoq.Mock.ofType(); - childProcess.setup((p) => p.stdin).returns(() => stdinStream.object); - const executionResult = { - proc: childProcess.object, - out: new Observable>((subscriber) => { - actualSubscriber = subscriber; - }), - dispose: noop, - }; - const importScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'sortImports.py'); - const expectedArgs = [importScript, '-', '--diff', '1', '2']; - processExeService - .setup((p) => - p.execObservable( - TypeMoq.It.isValue(expectedArgs), - TypeMoq.It.isValue({ token: undefined, cwd: path.sep }), - ), - ) - .returns(() => executionResult) - .verifiable(TypeMoq.Times.once()); - const expectedEdit = new WorkspaceEdit(); - editorUtils - .setup((e) => - e.getWorkspaceEditsFromPatch( - TypeMoq.It.isValue('Hello'), - TypeMoq.It.isValue('DIFF'), - TypeMoq.It.isAny(), - ), - ) - .returns(() => expectedEdit) - .verifiable(TypeMoq.Times.once()); - - const edit = await sortProvider._provideDocumentSortImportsEdits(uri); - - expect(edit).to.be.equal(expectedEdit); - shell.verifyAll(); - mockDoc.verifyAll(); - documentManager.verifyAll(); - }); - - test('If a second sort command is initiated before the execution of first one is finished, discard the result from first isort process', async () => { - // ----------------------Common setup between the 2 commands--------------------------- - const uri = Uri.file('something.py'); - const mockDoc = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc.setup((d) => d.lineCount).returns(() => 10); - mockDoc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => 'Hello'); - mockDoc.setup((d) => d.isDirty).returns(() => true); - mockDoc.setup((d) => d.uri).returns(() => uri); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)); - pythonSettings - .setup((s) => s.sortImports) - .returns(() => ({ path: 'CUSTOM_ISORT', args: [] } as ISortImportSettings)); - processServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)); - const result = new WorkspaceEdit(); - editorUtils - .setup((e) => - e.getWorkspaceEditsFromPatch( - TypeMoq.It.isValue('Hello'), - TypeMoq.It.isValue('DIFF'), - TypeMoq.It.isAny(), - ), - ) - .returns(() => result); - - // ----------------------Run the command once---------------------- - let firstSubscriber: Subscriber>; - const firstProcessResult = createDeferred | undefined>(); - const stdinStream1 = TypeMoq.Mock.ofType(); - stdinStream1.setup((s) => s.write('Hello')); - stdinStream1 - .setup((s) => s.end()) - .callback(async () => { - // Wait until the process has returned with results - const processResult = await firstProcessResult.promise; - firstSubscriber.next(processResult); - firstSubscriber.complete(); - }) - .verifiable(TypeMoq.Times.once()); - const firstChildProcess = TypeMoq.Mock.ofType(); - firstChildProcess.setup((p) => p.stdin).returns(() => stdinStream1.object); - const firstExecutionResult = { - proc: firstChildProcess.object, - out: new Observable>((subscriber) => { - firstSubscriber = subscriber; - }), - dispose: noop, - }; - processService - .setup((p) => p.execObservable(TypeMoq.It.isValue('CUSTOM_ISORT'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => firstExecutionResult); - - // The first execution isn't immediately resolved, so don't wait on the promise - const firstExecutionDeferred = createDeferredFromPromise(sortProvider.provideDocumentSortImportsEdits(uri)); - // Yield control to the first execution, so all the mock setups are used. - await sleep(1); - - // ----------------------Run the command again---------------------- - let secondSubscriber: Subscriber>; - const stdinStream2 = TypeMoq.Mock.ofType(); - stdinStream2.setup((s) => s.write('Hello')); - stdinStream2 - .setup((s) => s.end()) - .callback(() => { - // The second process immediately returns with results - secondSubscriber.next({ source: 'stdout', out: 'DIFF' }); - secondSubscriber.complete(); - }) - .verifiable(TypeMoq.Times.once()); - const secondChildProcess = TypeMoq.Mock.ofType(); - secondChildProcess.setup((p) => p.stdin).returns(() => stdinStream2.object); - const secondExecutionResult = { - proc: secondChildProcess.object, - out: new Observable>((subscriber) => { - secondSubscriber = subscriber; - }), - dispose: noop, - }; - processService.reset(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - processService - .setup((p) => p.execObservable(TypeMoq.It.isValue('CUSTOM_ISORT'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => secondExecutionResult); - - // // The second execution should immediately return with results - let edit = await sortProvider.provideDocumentSortImportsEdits(uri); - - // ----------------------Verify results---------------------- - expect(edit).to.be.equal(result, 'Second execution result is incorrect'); - expect(firstExecutionDeferred.completed).to.equal(false, "The first execution shouldn't finish yet"); - stdinStream2.verifyAll(); - - // The first process returns with results - firstProcessResult.resolve({ source: 'stdout', out: 'DIFF' }); - - edit = await firstExecutionDeferred.promise; - expect(edit).to.be.equal(undefined, 'The results from the first execution should be discarded'); - stdinStream1.verifyAll(); - }); - - test('If isort raises a warning message related to isort5 upgrade guide, show message', async () => { - const _showWarningAndOptionallyShowOutput = sinon.stub( - SortImportsEditingProvider.prototype, - '_showWarningAndOptionallyShowOutput', - ); - _showWarningAndOptionallyShowOutput.resolves(); - const uri = Uri.file('something.py'); - const mockDoc = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockDoc.setup((d: any) => d.then).returns(() => undefined); - mockDoc.setup((d) => d.lineCount).returns(() => 10); - mockDoc.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => 'Hello'); - mockDoc.setup((d) => d.isDirty).returns(() => true); - mockDoc.setup((d) => d.uri).returns(() => uri); - documentManager - .setup((d) => d.openTextDocument(TypeMoq.It.isValue(uri))) - .returns(() => Promise.resolve(mockDoc.object)); - pythonSettings - .setup((s) => s.sortImports) - .returns(() => ({ path: 'CUSTOM_ISORT', args: [] } as ISortImportSettings)); - processServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)); - const result = new WorkspaceEdit(); - editorUtils - .setup((e) => - e.getWorkspaceEditsFromPatch( - TypeMoq.It.isValue('Hello'), - TypeMoq.It.isValue('DIFF'), - TypeMoq.It.isAny(), - ), - ) - .returns(() => result); - - // ----------------------Run the command---------------------- - let subscriber: Subscriber>; - const stdinStream = TypeMoq.Mock.ofType(); - stdinStream.setup((s) => s.write('Hello')); - stdinStream - .setup((s) => s.end()) - .callback(() => { - subscriber.next({ source: 'stdout', out: 'DIFF' }); - subscriber.next({ source: 'stderr', out: 'Some warning related to isort5 (W0503)' }); - subscriber.complete(); - }) - .verifiable(TypeMoq.Times.once()); - const childProcess = TypeMoq.Mock.ofType(); - childProcess.setup((p) => p.stdin).returns(() => stdinStream.object); - const executionResult = { - proc: childProcess.object, - out: new Observable>((s) => { - subscriber = s; - }), - dispose: noop, - }; - processService.reset(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((d: any) => d.then).returns(() => undefined); - processService - .setup((p) => p.execObservable(TypeMoq.It.isValue('CUSTOM_ISORT'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => executionResult); - - const edit = await sortProvider.provideDocumentSortImportsEdits(uri); - - // ----------------------Verify results---------------------- - expect(edit).to.be.equal(result, 'Execution result is incorrect'); - assert.ok(_showWarningAndOptionallyShowOutput.calledOnce); - stdinStream.verifyAll(); - }); - - test('If user clicks show output on the isort5 warning prompt, show the Python output', async () => { - const neverShowAgain = TypeMoq.Mock.ofType>(); - persistentStateFactory - .setup((p) => p.createGlobalPersistentState(TypeMoq.It.isAny(), false)) - .returns(() => neverShowAgain.object); - neverShowAgain.setup((p) => p.value).returns(() => false); - shell - .setup((s) => - s.showWarningMessage( - Diagnostics.checkIsort5UpgradeGuide, - Common.openOutputPanel, - Common.doNotShowAgain, - ), - ) - .returns(() => Promise.resolve(Common.openOutputPanel)); - commandManager.setup((c) => c.executeCommand(Commands.ViewOutput)).verifiable(TypeMoq.Times.once()); - await sortProvider._showWarningAndOptionallyShowOutput(); - }); - - test('If user clicks do not show again on the isort5 warning prompt, do not show the prompt again', async () => { - const neverShowAgain = TypeMoq.Mock.ofType>(); - persistentStateFactory - .setup((p) => p.createGlobalPersistentState(TypeMoq.It.isAny(), false)) - .returns(() => neverShowAgain.object); - let doNotShowAgainValue = false; - neverShowAgain.setup((p) => p.value).returns(() => doNotShowAgainValue); - neverShowAgain - .setup((p) => p.updateValue(true)) - .returns(() => { - doNotShowAgainValue = true; - return Promise.resolve(); - }); - shell - .setup((s) => - s.showWarningMessage( - Diagnostics.checkIsort5UpgradeGuide, - Common.openOutputPanel, - Common.doNotShowAgain, - ), - ) - .returns(() => Promise.resolve(Common.doNotShowAgain)) - .verifiable(TypeMoq.Times.once()); - - await sortProvider._showWarningAndOptionallyShowOutput(); - shell.verifyAll(); - - await sortProvider._showWarningAndOptionallyShowOutput(); - await sortProvider._showWarningAndOptionallyShowOutput(); - shell.verifyAll(); - }); -}); diff --git a/src/test/providers/serviceRegistry.unit.test.ts b/src/test/providers/serviceRegistry.unit.test.ts index 2edc42c05860..007638ab77b6 100644 --- a/src/test/providers/serviceRegistry.unit.test.ts +++ b/src/test/providers/serviceRegistry.unit.test.ts @@ -8,9 +8,7 @@ import { IExtensionSingleActivationService } from '../../client/activation/types import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceManager } from '../../client/ioc/types'; import { CodeActionProviderService } from '../../client/providers/codeActionProvider/main'; -import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; import { registerTypes } from '../../client/providers/serviceRegistry'; -import { ISortImportsEditingProvider } from '../../client/providers/types'; suite('Common Providers Service Registry', () => { let serviceManager: IServiceManager; @@ -21,12 +19,6 @@ suite('Common Providers Service Registry', () => { test('Ensure services are registered', async () => { registerTypes(instance(serviceManager)); - verify( - serviceManager.addSingleton( - ISortImportsEditingProvider, - SortImportsEditingProvider, - ), - ).once(); verify( serviceManager.addSingleton( IExtensionSingleActivationService,