From 40afea5b2f7b06a28b9455f29c24dd03894f2aba Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Tue, 6 Sep 2022 09:54:20 -0700 Subject: [PATCH 1/9] Remove DI from debug configuration providers --- .../configuration/providers/djangoLaunch.ts | 19 ++++++++----------- .../configuration/providers/fastapiLaunch.ts | 10 ++++------ .../configuration/providers/flaskLaunch.ts | 11 +++++------ .../configuration/providers/pyramidLaunch.ts | 19 +++++++------------ .../providers/djangoLaunch.unit.test.ts | 6 +----- .../providers/fastapiLaunch.unit.test.ts | 8 ++++---- .../providers/flaskLaunch.unit.test.ts | 10 +++++----- .../providers/pyramidLaunch.unit.test.ts | 14 +++++--------- 8 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 0b80f9e0cf14..8bbf70d3bf69 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -5,10 +5,9 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs'; import { Uri, WorkspaceFolder } from 'vscode'; import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { SystemVariables } from '../../../../common/variables/systemVariables'; @@ -22,15 +21,11 @@ const workspaceFolderToken = '${workspaceFolder}'; @injectable() export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils, - ) {} + constructor(@inject(IWorkspaceService) private readonly workspace: IWorkspaceService) {} public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { const program = await this.getManagePyPath(state.folder); let manuallyEnteredAValue: boolean | undefined; - const defaultProgram = `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; + const defaultProgram = `${workspaceFolderToken}${path.sep}manage.py`; const config: Partial = { name: DebugConfigStrings.django.snippet.name, type: DebuggerTypeName, @@ -70,7 +65,7 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati return error; } const resolvedPath = this.resolveVariables(selected, folder ? folder.uri : undefined); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { + if (selected !== defaultValue && !fs.existsSync(resolvedPath)) { return error; } if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { @@ -79,7 +74,9 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati return; } protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { + console.log('1', this.workspace); const systemVariables = new SystemVariables(resource, undefined, this.workspace); + return systemVariables.resolveAny(pythonPath); } @@ -88,8 +85,8 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; + if (fs.existsSync(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}manage.py`; } } } diff --git a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts index a534ec21379c..8e02af9dca1b 100644 --- a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts @@ -3,10 +3,9 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import * as path from 'path'; import { WorkspaceFolder } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; @@ -17,12 +16,11 @@ import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationPro @injectable() export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IFileSystem) private fs: IFileSystem) {} public isSupported(debugConfigurationType: DebugConfigurationType): boolean { return debugConfigurationType === DebugConfigurationType.launchFastAPI; } public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); + const application = this.getApplicationPath(state.folder); let manuallyEnteredAValue: boolean | undefined; const config: Partial = { name: DebugConfigStrings.fastapi.snippet.name, @@ -59,12 +57,12 @@ export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurat }); Object.assign(state.config, config); } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + protected getApplicationPath(folder: WorkspaceFolder | undefined): string | undefined { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { + if (defaultLocationOfManagePy) { return 'main.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts index 034308c73cda..dec1bab552d6 100644 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts @@ -3,10 +3,10 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs'; import { WorkspaceFolder } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; @@ -17,12 +17,11 @@ import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationPro @injectable() export class FlaskLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IFileSystem) private fs: IFileSystem) {} public isSupported(debugConfigurationType: DebugConfigurationType): boolean { return debugConfigurationType === DebugConfigurationType.launchFlask; } public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); + const application = this.getApplicationPath(state.folder); let manuallyEnteredAValue: boolean | undefined; const config: Partial = { name: DebugConfigStrings.flask.snippet.name, @@ -63,12 +62,12 @@ export class FlaskLaunchDebugConfigurationProvider implements IDebugConfiguratio }); Object.assign(state.config, config); } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + protected getApplicationPath(folder: WorkspaceFolder | undefined): string | undefined { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { + if (fs.existsSync(defaultLocationOfManagePy)) { return 'app.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index 04233df11039..f2cdc09bdfd1 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -5,10 +5,9 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs'; import { Uri, WorkspaceFolder } from 'vscode'; import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { SystemVariables } from '../../../../common/variables/systemVariables'; @@ -25,14 +24,10 @@ const workspaceFolderToken = '${workspaceFolder}'; @injectable() export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils, - ) {} + constructor(@inject(IWorkspaceService) private readonly workspace: IWorkspaceService) {} public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { const iniPath = await this.getDevelopmentIniPath(state.folder); - const defaultIni = `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; + const defaultIni = `${workspaceFolderToken}${path.sep}development.ini`; let manuallyEnteredAValue: boolean | undefined; const config: Partial = { @@ -83,7 +78,7 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat return error; } const resolvedPath = this.resolveVariables(selected, folder.uri); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { + if (selected !== defaultValue && !fs.existsSync(resolvedPath)) { return error; } if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { @@ -95,13 +90,13 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat return systemVariables.resolveAny(pythonPath); } - protected async getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { + protected getDevelopmentIniPath(folder: WorkspaceFolder | undefined): string | undefined { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; + if (fs.existsSync(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}development.ini`; } } } diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts index 6423e19ad76b..903a6740c1c2 100644 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts @@ -39,11 +39,7 @@ suite('Debugging - Configuration Provider Django', () => { workspaceService = mock(WorkspaceService); pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestDjangoLaunchDebugConfigurationProvider( - instance(fs), - instance(workspaceService), - instance(pathUtils), - ); + provider = new TestDjangoLaunchDebugConfigurationProvider(instance(workspaceService)); }); test("getManagePyPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; diff --git a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts index 35ef98ce9cf8..cecfc71bc45a 100644 --- a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts @@ -20,14 +20,14 @@ suite('Debugging - Configuration Provider FastAPI', () => { let provider: TestFastAPILaunchDebugConfigurationProvider; let input: MultiStepInput; class TestFastAPILaunchDebugConfigurationProvider extends FastAPILaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { + public getApplicationPath(folder: WorkspaceFolder): string | undefined { return super.getApplicationPath(folder); } } setup(() => { fs = mock(FileSystem); input = mock>(MultiStepInput); - provider = new TestFastAPILaunchDebugConfigurationProvider(instance(fs)); + provider = new TestFastAPILaunchDebugConfigurationProvider(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; @@ -51,7 +51,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); + provider.getApplicationPath = () => 'xyz.py'; await provider.buildConfiguration(instance(input), state); @@ -70,7 +70,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); + provider.getApplicationPath = () => undefined; when(input.showInputBox(anything())).thenResolve('main'); diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts index c8beab640ab1..c9e3979f17ce 100644 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts @@ -20,14 +20,14 @@ suite('Debugging - Configuration Provider Flask', () => { let provider: TestFlaskLaunchDebugConfigurationProvider; let input: MultiStepInput; class TestFlaskLaunchDebugConfigurationProvider extends FlaskLaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { + public getApplicationPath(folder: WorkspaceFolder): string | undefined { return super.getApplicationPath(folder); } } setup(() => { fs = mock(FileSystem); input = mock>(MultiStepInput); - provider = new TestFlaskLaunchDebugConfigurationProvider(instance(fs)); + provider = new TestFlaskLaunchDebugConfigurationProvider(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; @@ -51,7 +51,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); + provider.getApplicationPath = () => 'xyz.py'; await provider.buildConfiguration(instance(input), state); @@ -74,7 +74,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); + provider.getApplicationPath = () => undefined; when(input.showInputBox(anything())).thenResolve('hello'); @@ -99,7 +99,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); + provider.getApplicationPath = () => undefined; when(input.showInputBox(anything())).thenResolve(); diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts index 04a74dfd819b..7f7ab234839c 100644 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts @@ -30,7 +30,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { return super.resolveVariables(pythonPath, resource); } - public async getDevelopmentIniPath(folder: WorkspaceFolder): Promise { + public getDevelopmentIniPath(folder: WorkspaceFolder): string | undefined { return super.getDevelopmentIniPath(folder); } } @@ -39,11 +39,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { workspaceService = mock(WorkspaceService); pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestPyramidLaunchDebugConfigurationProvider( - instance(fs), - instance(workspaceService), - instance(pathUtils), - ); + provider = new TestPyramidLaunchDebugConfigurationProvider(instance(workspaceService)); }); test("getDevelopmentIniPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; @@ -125,7 +121,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with valid ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve('xyz.ini'); + provider.getDevelopmentIniPath = () => 'xyz.ini'; when(pathUtils.separator).thenReturn('-'); await provider.buildConfiguration(instance(input), state); @@ -146,7 +142,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with selected ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); + provider.getDevelopmentIniPath = () => undefined; when(pathUtils.separator).thenReturn('-'); when(input.showInputBox(anything())).thenResolve('hello'); @@ -168,7 +164,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with default ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); + provider.getDevelopmentIniPath = () => undefined; const workspaceFolderToken = '${workspaceFolder}'; const defaultIni = `${workspaceFolderToken}-development.ini`; From fa3769fc3ee3c1787fee48b2ea5ff11c75dfcf84 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Tue, 6 Sep 2022 09:58:22 -0700 Subject: [PATCH 2/9] Clean code --- .../debugger/extension/configuration/providers/djangoLaunch.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 8bbf70d3bf69..0a884f0a01e5 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -74,9 +74,7 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati return; } protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - console.log('1', this.workspace); const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); } From bedd5824db19a87bd09111e671342e24ebb76e25 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 9 Sep 2022 11:13:10 -0700 Subject: [PATCH 3/9] Update pending DI related to workspace --- src/client/common/utils/workspaceFolder.ts | 13 +++ .../configuration/providers/common.ts | 40 ++++++++++ .../configuration/providers/djangoLaunch.ts | 26 +++--- .../configuration/providers/fastapiLaunch.ts | 7 +- .../configuration/providers/flaskLaunch.ts | 8 +- .../configuration/providers/pyramidLaunch.ts | 24 +++--- .../providers/djangoLaunch.unit.test.ts | 77 +++++++----------- .../providers/fastapiLaunch.unit.test.ts | 24 +++--- .../providers/flaskLaunch.unit.test.ts | 26 +++--- .../providers/pyramidLaunch.unit.test.ts | 79 ++++++++----------- 10 files changed, 163 insertions(+), 161 deletions(-) create mode 100644 src/client/common/utils/workspaceFolder.ts create mode 100644 src/client/debugger/extension/configuration/providers/common.ts diff --git a/src/client/common/utils/workspaceFolder.ts b/src/client/common/utils/workspaceFolder.ts new file mode 100644 index 000000000000..ddd98c751562 --- /dev/null +++ b/src/client/common/utils/workspaceFolder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; + +export function getWorkspaceFolder(uri: vscode.Uri): vscode.WorkspaceFolder | undefined { + return vscode.workspace.getWorkspaceFolder(uri); +} diff --git a/src/client/debugger/extension/configuration/providers/common.ts b/src/client/debugger/extension/configuration/providers/common.ts new file mode 100644 index 000000000000..92197dfd73e6 --- /dev/null +++ b/src/client/debugger/extension/configuration/providers/common.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { WorkspaceFolder } from 'vscode'; +import { getWorkspaceFolder } from '../../../../common/utils/workspaceFolder'; + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +function isString(str: any): str is string { + if (typeof str === 'string' || str instanceof String) { + return true; + } + + return false; +} + +export function resolveVariables( + value: string, + rootFolder: string | undefined, + folder: WorkspaceFolder | undefined, +): string { + const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined; + const values: any = {}; + values.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; + + const regexp = /\$\{(.*?)\}/g; + return value.replace(regexp, (match: string, name: string) => { + const newValue = values[name]; + if (isString(newValue)) { + return newValue; + } + return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; + }); +} diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 0a884f0a01e5..96381a081d3a 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -3,25 +3,23 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import * as vscode from 'vscode'; +import { injectable } from 'inversify'; import * as path from 'path'; -import * as fs from 'fs'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; +import * as fs from 'fs-extra'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { resolveVariables } from './common'; const workspaceFolderToken = '${workspaceFolder}'; @injectable() export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IWorkspaceService) private readonly workspace: IWorkspaceService) {} public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { const program = await this.getManagePyPath(state.folder); let manuallyEnteredAValue: boolean | undefined; @@ -56,7 +54,7 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati Object.assign(state.config, config); } public async validateManagePy( - folder: WorkspaceFolder | undefined, + folder: vscode.WorkspaceFolder | undefined, defaultValue: string, selected?: string, ): Promise { @@ -64,8 +62,9 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati if (!selected || selected.trim().length === 0) { return error; } - const resolvedPath = this.resolveVariables(selected, folder ? folder.uri : undefined); - if (selected !== defaultValue && !fs.existsSync(resolvedPath)) { + const resolvedPath = resolveVariables(selected, undefined, folder); + + if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { return error; } if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { @@ -73,17 +72,12 @@ export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurati } return; } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); - } - - protected async getManagePyPath(folder: WorkspaceFolder | undefined): Promise { + protected async getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (fs.existsSync(defaultLocationOfManagePy)) { + if (await fs.pathExists(defaultLocationOfManagePy)) { return `${workspaceFolderToken}${path.sep}manage.py`; } } diff --git a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts index 8e02af9dca1b..6c57b93dabb4 100644 --- a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts @@ -5,6 +5,7 @@ import { injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; @@ -20,7 +21,7 @@ export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurat return debugConfigurationType === DebugConfigurationType.launchFastAPI; } public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = this.getApplicationPath(state.folder); + const application = await this.getApplicationPath(state.folder); let manuallyEnteredAValue: boolean | undefined; const config: Partial = { name: DebugConfigStrings.fastapi.snippet.name, @@ -57,12 +58,12 @@ export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurat }); Object.assign(state.config, config); } - protected getApplicationPath(folder: WorkspaceFolder | undefined): string | undefined { + protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); - if (defaultLocationOfManagePy) { + if (await fs.pathExists(defaultLocationOfManagePy)) { return 'main.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts index dec1bab552d6..cb379dcdba3d 100644 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts @@ -5,7 +5,7 @@ import { injectable } from 'inversify'; import * as path from 'path'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; @@ -21,7 +21,7 @@ export class FlaskLaunchDebugConfigurationProvider implements IDebugConfiguratio return debugConfigurationType === DebugConfigurationType.launchFlask; } public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = this.getApplicationPath(state.folder); + const application = await this.getApplicationPath(state.folder); let manuallyEnteredAValue: boolean | undefined; const config: Partial = { name: DebugConfigStrings.flask.snippet.name, @@ -62,12 +62,12 @@ export class FlaskLaunchDebugConfigurationProvider implements IDebugConfiguratio }); Object.assign(state.config, config); } - protected getApplicationPath(folder: WorkspaceFolder | undefined): string | undefined { + protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (fs.existsSync(defaultLocationOfManagePy)) { + if (await fs.pathExists(defaultLocationOfManagePy)) { return 'app.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index f2cdc09bdfd1..d0813aa744f7 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -3,20 +3,19 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import * as vscode from 'vscode'; import * as path from 'path'; -import * as fs from 'fs'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; +import * as fs from 'fs-extra'; +import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; import * as nls from 'vscode-nls'; +import { resolveVariables } from './common'; const localize: nls.LocalizeFunc = nls.loadMessageBundle(); @@ -24,7 +23,6 @@ const workspaceFolderToken = '${workspaceFolder}'; @injectable() export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IWorkspaceService) private readonly workspace: IWorkspaceService) {} public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { const iniPath = await this.getDevelopmentIniPath(state.folder); const defaultIni = `${workspaceFolderToken}${path.sep}development.ini`; @@ -66,7 +64,7 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat Object.assign(state.config, config); } public async validateIniPath( - folder: WorkspaceFolder | undefined, + folder: vscode.WorkspaceFolder | undefined, defaultValue: string, selected?: string, ): Promise { @@ -77,25 +75,21 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat if (!selected || selected.trim().length === 0) { return error; } - const resolvedPath = this.resolveVariables(selected, folder.uri); - if (selected !== defaultValue && !fs.existsSync(resolvedPath)) { + const resolvedPath = resolveVariables(selected, undefined, folder); + if (selected !== defaultValue && !fs.pathExists(resolvedPath)) { return error; } if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { return error; } } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); - } - protected getDevelopmentIniPath(folder: WorkspaceFolder | undefined): string | undefined { + protected async getDevelopmentIniPath(folder: vscode.WorkspaceFolder | undefined): Promise { if (!folder) { return; } const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (fs.existsSync(defaultLocationOfManagePy)) { + if (await fs.pathExists(defaultLocationOfManagePy)) { return `${workspaceFolderToken}${path.sep}development.ini`; } } diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts index 903a6740c1c2..36c8c304c598 100644 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts @@ -3,49 +3,45 @@ 'use strict'; +import { Uri, WorkspaceFolder } from 'vscode'; import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../../client/common/application/types'; -import { WorkspaceService } from '../../../../../client/common/application/workspace'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { PathUtils } from '../../../../../client/common/platform/pathUtils'; -import { IFileSystem } from '../../../../../client/common/platform/types'; -import { IPathUtils } from '../../../../../client/common/types'; +import * as workspaceFolder from '../../../../../client/common/utils/workspaceFolder'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; import { DjangoLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; suite('Debugging - Configuration Provider Django', () => { - let fs: IFileSystem; - let workspaceService: IWorkspaceService; - let pathUtils: IPathUtils; + let pathExistsStub: sinon.SinonStub; + let pathSeparatorStub: sinon.SinonStub; + let workspaceStub: sinon.SinonStub; let provider: TestDjangoLaunchDebugConfigurationProvider; let input: MultiStepInput; class TestDjangoLaunchDebugConfigurationProvider extends DjangoLaunchDebugConfigurationProvider { - public resolveVariables(pythonPath: string, resource: Uri | undefined): string { - return super.resolveVariables(pythonPath, resource); - } - public async getManagePyPath(folder: WorkspaceFolder): Promise { return super.getManagePyPath(folder); } } setup(() => { - fs = mock(FileSystem); - workspaceService = mock(WorkspaceService); - pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestDjangoLaunchDebugConfigurationProvider(instance(workspaceService)); + provider = new TestDjangoLaunchDebugConfigurationProvider(); + pathExistsStub = sinon.stub(fs, 'pathExists'); + pathSeparatorStub = sinon.stub(path, 'sep'); + workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); + }); + teardown(() => { + sinon.restore(); }); test("getManagePyPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - when(fs.fileExists(managePyPath)).thenResolve(false); - + pathExistsStub.withArgs(managePyPath).resolves(false); const file = await provider.getManagePyPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); @@ -53,68 +49,55 @@ suite('Debugging - Configuration Provider Django', () => { test('getManagePyPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - - when(pathUtils.separator).thenReturn('-'); - when(fs.fileExists(managePyPath)).thenResolve(true); - + pathExistsStub.withArgs(managePyPath).resolves(true); + pathSeparatorStub.value('-'); const file = await provider.getManagePyPath(folder); expect(file).to.be.equal('${workspaceFolder}-manage.py'); }); test('Resolve variables (with resource)', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(folder); - - const resolvedPath = provider.resolveVariables('${workspaceFolder}/one.py', Uri.file('')); + workspaceStub.returns(folder); + const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder); expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`); }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateManagePy(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateManagePy(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => ''; - const error = await provider.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz'; - - when(fs.fileExists('xyz')).thenResolve(false); + pathExistsStub.withArgs('xyz').resolves(false); const error = await provider.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.txt'; - - when(fs.fileExists('xyz.txt')).thenResolve(true); + pathExistsStub.withArgs('xyz.txt').resolves(true); const error = await provider.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.py'; - - when(fs.fileExists('xyz.py')).thenResolve(true); - const error = await provider.validateManagePy(folder, '', 'x'); + pathExistsStub.withArgs('xyz.py').resolves(true); + const error = await provider.validateManagePy(folder, '', 'xyz.py'); expect(error).to.be.equal(undefined, 'should not have errors'); }); @@ -122,8 +105,7 @@ suite('Debugging - Configuration Provider Django', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; provider.getManagePyPath = () => Promise.resolve('xyz.py'); - when(pathUtils.separator).thenReturn('-'); - + pathSeparatorStub.value('-'); await provider.buildConfiguration(instance(input), state); const config = { @@ -141,10 +123,8 @@ suite('Debugging - Configuration Provider Django', () => { test('Launch JSON with selected managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve(undefined); - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); const config = { @@ -162,13 +142,10 @@ suite('Debugging - Configuration Provider Django', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve(undefined); const workspaceFolderToken = '${workspaceFolder}'; const defaultProgram = `${workspaceFolderToken}-manage.py`; - - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); const config = { diff --git a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts index cecfc71bc45a..2b1eca96e21d 100644 --- a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts @@ -5,10 +5,10 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; import { Uri, WorkspaceFolder } from 'vscode'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { IFileSystem } from '../../../../../client/common/platform/types'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; @@ -16,24 +16,26 @@ import { FastAPILaunchDebugConfigurationProvider } from '../../../../../client/d import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider FastAPI', () => { - let fs: IFileSystem; let provider: TestFastAPILaunchDebugConfigurationProvider; let input: MultiStepInput; + let pathExistsStub: sinon.SinonStub; class TestFastAPILaunchDebugConfigurationProvider extends FastAPILaunchDebugConfigurationProvider { - public getApplicationPath(folder: WorkspaceFolder): string | undefined { + public async getApplicationPath(folder: WorkspaceFolder): Promise { return super.getApplicationPath(folder); } } setup(() => { - fs = mock(FileSystem); input = mock>(MultiStepInput); provider = new TestFastAPILaunchDebugConfigurationProvider(); + pathExistsStub = sinon.stub(fs, 'pathExists'); + }); + teardown(() => { + sinon.restore(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); - when(fs.fileExists(appPyPath)).thenResolve(false); - + pathExistsStub.withArgs(appPyPath).resolves(false); const file = await provider.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); @@ -41,9 +43,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('getApplicationPath should find path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); - - when(fs.fileExists(appPyPath)).thenResolve(true); - + pathExistsStub.withArgs(appPyPath).resolves(true); const file = await provider.getApplicationPath(folder); expect(file).to.be.equal('main.py'); @@ -51,7 +51,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => 'xyz.py'; + provider.getApplicationPath = () => Promise.resolve('xyz.py'); await provider.buildConfiguration(instance(input), state); @@ -70,7 +70,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => undefined; + provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('main'); diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts index c9e3979f17ce..2b483a1353d4 100644 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts @@ -5,10 +5,10 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; import { Uri, WorkspaceFolder } from 'vscode'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { IFileSystem } from '../../../../../client/common/platform/types'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; @@ -16,24 +16,26 @@ import { FlaskLaunchDebugConfigurationProvider } from '../../../../../client/deb import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Flask', () => { - let fs: IFileSystem; + let pathExistsStub: sinon.SinonStub; let provider: TestFlaskLaunchDebugConfigurationProvider; let input: MultiStepInput; class TestFlaskLaunchDebugConfigurationProvider extends FlaskLaunchDebugConfigurationProvider { - public getApplicationPath(folder: WorkspaceFolder): string | undefined { + public async getApplicationPath(folder: WorkspaceFolder): Promise { return super.getApplicationPath(folder); } } setup(() => { - fs = mock(FileSystem); input = mock>(MultiStepInput); provider = new TestFlaskLaunchDebugConfigurationProvider(); + pathExistsStub = sinon.stub(fs, 'pathExists'); + }); + teardown(() => { + sinon.restore(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); - when(fs.fileExists(appPyPath)).thenResolve(false); - + pathExistsStub.withArgs(appPyPath).resolves(false); const file = await provider.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); @@ -41,9 +43,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('getApplicationPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); - - when(fs.fileExists(appPyPath)).thenResolve(true); - + pathExistsStub.withArgs(appPyPath).resolves(true); const file = await provider.getApplicationPath(folder); expect(file).to.be.equal('app.py'); @@ -51,7 +51,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => 'xyz.py'; + provider.getApplicationPath = () => Promise.resolve('xyz.py'); await provider.buildConfiguration(instance(input), state); @@ -74,7 +74,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => undefined; + provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('hello'); @@ -99,7 +99,7 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => undefined; + provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve(); diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts index 7f7ab234839c..46d424283653 100644 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts @@ -5,47 +5,43 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../../client/common/application/types'; -import { WorkspaceService } from '../../../../../client/common/application/workspace'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { PathUtils } from '../../../../../client/common/platform/pathUtils'; -import { IFileSystem } from '../../../../../client/common/platform/types'; -import { IPathUtils } from '../../../../../client/common/types'; +import * as workspaceFolder from '../../../../../client/common/utils/workspaceFolder'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; import { PyramidLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Pyramid', () => { - let fs: IFileSystem; - let workspaceService: IWorkspaceService; - let pathUtils: IPathUtils; let provider: TestPyramidLaunchDebugConfigurationProvider; let input: MultiStepInput; + let pathExistsStub: sinon.SinonStub; + let pathSeparatorStub: sinon.SinonStub; + let workspaceStub: sinon.SinonStub; class TestPyramidLaunchDebugConfigurationProvider extends PyramidLaunchDebugConfigurationProvider { - public resolveVariables(pythonPath: string, resource: Uri | undefined): string { - return super.resolveVariables(pythonPath, resource); - } - - public getDevelopmentIniPath(folder: WorkspaceFolder): string | undefined { + public async getDevelopmentIniPath(folder: WorkspaceFolder): Promise { return super.getDevelopmentIniPath(folder); } } setup(() => { - fs = mock(FileSystem); - workspaceService = mock(WorkspaceService); - pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestPyramidLaunchDebugConfigurationProvider(instance(workspaceService)); + provider = new TestPyramidLaunchDebugConfigurationProvider(); + pathExistsStub = sinon.stub(fs, 'pathExists'); + pathSeparatorStub = sinon.stub(path, 'sep'); + workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); + }); + teardown(() => { + sinon.restore(); }); test("getDevelopmentIniPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); - when(fs.fileExists(managePyPath)).thenResolve(false); - + pathExistsStub.withArgs(managePyPath).resolves(false); const file = await provider.getDevelopmentIniPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); @@ -53,76 +49,63 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('getDevelopmentIniPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); - - when(pathUtils.separator).thenReturn('-'); - when(fs.fileExists(managePyPath)).thenResolve(true); - + pathSeparatorStub.value('-'); + pathExistsStub.withArgs(managePyPath).resolves(true); const file = await provider.getDevelopmentIniPath(folder); expect(file).to.be.equal('${workspaceFolder}-development.ini'); }); test('Resolve variables (with resource)', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(folder); - - const resolvedPath = provider.resolveVariables('${workspaceFolder}/one.py', Uri.file('')); + workspaceStub.returns(folder); + const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder); expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`); }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateIniPath(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateIniPath(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => ''; - const error = await provider.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz'; - - when(fs.fileExists('xyz')).thenResolve(false); + pathExistsStub.withArgs('xyz').resolves(false); const error = await provider.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.txt'; - - when(fs.fileExists('xyz.txt')).thenResolve(true); + pathExistsStub.withArgs('xyz.txt').resolves(true); const error = await provider.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); - test('Validation of path should return errors if resolved path is ini', async () => { + test('Validation of path should not return errors if resolved path is ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.ini'; - - when(fs.fileExists('xyz.ini')).thenResolve(true); - const error = await provider.validateIniPath(folder, '', 'x'); + pathExistsStub.withArgs('xyz.ini').resolves(true); + const error = await provider.validateIniPath(folder, '', 'xyz.ini'); expect(error).to.be.equal(undefined, 'should not have errors'); }); test('Launch JSON with valid ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => 'xyz.ini'; - when(pathUtils.separator).thenReturn('-'); + provider.getDevelopmentIniPath = () => Promise.resolve('xyz.ini'); + pathSeparatorStub.value('-'); await provider.buildConfiguration(instance(input), state); @@ -142,8 +125,8 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with selected ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => undefined; - when(pathUtils.separator).thenReturn('-'); + provider.getDevelopmentIniPath = () => Promise.resolve(undefined); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); await provider.buildConfiguration(instance(input), state); @@ -164,11 +147,11 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with default ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => undefined; + provider.getDevelopmentIniPath = () => Promise.resolve(undefined); const workspaceFolderToken = '${workspaceFolder}'; const defaultIni = `${workspaceFolderToken}-development.ini`; - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); await provider.buildConfiguration(instance(input), state); From 982e81eb1017918caf0d78ad5b53d0a219f1b5b3 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 9 Sep 2022 11:22:17 -0700 Subject: [PATCH 4/9] Update function syntax --- .../debugger/extension/configuration/providers/common.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/extension/configuration/providers/common.ts b/src/client/debugger/extension/configuration/providers/common.ts index 92197dfd73e6..e74495ccaf90 100644 --- a/src/client/debugger/extension/configuration/providers/common.ts +++ b/src/client/debugger/extension/configuration/providers/common.ts @@ -26,12 +26,12 @@ export function resolveVariables( folder: WorkspaceFolder | undefined, ): string { const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined; - const values: any = {}; - values.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; + const variablesObject: { [key: string]: any } = {}; + variablesObject.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; const regexp = /\$\{(.*?)\}/g; return value.replace(regexp, (match: string, name: string) => { - const newValue = values[name]; + const newValue = variablesObject[name]; if (isString(newValue)) { return newValue; } From 3cb343856ace6ab2d2e27787a5945e3d8edadce0 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 9 Sep 2022 11:28:25 -0700 Subject: [PATCH 5/9] Clean code --- .../extension/configuration/providers/pyramidLaunch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index d0813aa744f7..6c25599569b2 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -3,9 +3,9 @@ 'use strict'; -import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs-extra'; +import { WorkspaceFolder } from 'vscode'; import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; @@ -64,7 +64,7 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat Object.assign(state.config, config); } public async validateIniPath( - folder: vscode.WorkspaceFolder | undefined, + folder: WorkspaceFolder | undefined, defaultValue: string, selected?: string, ): Promise { @@ -84,7 +84,7 @@ export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurat } } - protected async getDevelopmentIniPath(folder: vscode.WorkspaceFolder | undefined): Promise { + protected async getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { if (!folder) { return; } From 43a69a3e9576399c6c1febaddc31e4ffae99ddd7 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 14 Sep 2022 15:29:29 -0700 Subject: [PATCH 6/9] Update provider classes to functions --- .../debugConfigurationService.ts | 35 ++++- .../configuration/providers/common.ts | 36 +++++ .../configuration/providers/djangoLaunch.ts | 118 ++++++++-------- .../configuration/providers/fastapiLaunch.ts | 94 ++++++------- .../configuration/providers/fileLaunch.ts | 39 +++--- .../configuration/providers/flaskLaunch.ts | 102 +++++++------- .../configuration/providers/moduleLaunch.ts | 63 ++++----- .../configuration/providers/pidAttach.ts | 37 +++-- .../providers/providerFactory.ts | 52 ------- .../configuration/providers/pyramidLaunch.ts | 132 +++++++++--------- .../configuration/providers/remoteAttach.ts | 113 ++++++--------- .../debugger/extension/configuration/types.ts | 6 - .../debugger/extension/serviceRegistry.ts | 61 +------- src/client/debugger/extension/types.ts | 8 -- .../debugConfigurationService.unit.test.ts | 13 +- .../providers/djangoLaunch.unit.test.ts | 52 ++----- .../providers/fastapiLaunch.unit.test.ts | 22 +-- .../providers/fileLaunch.unit.test.ts | 8 +- .../providers/flaskLaunch.unit.test.ts | 29 ++-- .../providers/moduleLaunch.unit.test.ts | 10 +- .../providers/pidAttach.unit.test.ts | 8 +- .../providers/providerFactory.unit.test.ts | 37 ----- .../providers/pyramidLaunch.unit.test.ts | 39 ++---- .../providers/remoteAttach.unit.test.ts | 43 +++--- .../extension/serviceRegistry.unit.test.ts | 80 +---------- 25 files changed, 467 insertions(+), 770 deletions(-) delete mode 100644 src/client/debugger/extension/configuration/providers/providerFactory.ts delete mode 100644 src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts index c2e8593797f0..0c3fbfb709f6 100644 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/debugConfigurationService.ts @@ -8,14 +8,20 @@ import { cloneDeep } from 'lodash'; import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { - IMultiStepInput, IMultiStepInputFactory, InputStep, IQuickPickParameters, + MultiStepInput, } from '../../../common/utils/multiStepInput'; import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; -import { IDebugConfigurationProviderFactory, IDebugConfigurationResolver } from './types'; +import { buildDjangoLaunchDebugConfiguration } from './providers/djangoLaunch'; +import { buildFastAPILaunchDebugConfiguration } from './providers/fastapiLaunch'; +import { buildFlaskLaunchDebugConfiguration } from './providers/flaskLaunch'; +import { buildModuleLaunchConfiguration } from './providers/moduleLaunch'; +import { buildPidAttachConfiguration } from './providers/pidAttach'; +import { buildRemoteAttachConfiguration } from './providers/remoteAttach'; +import { IDebugConfigurationResolver } from './types'; @injectable() export class PythonDebugConfigurationService implements IDebugConfigurationService { @@ -27,8 +33,6 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationResolver) @named('launch') private readonly launchResolver: IDebugConfigurationResolver, - @inject(IDebugConfigurationProviderFactory) - private readonly providerFactory: IDebugConfigurationProviderFactory, @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, ) {} @@ -102,7 +106,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi } protected async pickDebugConfiguration( - input: IMultiStepInput, + input: MultiStepInput, state: DebugConfigurationState, ): Promise | void> { type DebugConfigurationQuickPickItem = QuickPickItem & { type: DebugConfigurationType }; @@ -148,6 +152,23 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi description: DebugConfigStrings.pyramid.selectConfiguration.description, }, ]; + const debugConfigurations = new Map< + DebugConfigurationType, + ( + input: MultiStepInput, + state: DebugConfigurationState, + ) => Promise> + >(); + debugConfigurations.set(DebugConfigurationType.launchDjango, buildDjangoLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFastAPI, buildFastAPILaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFile, buildDjangoLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFlask, buildFlaskLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchModule, buildModuleLaunchConfiguration); + debugConfigurations.set(DebugConfigurationType.pidAttach, buildPidAttachConfiguration); + debugConfigurations.set(DebugConfigurationType.remoteAttach, buildRemoteAttachConfiguration); + debugConfigurations.set(DebugConfigurationType.launchPyramid, buildPidAttachConfiguration); + buildRemoteAttachConfiguration; + state.config = {}; const pick = await input.showQuickPick< DebugConfigurationQuickPickItem, @@ -159,8 +180,8 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi items: items, }); if (pick) { - const provider = this.providerFactory.create(pick.type); - return provider.buildConfiguration.bind(provider); + const pickedDebugConfiguration = debugConfigurations.get(pick.type)!; + return pickedDebugConfiguration(input, state); } } } diff --git a/src/client/debugger/extension/configuration/providers/common.ts b/src/client/debugger/extension/configuration/providers/common.ts index e74495ccaf90..22ad634b40b4 100644 --- a/src/client/debugger/extension/configuration/providers/common.ts +++ b/src/client/debugger/extension/configuration/providers/common.ts @@ -7,7 +7,15 @@ 'use strict'; import { WorkspaceFolder } from 'vscode'; +import { DebugConfigStrings } from '../../../../common/utils/localize'; +import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { getWorkspaceFolder } from '../../../../common/utils/workspaceFolder'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; +import { AttachRequestArguments } from '../../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; + +const defaultPort = 5678; /** * @returns whether the provided parameter is a JavaScript String or not. @@ -38,3 +46,31 @@ export function resolveVariables( return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; }); } + +export async function configurePort( + input: MultiStepInput, + config: Partial, +): Promise { + const connect = config.connect || (config.connect = {}); + const port = await input.showInputBox({ + title: DebugConfigStrings.attach.enterRemotePort.title, + step: 2, + totalSteps: 2, + value: (connect.port || defaultPort).toString(), + prompt: DebugConfigStrings.attach.enterRemotePort.prompt, + validate: (value) => + Promise.resolve( + value && /^\d+$/.test(value.trim()) ? undefined : DebugConfigStrings.attach.enterRemotePort.invalid, + ), + }); + if (port && /^\d+$/.test(port.trim())) { + connect.port = parseInt(port, 10); + } + if (!connect.port) { + connect.port = defaultPort; + } + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.remoteAttach, + manuallyEnteredAValue: connect.port !== defaultPort, + }); +} diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 96381a081d3a..5d0f18f20063 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -4,7 +4,6 @@ 'use strict'; import * as vscode from 'vscode'; -import { injectable } from 'inversify'; import * as path from 'path'; import * as fs from 'fs-extra'; import { DebugConfigStrings } from '../../../../common/utils/localize'; @@ -13,72 +12,75 @@ import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; import { resolveVariables } from './common'; const workspaceFolderToken = '${workspaceFolder}'; -@injectable() -export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const program = await this.getManagePyPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const defaultProgram = `${workspaceFolderToken}${path.sep}manage.py`; - const config: Partial = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: program || defaultProgram, - args: ['runserver'], - django: true, - justMyCode: true, - }; - if (!program) { - const selectedProgram = await input.showInputBox({ - title: DebugConfigStrings.django.enterManagePyPath.title, - value: defaultProgram, - prompt: DebugConfigStrings.django.enterManagePyPath.prompt, - validate: (value) => this.validateManagePy(state.folder, defaultProgram, value), - }); - if (selectedProgram) { - manuallyEnteredAValue = true; - config.program = selectedProgram; - } +export async function buildDjangoLaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const program = await getManagePyPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const defaultProgram = `${workspaceFolderToken}${path.sep}manage.py`; + const config: Partial = { + name: DebugConfigStrings.django.snippet.name, + type: DebuggerTypeName, + request: 'launch', + program: program || defaultProgram, + args: ['runserver'], + django: true, + justMyCode: true, + }; + if (!program) { + const selectedProgram = await input.showInputBox({ + title: DebugConfigStrings.django.enterManagePyPath.title, + value: defaultProgram, + prompt: DebugConfigStrings.django.enterManagePyPath.prompt, + validate: (value) => validateManagePy(state.folder, defaultProgram, value), + }); + if (selectedProgram) { + manuallyEnteredAValue = true; + config.program = selectedProgram; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - autoDetectedDjangoManagePyPath: !!program, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + autoDetectedDjangoManagePyPath: !!program, + manuallyEnteredAValue, + }); + + Object.assign(state.config, config); +} + +export async function validateManagePy( + folder: vscode.WorkspaceFolder | undefined, + defaultValue: string, + selected?: string, +): Promise { + const error = DebugConfigStrings.django.enterManagePyPath.invalid; + if (!selected || selected.trim().length === 0) { + return error; } - public async validateManagePy( - folder: vscode.WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, - ): Promise { - const error = DebugConfigStrings.django.enterManagePyPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = resolveVariables(selected, undefined, folder); + const resolvedPath = resolveVariables(selected, undefined, folder); - if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { - return error; - } + if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { + return error; + } + if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { + return error; + } + return; +} + +export async function getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { + if (!folder) { return; } - protected async getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${path.sep}manage.py`; - } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}manage.py`; } } diff --git a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts index 6c57b93dabb4..6a4d3676ccab 100644 --- a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts @@ -3,7 +3,6 @@ 'use strict'; -import { injectable } from 'inversify'; import * as path from 'path'; import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; @@ -13,58 +12,55 @@ import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public isSupported(debugConfigurationType: DebugConfigurationType): boolean { - return debugConfigurationType === DebugConfigurationType.launchFastAPI; - } - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.fastapi.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'uvicorn', - args: ['main:app'], - jinja: true, - justMyCode: true, - }; +export async function buildFastAPILaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const application = await getApplicationPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.fastapi.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'uvicorn', + args: ['main:app'], + jinja: true, + justMyCode: true, + }; - if (!application) { - const selectedPath = await input.showInputBox({ - title: DebugConfigStrings.fastapi.enterAppPathOrNamePath.title, - value: 'main.py', - prompt: DebugConfigStrings.fastapi.enterAppPathOrNamePath.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 - ? undefined - : DebugConfigStrings.fastapi.enterAppPathOrNamePath.invalid, - ), - }); - if (selectedPath) { - manuallyEnteredAValue = true; - config.args = [`${path.basename(selectedPath, '.py').replace('/', '.')}:app`]; - } + if (!application) { + const selectedPath = await input.showInputBox({ + title: DebugConfigStrings.fastapi.enterAppPathOrNamePath.title, + value: 'main.py', + prompt: DebugConfigStrings.fastapi.enterAppPathOrNamePath.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 + ? undefined + : DebugConfigStrings.fastapi.enterAppPathOrNamePath.invalid, + ), + }); + if (selectedPath) { + manuallyEnteredAValue = true; + config.args = [`${path.basename(selectedPath, '.py').replace('/', '.')}:app`]; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchFastAPI, - autoDetectedFastAPIMainPyPath: !!application, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFastAPI, + autoDetectedFastAPIMainPyPath: !!application, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} +export async function getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return 'main.py'; - } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return 'main.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/fileLaunch.ts b/src/client/debugger/extension/configuration/providers/fileLaunch.ts index d6d51cb0528c..6fcd9d671aad 100644 --- a/src/client/debugger/extension/configuration/providers/fileLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fileLaunch.ts @@ -3,31 +3,28 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; +import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FileLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.launchFile }, - false, - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.file.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: '${file}', - console: 'integratedTerminal', - justMyCode: true, - }; - Object.assign(state.config, config); - } +export async function buildFileLaunchDebugConfiguration( + _input: MultiStepInput, + state: DebugConfigurationState, +) { + const config: Partial = { + name: DebugConfigStrings.file.snippet.name, + type: DebuggerTypeName, + request: 'launch', + program: '${file}', + console: 'integratedTerminal', + justMyCode: true, + }; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFastAPI, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts index cb379dcdba3d..4433caa6138a 100644 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts @@ -3,7 +3,6 @@ 'use strict'; -import { injectable } from 'inversify'; import * as path from 'path'; import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; @@ -13,62 +12,59 @@ import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FlaskLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public isSupported(debugConfigurationType: DebugConfigurationType): boolean { - return debugConfigurationType === DebugConfigurationType.launchFlask; - } - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.flask.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: application || 'app.py', - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }; +export async function buildFlaskLaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const application = await getApplicationPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.flask.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'flask', + env: { + FLASK_APP: application || 'app.py', + FLASK_DEBUG: '1', + }, + args: ['run', '--no-debugger', '--no-reload'], + jinja: true, + justMyCode: true, + }; - if (!application) { - const selectedApp = await input.showInputBox({ - title: DebugConfigStrings.flask.enterAppPathOrNamePath.title, - value: 'app.py', - prompt: DebugConfigStrings.flask.enterAppPathOrNamePath.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 - ? undefined - : DebugConfigStrings.flask.enterAppPathOrNamePath.invalid, - ), - }); - if (selectedApp) { - manuallyEnteredAValue = true; - config.env!.FLASK_APP = selectedApp; - } + if (!application) { + const selectedApp = await input.showInputBox({ + title: DebugConfigStrings.flask.enterAppPathOrNamePath.title, + value: 'app.py', + prompt: DebugConfigStrings.flask.enterAppPathOrNamePath.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 + ? undefined + : DebugConfigStrings.flask.enterAppPathOrNamePath.invalid, + ), + }); + if (selectedApp) { + manuallyEnteredAValue = true; + config.env!.FLASK_APP = selectedApp; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchFlask, - autoDetectedFlaskAppPyPath: !!application, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFlask, + autoDetectedFlaskAppPyPath: !!application, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} +export async function getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return 'app.py'; - } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return 'app.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts index 9134655e1143..1b644833f593 100644 --- a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts @@ -3,44 +3,43 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class ModuleLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.module.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: DebugConfigStrings.module.snippet.default, - justMyCode: true, - }; - const selectedModule = await input.showInputBox({ - title: DebugConfigStrings.module.enterModule.title, - value: config.module || DebugConfigStrings.module.enterModule.default, - prompt: DebugConfigStrings.module.enterModule.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.module.enterModule.invalid, - ), - }); - if (selectedModule) { - manuallyEnteredAValue = true; - config.module = selectedModule; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchModule, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); +export async function buildModuleLaunchConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.module.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: DebugConfigStrings.module.snippet.default, + justMyCode: true, + }; + const selectedModule = await input.showInputBox({ + title: DebugConfigStrings.module.enterModule.title, + value: config.module || DebugConfigStrings.module.enterModule.default, + prompt: DebugConfigStrings.module.enterModule.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 ? undefined : DebugConfigStrings.module.enterModule.invalid, + ), + }); + if (selectedModule) { + manuallyEnteredAValue = true; + config.module = selectedModule; } + + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchModule, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/pidAttach.ts b/src/client/debugger/extension/configuration/providers/pidAttach.ts index 8225ec175e65..c9bb7656d6ca 100644 --- a/src/client/debugger/extension/configuration/providers/pidAttach.ts +++ b/src/client/debugger/extension/configuration/providers/pidAttach.ts @@ -3,30 +3,27 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; +import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class PidAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.pidAttach }, - false, - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.attachPid.snippet.name, - type: DebuggerTypeName, - request: 'attach', - processId: '${command:pickProcess}', - justMyCode: true, - }; - Object.assign(state.config, config); - } +export async function buildPidAttachConfiguration( + _input: MultiStepInput, + state: DebugConfigurationState, +) { + const config: Partial = { + name: DebugConfigStrings.attachPid.snippet.name, + type: DebuggerTypeName, + request: 'attach', + processId: '${command:pickProcess}', + justMyCode: true, + }; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.pidAttach, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/providerFactory.ts b/src/client/debugger/extension/configuration/providers/providerFactory.ts deleted file mode 100644 index 18f5a18a9ef4..000000000000 --- a/src/client/debugger/extension/configuration/providers/providerFactory.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; -import { IDebugConfigurationProviderFactory } from '../types'; - -@injectable() -export class DebugConfigurationProviderFactory implements IDebugConfigurationProviderFactory { - private readonly providers: Map; - constructor( - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFastAPI) - fastapiProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFlask) - flaskProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchDjango) - djangoProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchModule) - moduleProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFile) - fileProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchPyramid) - pyramidProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.remoteAttach) - remoteAttachProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.pidAttach) - pidAttachProvider: IDebugConfigurationProvider, - ) { - this.providers = new Map(); - this.providers.set(DebugConfigurationType.launchDjango, djangoProvider); - this.providers.set(DebugConfigurationType.launchFastAPI, fastapiProvider); - this.providers.set(DebugConfigurationType.launchFlask, flaskProvider); - this.providers.set(DebugConfigurationType.launchFile, fileProvider); - this.providers.set(DebugConfigurationType.launchModule, moduleProvider); - this.providers.set(DebugConfigurationType.launchPyramid, pyramidProvider); - this.providers.set(DebugConfigurationType.remoteAttach, remoteAttachProvider); - this.providers.set(DebugConfigurationType.pidAttach, pidAttachProvider); - } - public create(configurationType: DebugConfigurationType): IDebugConfigurationProvider { - return this.providers.get(configurationType)!; - } -} diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index 6c25599569b2..990d91445e12 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -6,14 +6,13 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; import * as nls from 'vscode-nls'; import { resolveVariables } from './common'; @@ -21,76 +20,77 @@ const localize: nls.LocalizeFunc = nls.loadMessageBundle(); const workspaceFolderToken = '${workspaceFolder}'; -@injectable() -export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const iniPath = await this.getDevelopmentIniPath(state.folder); - const defaultIni = `${workspaceFolderToken}${path.sep}development.ini`; - let manuallyEnteredAValue: boolean | undefined; +export async function buildPyramidLaunchConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const iniPath = await getDevelopmentIniPath(state.folder); + const defaultIni = `${workspaceFolderToken}${path.sep}development.ini`; + let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.pyramid.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: [iniPath || defaultIni], - pyramid: true, - jinja: true, - justMyCode: true, - }; + const config: Partial = { + name: DebugConfigStrings.pyramid.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'pyramid.scripts.pserve', + args: [iniPath || defaultIni], + pyramid: true, + jinja: true, + justMyCode: true, + }; - if (!iniPath) { - const selectedIniPath = await input.showInputBox({ - title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title, - value: defaultIni, - prompt: localize( - 'debug.pyramidEnterDevelopmentIniPathPrompt', - 'Enter the path to development.ini ({0} points to the root of the current workspace folder)', - workspaceFolderToken, - ), - validate: (value) => this.validateIniPath(state ? state.folder : undefined, defaultIni, value), - }); - if (selectedIniPath) { - manuallyEnteredAValue = true; - config.args = [selectedIniPath]; - } + if (!iniPath) { + const selectedIniPath = await input.showInputBox({ + title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title, + value: defaultIni, + prompt: localize( + 'debug.pyramidEnterDevelopmentIniPathPrompt', + 'Enter the path to development.ini ({0} points to the root of the current workspace folder)', + workspaceFolderToken, + ), + validate: (value) => validateIniPath(state ? state.folder : undefined, defaultIni, value), + }); + if (selectedIniPath) { + manuallyEnteredAValue = true; + config.args = [selectedIniPath]; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchPyramid, - autoDetectedPyramidIniPath: !!iniPath, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchPyramid, + autoDetectedPyramidIniPath: !!iniPath, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} + +export async function validateIniPath( + folder: WorkspaceFolder | undefined, + defaultValue: string, + selected?: string, +): Promise { + if (!folder) { + return; } - public async validateIniPath( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, - ): Promise { - if (!folder) { - return; - } - const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = resolveVariables(selected, undefined, folder); - if (selected !== defaultValue && !fs.pathExists(resolvedPath)) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { - return error; - } + const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid; + if (!selected || selected.trim().length === 0) { + return error; + } + const resolvedPath = resolveVariables(selected, undefined, folder); + if (selected !== defaultValue && !fs.pathExists(resolvedPath)) { + return error; } + if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { + return error; + } +} - protected async getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${path.sep}development.ini`; - } +export async function getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; + } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}development.ini`; } } diff --git a/src/client/debugger/extension/configuration/providers/remoteAttach.ts b/src/client/debugger/extension/configuration/providers/remoteAttach.ts index c97ade2a83ef..a4014ccd57c4 100644 --- a/src/client/debugger/extension/configuration/providers/remoteAttach.ts +++ b/src/client/debugger/extension/configuration/providers/remoteAttach.ts @@ -3,90 +3,59 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { InputStep, MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; +import { configurePort } from './common'; const defaultHost = 'localhost'; const defaultPort = 5678; -@injectable() -export class RemoteAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ): Promise | void> { - const config: Partial = { - name: DebugConfigStrings.attach.snippet.name, - type: DebuggerTypeName, - request: 'attach', - connect: { - host: defaultHost, - port: defaultPort, +export async function buildRemoteAttachConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +): Promise | void> { + const config: Partial = { + name: DebugConfigStrings.attach.snippet.name, + type: DebuggerTypeName, + request: 'attach', + connect: { + host: defaultHost, + port: defaultPort, + }, + pathMappings: [ + { + localRoot: '${workspaceFolder}', + remoteRoot: '.', }, - pathMappings: [ - { - localRoot: '${workspaceFolder}', - remoteRoot: '.', - }, - ], - justMyCode: true, - }; + ], + justMyCode: true, + }; - const connect = config.connect!; - connect.host = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemoteHost.title, - step: 1, - totalSteps: 2, - value: connect.host || defaultHost, - prompt: DebugConfigStrings.attach.enterRemoteHost.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.attach.enterRemoteHost.invalid, - ), - }); - if (!connect.host) { - connect.host = defaultHost; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.host !== defaultHost, - }); - Object.assign(state.config, config); - return (_) => this.configurePort(input, state.config); + const connect = config.connect!; + connect.host = await input.showInputBox({ + title: DebugConfigStrings.attach.enterRemoteHost.title, + step: 1, + totalSteps: 2, + value: connect.host || defaultHost, + prompt: DebugConfigStrings.attach.enterRemoteHost.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 ? undefined : DebugConfigStrings.attach.enterRemoteHost.invalid, + ), + }); + if (!connect.host) { + connect.host = defaultHost; } - protected async configurePort( - input: MultiStepInput, - config: Partial, - ) { - const connect = config.connect || (config.connect = {}); - const port = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemotePort.title, - step: 2, - totalSteps: 2, - value: (connect.port || defaultPort).toString(), - prompt: DebugConfigStrings.attach.enterRemotePort.prompt, - validate: (value) => - Promise.resolve( - value && /^\d+$/.test(value.trim()) ? undefined : DebugConfigStrings.attach.enterRemotePort.invalid, - ), - }); - if (port && /^\d+$/.test(port.trim())) { - connect.port = parseInt(port, 10); - } - if (!connect.port) { - connect.port = defaultPort; - } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.port !== defaultPort, - }); - } + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.remoteAttach, + manuallyEnteredAValue: connect.host !== defaultHost, + }); + Object.assign(state.config, config); + return (_) => configurePort(input, state.config); } diff --git a/src/client/debugger/extension/configuration/types.ts b/src/client/debugger/extension/configuration/types.ts index 0dbf32c8d68b..c888fc89ddec 100644 --- a/src/client/debugger/extension/configuration/types.ts +++ b/src/client/debugger/extension/configuration/types.ts @@ -4,7 +4,6 @@ 'use strict'; import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../types'; export const IDebugConfigurationResolver = Symbol('IDebugConfigurationResolver'); export interface IDebugConfigurationResolver { @@ -21,11 +20,6 @@ export interface IDebugConfigurationResolver { ): Promise; } -export const IDebugConfigurationProviderFactory = Symbol('IDebugConfigurationProviderFactory'); -export interface IDebugConfigurationProviderFactory { - create(configurationType: DebugConfigurationType): IDebugConfigurationProvider; -} - export const ILaunchJsonReader = Symbol('ILaunchJsonReader'); export interface ILaunchJsonReader { getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise; diff --git a/src/client/debugger/extension/serviceRegistry.ts b/src/client/debugger/extension/serviceRegistry.ts index 3ffdf5193d2b..4322e9d31df5 100644 --- a/src/client/debugger/extension/serviceRegistry.ts +++ b/src/client/debugger/extension/serviceRegistry.ts @@ -19,31 +19,16 @@ import { LaunchJsonCompletionProvider } from './configuration/launch.json/comple import { InterpreterPathCommand } from './configuration/launch.json/interpreterPathCommand'; import { LaunchJsonReader } from './configuration/launch.json/launchJsonReader'; import { LaunchJsonUpdaterService } from './configuration/launch.json/updaterService'; -import { DjangoLaunchDebugConfigurationProvider } from './configuration/providers/djangoLaunch'; -import { FastAPILaunchDebugConfigurationProvider } from './configuration/providers/fastapiLaunch'; -import { FileLaunchDebugConfigurationProvider } from './configuration/providers/fileLaunch'; -import { FlaskLaunchDebugConfigurationProvider } from './configuration/providers/flaskLaunch'; -import { ModuleLaunchDebugConfigurationProvider } from './configuration/providers/moduleLaunch'; -import { PidAttachDebugConfigurationProvider } from './configuration/providers/pidAttach'; -import { DebugConfigurationProviderFactory } from './configuration/providers/providerFactory'; -import { PyramidLaunchDebugConfigurationProvider } from './configuration/providers/pyramidLaunch'; -import { RemoteAttachDebugConfigurationProvider } from './configuration/providers/remoteAttach'; import { AttachConfigurationResolver } from './configuration/resolvers/attach'; import { DebugEnvironmentVariablesHelper, IDebugEnvironmentVariablesService } from './configuration/resolvers/helper'; import { LaunchConfigurationResolver } from './configuration/resolvers/launch'; -import { - IDebugConfigurationProviderFactory, - IDebugConfigurationResolver, - ILaunchJsonReader, -} from './configuration/types'; +import { IDebugConfigurationResolver, ILaunchJsonReader } from './configuration/types'; import { DebugCommands } from './debugCommands'; import { ChildProcessAttachEventHandler } from './hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from './hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './hooks/types'; import { - DebugConfigurationType, IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, IDebugConfigurationService, IDebuggerBanner, IDebugSessionLoggingFactory, @@ -85,50 +70,6 @@ export function registerTypes(serviceManager: IServiceManager) { AttachConfigurationResolver, 'attach', ); - serviceManager.addSingleton( - IDebugConfigurationProviderFactory, - DebugConfigurationProviderFactory, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FileLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFile, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - DjangoLaunchDebugConfigurationProvider, - DebugConfigurationType.launchDjango, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FastAPILaunchDebugConfigurationProvider, - DebugConfigurationType.launchFastAPI, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FlaskLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFlask, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - RemoteAttachDebugConfigurationProvider, - DebugConfigurationType.remoteAttach, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - ModuleLaunchDebugConfigurationProvider, - DebugConfigurationType.launchModule, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PyramidLaunchDebugConfigurationProvider, - DebugConfigurationType.launchPyramid, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PidAttachDebugConfigurationProvider, - DebugConfigurationType.pidAttach, - ); serviceManager.addSingleton( IDebugEnvironmentVariablesService, DebugEnvironmentVariablesHelper, diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index 273a398f6e40..1e5b724975a9 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -13,7 +13,6 @@ import { WorkspaceFolder, } from 'vscode'; -import { InputStep, MultiStepInput } from '../../common/utils/multiStepInput'; import { DebugConfigurationArguments } from '../types'; export const IDebugConfigurationService = Symbol('IDebugConfigurationService'); @@ -27,18 +26,11 @@ export interface IDebuggerBanner { initialize(): void; } -export const IDebugConfigurationProvider = Symbol('IDebugConfigurationProvider'); export type DebugConfigurationState = { config: Partial; folder?: WorkspaceFolder; token?: CancellationToken; }; -export interface IDebugConfigurationProvider { - buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ): Promise | void>; -} export enum DebugConfigurationType { launchFile = 'launchFile', diff --git a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts b/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts index 8495d4820c0a..85c45407a137 100644 --- a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts +++ b/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts @@ -4,12 +4,10 @@ 'use strict'; import { expect } from 'chai'; -import { instance, mock } from 'ts-mockito'; import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; -import { IMultiStepInput, IMultiStepInputFactory } from '../../../../client/common/utils/multiStepInput'; +import { IMultiStepInputFactory, MultiStepInput } from '../../../../client/common/utils/multiStepInput'; import { PythonDebugConfigurationService } from '../../../../client/debugger/extension/configuration/debugConfigurationService'; -import { DebugConfigurationProviderFactory } from '../../../../client/debugger/extension/configuration/providers/providerFactory'; import { IDebugConfigurationResolver } from '../../../../client/debugger/extension/configuration/types'; import { DebugConfigurationState } from '../../../../client/debugger/extension/types'; import { AttachRequestArguments, LaunchRequestArguments } from '../../../../client/debugger/types'; @@ -19,11 +17,10 @@ suite('Debugging - Configuration Service', () => { let launchResolver: typemoq.IMock>; let configService: TestPythonDebugConfigurationService; let multiStepFactory: typemoq.IMock; - let providerFactory: DebugConfigurationProviderFactory; class TestPythonDebugConfigurationService extends PythonDebugConfigurationService { public async pickDebugConfiguration( - input: IMultiStepInput, + input: MultiStepInput, state: DebugConfigurationState, ) { return super.pickDebugConfiguration(input, state); @@ -33,12 +30,10 @@ suite('Debugging - Configuration Service', () => { attachResolver = typemoq.Mock.ofType>(); launchResolver = typemoq.Mock.ofType>(); multiStepFactory = typemoq.Mock.ofType(); - providerFactory = mock(DebugConfigurationProviderFactory); configService = new TestPythonDebugConfigurationService( attachResolver.object, launchResolver.object, - instance(providerFactory), multiStepFactory.object, ); }); @@ -93,7 +88,7 @@ suite('Debugging - Configuration Service', () => { }); test('Picker should be displayed', async () => { const state = ({ configs: [], folder: {}, token: undefined } as any) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); + const multiStepInput = typemoq.Mock.ofType>(); multiStepInput .setup((i) => i.showQuickPick(typemoq.It.isAny())) .returns(() => Promise.resolve(undefined as any)) @@ -105,7 +100,7 @@ suite('Debugging - Configuration Service', () => { }); test('Existing Configuration items must be removed before displaying picker', async () => { const state = ({ configs: [1, 2, 3], folder: {}, token: undefined } as any) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); + const multiStepInput = typemoq.Mock.ofType>(); multiStepInput .setup((i) => i.showQuickPick(typemoq.It.isAny())) .returns(() => Promise.resolve(undefined as any)) diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts index 36c8c304c598..31d1e48d9fa4 100644 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts @@ -3,7 +3,7 @@ 'use strict'; -import { Uri, WorkspaceFolder } from 'vscode'; +import { Uri } from 'vscode'; import { expect } from 'chai'; import * as path from 'path'; import * as fs from 'fs-extra'; @@ -13,24 +13,19 @@ import * as workspaceFolder from '../../../../../client/common/utils/workspaceFo import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { DjangoLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; +import * as djangoLaunch from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; + suite('Debugging - Configuration Provider Django', () => { let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; let workspaceStub: sinon.SinonStub; - let provider: TestDjangoLaunchDebugConfigurationProvider; let input: MultiStepInput; - class TestDjangoLaunchDebugConfigurationProvider extends DjangoLaunchDebugConfigurationProvider { - public async getManagePyPath(folder: WorkspaceFolder): Promise { - return super.getManagePyPath(folder); - } - } + setup(() => { input = mock>(MultiStepInput); - provider = new TestDjangoLaunchDebugConfigurationProvider(); pathExistsStub = sinon.stub(fs, 'pathExists'); pathSeparatorStub = sinon.stub(path, 'sep'); workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); @@ -42,7 +37,7 @@ suite('Debugging - Configuration Provider Django', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); pathExistsStub.withArgs(managePyPath).resolves(false); - const file = await provider.getManagePyPath(folder); + const file = await djangoLaunch.getManagePyPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); @@ -51,7 +46,7 @@ suite('Debugging - Configuration Provider Django', () => { const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); pathExistsStub.withArgs(managePyPath).resolves(true); pathSeparatorStub.value('-'); - const file = await provider.getManagePyPath(folder); + const file = await djangoLaunch.getManagePyPath(folder); expect(file).to.be.equal('${workspaceFolder}-manage.py'); }); @@ -64,68 +59,49 @@ suite('Debugging - Configuration Provider Django', () => { }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateManagePy(folder, ''); + const error = await djangoLaunch.validateManagePy(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateManagePy(folder, '', ''); + const error = await djangoLaunch.validateManagePy(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateManagePy(folder, '', 'x'); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz').resolves(false); - const error = await provider.validateManagePy(folder, '', 'x'); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz.txt').resolves(true); - const error = await provider.validateManagePy(folder, '', 'x'); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz.py').resolves(true); - const error = await provider.validateManagePy(folder, '', 'xyz.py'); + const error = await djangoLaunch.validateManagePy(folder, '', 'xyz.py'); expect(error).to.be.equal(undefined, 'should not have errors'); }); - test('Launch JSON with valid python path', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve('xyz.py'); - pathSeparatorStub.value('-'); - await provider.buildConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: 'xyz.py', - args: ['runserver'], - django: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); test('Launch JSON with selected managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.django.snippet.name, @@ -146,7 +122,7 @@ suite('Debugging - Configuration Provider Django', () => { const defaultProgram = `${workspaceFolderToken}-manage.py`; pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.django.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts index 2b1eca96e21d..f6c20985e4da 100644 --- a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts @@ -8,25 +8,19 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; +import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FastAPILaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/fastapiLaunch'; +import * as fastApiLaunch from '../../../../../client/debugger/extension/configuration/providers/fastapiLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider FastAPI', () => { - let provider: TestFastAPILaunchDebugConfigurationProvider; let input: MultiStepInput; let pathExistsStub: sinon.SinonStub; - class TestFastAPILaunchDebugConfigurationProvider extends FastAPILaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { - return super.getApplicationPath(folder); - } - } + setup(() => { input = mock>(MultiStepInput); - provider = new TestFastAPILaunchDebugConfigurationProvider(); pathExistsStub = sinon.stub(fs, 'pathExists'); }); teardown(() => { @@ -36,7 +30,7 @@ suite('Debugging - Configuration Provider FastAPI', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); pathExistsStub.withArgs(appPyPath).resolves(false); - const file = await provider.getApplicationPath(folder); + const file = await fastApiLaunch.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); @@ -44,16 +38,15 @@ suite('Debugging - Configuration Provider FastAPI', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); pathExistsStub.withArgs(appPyPath).resolves(true); - const file = await provider.getApplicationPath(folder); + const file = await fastApiLaunch.getApplicationPath(folder); expect(file).to.be.equal('main.py'); }); test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); - await provider.buildConfiguration(instance(input), state); + await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.fastapi.snippet.name, @@ -70,11 +63,10 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('main'); - await provider.buildConfiguration(instance(input), state); + await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.fastapi.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts index c074ad33a01c..60f2b199bbd2 100644 --- a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts @@ -8,18 +8,14 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FileLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/fileLaunch'; +import { buildFileLaunchDebugConfiguration } from '../../../../../client/debugger/extension/configuration/providers/fileLaunch'; suite('Debugging - Configuration Provider File', () => { - let provider: FileLaunchDebugConfigurationProvider; - setup(() => { - provider = new FileLaunchDebugConfigurationProvider(); - }); test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - await provider.buildConfiguration(undefined as any, state); + await buildFileLaunchDebugConfiguration(undefined as any, state); const config = { name: DebugConfigStrings.file.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts index 2b483a1353d4..6715844167b2 100644 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts @@ -8,25 +8,18 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; +import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FlaskLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/flaskLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; +import * as flaskLaunch from '../../../../../client/debugger/extension/configuration/providers/flaskLaunch'; suite('Debugging - Configuration Provider Flask', () => { let pathExistsStub: sinon.SinonStub; - let provider: TestFlaskLaunchDebugConfigurationProvider; let input: MultiStepInput; - class TestFlaskLaunchDebugConfigurationProvider extends FlaskLaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { - return super.getApplicationPath(folder); - } - } setup(() => { input = mock>(MultiStepInput); - provider = new TestFlaskLaunchDebugConfigurationProvider(); pathExistsStub = sinon.stub(fs, 'pathExists'); }); teardown(() => { @@ -36,7 +29,7 @@ suite('Debugging - Configuration Provider Flask', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); pathExistsStub.withArgs(appPyPath).resolves(false); - const file = await provider.getApplicationPath(folder); + const file = await flaskLaunch.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); @@ -44,16 +37,15 @@ suite('Debugging - Configuration Provider Flask', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); pathExistsStub.withArgs(appPyPath).resolves(true); - const file = await provider.getApplicationPath(folder); + const file = await flaskLaunch.getApplicationPath(folder); expect(file).to.be.equal('app.py'); }); - test('Launch JSON with valid python path', async () => { + test.only('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, @@ -61,7 +53,7 @@ suite('Debugging - Configuration Provider Flask', () => { request: 'launch', module: 'flask', env: { - FLASK_APP: 'xyz.py', + FLASK_APP: 'app.py', FLASK_DEBUG: '1', }, args: ['run', '--no-debugger', '--no-reload'], @@ -74,11 +66,10 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, @@ -99,11 +90,9 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); - when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts index c0571a1bf30f..2508db506ca2 100644 --- a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts @@ -10,14 +10,10 @@ import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { ModuleLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/moduleLaunch'; +import { buildModuleLaunchConfiguration } from '../../../../../client/debugger/extension/configuration/providers/moduleLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Module', () => { - let provider: ModuleLaunchDebugConfigurationProvider; - setup(() => { - provider = new ModuleLaunchDebugConfigurationProvider(); - }); test('Launch JSON with default module name', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; @@ -25,7 +21,7 @@ suite('Debugging - Configuration Provider Module', () => { when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await buildModuleLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.module.snippet.name, @@ -44,7 +40,7 @@ suite('Debugging - Configuration Provider Module', () => { when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await buildModuleLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.module.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts index 696179ea9a12..db9de7e0f58c 100644 --- a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts @@ -8,18 +8,14 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { PidAttachDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/pidAttach'; +import { buildPidAttachConfiguration } from '../../../../../client/debugger/extension/configuration/providers/pidAttach'; suite('Debugging - Configuration Provider File', () => { - let provider: PidAttachDebugConfigurationProvider; - setup(() => { - provider = new PidAttachDebugConfigurationProvider(); - }); test('Launch JSON with default process id', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - await provider.buildConfiguration(undefined as any, state); + await buildPidAttachConfiguration(undefined as any, state); const config = { name: DebugConfigStrings.attachPid.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts b/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts deleted file mode 100644 index a786347ed8d1..000000000000 --- a/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { getNamesAndValues } from '../../../../../client/common/utils/enum'; -import { DebugConfigurationProviderFactory } from '../../../../../client/debugger/extension/configuration/providers/providerFactory'; -import { IDebugConfigurationProviderFactory } from '../../../../../client/debugger/extension/configuration/types'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider Factory', () => { - let mappedProviders: Map; - let factory: IDebugConfigurationProviderFactory; - setup(() => { - mappedProviders = new Map(); - getNamesAndValues(DebugConfigurationType).forEach((item) => { - mappedProviders.set(item.value, (item.value as any) as IDebugConfigurationProvider); - }); - factory = new DebugConfigurationProviderFactory( - mappedProviders.get(DebugConfigurationType.launchFastAPI)!, - mappedProviders.get(DebugConfigurationType.launchFlask)!, - mappedProviders.get(DebugConfigurationType.launchDjango)!, - mappedProviders.get(DebugConfigurationType.launchModule)!, - mappedProviders.get(DebugConfigurationType.launchFile)!, - mappedProviders.get(DebugConfigurationType.launchPyramid)!, - mappedProviders.get(DebugConfigurationType.remoteAttach)!, - mappedProviders.get(DebugConfigurationType.pidAttach)!, - ); - }); - getNamesAndValues(DebugConfigurationType).forEach((item) => { - test(`Configuration Provider for ${item.name}`, () => { - const provider = factory.create(item.value); - expect(provider).to.equal(mappedProviders.get(item.value)); - }); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts index 46d424283653..e31aa2d426ce 100644 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts @@ -8,29 +8,23 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; +import { Uri } from 'vscode'; import * as workspaceFolder from '../../../../../client/common/utils/workspaceFolder'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; -import { PyramidLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; +import * as pyramidLaunch from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Pyramid', () => { - let provider: TestPyramidLaunchDebugConfigurationProvider; let input: MultiStepInput; let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; let workspaceStub: sinon.SinonStub; - class TestPyramidLaunchDebugConfigurationProvider extends PyramidLaunchDebugConfigurationProvider { - public async getDevelopmentIniPath(folder: WorkspaceFolder): Promise { - return super.getDevelopmentIniPath(folder); - } - } + setup(() => { input = mock>(MultiStepInput); - provider = new TestPyramidLaunchDebugConfigurationProvider(); pathExistsStub = sinon.stub(fs, 'pathExists'); pathSeparatorStub = sinon.stub(path, 'sep'); workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); @@ -42,7 +36,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); pathExistsStub.withArgs(managePyPath).resolves(false); - const file = await provider.getDevelopmentIniPath(folder); + const file = await pyramidLaunch.getDevelopmentIniPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); @@ -51,7 +45,7 @@ suite('Debugging - Configuration Provider Pyramid', () => { const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); pathSeparatorStub.value('-'); pathExistsStub.withArgs(managePyPath).resolves(true); - const file = await provider.getDevelopmentIniPath(folder); + const file = await pyramidLaunch.getDevelopmentIniPath(folder); expect(file).to.be.equal('${workspaceFolder}-development.ini'); }); @@ -64,57 +58,56 @@ suite('Debugging - Configuration Provider Pyramid', () => { }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateIniPath(folder, ''); + const error = await pyramidLaunch.validateIniPath(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateIniPath(folder, '', ''); + const error = await pyramidLaunch.validateIniPath(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await provider.validateIniPath(folder, '', 'x'); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz').resolves(false); - const error = await provider.validateIniPath(folder, '', 'x'); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz.txt').resolves(true); - const error = await provider.validateIniPath(folder, '', 'x'); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should not return errors if resolved path is ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; pathExistsStub.withArgs('xyz.ini').resolves(true); - const error = await provider.validateIniPath(folder, '', 'xyz.ini'); + const error = await pyramidLaunch.validateIniPath(folder, '', 'xyz.ini'); expect(error).to.be.equal(undefined, 'should not have errors'); }); test('Launch JSON with valid ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve('xyz.ini'); pathSeparatorStub.value('-'); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, type: DebuggerTypeName, request: 'launch', module: 'pyramid.scripts.pserve', - args: ['xyz.ini'], + args: ['${workspaceFolder}-development.ini'], pyramid: true, jinja: true, justMyCode: true, @@ -125,11 +118,10 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with selected ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, @@ -147,14 +139,13 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with default ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); const workspaceFolderToken = '${workspaceFolder}'; const defaultIni = `${workspaceFolderToken}-development.ini`; pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts index 7bf88543bdad..4fb8b5d98e2c 100644 --- a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts @@ -5,34 +5,29 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as sinon from 'sinon'; import { anything, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { RemoteAttachDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/remoteAttach'; +import * as common from '../../../../../client/debugger/extension/configuration/providers/common'; +import * as remoteAttach from '../../../../../client/debugger/extension/configuration/providers/remoteAttach'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import { AttachRequestArguments } from '../../../../../client/debugger/types'; suite('Debugging - Configuration Provider Remote Attach', () => { - let provider: TestRemoteAttachDebugConfigurationProvider; let input: MultiStepInput; - class TestRemoteAttachDebugConfigurationProvider extends RemoteAttachDebugConfigurationProvider { - public async configurePort( - i: MultiStepInput, - config: Partial, - ) { - return super.configurePort(i, config); - } - } + setup(() => { input = mock>(MultiStepInput); - provider = new TestRemoteAttachDebugConfigurationProvider(); + }); + teardown(() => { + sinon.restore(); }); test('Configure port will display prompt', async () => { when(input.showInputBox(anything())).thenResolve(); - await provider.configurePort(instance(input), {}); + await common.configurePort(instance(input), {}); verify(input.showInputBox(anything())).once(); }); @@ -40,7 +35,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('xyz'); - await provider.configurePort(instance(input), config); + await common.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -49,7 +44,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve(); - await provider.configurePort(instance(input), config); + await common.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -58,7 +53,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('1234'); - await provider.configurePort(instance(input), config); + await common.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 1234 } }); @@ -68,12 +63,12 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const state = { config: {}, folder }; let portConfigured = false; when(input.showInputBox(anything())).thenResolve(); - provider.configurePort = () => { + + sinon.stub(common, 'configurePort').callsFake(async () => { portConfigured = true; - return Promise.resolve(); - }; + }); - const configurePort = await provider.buildConfiguration(instance(input), state); + const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); if (configurePort) { await configurePort!(input, state); } @@ -103,13 +98,11 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const state = { config: {}, folder }; let portConfigured = false; when(input.showInputBox(anything())).thenResolve('Hello'); - provider.configurePort = (_, cfg) => { + sinon.stub(common, 'configurePort').callsFake(async (_, cfg) => { portConfigured = true; cfg.connect!.port = 9999; - return Promise.resolve(); - }; - - const configurePort = await provider.buildConfiguration(instance(input), state); + }); + const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); if (configurePort) { await configurePort(input, state); } diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index 6dc27ba2ed9c..28ae70d7bf98 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -17,31 +17,16 @@ import { LaunchJsonCompletionProvider } from '../../../client/debugger/extension import { InterpreterPathCommand } from '../../../client/debugger/extension/configuration/launch.json/interpreterPathCommand'; import { LaunchJsonReader } from '../../../client/debugger/extension/configuration/launch.json/launchJsonReader'; import { LaunchJsonUpdaterService } from '../../../client/debugger/extension/configuration/launch.json/updaterService'; -import { DjangoLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/djangoLaunch'; -import { FastAPILaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/fastapiLaunch'; -import { FileLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/fileLaunch'; -import { FlaskLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/flaskLaunch'; -import { ModuleLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/moduleLaunch'; -import { PidAttachDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/pidAttach'; -import { DebugConfigurationProviderFactory } from '../../../client/debugger/extension/configuration/providers/providerFactory'; -import { PyramidLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/pyramidLaunch'; -import { RemoteAttachDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/remoteAttach'; import { AttachConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/attach'; import { LaunchConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/launch'; -import { - IDebugConfigurationProviderFactory, - IDebugConfigurationResolver, - ILaunchJsonReader, -} from '../../../client/debugger/extension/configuration/types'; +import { IDebugConfigurationResolver, ILaunchJsonReader } from '../../../client/debugger/extension/configuration/types'; import { DebugCommands } from '../../../client/debugger/extension/debugCommands'; import { ChildProcessAttachEventHandler } from '../../../client/debugger/extension/hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from '../../../client/debugger/extension/hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from '../../../client/debugger/extension/hooks/types'; import { registerTypes } from '../../../client/debugger/extension/serviceRegistry'; import { - DebugConfigurationType, IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, IDebugConfigurationService, IDebuggerBanner, IDebugSessionLoggingFactory, @@ -123,69 +108,6 @@ suite('Debugging - Service Registry', () => { 'attach', ), ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProviderFactory, - DebugConfigurationProviderFactory, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FileLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFile, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - DjangoLaunchDebugConfigurationProvider, - DebugConfigurationType.launchDjango, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FastAPILaunchDebugConfigurationProvider, - DebugConfigurationType.launchFastAPI, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FlaskLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFlask, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - RemoteAttachDebugConfigurationProvider, - DebugConfigurationType.remoteAttach, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - ModuleLaunchDebugConfigurationProvider, - DebugConfigurationType.launchModule, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - PyramidLaunchDebugConfigurationProvider, - DebugConfigurationType.launchPyramid, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - PidAttachDebugConfigurationProvider, - DebugConfigurationType.pidAttach, - ), - ).once(); - verify( serviceManager.addSingleton( IExtensionSingleActivationService, From 0aab9f81779492b53ac43806c93803af8fbe8964 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 14 Sep 2022 15:33:21 -0700 Subject: [PATCH 7/9] update DebugCofigServer --- .../extension/configuration/debugConfigurationService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts index 0c3fbfb709f6..c5413b3662e0 100644 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/debugConfigurationService.ts @@ -17,9 +17,11 @@ import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArgum import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; import { buildDjangoLaunchDebugConfiguration } from './providers/djangoLaunch'; import { buildFastAPILaunchDebugConfiguration } from './providers/fastapiLaunch'; +import { buildFileLaunchDebugConfiguration } from './providers/fileLaunch'; import { buildFlaskLaunchDebugConfiguration } from './providers/flaskLaunch'; import { buildModuleLaunchConfiguration } from './providers/moduleLaunch'; import { buildPidAttachConfiguration } from './providers/pidAttach'; +import { buildPyramidLaunchConfiguration } from './providers/pyramidLaunch'; import { buildRemoteAttachConfiguration } from './providers/remoteAttach'; import { IDebugConfigurationResolver } from './types'; @@ -161,13 +163,12 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi >(); debugConfigurations.set(DebugConfigurationType.launchDjango, buildDjangoLaunchDebugConfiguration); debugConfigurations.set(DebugConfigurationType.launchFastAPI, buildFastAPILaunchDebugConfiguration); - debugConfigurations.set(DebugConfigurationType.launchFile, buildDjangoLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFile, buildFileLaunchDebugConfiguration); debugConfigurations.set(DebugConfigurationType.launchFlask, buildFlaskLaunchDebugConfiguration); debugConfigurations.set(DebugConfigurationType.launchModule, buildModuleLaunchConfiguration); debugConfigurations.set(DebugConfigurationType.pidAttach, buildPidAttachConfiguration); debugConfigurations.set(DebugConfigurationType.remoteAttach, buildRemoteAttachConfiguration); - debugConfigurations.set(DebugConfigurationType.launchPyramid, buildPidAttachConfiguration); - buildRemoteAttachConfiguration; + debugConfigurations.set(DebugConfigurationType.launchPyramid, buildPyramidLaunchConfiguration); state.config = {}; const pick = await input.showQuickPick< From 81510180ba3bb472555c6d31c84dd2ac28b8817d Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 14 Sep 2022 15:38:17 -0700 Subject: [PATCH 8/9] Fix tests --- .../extension/configuration/providers/flaskLaunch.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts index 6715844167b2..08fb5259b282 100644 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts @@ -41,7 +41,7 @@ suite('Debugging - Configuration Provider Flask', () => { expect(file).to.be.equal('app.py'); }); - test.only('Launch JSON with valid python path', async () => { + test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; From 222f8f07b7f83e2aae8c087b996ae1e07400a4a0 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 14 Sep 2022 17:46:25 -0700 Subject: [PATCH 9/9] Fix files names --- .../configuration/providers/djangoLaunch.ts | 2 +- .../configuration/providers/pyramidLaunch.ts | 2 +- .../configuration/providers/remoteAttach.ts | 2 +- .../extension/configuration/utils/common.ts | 40 +++++++++++++++++++ .../common.ts => utils/configuration.ts} | 32 --------------- .../configuration}/utils/workspaceFolder.ts | 0 .../providers/djangoLaunch.unit.test.ts | 5 +-- .../providers/pyramidLaunch.unit.test.ts | 4 +- .../providers/remoteAttach.unit.test.ts | 14 +++---- 9 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 src/client/debugger/extension/configuration/utils/common.ts rename src/client/debugger/extension/configuration/{providers/common.ts => utils/configuration.ts} (64%) rename src/client/{common => debugger/extension/configuration}/utils/workspaceFolder.ts (100%) diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 5d0f18f20063..da4e199fcfc4 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -13,7 +13,7 @@ import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -import { resolveVariables } from './common'; +import { resolveVariables } from '../utils/common'; const workspaceFolderToken = '${workspaceFolder}'; diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index 990d91445e12..dd82518720c4 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -14,7 +14,7 @@ import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; import * as nls from 'vscode-nls'; -import { resolveVariables } from './common'; +import { resolveVariables } from '../utils/common'; const localize: nls.LocalizeFunc = nls.loadMessageBundle(); diff --git a/src/client/debugger/extension/configuration/providers/remoteAttach.ts b/src/client/debugger/extension/configuration/providers/remoteAttach.ts index a4014ccd57c4..a43c48b664af 100644 --- a/src/client/debugger/extension/configuration/providers/remoteAttach.ts +++ b/src/client/debugger/extension/configuration/providers/remoteAttach.ts @@ -10,7 +10,7 @@ import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { AttachRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -import { configurePort } from './common'; +import { configurePort } from '../utils/configuration'; const defaultHost = 'localhost'; const defaultPort = 5678; diff --git a/src/client/debugger/extension/configuration/utils/common.ts b/src/client/debugger/extension/configuration/utils/common.ts new file mode 100644 index 000000000000..4767d7c928e0 --- /dev/null +++ b/src/client/debugger/extension/configuration/utils/common.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { WorkspaceFolder } from 'vscode'; +import { getWorkspaceFolder } from './workspaceFolder'; + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +function isString(str: any): str is string { + if (typeof str === 'string' || str instanceof String) { + return true; + } + + return false; +} + +export function resolveVariables( + value: string, + rootFolder: string | undefined, + folder: WorkspaceFolder | undefined, +): string { + const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined; + const variablesObject: { [key: string]: any } = {}; + variablesObject.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; + + const regexp = /\$\{(.*?)\}/g; + return value.replace(regexp, (match: string, name: string) => { + const newValue = variablesObject[name]; + if (isString(newValue)) { + return newValue; + } + return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; + }); +} diff --git a/src/client/debugger/extension/configuration/providers/common.ts b/src/client/debugger/extension/configuration/utils/configuration.ts similarity index 64% rename from src/client/debugger/extension/configuration/providers/common.ts rename to src/client/debugger/extension/configuration/utils/configuration.ts index 22ad634b40b4..37fb500dbfdd 100644 --- a/src/client/debugger/extension/configuration/providers/common.ts +++ b/src/client/debugger/extension/configuration/utils/configuration.ts @@ -6,10 +6,8 @@ 'use strict'; -import { WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { getWorkspaceFolder } from '../../../../common/utils/workspaceFolder'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { AttachRequestArguments } from '../../../types'; @@ -17,36 +15,6 @@ import { DebugConfigurationState, DebugConfigurationType } from '../../types'; const defaultPort = 5678; -/** - * @returns whether the provided parameter is a JavaScript String or not. - */ -function isString(str: any): str is string { - if (typeof str === 'string' || str instanceof String) { - return true; - } - - return false; -} - -export function resolveVariables( - value: string, - rootFolder: string | undefined, - folder: WorkspaceFolder | undefined, -): string { - const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined; - const variablesObject: { [key: string]: any } = {}; - variablesObject.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; - - const regexp = /\$\{(.*?)\}/g; - return value.replace(regexp, (match: string, name: string) => { - const newValue = variablesObject[name]; - if (isString(newValue)) { - return newValue; - } - return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; - }); -} - export async function configurePort( input: MultiStepInput, config: Partial, diff --git a/src/client/common/utils/workspaceFolder.ts b/src/client/debugger/extension/configuration/utils/workspaceFolder.ts similarity index 100% rename from src/client/common/utils/workspaceFolder.ts rename to src/client/debugger/extension/configuration/utils/workspaceFolder.ts diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts index 31d1e48d9fa4..479fdfebb53e 100644 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts @@ -9,13 +9,12 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import * as workspaceFolder from '../../../../../client/common/utils/workspaceFolder'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; - +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; +import * as workspaceFolder from '../../../../../client/debugger/extension/configuration/utils/workspaceFolder'; import * as djangoLaunch from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; suite('Debugging - Configuration Provider Django', () => { diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts index e31aa2d426ce..8a5d29206180 100644 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts @@ -9,11 +9,11 @@ import * as fs from 'fs-extra'; import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; import { Uri } from 'vscode'; -import * as workspaceFolder from '../../../../../client/common/utils/workspaceFolder'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { resolveVariables } from '../../../../../client/debugger/extension/configuration/providers/common'; +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; +import * as workspaceFolder from '../../../../../client/debugger/extension/configuration/utils/workspaceFolder'; import * as pyramidLaunch from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; diff --git a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts index 4fb8b5d98e2c..323cda94a1eb 100644 --- a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts @@ -11,7 +11,7 @@ import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import * as common from '../../../../../client/debugger/extension/configuration/providers/common'; +import * as configuration from '../../../../../client/debugger/extension/configuration/utils/configuration'; import * as remoteAttach from '../../../../../client/debugger/extension/configuration/providers/remoteAttach'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; @@ -27,7 +27,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { test('Configure port will display prompt', async () => { when(input.showInputBox(anything())).thenResolve(); - await common.configurePort(instance(input), {}); + await configuration.configurePort(instance(input), {}); verify(input.showInputBox(anything())).once(); }); @@ -35,7 +35,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('xyz'); - await common.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -44,7 +44,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve(); - await common.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -53,7 +53,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('1234'); - await common.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 1234 } }); @@ -64,7 +64,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { let portConfigured = false; when(input.showInputBox(anything())).thenResolve(); - sinon.stub(common, 'configurePort').callsFake(async () => { + sinon.stub(configuration, 'configurePort').callsFake(async () => { portConfigured = true; }); @@ -98,7 +98,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const state = { config: {}, folder }; let portConfigured = false; when(input.showInputBox(anything())).thenResolve('Hello'); - sinon.stub(common, 'configurePort').callsFake(async (_, cfg) => { + sinon.stub(configuration, 'configurePort').callsFake(async (_, cfg) => { portConfigured = true; cfg.connect!.port = 9999; });