From 4473a16af10cd3bbe6c00ebbdbedd156bd32bddb Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Mon, 19 Feb 2024 18:43:45 -0800 Subject: [PATCH 01/18] Added a new command. --- package.json | 5 ++++ package.nls.json | 1 + .../lint/configureLintingToolsCommand.ts | 23 +++++++++++++++++++ src/extension.ts | 4 ++++ 4 files changed, 33 insertions(+) create mode 100644 src/commands/lint/configureLintingToolsCommand.ts diff --git a/package.json b/package.json index 8e4ca4f..879e925 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,11 @@ "command": "salesforcedx-vscode-offline-app.onboardingWizard", "title": "%extension.commands.config-wizard.title%", "category": "%extension.commands.config-wizard.category%" + }, + { + "command": "'salesforcedx-vscode-offline-app.configureLintTools'", + "title": "%extension.commands.config-linting-tools.title%", + "category": "%extension.commands.config-wizard.category%" } ] }, diff --git a/package.nls.json b/package.nls.json index 4ed2ed3..339add9 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,6 +1,7 @@ { "extension.commands.config-wizard.title": "Configuration Wizard", "extension.commands.config-wizard.category": "Offline Starter Kit", + "extension.commands.config-linting-tools.title": "Configure Linting Tools", "extension.displayName": "Salesforce Mobile Extensions for Visual Studio Code", "extension.description": "Tools to help developers create their Salesforce Mobile experiences in a VSCode development environment." } diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts new file mode 100644 index 0000000..59c75a9 --- /dev/null +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ + +import * as vscode from 'vscode'; + +const configureLintToolsCommand = 'salesforcedx-vscode-offline-app.configureLintTools'; + +export function onActivate(context: vscode.ExtensionContext) { + console.log('Activated'); +} + +export function registerCommand(context: vscode.ExtensionContext) { + vscode.commands.registerCommand( + configureLintToolsCommand, + async () => { + return Promise.resolve(); + } + ); +} diff --git a/src/extension.ts b/src/extension.ts index ae27a1b..a131f76 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; import * as onboardingWizard from './commands/wizard/onboardingWizard'; +import * as configureLintingToolsCommand from './commands/lint/configureLintingToolsCommand'; import { CoreExtensionService } from './services/CoreExtensionService'; export function activate(context: vscode.ExtensionContext) { @@ -28,6 +29,9 @@ export function activate(context: vscode.ExtensionContext) { onboardingWizard.registerCommand(context); onboardingWizard.onActivate(context); + + configureLintingToolsCommand.registerCommand(context); + configureLintingToolsCommand.onActivate(context); } // This method is called when your extension is deactivated From 58c42fe89e0747098a24535045d0ddcf14e454cc Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 21 Feb 2024 11:51:00 -0800 Subject: [PATCH 02/18] MVP. --- package.json | 8 +- .../lint/configureLintingToolsCommand.ts | 110 +++++++++++++++++- src/utils/constants.ts | 2 + src/utils/workspaceUtils.ts | 55 ++++++++- 4 files changed, 168 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 879e925..482d2e1 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,14 @@ { "command": "salesforcedx-vscode-offline-app.onboardingWizard", "title": "%extension.commands.config-wizard.title%", - "category": "%extension.commands.config-wizard.category%" + "category": "%extension.commands.config-wizard.category%", + "when": "sfdx_project_opened && resource =~ /.*\\/lwc\\/[^\\/]+(\\/[^\\/]+\\.(html|css|js))?$/" }, { - "command": "'salesforcedx-vscode-offline-app.configureLintTools'", + "command": "salesforcedx-vscode-offline-app.configureLintingTools", "title": "%extension.commands.config-linting-tools.title%", - "category": "%extension.commands.config-wizard.category%" + "category": "%extension.commands.config-wizard.category%", + "when": "sfdx_project_opened && resource =~ /.*\\/lwc\\/[^\\/]+(\\/[^\\/]+\\.(html|css|js))?$/" } ] }, diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 59c75a9..1779051 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -6,18 +6,122 @@ */ import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { WorkspaceUtils } from '../../utils/workspaceUtils'; -const configureLintToolsCommand = 'salesforcedx-vscode-offline-app.configureLintTools'; +const configureLintingToolsCommand = 'salesforcedx-vscode-offline-app.configureLintingTools'; +const eslintDependencies = [ + ["@salesforce/eslint-plugin-lwc-graph-analyzer", "^0.9.0"], + ["eslint", "^8.47.0"] +]; +const lwcGraphAnalyzerRecommended: string = "plugin:@salesforce/lwc-graph-analyzer/recommended"; +const eslintRecommended = "eslint:recommended"; + +interface PackageJson { + devDependencies?: Record; +} export function onActivate(context: vscode.ExtensionContext) { console.log('Activated'); + + vscode.commands.executeCommand( + 'setContext', + 'sfdx_project_opened', + WorkspaceUtils.isSfdxProjectOpened() + ); +} + +function updateDevDependencies() { + const packageJson: PackageJson = WorkspaceUtils.getPackageJson(); + const devDependencies = packageJson.devDependencies; + let modified = false; + + if (devDependencies) { + eslintDependencies.forEach(nameValuePair => { + const [name, value] = nameValuePair; + if (!devDependencies[name]) { + devDependencies[name] = value; + packageJson.devDependencies = devDependencies; + modified = true; + } + }); + } + + if (modified) { + WorkspaceUtils.setPackageJson(packageJson); + } +} + +function updateEslintrc() { + const eslintrcPath = path.join(WorkspaceUtils.LWC_PATH, '.eslintrc.json'); + + if (fs.existsSync(eslintrcPath)) { + const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); + if (eslintrc && eslintrc.extends) { + let modified = false; + + if (!eslintrc.extends[eslintRecommended]) { + eslintrc.extends.push(eslintRecommended); + modified = true; + } + + if (!eslintrc.extends[lwcGraphAnalyzerRecommended]) { + eslintrc.extends.push(lwcGraphAnalyzerRecommended); + modified = true; + } + + if (modified) { + fs.writeFileSync(eslintrcPath, JSON.stringify(eslintrc, null, 2)); + } + } + } else { + // Create eslintrc + fs.writeFile(eslintrcPath, `{"extends":["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, (err) => { + if (err) { + throw err; + } + }); + } } export function registerCommand(context: vscode.ExtensionContext) { vscode.commands.registerCommand( - configureLintToolsCommand, + configureLintingToolsCommand, async () => { - return Promise.resolve(); + if (!WorkspaceUtils.lwcFolderExists()) { + return Promise.reject("no lwc folder"); + } + + if (!WorkspaceUtils.packageJsonExists()) { + return Promise.reject("no package.json"); + } + + // Ask user to add eslint plugin + const result = await vscode.window.showInformationMessage( + vscode.l10n.t('Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?'), + { title: vscode.l10n.t('Yes') }, + { title: vscode.l10n.t('No') } + ); + + if (!result || result.title === vscode.l10n.t('No')) { + return Promise.resolve(false); + } else { + try { + updateDevDependencies(); + } catch (error) { + return Promise.reject(`Error updating package.json: ${error}`); + } + + try { + updateEslintrc(); + } catch (error) { + return Promise.reject(`Error updating .eslintrc.json: ${error}`); + } + + return Promise.resolve(); + } } ); } + \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 9cdfa83..13faa1a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -7,3 +7,5 @@ export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1'; export const CORE_EXTENSION_ID = 'salesforce.salesforcedx-vscode-core'; +export const SFDX_PROJECT_FILE = 'sfdx-project.json'; +export const PACKAGE_JSON = 'package.json'; \ No newline at end of file diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index 5dcc112..8b3eb5b 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -5,8 +5,10 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { workspace } from 'vscode'; +import { workspace, WorkspaceFolder } from 'vscode'; import { access } from 'fs/promises'; +import { PACKAGE_JSON, SFDX_PROJECT_FILE } from './constants'; +import * as fs from 'fs'; import * as path from 'path'; export class WorkspaceUtils { @@ -67,6 +69,57 @@ export class WorkspaceUtils { return resolve(staticResourcesPath); }); } + + static hasRootWorkspace(ws: typeof workspace = workspace): boolean { + return (ws?.workspaceFolders?.length ?? 0) > 0; + } + + static getRootWorkspace(): WorkspaceFolder { + return this.hasRootWorkspace() + ? workspace.workspaceFolders![0] + : ({} as WorkspaceFolder); + } + + static getRootWorkspacePath(): string { + return this.getRootWorkspace().uri + ? this.getRootWorkspace().uri.fsPath + : ''; + } + + static getPackageJson(): object { + return JSON.parse(fs.readFileSync(path.join( + WorkspaceUtils.getRootWorkspacePath(), + 'package.json' + ), 'utf8')); + } + + static setPackageJson(packageJson: object) { + fs.writeFileSync(path.join( + WorkspaceUtils.getRootWorkspacePath(), + 'package.json'), JSON.stringify(packageJson, null, 2) + ); + } + + static packageJsonExists(): boolean { + return fs.existsSync( + path.join(WorkspaceUtils.getRootWorkspacePath(), PACKAGE_JSON) + ); + } + + static lwcFolderExists(): boolean { + return fs.existsSync( + WorkspaceUtils.LWC_PATH + ); + } + + static isSfdxProjectOpened(): boolean { + return ( + WorkspaceUtils.hasRootWorkspace() && + fs.existsSync( + path.join(WorkspaceUtils.getRootWorkspacePath(), SFDX_PROJECT_FILE) + ) + ); + } } export class NoWorkspaceError extends Error { From a41a830ba299c13e0bf8a82d628ea497e97b04b5 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 21 Feb 2024 13:15:40 -0800 Subject: [PATCH 03/18] Prettier. --- .../lint/configureLintingToolsCommand.ts | 103 ++++++++++-------- src/utils/constants.ts | 2 +- src/utils/workspaceUtils.ts | 30 +++-- 3 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 1779051..e21927e 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -10,13 +10,15 @@ import * as fs from 'fs'; import * as path from 'path'; import { WorkspaceUtils } from '../../utils/workspaceUtils'; -const configureLintingToolsCommand = 'salesforcedx-vscode-offline-app.configureLintingTools'; +const configureLintingToolsCommand = + 'salesforcedx-vscode-offline-app.configureLintingTools'; const eslintDependencies = [ - ["@salesforce/eslint-plugin-lwc-graph-analyzer", "^0.9.0"], - ["eslint", "^8.47.0"] + ['@salesforce/eslint-plugin-lwc-graph-analyzer', '^0.9.0'], + ['eslint', '^8.47.0'] ]; -const lwcGraphAnalyzerRecommended: string = "plugin:@salesforce/lwc-graph-analyzer/recommended"; -const eslintRecommended = "eslint:recommended"; +const lwcGraphAnalyzerRecommended: string = + 'plugin:@salesforce/lwc-graph-analyzer/recommended'; +const eslintRecommended = 'eslint:recommended'; interface PackageJson { devDependencies?: Record; @@ -38,16 +40,16 @@ function updateDevDependencies() { let modified = false; if (devDependencies) { - eslintDependencies.forEach(nameValuePair => { + eslintDependencies.forEach((nameValuePair) => { const [name, value] = nameValuePair; if (!devDependencies[name]) { - devDependencies[name] = value; + devDependencies[name] = value; packageJson.devDependencies = devDependencies; modified = true; } }); } - + if (modified) { WorkspaceUtils.setPackageJson(packageJson); } @@ -55,7 +57,7 @@ function updateDevDependencies() { function updateEslintrc() { const eslintrcPath = path.join(WorkspaceUtils.LWC_PATH, '.eslintrc.json'); - + if (fs.existsSync(eslintrcPath)) { const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); if (eslintrc && eslintrc.extends) { @@ -72,56 +74,63 @@ function updateEslintrc() { } if (modified) { - fs.writeFileSync(eslintrcPath, JSON.stringify(eslintrc, null, 2)); + fs.writeFileSync( + eslintrcPath, + JSON.stringify(eslintrc, null, 2) + ); } } } else { // Create eslintrc - fs.writeFile(eslintrcPath, `{"extends":["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, (err) => { - if (err) { - throw err; + fs.writeFile( + eslintrcPath, + `{"extends":["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, + (err) => { + if (err) { + throw err; + } } - }); + ); } } export function registerCommand(context: vscode.ExtensionContext) { - vscode.commands.registerCommand( - configureLintingToolsCommand, - async () => { - if (!WorkspaceUtils.lwcFolderExists()) { - return Promise.reject("no lwc folder"); - } + vscode.commands.registerCommand(configureLintingToolsCommand, async () => { + if (!WorkspaceUtils.lwcFolderExists()) { + return Promise.reject('no lwc folder'); + } + + if (!WorkspaceUtils.packageJsonExists()) { + return Promise.reject('no package.json'); + } + + // Ask user to add eslint plugin + const result = await vscode.window.showInformationMessage( + vscode.l10n.t( + 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?' + ), + { title: vscode.l10n.t('Yes') }, + { title: vscode.l10n.t('No') } + ); - if (!WorkspaceUtils.packageJsonExists()) { - return Promise.reject("no package.json"); + if (!result || result.title === vscode.l10n.t('No')) { + return Promise.resolve(false); + } else { + try { + updateDevDependencies(); + } catch (error) { + return Promise.reject(`Error updating package.json: ${error}`); } - // Ask user to add eslint plugin - const result = await vscode.window.showInformationMessage( - vscode.l10n.t('Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?'), - { title: vscode.l10n.t('Yes') }, - { title: vscode.l10n.t('No') } - ); - - if (!result || result.title === vscode.l10n.t('No')) { - return Promise.resolve(false); - } else { - try { - updateDevDependencies(); - } catch (error) { - return Promise.reject(`Error updating package.json: ${error}`); - } - - try { - updateEslintrc(); - } catch (error) { - return Promise.reject(`Error updating .eslintrc.json: ${error}`); - } - - return Promise.resolve(); + try { + updateEslintrc(); + } catch (error) { + return Promise.reject( + `Error updating .eslintrc.json: ${error}` + ); } + + return Promise.resolve(); } - ); + }); } - \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 13faa1a..e6605c4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -8,4 +8,4 @@ export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1'; export const CORE_EXTENSION_ID = 'salesforce.salesforcedx-vscode-core'; export const SFDX_PROJECT_FILE = 'sfdx-project.json'; -export const PACKAGE_JSON = 'package.json'; \ No newline at end of file +export const PACKAGE_JSON = 'package.json'; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index 8b3eb5b..731c4c5 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -87,16 +87,21 @@ export class WorkspaceUtils { } static getPackageJson(): object { - return JSON.parse(fs.readFileSync(path.join( - WorkspaceUtils.getRootWorkspacePath(), - 'package.json' - ), 'utf8')); + return JSON.parse( + fs.readFileSync( + path.join( + WorkspaceUtils.getRootWorkspacePath(), + 'package.json' + ), + 'utf8' + ) + ); } static setPackageJson(packageJson: object) { - fs.writeFileSync(path.join( - WorkspaceUtils.getRootWorkspacePath(), - 'package.json'), JSON.stringify(packageJson, null, 2) + fs.writeFileSync( + path.join(WorkspaceUtils.getRootWorkspacePath(), 'package.json'), + JSON.stringify(packageJson, null, 2) ); } @@ -105,18 +110,19 @@ export class WorkspaceUtils { path.join(WorkspaceUtils.getRootWorkspacePath(), PACKAGE_JSON) ); } - + static lwcFolderExists(): boolean { - return fs.existsSync( - WorkspaceUtils.LWC_PATH - ); + return fs.existsSync(WorkspaceUtils.LWC_PATH); } static isSfdxProjectOpened(): boolean { return ( WorkspaceUtils.hasRootWorkspace() && fs.existsSync( - path.join(WorkspaceUtils.getRootWorkspacePath(), SFDX_PROJECT_FILE) + path.join( + WorkspaceUtils.getRootWorkspacePath(), + SFDX_PROJECT_FILE + ) ) ); } From 370189ce73b0a45be899f84536b3d716f75f788c Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Mon, 26 Feb 2024 17:24:07 -0800 Subject: [PATCH 04/18] Message to run npm install. --- .../lint/configureLintingToolsCommand.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index e21927e..f9e64a8 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -19,14 +19,13 @@ const eslintDependencies = [ const lwcGraphAnalyzerRecommended: string = 'plugin:@salesforce/lwc-graph-analyzer/recommended'; const eslintRecommended = 'eslint:recommended'; +const tabSpaces = 2; interface PackageJson { devDependencies?: Record; } export function onActivate(context: vscode.ExtensionContext) { - console.log('Activated'); - vscode.commands.executeCommand( 'setContext', 'sfdx_project_opened', @@ -44,13 +43,13 @@ function updateDevDependencies() { const [name, value] = nameValuePair; if (!devDependencies[name]) { devDependencies[name] = value; - packageJson.devDependencies = devDependencies; modified = true; } }); } if (modified) { + // Save json only if the content was modified. WorkspaceUtils.setPackageJson(packageJson); } } @@ -60,23 +59,26 @@ function updateEslintrc() { if (fs.existsSync(eslintrcPath)) { const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); - if (eslintrc && eslintrc.extends) { + const eslintrcExtends = eslintrc.extends as Array; + + if (eslintrc && eslintrcExtends) { let modified = false; - if (!eslintrc.extends[eslintRecommended]) { - eslintrc.extends.push(eslintRecommended); + if (!eslintrcExtends.includes(eslintRecommended)) { + eslintrcExtends.push(eslintRecommended); modified = true; } - if (!eslintrc.extends[lwcGraphAnalyzerRecommended]) { + if (!eslintrcExtends.includes(lwcGraphAnalyzerRecommended)) { eslintrc.extends.push(lwcGraphAnalyzerRecommended); modified = true; } if (modified) { + // Save json only if the content was modified. fs.writeFileSync( eslintrcPath, - JSON.stringify(eslintrc, null, 2) + JSON.stringify(eslintrc, null, tabSpaces) ); } } @@ -84,7 +86,7 @@ function updateEslintrc() { // Create eslintrc fs.writeFile( eslintrcPath, - `{"extends":["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, + `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, (err) => { if (err) { throw err; @@ -97,11 +99,11 @@ function updateEslintrc() { export function registerCommand(context: vscode.ExtensionContext) { vscode.commands.registerCommand(configureLintingToolsCommand, async () => { if (!WorkspaceUtils.lwcFolderExists()) { - return Promise.reject('no lwc folder'); + return Promise.reject('LWC folder does not exist.'); } if (!WorkspaceUtils.packageJsonExists()) { - return Promise.reject('no package.json'); + return Promise.reject('The project does not contain package.json.'); } // Ask user to add eslint plugin @@ -130,6 +132,12 @@ export function registerCommand(context: vscode.ExtensionContext) { ); } + await vscode.window.showInformationMessage( + vscode.l10n.t( + `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.` + ), + { title: vscode.l10n.t('OK') } + ); return Promise.resolve(); } }); From b3c55a4a973ee15127c52750a5404bcc0a1f79d9 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 27 Feb 2024 21:40:24 -0800 Subject: [PATCH 05/18] Unit tests. Refactor. --- .../lint/configureLintingToolsCommand.ts | 116 +++++++++++------- src/test/suite/utils/workspaceUtils.test.ts | 44 +++++++ src/utils/constants.ts | 1 + src/utils/workspaceUtils.ts | 45 ++----- 4 files changed, 124 insertions(+), 82 deletions(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index f9e64a8..3934ce8 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -5,10 +5,11 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import * as vscode from 'vscode'; +import { commands, l10n, window, ExtensionContext } from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { WorkspaceUtils } from '../../utils/workspaceUtils'; +import { TAB_SPACES } from '../../utils/constants'; const configureLintingToolsCommand = 'salesforcedx-vscode-offline-app.configureLintingTools'; @@ -19,14 +20,13 @@ const eslintDependencies = [ const lwcGraphAnalyzerRecommended: string = 'plugin:@salesforce/lwc-graph-analyzer/recommended'; const eslintRecommended = 'eslint:recommended'; -const tabSpaces = 2; interface PackageJson { devDependencies?: Record; } -export function onActivate(context: vscode.ExtensionContext) { - vscode.commands.executeCommand( +export function onActivate(context: ExtensionContext) { + commands.executeCommand( 'setContext', 'sfdx_project_opened', WorkspaceUtils.isSfdxProjectOpened() @@ -78,67 +78,89 @@ function updateEslintrc() { // Save json only if the content was modified. fs.writeFileSync( eslintrcPath, - JSON.stringify(eslintrc, null, tabSpaces) + JSON.stringify(eslintrc, null, TAB_SPACES) ); } } } else { // Create eslintrc - fs.writeFile( + fs.writeFileSync( eslintrcPath, - `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}`, - (err) => { - if (err) { - throw err; - } - } + `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}` ); } } -export function registerCommand(context: vscode.ExtensionContext) { - vscode.commands.registerCommand(configureLintingToolsCommand, async () => { - if (!WorkspaceUtils.lwcFolderExists()) { - return Promise.reject('LWC folder does not exist.'); - } - - if (!WorkspaceUtils.packageJsonExists()) { - return Promise.reject('The project does not contain package.json.'); - } - - // Ask user to add eslint plugin - const result = await vscode.window.showInformationMessage( - vscode.l10n.t( - 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?' - ), - { title: vscode.l10n.t('Yes') }, - { title: vscode.l10n.t('No') } - ); +async function showSimpleErrorMessage(message: string) { + await window.showErrorMessage(l10n.t(message), { + title: l10n.t('OK') + }); +} - if (!result || result.title === vscode.l10n.t('No')) { - return Promise.resolve(false); - } else { - try { - updateDevDependencies(); - } catch (error) { - return Promise.reject(`Error updating package.json: ${error}`); +export class ConfigureLintingToolsCommand { + static async configure(): Promise { + try { + if (!WorkspaceUtils.lwcFolderExists()) { + await showSimpleErrorMessage('LWC folder does not exist.'); + return Promise.resolve(false); } - try { - updateEslintrc(); - } catch (error) { - return Promise.reject( - `Error updating .eslintrc.json: ${error}` + if (!WorkspaceUtils.packageJsonExists()) { + await showSimpleErrorMessage( + 'The project does not contain package.json.' ); + return Promise.resolve(false); } - await vscode.window.showInformationMessage( - vscode.l10n.t( - `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.` + // Ask user to add eslint plugin + const result = await window.showInformationMessage( + l10n.t( + 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?' ), - { title: vscode.l10n.t('OK') } + { title: l10n.t('Yes') }, + { title: l10n.t('No') } + ); + + if (!result || result.title === l10n.t('No')) { + return Promise.resolve(false); + } else { + try { + updateDevDependencies(); + } catch (error) { + await showSimpleErrorMessage( + `Error updating package.json: ${error}` + ); + return Promise.resolve(false); + } + + try { + updateEslintrc(); + } catch (error) { + await showSimpleErrorMessage( + `Error updating .eslintrc.json: ${error}` + ); + return Promise.resolve(false); + } + + await window.showInformationMessage( + l10n.t( + `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.` + ), + { title: l10n.t('OK') } + ); + return Promise.resolve(true); + } + } catch (error) { + await showSimpleErrorMessage( + `There was an error trying to update developer dependency in package.json: ${error}` ); - return Promise.resolve(); + return Promise.resolve(false); } + } +} + +export function registerCommand(context: ExtensionContext) { + commands.registerCommand(configureLintingToolsCommand, async () => { + await ConfigureLintingToolsCommand.configure(); }); } diff --git a/src/test/suite/utils/workspaceUtils.test.ts b/src/test/suite/utils/workspaceUtils.test.ts index a715660..fc82525 100644 --- a/src/test/suite/utils/workspaceUtils.test.ts +++ b/src/test/suite/utils/workspaceUtils.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as fs from 'fs'; import { mkdir } from 'fs/promises'; import { NoStaticResourcesDirError, @@ -19,6 +20,7 @@ import { } from '../../TestHelper'; import { afterEach, beforeEach } from 'mocha'; import * as sinon from 'sinon'; +import { PACKAGE_JSON, SFDX_PROJECT_FILE } from '../../../utils/constants'; suite('Workspace Test Suite', () => { let getWorkspaceDirStub: sinon.SinonStub<[], string>; @@ -79,4 +81,46 @@ suite('Workspace Test Suite', () => { const outputDir = await WorkspaceUtils.getStaticResourcesDir(); assert.equal(outputDir, staticResourcesAbsPath); }); + + test('Existence of package.json can be determined', () => { + let exists = WorkspaceUtils.packageJsonExists(); + assert.equal(exists, false); + + const packageJson = { a: 'b' }; + WorkspaceUtils.setPackageJson(packageJson); + + exists = WorkspaceUtils.packageJsonExists(); + assert.equal(exists, true); + + const content = WorkspaceUtils.getPackageJson(); + assert.equal(JSON.stringify(content), JSON.stringify(packageJson)); + }); + + test('Existence of LWC folder can be determined', async () => { + let exists = WorkspaceUtils.lwcFolderExists(); + assert.equal(exists, false); + + const lwcPath = path.join( + tempProjectDirManager.projectDir, + WorkspaceUtils.LWC_PATH + ); + await mkdir(lwcPath, { recursive: true }); + + exists = WorkspaceUtils.lwcFolderExists(); + assert.equal(exists, true); + }); + + test('Sfdx project is opened', () => { + let opened = WorkspaceUtils.isSfdxProjectOpened(); + assert.equal(opened, false); + + const sfdxJson = path.join( + tempProjectDirManager.projectDir, + SFDX_PROJECT_FILE + ); + fs.writeFileSync(sfdxJson, ''); + + opened = WorkspaceUtils.isSfdxProjectOpened(); + assert.equal(opened, true); + }); }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e6605c4..117927e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -9,3 +9,4 @@ export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1'; export const CORE_EXTENSION_ID = 'salesforce.salesforcedx-vscode-core'; export const SFDX_PROJECT_FILE = 'sfdx-project.json'; export const PACKAGE_JSON = 'package.json'; +export const TAB_SPACES = 2; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index 731c4c5..3958a6b 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -7,7 +7,7 @@ import { workspace, WorkspaceFolder } from 'vscode'; import { access } from 'fs/promises'; -import { PACKAGE_JSON, SFDX_PROJECT_FILE } from './constants'; +import { PACKAGE_JSON, SFDX_PROJECT_FILE, TAB_SPACES } from './constants'; import * as fs from 'fs'; import * as path from 'path'; @@ -70,29 +70,10 @@ export class WorkspaceUtils { }); } - static hasRootWorkspace(ws: typeof workspace = workspace): boolean { - return (ws?.workspaceFolders?.length ?? 0) > 0; - } - - static getRootWorkspace(): WorkspaceFolder { - return this.hasRootWorkspace() - ? workspace.workspaceFolders![0] - : ({} as WorkspaceFolder); - } - - static getRootWorkspacePath(): string { - return this.getRootWorkspace().uri - ? this.getRootWorkspace().uri.fsPath - : ''; - } - static getPackageJson(): object { return JSON.parse( fs.readFileSync( - path.join( - WorkspaceUtils.getRootWorkspacePath(), - 'package.json' - ), + path.join(this.getWorkspaceDir(), PACKAGE_JSON), 'utf8' ) ); @@ -100,30 +81,24 @@ export class WorkspaceUtils { static setPackageJson(packageJson: object) { fs.writeFileSync( - path.join(WorkspaceUtils.getRootWorkspacePath(), 'package.json'), - JSON.stringify(packageJson, null, 2) + path.join(this.getWorkspaceDir(), PACKAGE_JSON), + JSON.stringify(packageJson, null, TAB_SPACES) ); } static packageJsonExists(): boolean { - return fs.existsSync( - path.join(WorkspaceUtils.getRootWorkspacePath(), PACKAGE_JSON) - ); + return fs.existsSync(path.join(this.getWorkspaceDir(), PACKAGE_JSON)); } static lwcFolderExists(): boolean { - return fs.existsSync(WorkspaceUtils.LWC_PATH); + return fs.existsSync( + path.join(this.getWorkspaceDir(), WorkspaceUtils.LWC_PATH) + ); } static isSfdxProjectOpened(): boolean { - return ( - WorkspaceUtils.hasRootWorkspace() && - fs.existsSync( - path.join( - WorkspaceUtils.getRootWorkspacePath(), - SFDX_PROJECT_FILE - ) - ) + return fs.existsSync( + path.join(this.getWorkspaceDir(), SFDX_PROJECT_FILE) ); } } From 3542df18d1c1edcfc81d86d325c73a0e8be2e61d Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 28 Feb 2024 11:34:39 -0800 Subject: [PATCH 06/18] Refactor. --- .../lint/configureLintingToolsCommand.ts | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 3934ce8..451b6ce 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -25,6 +25,12 @@ interface PackageJson { devDependencies?: Record; } +enum MessageType { + Error, + InformationYesNo, + InformationOk +} + export function onActivate(context: ExtensionContext) { commands.executeCommand( 'setContext', @@ -33,7 +39,7 @@ export function onActivate(context: ExtensionContext) { ); } -function updateDevDependencies() { +function updateDevDependencies(): boolean { const packageJson: PackageJson = WorkspaceUtils.getPackageJson(); const devDependencies = packageJson.devDependencies; let modified = false; @@ -52,6 +58,8 @@ function updateDevDependencies() { // Save json only if the content was modified. WorkspaceUtils.setPackageJson(packageJson); } + + return modified; } function updateEslintrc() { @@ -91,67 +99,85 @@ function updateEslintrc() { } } -async function showSimpleErrorMessage(message: string) { - await window.showErrorMessage(l10n.t(message), { - title: l10n.t('OK') - }); +async function showMessage( + message: string, + messageType: MessageType = MessageType.Error +): Promise<{ title: string } | undefined> { + const localizedMessage = l10n.t(message); + switch (messageType) { + case MessageType.Error: + return await window.showErrorMessage(localizedMessage, { + title: l10n.t('OK') + }); + case MessageType.InformationYesNo: + return await window.showInformationMessage( + localizedMessage, + { title: l10n.t('Yes') }, + { title: l10n.t('No') } + ); + case MessageType.InformationOk: + return await await window.showInformationMessage(localizedMessage, { + title: l10n.t('OK') + }); + } } export class ConfigureLintingToolsCommand { static async configure(): Promise { try { if (!WorkspaceUtils.lwcFolderExists()) { - await showSimpleErrorMessage('LWC folder does not exist.'); + await showMessage('LWC folder does not exist.'); return Promise.resolve(false); } if (!WorkspaceUtils.packageJsonExists()) { - await showSimpleErrorMessage( - 'The project does not contain package.json.' - ); + await showMessage('The project does not contain package.json.'); return Promise.resolve(false); } // Ask user to add eslint plugin - const result = await window.showInformationMessage( - l10n.t( - 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?' - ), - { title: l10n.t('Yes') }, - { title: l10n.t('No') } + const result = await showMessage( + 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?', + MessageType.InformationYesNo ); if (!result || result.title === l10n.t('No')) { return Promise.resolve(false); } else { + let modified = false; + try { - updateDevDependencies(); + modified = updateDevDependencies(); } catch (error) { - await showSimpleErrorMessage( - `Error updating package.json: ${error}` - ); + await showMessage(`Error updating package.json: ${error}`); return Promise.resolve(false); } try { updateEslintrc(); } catch (error) { - await showSimpleErrorMessage( + await showMessage( `Error updating .eslintrc.json: ${error}` ); return Promise.resolve(false); } - await window.showInformationMessage( - l10n.t( - `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.` - ), - { title: l10n.t('OK') } - ); + if (modified) { + await showMessage( + `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.`, + MessageType.InformationOk + ); + } else { + await showMessage( + `No update was made in package.json. It already includes eslint plugin for LWC data graph analysis.`, + MessageType.InformationOk + ); + } + return Promise.resolve(true); } } catch (error) { - await showSimpleErrorMessage( + await showMessage( `There was an error trying to update developer dependency in package.json: ${error}` ); return Promise.resolve(false); From d05711569866d29bc2ab7ee3f6fa4169c4114ecb Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 28 Feb 2024 15:48:36 -0800 Subject: [PATCH 07/18] More tests. More refactor. --- package.json | 5 +- .../lint/configureLintingToolsCommand.ts | 198 +++++++++--------- .../lint/configureLintingToolsCommand.test.ts | 157 ++++++++++++++ src/test/suite/utils/workspaceUtils.test.ts | 2 +- src/utils/workspaceUtils.ts | 2 +- 5 files changed, 265 insertions(+), 99 deletions(-) create mode 100644 src/test/suite/commands/lint/configureLintingToolsCommand.test.ts diff --git a/package.json b/package.json index 482d2e1..f3206c2 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,13 @@ { "command": "salesforcedx-vscode-offline-app.onboardingWizard", "title": "%extension.commands.config-wizard.title%", - "category": "%extension.commands.config-wizard.category%", - "when": "sfdx_project_opened && resource =~ /.*\\/lwc\\/[^\\/]+(\\/[^\\/]+\\.(html|css|js))?$/" + "category": "%extension.commands.config-wizard.category%" }, { "command": "salesforcedx-vscode-offline-app.configureLintingTools", "title": "%extension.commands.config-linting-tools.title%", "category": "%extension.commands.config-wizard.category%", - "when": "sfdx_project_opened && resource =~ /.*\\/lwc\\/[^\\/]+(\\/[^\\/]+\\.(html|css|js))?$/" + "when": "sfdx_project_opened" } ] }, diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 451b6ce..f306e61 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -39,104 +39,23 @@ export function onActivate(context: ExtensionContext) { ); } -function updateDevDependencies(): boolean { - const packageJson: PackageJson = WorkspaceUtils.getPackageJson(); - const devDependencies = packageJson.devDependencies; - let modified = false; - - if (devDependencies) { - eslintDependencies.forEach((nameValuePair) => { - const [name, value] = nameValuePair; - if (!devDependencies[name]) { - devDependencies[name] = value; - modified = true; - } - }); - } - - if (modified) { - // Save json only if the content was modified. - WorkspaceUtils.setPackageJson(packageJson); - } - - return modified; -} - -function updateEslintrc() { - const eslintrcPath = path.join(WorkspaceUtils.LWC_PATH, '.eslintrc.json'); - - if (fs.existsSync(eslintrcPath)) { - const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); - const eslintrcExtends = eslintrc.extends as Array; - - if (eslintrc && eslintrcExtends) { - let modified = false; - - if (!eslintrcExtends.includes(eslintRecommended)) { - eslintrcExtends.push(eslintRecommended); - modified = true; - } - - if (!eslintrcExtends.includes(lwcGraphAnalyzerRecommended)) { - eslintrc.extends.push(lwcGraphAnalyzerRecommended); - modified = true; - } - - if (modified) { - // Save json only if the content was modified. - fs.writeFileSync( - eslintrcPath, - JSON.stringify(eslintrc, null, TAB_SPACES) - ); - } - } - } else { - // Create eslintrc - fs.writeFileSync( - eslintrcPath, - `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}` - ); - } -} - -async function showMessage( - message: string, - messageType: MessageType = MessageType.Error -): Promise<{ title: string } | undefined> { - const localizedMessage = l10n.t(message); - switch (messageType) { - case MessageType.Error: - return await window.showErrorMessage(localizedMessage, { - title: l10n.t('OK') - }); - case MessageType.InformationYesNo: - return await window.showInformationMessage( - localizedMessage, - { title: l10n.t('Yes') }, - { title: l10n.t('No') } - ); - case MessageType.InformationOk: - return await await window.showInformationMessage(localizedMessage, { - title: l10n.t('OK') - }); - } -} - export class ConfigureLintingToolsCommand { static async configure(): Promise { try { if (!WorkspaceUtils.lwcFolderExists()) { - await showMessage('LWC folder does not exist.'); + await this.showMessage('LWC folder does not exist.'); return Promise.resolve(false); } if (!WorkspaceUtils.packageJsonExists()) { - await showMessage('The project does not contain package.json.'); + await this.showMessage( + 'The project does not contain package.json.' + ); return Promise.resolve(false); } // Ask user to add eslint plugin - const result = await showMessage( + const result = await this.showMessage( 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?', MessageType.InformationYesNo ); @@ -145,30 +64,31 @@ export class ConfigureLintingToolsCommand { return Promise.resolve(false); } else { let modified = false; - try { - modified = updateDevDependencies(); + modified = this.updateDevDependencies(); } catch (error) { - await showMessage(`Error updating package.json: ${error}`); + await this.showMessage( + `Error updating package.json: ${error}` + ); return Promise.resolve(false); } try { - updateEslintrc(); + this.updateEslintrc(); } catch (error) { - await showMessage( + await this.showMessage( `Error updating .eslintrc.json: ${error}` ); return Promise.resolve(false); } if (modified) { - await showMessage( + await this.showMessage( `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.`, MessageType.InformationOk ); } else { - await showMessage( + await this.showMessage( `No update was made in package.json. It already includes eslint plugin for LWC data graph analysis.`, MessageType.InformationOk ); @@ -177,12 +97,102 @@ export class ConfigureLintingToolsCommand { return Promise.resolve(true); } } catch (error) { - await showMessage( + await this.showMessage( `There was an error trying to update developer dependency in package.json: ${error}` ); return Promise.resolve(false); } } + + static updateDevDependencies(): boolean { + const packageJson: PackageJson = WorkspaceUtils.getPackageJson(); + const devDependencies = packageJson.devDependencies; + let modified = false; + + if (devDependencies) { + eslintDependencies.forEach((nameValuePair) => { + const [name, value] = nameValuePair; + if (!devDependencies[name]) { + devDependencies[name] = value; + modified = true; + } + }); + } + + if (modified) { + // Save json only if the content was modified. + WorkspaceUtils.setPackageJson(packageJson); + } + + return modified; + } + + static updateEslintrc() { + const eslintrcPath = path.join( + WorkspaceUtils.getWorkspaceDir(), + WorkspaceUtils.LWC_PATH, + '.eslintrc.json' + ); + + if (fs.existsSync(eslintrcPath)) { + const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); + const eslintrcExtends = eslintrc.extends as Array; + + if (eslintrc && eslintrcExtends) { + let modified = false; + + if (!eslintrcExtends.includes(eslintRecommended)) { + eslintrcExtends.push(eslintRecommended); + modified = true; + } + + if (!eslintrcExtends.includes(lwcGraphAnalyzerRecommended)) { + eslintrc.extends.push(lwcGraphAnalyzerRecommended); + modified = true; + } + + if (modified) { + // Save json only if the content was modified. + fs.writeFileSync( + eslintrcPath, + JSON.stringify(eslintrc, null, TAB_SPACES) + ); + } + } + } else { + // Create eslintrc + fs.writeFileSync( + eslintrcPath, + `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}` + ); + } + } + + static async showMessage( + message: string, + messageType: MessageType = MessageType.Error + ): Promise<{ title: string } | undefined> { + const localizedMessage = l10n.t(message); + switch (messageType) { + case MessageType.Error: + return await window.showErrorMessage(localizedMessage, { + title: l10n.t('OK') + }); + case MessageType.InformationYesNo: + return await window.showInformationMessage( + localizedMessage, + { title: l10n.t('Yes') }, + { title: l10n.t('No') } + ); + case MessageType.InformationOk: + return await await window.showInformationMessage( + localizedMessage, + { + title: l10n.t('OK') + } + ); + } + } } export function registerCommand(context: ExtensionContext) { diff --git a/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts b/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts new file mode 100644 index 0000000..5684376 --- /dev/null +++ b/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as path from 'path'; +import * as fs from 'fs'; +import { window } from 'vscode'; +import { afterEach } from 'mocha'; +import { ConfigureLintingToolsCommand } from '../../../../commands/lint/configureLintingToolsCommand'; +import { WorkspaceUtils } from '../../../../utils/workspaceUtils'; +import { + TempProjectDirManager, + setupTempWorkspaceDirectoryStub +} from '../../../TestHelper'; + +suite('Configure Linting Tools Command Test Suite', () => { + afterEach(function () { + sinon.restore(); + }); + + test('Configure linting cancelled because LWC folder does not exist', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(false); + const showErrorMessageStub = sinon.stub(window, 'showErrorMessage'); + showErrorMessageStub.onCall(0).resolves({ title: 'OK' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, false); + }); + + test('Configure linting cancelled because package.json does not exist', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + sinon.stub(WorkspaceUtils, 'packageJsonExists').returns(false); + const showErrorMessageStub = sinon.stub(window, 'showErrorMessage'); + showErrorMessageStub.onCall(0).resolves({ title: 'OK' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, false); + }); + + test('Configure linting cancelled by the user', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + sinon.stub(WorkspaceUtils, 'packageJsonExists').returns(true); + const showInformationMessageStub = sinon.stub( + window, + 'showInformationMessage' + ); + showInformationMessageStub.onCall(0).resolves({ title: 'No' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, false); + }); + + test('Configure linting cancelled because updating pacakge.json failed', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + sinon.stub(WorkspaceUtils, 'packageJsonExists').returns(true); + const showInformationMessageStub = sinon.stub( + window, + 'showInformationMessage' + ); + showInformationMessageStub.onCall(0).resolves({ title: 'Yes' }); + sinon + .stub(ConfigureLintingToolsCommand, 'updateDevDependencies') + .throws('error'); + const showErrorMessageStub = sinon.stub(window, 'showErrorMessage'); + showErrorMessageStub.onCall(0).resolves({ title: 'OK' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, false); + }); + + test('Configure linting cancelled because updating .eslintrc.json failed', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + sinon.stub(WorkspaceUtils, 'packageJsonExists').returns(true); + const showInformationMessageStub = sinon.stub( + window, + 'showInformationMessage' + ); + showInformationMessageStub.onCall(0).resolves({ title: 'Yes' }); + sinon + .stub(ConfigureLintingToolsCommand, 'updateDevDependencies') + .returns(true); + sinon + .stub(ConfigureLintingToolsCommand, 'updateEslintrc') + .throws('error'); + const showErrorMessageStub = sinon.stub(window, 'showErrorMessage'); + showErrorMessageStub.onCall(0).resolves({ title: 'OK' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, false); + }); + + test('Configure linting did not update package.json because plugin was already included in the dev dependency', async () => { + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + sinon.stub(WorkspaceUtils, 'packageJsonExists').returns(true); + let showInformationMessageStub = sinon.stub( + window, + 'showInformationMessage' + ); + showInformationMessageStub.onCall(0).resolves({ title: 'Yes' }); + sinon + .stub(ConfigureLintingToolsCommand, 'updateDevDependencies') + .returns(false); + sinon.stub(ConfigureLintingToolsCommand, 'updateEslintrc').returns(); + showInformationMessageStub = sinon.stub(window, 'showErrorMessage'); + showInformationMessageStub.onCall(0).resolves({ title: 'OK' }); + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, true); + }); + + test('Configure linting updated package.json successfully', async () => { + let getWorkspaceDirStub: sinon.SinonStub<[], string>; + let tempProjectDirManager: TempProjectDirManager; + tempProjectDirManager = + await TempProjectDirManager.createTempProjectDir(); + getWorkspaceDirStub = setupTempWorkspaceDirectoryStub( + tempProjectDirManager + ); + const packageJson = { devDependencies: { lwc: '1.2.3' } }; + WorkspaceUtils.setPackageJson(packageJson); + + sinon.stub(WorkspaceUtils, 'lwcFolderExists').returns(true); + let showInformationMessageStub = sinon.stub( + window, + 'showInformationMessage' + ); + showInformationMessageStub.onCall(0).resolves({ title: 'Yes' }); + showInformationMessageStub.onCall(1).resolves({ title: 'Yes' }); + + try { + // Creating directories recursively + const lwcPath = path.join( + WorkspaceUtils.getWorkspaceDir(), + WorkspaceUtils.LWC_PATH + ); + fs.mkdirSync(lwcPath, { recursive: true }); + } catch (error) { + console.error('Error creating directories:', error); + } + + const result = await ConfigureLintingToolsCommand.configure(); + assert.equal(result, true); + + const content = WorkspaceUtils.getPackageJson(); + const updatedPackageJson = { + devDependencies: { + lwc: '1.2.3', + // eslint-disable-next-line @typescript-eslint/naming-convention + '@salesforce/eslint-plugin-lwc-graph-analyzer': '^0.9.0', + eslint: '^8.47.0' + } + }; + assert.equal( + JSON.stringify(updatedPackageJson), + JSON.stringify(content) + ); + }); +}); diff --git a/src/test/suite/utils/workspaceUtils.test.ts b/src/test/suite/utils/workspaceUtils.test.ts index fc82525..96d8fd9 100644 --- a/src/test/suite/utils/workspaceUtils.test.ts +++ b/src/test/suite/utils/workspaceUtils.test.ts @@ -20,7 +20,7 @@ import { } from '../../TestHelper'; import { afterEach, beforeEach } from 'mocha'; import * as sinon from 'sinon'; -import { PACKAGE_JSON, SFDX_PROJECT_FILE } from '../../../utils/constants'; +import { SFDX_PROJECT_FILE } from '../../../utils/constants'; suite('Workspace Test Suite', () => { let getWorkspaceDirStub: sinon.SinonStub<[], string>; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index 3958a6b..176f0a2 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { workspace, WorkspaceFolder } from 'vscode'; +import { workspace } from 'vscode'; import { access } from 'fs/promises'; import { PACKAGE_JSON, SFDX_PROJECT_FILE, TAB_SPACES } from './constants'; import * as fs from 'fs'; From a0a503d114db95538d7acab259aa539bbcee3cb9 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 29 Feb 2024 13:49:26 -0800 Subject: [PATCH 08/18] Update after code review. --- package.json | 8 ++++++++ src/utils/workspaceUtils.ts | 28 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f3206c2..56b4465 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,14 @@ "main": "./out/extension.js", "l10n": "./l10n", "contributes": { + "menus": { + "commandPalette": [ + { + "command": "salesforcedx-vscode-offline-app.configureLintingTools", + "when": "sfdx_project_opened" + } + ] + }, "commands": [ { "command": "salesforcedx-vscode-offline-app.onboardingWizard", diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index 176f0a2..c2e0114 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -87,19 +87,33 @@ export class WorkspaceUtils { } static packageJsonExists(): boolean { - return fs.existsSync(path.join(this.getWorkspaceDir(), PACKAGE_JSON)); + try { + return fs.existsSync( + path.join(this.getWorkspaceDir(), PACKAGE_JSON) + ); + } catch { + return false; + } } static lwcFolderExists(): boolean { - return fs.existsSync( - path.join(this.getWorkspaceDir(), WorkspaceUtils.LWC_PATH) - ); + try { + return fs.existsSync( + path.join(this.getWorkspaceDir(), WorkspaceUtils.LWC_PATH) + ); + } catch { + return false; + } } static isSfdxProjectOpened(): boolean { - return fs.existsSync( - path.join(this.getWorkspaceDir(), SFDX_PROJECT_FILE) - ); + try { + return fs.existsSync( + path.join(this.getWorkspaceDir(), SFDX_PROJECT_FILE) + ); + } catch { + return false; + } } } From 50ec68e7679b30b261ee78caf3eb810e81cd7244 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 29 Feb 2024 15:07:15 -0800 Subject: [PATCH 09/18] Update after code review. --- package.json | 3 +-- src/commands/lint/configureLintingToolsCommand.ts | 8 -------- src/extension.ts | 8 +++++++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 56b4465..b3d3d8a 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,7 @@ { "command": "salesforcedx-vscode-offline-app.configureLintingTools", "title": "%extension.commands.config-linting-tools.title%", - "category": "%extension.commands.config-wizard.category%", - "when": "sfdx_project_opened" + "category": "%extension.commands.config-wizard.category%" } ] }, diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index f306e61..3fd39af 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -31,14 +31,6 @@ enum MessageType { InformationOk } -export function onActivate(context: ExtensionContext) { - commands.executeCommand( - 'setContext', - 'sfdx_project_opened', - WorkspaceUtils.isSfdxProjectOpened() - ); -} - export class ConfigureLintingToolsCommand { static async configure(): Promise { try { diff --git a/src/extension.ts b/src/extension.ts index a131f76..638cb7f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,6 +11,7 @@ import * as vscode from 'vscode'; import * as onboardingWizard from './commands/wizard/onboardingWizard'; import * as configureLintingToolsCommand from './commands/lint/configureLintingToolsCommand'; import { CoreExtensionService } from './services/CoreExtensionService'; +import { WorkspaceUtils } from './utils/workspaceUtils'; export function activate(context: vscode.ExtensionContext) { // We need to do this first in case any other services need access to those provided by the core extension @@ -27,11 +28,16 @@ export function activate(context: vscode.ExtensionContext) { return; } + vscode.commands.executeCommand( + 'setContext', + 'sfdx_project_opened', + WorkspaceUtils.isSfdxProjectOpened() + ); + onboardingWizard.registerCommand(context); onboardingWizard.onActivate(context); configureLintingToolsCommand.registerCommand(context); - configureLintingToolsCommand.onActivate(context); } // This method is called when your extension is deactivated From 7eb167a21b01170cf5ec7f097b6dc70f70d27928 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 5 Mar 2024 11:13:40 -0800 Subject: [PATCH 10/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 3fd39af..7bf97ac 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -48,7 +48,7 @@ export class ConfigureLintingToolsCommand { // Ask user to add eslint plugin const result = await this.showMessage( - 'Do you want to add eslint plugin for LWC data graph anaylsis to your package.json?', + 'Do you want to add the ESLint plugin for LWC graph analysis to your project? This will give you linting feedback on code patterns that will not support your LWCs working offline, for mobile use cases.', MessageType.InformationYesNo ); From 083f07068c191fb42c8fa0cd6286476229ee1c5e Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 5 Mar 2024 11:13:58 -0800 Subject: [PATCH 11/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 7bf97ac..3f8cd18 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -76,7 +76,7 @@ export class ConfigureLintingToolsCommand { if (modified) { await this.showMessage( - `Updated developer dependency in package.json. Run package manager such as npmr/yarn/pnpm to update node modules.`, + `Updated package.json to include offline linting packages and dependencies. In the Terminal window, be sure to run the install command for your configured package manager, to install the updated dependencies. For example, "npm install" or "yarn install".`, MessageType.InformationOk ); } else { From 80315e46de748fac94d53a0212afee3080b07eec Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 5 Mar 2024 11:14:12 -0800 Subject: [PATCH 12/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 3f8cd18..9809c2e 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -81,7 +81,7 @@ export class ConfigureLintingToolsCommand { ); } else { await this.showMessage( - `No update was made in package.json. It already includes eslint plugin for LWC data graph analysis.`, + `All offline linting packages and dependencies are already configured in your project. No update has been made to package.json.`, MessageType.InformationOk ); } From 1cd256290e9d8a41675c16d1b504ae9426be1798 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 5 Mar 2024 11:14:36 -0800 Subject: [PATCH 13/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 9809c2e..9f5b2eb 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -90,7 +90,7 @@ export class ConfigureLintingToolsCommand { } } catch (error) { await this.showMessage( - `There was an error trying to update developer dependency in package.json: ${error}` + `There was an error trying to update either the offline linting dependencies or linting configuration: ${error}` ); return Promise.resolve(false); } From e3f0d47ea9afa55a45fee004d302255604860607 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 6 Mar 2024 14:32:15 -0800 Subject: [PATCH 14/18] Update after code review. --- package.json | 19 ++- package.nls.json | 3 + .../lint/configureLintingToolsCommand.ts | 116 ++++++++++++------ .../lint/configureLintingToolsCommand.test.ts | 4 +- src/utils/constants.ts | 2 +- src/utils/workspaceUtils.ts | 8 +- 6 files changed, 106 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index b3d3d8a..d158aa2 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,24 @@ { "command": "salesforcedx-vscode-offline-app.configureLintingTools", "title": "%extension.commands.config-linting-tools.title%", - "category": "%extension.commands.config-wizard.category%" + "category": "%extension.commands.salesforce-mobile-offline.category%" + } + ], + "configuration": { + "title": "%salesforce.mobile.extensions%", + "properties": { + "@salesforce/eslint-plugin-lwc-graph-analyzer": { + "type": "string", + "default": "^0.9.0", + "description": "%extension.commands.salesforce-mobile-offline.komaci.version%" + }, + "eslint": { + "type": "string", + "default": "^8.47.0", + "description": "%extension.commands.salesforce-mobile-offline.komaci.version%" + } } - ] + } }, "volta": { "node": "18.17.1" diff --git a/package.nls.json b/package.nls.json index 339add9..0e7b620 100644 --- a/package.nls.json +++ b/package.nls.json @@ -2,6 +2,9 @@ "extension.commands.config-wizard.title": "Configuration Wizard", "extension.commands.config-wizard.category": "Offline Starter Kit", "extension.commands.config-linting-tools.title": "Configure Linting Tools", + "extension.commands.salesforce-mobile-offline.category": "Salesforce Mobile Offline", + "extension.commands.salesforce-mobile-offline.komaci.version": "Version of ESLint Plugin LWC Graph Analyzer to include in devDependencies", + "salesforce.mobile.extensions": "Salesforce Mobile Extensions", "extension.displayName": "Salesforce Mobile Extensions for Visual Studio Code", "extension.description": "Tools to help developers create their Salesforce Mobile experiences in a VSCode development environment." } diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 9f5b2eb..4bd1e5c 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -5,18 +5,29 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { commands, l10n, window, ExtensionContext } from 'vscode'; +import { commands, l10n, window, workspace, ExtensionContext } from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { WorkspaceUtils } from '../../utils/workspaceUtils'; -import { TAB_SPACES } from '../../utils/constants'; +import { JSON_INDENTATION_SPACES } from '../../utils/constants'; +import * as vscode from 'vscode'; + +const config = vscode.workspace.getConfiguration(); +const eslintPluginLwcGraphAnalyzer = + '@salesforce/eslint-plugin-lwc-graph-analyzer'; +const eslintPluginLwcGraphAnalyzerVersion = config.get( + eslintPluginLwcGraphAnalyzer +) as string; +const eslint = 'eslint'; +const eslintVersion = config.get(eslint) as string; const configureLintingToolsCommand = 'salesforcedx-vscode-offline-app.configureLintingTools'; const eslintDependencies = [ - ['@salesforce/eslint-plugin-lwc-graph-analyzer', '^0.9.0'], - ['eslint', '^8.47.0'] + [eslintPluginLwcGraphAnalyzer, eslintPluginLwcGraphAnalyzerVersion], + [eslint, eslintVersion] ]; + const lwcGraphAnalyzerRecommended: string = 'plugin:@salesforce/lwc-graph-analyzer/recommended'; const eslintRecommended = 'eslint:recommended'; @@ -35,13 +46,15 @@ export class ConfigureLintingToolsCommand { static async configure(): Promise { try { if (!WorkspaceUtils.lwcFolderExists()) { - await this.showMessage('LWC folder does not exist.'); + await this.showMessage( + 'LWC folder does not exist. The folder is required to create a configuration file for eslint.' + ); return Promise.resolve(false); } if (!WorkspaceUtils.packageJsonExists()) { await this.showMessage( - 'The project does not contain package.json.' + 'The sfdx project does not contain package.json. It is required to specify dependent eslint packages.' ); return Promise.resolve(false); } @@ -55,9 +68,9 @@ export class ConfigureLintingToolsCommand { if (!result || result.title === l10n.t('No')) { return Promise.resolve(false); } else { - let modified = false; + let modifiedDevDependencies = false; try { - modified = this.updateDevDependencies(); + modifiedDevDependencies = this.updateDevDependencies(); } catch (error) { await this.showMessage( `Error updating package.json: ${error}` @@ -65,8 +78,9 @@ export class ConfigureLintingToolsCommand { return Promise.resolve(false); } + let modifiedEslintrc = false; try { - this.updateEslintrc(); + modifiedEslintrc = this.updateEslintrc(); } catch (error) { await this.showMessage( `Error updating .eslintrc.json: ${error}` @@ -74,13 +88,22 @@ export class ConfigureLintingToolsCommand { return Promise.resolve(false); } - if (modified) { - await this.showMessage( + if (modifiedDevDependencies) { + this.showMessage( `Updated package.json to include offline linting packages and dependencies. In the Terminal window, be sure to run the install command for your configured package manager, to install the updated dependencies. For example, "npm install" or "yarn install".`, MessageType.InformationOk ); - } else { - await this.showMessage( + } + + if (modifiedEslintrc) { + this.showMessage( + `Updated .eslintrc.json to include recommended linting rules.`, + MessageType.InformationOk + ); + } + + if (!modifiedDevDependencies && !modifiedEslintrc) { + this.showMessage( `All offline linting packages and dependencies are already configured in your project. No update has been made to package.json.`, MessageType.InformationOk ); @@ -119,7 +142,7 @@ export class ConfigureLintingToolsCommand { return modified; } - static updateEslintrc() { + static updateEslintrc(): boolean { const eslintrcPath = path.join( WorkspaceUtils.getWorkspaceDir(), WorkspaceUtils.LWC_PATH, @@ -128,35 +151,51 @@ export class ConfigureLintingToolsCommand { if (fs.existsSync(eslintrcPath)) { const eslintrc = JSON.parse(fs.readFileSync(eslintrcPath, 'utf-8')); + + if (!eslintrc.extends) { + eslintrc.extends = []; + } + const eslintrcExtends = eslintrc.extends as Array; - if (eslintrc && eslintrcExtends) { - let modified = false; + let modified = false; - if (!eslintrcExtends.includes(eslintRecommended)) { - eslintrcExtends.push(eslintRecommended); - modified = true; - } + if (!eslintrcExtends.includes(eslintRecommended)) { + eslintrcExtends.push(eslintRecommended); + modified = true; + } - if (!eslintrcExtends.includes(lwcGraphAnalyzerRecommended)) { - eslintrc.extends.push(lwcGraphAnalyzerRecommended); - modified = true; - } + if (!eslintrcExtends.includes(lwcGraphAnalyzerRecommended)) { + eslintrc.extends.push(lwcGraphAnalyzerRecommended); + modified = true; + } - if (modified) { - // Save json only if the content was modified. - fs.writeFileSync( - eslintrcPath, - JSON.stringify(eslintrc, null, TAB_SPACES) - ); - } + if (modified) { + // Save json only if the content was modified. + fs.writeFileSync( + eslintrcPath, + JSON.stringify(eslintrc, null, JSON_INDENTATION_SPACES) + ); } + + return modified; } else { // Create eslintrc - fs.writeFileSync( - eslintrcPath, - `{"extends": ["${eslintRecommended}", "${lwcGraphAnalyzerRecommended}"]}` + const eslintrc = { + extends: [ + `${eslintRecommended}`, + `${lwcGraphAnalyzerRecommended}` + ] + }; + const jsonString = JSON.stringify( + eslintrc, + null, + JSON_INDENTATION_SPACES ); + + fs.writeFileSync(eslintrcPath, jsonString); + + return true; } } @@ -177,12 +216,9 @@ export class ConfigureLintingToolsCommand { { title: l10n.t('No') } ); case MessageType.InformationOk: - return await await window.showInformationMessage( - localizedMessage, - { - title: l10n.t('OK') - } - ); + return await window.showInformationMessage(localizedMessage, { + title: l10n.t('OK') + }); } } } diff --git a/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts b/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts index 5684376..0a3e0f2 100644 --- a/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts +++ b/src/test/suite/commands/lint/configureLintingToolsCommand.test.ts @@ -100,7 +100,9 @@ suite('Configure Linting Tools Command Test Suite', () => { sinon .stub(ConfigureLintingToolsCommand, 'updateDevDependencies') .returns(false); - sinon.stub(ConfigureLintingToolsCommand, 'updateEslintrc').returns(); + sinon + .stub(ConfigureLintingToolsCommand, 'updateEslintrc') + .returns(false); showInformationMessageStub = sinon.stub(window, 'showErrorMessage'); showInformationMessageStub.onCall(0).resolves({ title: 'OK' }); const result = await ConfigureLintingToolsCommand.configure(); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 117927e..e92652c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -9,4 +9,4 @@ export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1'; export const CORE_EXTENSION_ID = 'salesforce.salesforcedx-vscode-core'; export const SFDX_PROJECT_FILE = 'sfdx-project.json'; export const PACKAGE_JSON = 'package.json'; -export const TAB_SPACES = 2; +export const JSON_INDENTATION_SPACES = 2; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index c2e0114..f19c5a3 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -7,7 +7,11 @@ import { workspace } from 'vscode'; import { access } from 'fs/promises'; -import { PACKAGE_JSON, SFDX_PROJECT_FILE, TAB_SPACES } from './constants'; +import { + PACKAGE_JSON, + SFDX_PROJECT_FILE, + JSON_INDENTATION_SPACES +} from './constants'; import * as fs from 'fs'; import * as path from 'path'; @@ -82,7 +86,7 @@ export class WorkspaceUtils { static setPackageJson(packageJson: object) { fs.writeFileSync( path.join(this.getWorkspaceDir(), PACKAGE_JSON), - JSON.stringify(packageJson, null, TAB_SPACES) + JSON.stringify(packageJson, null, JSON_INDENTATION_SPACES) ); } From a286f61d94c98289ba616f47a044143f8c431af5 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 6 Mar 2024 16:00:13 -0800 Subject: [PATCH 15/18] Fix config. --- package.json | 6 +++--- package.nls.json | 1 + .../lint/configureLintingToolsCommand.ts | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d158aa2..f569200 100644 --- a/package.json +++ b/package.json @@ -58,15 +58,15 @@ "configuration": { "title": "%salesforce.mobile.extensions%", "properties": { - "@salesforce/eslint-plugin-lwc-graph-analyzer": { + "mobileOfflineLinting.eslint-plugin-lwc-graph-analyzer": { "type": "string", "default": "^0.9.0", "description": "%extension.commands.salesforce-mobile-offline.komaci.version%" }, - "eslint": { + "mobileOfflineLinting.eslint": { "type": "string", "default": "^8.47.0", - "description": "%extension.commands.salesforce-mobile-offline.komaci.version%" + "description": "%extension.commands.salesforce-mobile-offline.eslint.version%" } } } diff --git a/package.nls.json b/package.nls.json index 0e7b620..65b976a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -4,6 +4,7 @@ "extension.commands.config-linting-tools.title": "Configure Linting Tools", "extension.commands.salesforce-mobile-offline.category": "Salesforce Mobile Offline", "extension.commands.salesforce-mobile-offline.komaci.version": "Version of ESLint Plugin LWC Graph Analyzer to include in devDependencies", + "extension.commands.salesforce-mobile-offline.eslint.version": "Version of ESLint to include in devDependencies", "salesforce.mobile.extensions": "Salesforce Mobile Extensions", "extension.displayName": "Salesforce Mobile Extensions for Visual Studio Code", "extension.description": "Tools to help developers create their Salesforce Mobile experiences in a VSCode development environment." diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 4bd1e5c..3600b18 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -15,11 +15,15 @@ import * as vscode from 'vscode'; const config = vscode.workspace.getConfiguration(); const eslintPluginLwcGraphAnalyzer = '@salesforce/eslint-plugin-lwc-graph-analyzer'; +const eslintPluginLwcGraphAnalyzerConfig = + 'mobileOfflineLinting.eslint-plugin-lwc-graph-analyzer'; const eslintPluginLwcGraphAnalyzerVersion = config.get( - eslintPluginLwcGraphAnalyzer + eslintPluginLwcGraphAnalyzerConfig ) as string; + const eslint = 'eslint'; -const eslintVersion = config.get(eslint) as string; +const eslintConfig = 'mobileOfflineLinting.eslint'; +const eslintVersion = config.get(eslintConfig) as string; const configureLintingToolsCommand = 'salesforcedx-vscode-offline-app.configureLintingTools'; @@ -90,7 +94,7 @@ export class ConfigureLintingToolsCommand { if (modifiedDevDependencies) { this.showMessage( - `Updated package.json to include offline linting packages and dependencies. In the Terminal window, be sure to run the install command for your configured package manager, to install the updated dependencies. For example, "npm install" or "yarn install".`, + `Updated package.json to include offline linting packages and dependencies.`, MessageType.InformationOk ); } @@ -102,6 +106,13 @@ export class ConfigureLintingToolsCommand { ); } + if (modifiedDevDependencies || modifiedEslintrc) { + this.showMessage( + `In the Terminal window, be sure to run the install command for your configured package manager, to install the updated dependencies. For example, "npm install" or "yarn install".`, + MessageType.InformationOk + ); + } + if (!modifiedDevDependencies && !modifiedEslintrc) { this.showMessage( `All offline linting packages and dependencies are already configured in your project. No update has been made to package.json.`, From 13dea035ecc88e4fd433aa09bb9c4209b3dc856f Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 7 Mar 2024 17:01:19 -0800 Subject: [PATCH 16/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 3600b18..ecb86e2 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -51,7 +51,7 @@ export class ConfigureLintingToolsCommand { try { if (!WorkspaceUtils.lwcFolderExists()) { await this.showMessage( - 'LWC folder does not exist. The folder is required to create a configuration file for eslint.' + 'The "force-app/main/default/lwc" folder does not exist in your project. This folder is required to create a configuration file for ESLint.' ); return Promise.resolve(false); } From e5fd8bb6291b3eb543e9f05c2f917ea7d1b16b40 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 7 Mar 2024 17:01:34 -0800 Subject: [PATCH 17/18] Update src/commands/lint/configureLintingToolsCommand.ts Co-authored-by: Kevin Hawkins --- src/commands/lint/configureLintingToolsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index ecb86e2..1f9da0b 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -58,7 +58,7 @@ export class ConfigureLintingToolsCommand { if (!WorkspaceUtils.packageJsonExists()) { await this.showMessage( - 'The sfdx project does not contain package.json. It is required to specify dependent eslint packages.' + 'Your project does not contain a "package.json" specification. You must have a package specification to configure these ESLint packages and their dependencies in your project.' ); return Promise.resolve(false); } From 9506c7dfc19819ae2b3bc6caf9a605749e3d8cf0 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 7 Mar 2024 17:04:47 -0800 Subject: [PATCH 18/18] Remove redundant import. --- src/commands/lint/configureLintingToolsCommand.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/lint/configureLintingToolsCommand.ts b/src/commands/lint/configureLintingToolsCommand.ts index 1f9da0b..e7c7e89 100644 --- a/src/commands/lint/configureLintingToolsCommand.ts +++ b/src/commands/lint/configureLintingToolsCommand.ts @@ -10,9 +10,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { WorkspaceUtils } from '../../utils/workspaceUtils'; import { JSON_INDENTATION_SPACES } from '../../utils/constants'; -import * as vscode from 'vscode'; -const config = vscode.workspace.getConfiguration(); +const config = workspace.getConfiguration(); const eslintPluginLwcGraphAnalyzer = '@salesforce/eslint-plugin-lwc-graph-analyzer'; const eslintPluginLwcGraphAnalyzerConfig =