From e528c9302d42abd5d3c84247b92ae14a1158d2a8 Mon Sep 17 00:00:00 2001 From: Michael Liao Date: Mon, 28 Mar 2022 16:49:00 -0700 Subject: [PATCH 1/2] [LaunchConfigManager]: Improve logic for configuring existing launch.json --- src/launchConfigManager.ts | 84 +++++++++++++++++++++++++------- test/launchConfigManager.test.ts | 12 +++-- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/launchConfigManager.ts b/src/launchConfigManager.ts index dd160401..89877ec6 100644 --- a/src/launchConfigManager.ts +++ b/src/launchConfigManager.ts @@ -7,7 +7,7 @@ import { SETTINGS_STORE_NAME, SETTINGS_DEFAULT_URL, } from './utils'; -export type LaunchConfig = 'None' | 'Unsupported' | vscode.DebugConfiguration; +export type LaunchConfig = 'None' | 'Unsupported' | string; export type CompoundConfig = { name: string, configurations: string[], @@ -64,6 +64,17 @@ const providedCompoundDebugConfigHeadless: CompoundConfig = { ], }; +export const extensionCompoundConfigs: CompoundConfig[] = [ + providedCompoundDebugConfigHeadless, + providedCompoundDebugConfig, +]; + +export const extensionConfigs: vscode.DebugConfiguration[] = [ + providedDebugConfig, + providedHeadlessDebugConfig, + providedLaunchDevToolsConfig, +]; + export class LaunchConfigManager { private launchConfig: LaunchConfig; private isValidConfig: boolean; @@ -102,13 +113,12 @@ export class LaunchConfigManager { if (fse.pathExistsSync(filePath)) { // Check if there is a supported debug config const configs = vscode.workspace.getConfiguration('launch', workspaceUri).get('configurations') as vscode.DebugConfiguration[]; - for (const config of configs) { - if (config.type === 'vscode-edge-devtools.debug' || config.type === 'msedge' || config.type === 'edge') { - void vscode.commands.executeCommand('setContext', 'launchJsonStatus', 'Supported'); - this.launchConfig = config; - this.isValidConfig = true; - return; - } + const compoundConfigs = vscode.workspace.getConfiguration('launch', workspaceUri).get('compounds') as CompoundConfig[]; + if (this.getMissingConfigs(configs, extensionConfigs).length === 0 && this.getMissingConfigs(compoundConfigs, extensionCompoundConfigs).length === 0) { + void vscode.commands.executeCommand('setContext', 'launchJsonStatus', 'Supported'); + this.launchConfig = extensionCompoundConfigs[0].name; // extensionCompoundConfigs[0].name => 'Launch Edge Headless and attach DevTools' + this.isValidConfig = true; + return; } void vscode.commands.executeCommand('setContext', 'launchJsonStatus', 'Unsupported'); this.launchConfig = 'Unsupported'; @@ -138,11 +148,20 @@ export class LaunchConfigManager { // Append a supported debug config to their list of configurations and update workspace configuration const launchJson = vscode.workspace.getConfiguration('launch', workspaceUri); const configs = launchJson.get('configurations') as vscode.DebugConfiguration[]; - configs.push(providedDebugConfig); - configs.push(providedHeadlessDebugConfig); - configs.push(providedLaunchDevToolsConfig); + const missingConfigs = this.getMissingConfigs(configs, extensionConfigs); + for (const missingConfig of missingConfigs) { + configs.push((missingConfig as vscode.DebugConfiguration)); + } await launchJson.update('configurations', configs) as unknown as Promise; + // Add compound configs + const compounds = launchJson.get('compounds') as CompoundConfig[]; + const missingCompoundConfigs = this.getMissingConfigs(compounds, extensionCompoundConfigs); + for (const missingCompoundConfig of missingCompoundConfigs) { + compounds.push((missingCompoundConfig as CompoundConfig)); + } + await launchJson.update('compounds', compounds) as unknown as Promise; + // Insert instruction comment let launchText = fse.readFileSync(workspaceUri.fsPath + relativePath).toString(); const re = /("url":.*startpage[\/\\]+index\.html",)/gm; @@ -151,12 +170,6 @@ export class LaunchConfigManager { launchText = launchText.replace(re, `${match ? match[0] : ''}${instructions}`); fse.writeFileSync(workspaceUri.fsPath + relativePath, launchText); - // Add compound configs - const compounds = launchJson.get('compounds') as CompoundConfig[]; - compounds.push(providedCompoundDebugConfigHeadless); - compounds.push(providedCompoundDebugConfig); - await launchJson.update('compounds', compounds) as unknown as Promise; - // Open launch.json in editor void vscode.commands.executeCommand('vscode.open', vscode.Uri.joinPath(workspaceUri, relativePath)); this.updateLaunchConfig(); @@ -165,4 +178,41 @@ export class LaunchConfigManager { isValidLaunchConfig(): boolean { return this.isValidConfig; } + + getMissingConfigs(userConfigs: Record[], extensionConfigs: Record[]): Record[] { + const missingConfigs: Record[] = []; + for (const extensionConfig of extensionConfigs) { + let configExists = false; + for (const userConfig of userConfigs) { + if (this.compareConfigs(userConfig, extensionConfig)) { + configExists = true; + break; + } + } + if (!configExists) { + missingConfigs.push(extensionConfig); + } + } + + return missingConfigs; + } + + compareConfigs(userConfig: Record, extensionConfig: Record): boolean { + for (const property of Object.keys(extensionConfig)) { + if (property === 'url' || property === 'presentation') { + continue; + } + if (Array.isArray(extensionConfig[property]) && Array.isArray(userConfig[property])) { + const userPropertySet = new Set((userConfig[property] as Array)); + for (const extensionConfigProperty of (extensionConfig[property] as Array)) { + if (!userPropertySet.has(extensionConfigProperty)) { + return false; + } + } + } else if (userConfig[property] !== extensionConfig[property]) { + return false; + } + } + return true; + } } diff --git a/test/launchConfigManager.test.ts b/test/launchConfigManager.test.ts index e6bab986..a19a92a0 100644 --- a/test/launchConfigManager.test.ts +++ b/test/launchConfigManager.test.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import {createFakeVSCode} from "./helpers/helpers"; -import { LaunchConfigManager, providedDebugConfig} from "../src/launchConfigManager"; +import { extensionCompoundConfigs, extensionConfigs, LaunchConfigManager, providedDebugConfig } from "../src/launchConfigManager"; jest.mock("vscode", () => createFakeVSCode(), { virtual: true }); jest.mock("fs-extra"); @@ -44,11 +44,17 @@ describe("launchConfigManager", () => { fse.pathExistsSync.mockImplementation(() => true); vscodeMock.workspace.getConfiguration.mockImplementation(() => { return { - get: (name: string) => [{type: 'vscode-edge-devtools.debug'}] + get: (name: string) => { + if (name === 'configurations') { + return extensionConfigs; + } else { + return extensionCompoundConfigs; + } + } } }); const launchConfigManager = LaunchConfigManager.instance; - expect(launchConfigManager.getLaunchConfig()).toEqual({type: 'vscode-edge-devtools.debug'}); + expect(launchConfigManager.getLaunchConfig()).toEqual('Launch Edge Headless and attach DevTools'); expect(vscodeMock.commands.executeCommand).toBeCalledWith('setContext', 'launchJsonStatus', 'Supported'); }); From 626fb54dcf81499d5c8d7e97727dac3a48cd377c Mon Sep 17 00:00:00 2001 From: Michael Liao Date: Thu, 31 Mar 2022 16:44:08 -0700 Subject: [PATCH 2/2] handle duplicate names + increase test coverage --- src/launchConfigManager.ts | 21 +++++++++++-- test/launchConfigManager.test.ts | 51 ++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/launchConfigManager.ts b/src/launchConfigManager.ts index 89877ec6..96dcb07e 100644 --- a/src/launchConfigManager.ts +++ b/src/launchConfigManager.ts @@ -147,7 +147,9 @@ export class LaunchConfigManager { // Append a supported debug config to their list of configurations and update workspace configuration const launchJson = vscode.workspace.getConfiguration('launch', workspaceUri); - const configs = launchJson.get('configurations') as vscode.DebugConfiguration[]; + let configs = launchJson.get('configurations') as vscode.DebugConfiguration[]; + configs = this.replaceDuplicateNameConfigs(configs, extensionConfigs) as vscode.DebugConfiguration[]; + const missingConfigs = this.getMissingConfigs(configs, extensionConfigs); for (const missingConfig of missingConfigs) { configs.push((missingConfig as vscode.DebugConfiguration)); @@ -155,7 +157,8 @@ export class LaunchConfigManager { await launchJson.update('configurations', configs) as unknown as Promise; // Add compound configs - const compounds = launchJson.get('compounds') as CompoundConfig[]; + let compounds = launchJson.get('compounds') as CompoundConfig[]; + compounds = this.replaceDuplicateNameConfigs(compounds, extensionCompoundConfigs) as CompoundConfig[]; const missingCompoundConfigs = this.getMissingConfigs(compounds, extensionCompoundConfigs); for (const missingCompoundConfig of missingCompoundConfigs) { compounds.push((missingCompoundConfig as CompoundConfig)); @@ -179,6 +182,20 @@ export class LaunchConfigManager { return this.isValidConfig; } + replaceDuplicateNameConfigs(userConfigs: Record[], extensionConfigs: Record[]): Record[] { + const configs = []; + const extensionConfigMap: Map> = new Map(); + for (const extensionConfig of extensionConfigs) { + extensionConfigMap.set((extensionConfig.name as string), extensionConfig); + } + for (const userConfig of userConfigs) { + const duplicateNameConfig = extensionConfigMap.get((userConfig.name as string)); + const addConfig = duplicateNameConfig ? duplicateNameConfig : userConfig; + configs.push(addConfig); + } + return configs; + } + getMissingConfigs(userConfigs: Record[], extensionConfigs: Record[]): Record[] { const missingConfigs: Record[] = []; for (const extensionConfig of extensionConfigs) { diff --git a/test/launchConfigManager.test.ts b/test/launchConfigManager.test.ts index a19a92a0..306db43a 100644 --- a/test/launchConfigManager.test.ts +++ b/test/launchConfigManager.test.ts @@ -68,7 +68,7 @@ describe("launchConfigManager", () => { }); describe('configureLaunchJson', () => { - it('adds a debug config to launch.json', async () => { + it('adds extension configs/compounds to launch.json', async () => { const vscodeMock = jest.requireMock("vscode"); const fse = jest.requireMock("fs-extra"); fse.readFileSync.mockImplementation((() => '')); @@ -86,8 +86,9 @@ describe("launchConfigManager", () => { }); vscodeMock.Uri.joinPath = jest.fn(); const launchConfigManager = LaunchConfigManager.instance; - launchConfigManager.configureLaunchJson(); - expect(vscodeMock.WorkspaceConfiguration.update).toBeCalledWith('configurations', expect.arrayContaining([expect.any(Object)])); + await launchConfigManager.configureLaunchJson(); + expect(vscodeMock.WorkspaceConfiguration.update).toBeCalledWith('configurations', expect.arrayContaining([...extensionConfigs])); + expect(vscodeMock.WorkspaceConfiguration.update).toHaveBeenCalledWith('compounds', expect.arrayContaining([...extensionCompoundConfigs])); }); it('inserts a comment after the url property', async () => { @@ -98,6 +99,50 @@ describe("launchConfigManager", () => { await launchConfigManager.configureLaunchJson(); expect(fse.writeFileSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining(expectedText)); }); + + it('replaces config with duplicate name with extension config', async () => { + const vscodeMock = jest.requireMock("vscode"); + const fse = jest.requireMock("fs-extra"); + fse.readFileSync.mockImplementation((() => '')); + vscodeMock.workspace.workspaceFolders = [{ + uri: 'file:///g%3A/GIT/testPage' + }]; + vscodeMock.WorkspaceConfiguration = { + update: jest.fn((name: string, value: any) => {}), + }; + vscodeMock.workspace.getConfiguration.mockImplementation(() => { + return { + get: jest.fn((name: string) => [{name: 'Launch Microsoft Edge in headless mode'}]), + update: vscodeMock.WorkspaceConfiguration.update, + } + }); + vscodeMock.Uri.joinPath = jest.fn(); + const launchConfigManager = LaunchConfigManager.instance; + launchConfigManager.configureLaunchJson(); + expect(vscodeMock.WorkspaceConfiguration.update).toBeCalledWith('configurations', Array(3).fill(expect.anything())); + }); + + it('retains user config', async () => { + const vscodeMock = jest.requireMock("vscode"); + const fse = jest.requireMock("fs-extra"); + fse.readFileSync.mockImplementation((() => '')); + vscodeMock.workspace.workspaceFolders = [{ + uri: 'file:///g%3A/GIT/testPage' + }]; + vscodeMock.WorkspaceConfiguration = { + update: jest.fn((name: string, value: any) => {}), + }; + vscodeMock.workspace.getConfiguration.mockImplementation(() => { + return { + get: jest.fn((name: string) => [{name: 'Personal config'}]), + update: vscodeMock.WorkspaceConfiguration.update, + } + }); + vscodeMock.Uri.joinPath = jest.fn(); + const launchConfigManager = LaunchConfigManager.instance; + launchConfigManager.configureLaunchJson(); + expect(vscodeMock.WorkspaceConfiguration.update).toBeCalledWith('configurations', Array(4).fill(expect.anything())); + }); }); afterAll(() => {