From a415e551a82eb02626bbacd2d681d78d3cd87ef1 Mon Sep 17 00:00:00 2001 From: flora lan Date: Mon, 7 Mar 2022 14:59:46 -0800 Subject: [PATCH 01/25] feat: rename lightning component --- .../salesforcedx-vscode-core/package.json | 12 +++ .../salesforcedx-vscode-core/package.nls.json | 1 + .../commands/forceRenameLightningComponent.ts | 90 +++++++++++++++++++ .../src/commands/index.ts | 3 + .../salesforcedx-vscode-core/src/index.ts | 6 ++ .../src/messages/i18n.ts | 5 +- 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index d15e425939..5aa42c4c49 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -305,6 +305,10 @@ { "command": "sfdx.create.manifest", "when": "sfdx:project_opened" + }, + { + "command": "sfdx.lightning.rename", + "when": "sfdx:project_opened && resource =~ /.*(/lwc/[^/]+(/[^/]+\\.(html|css|js|xml))?$/)|(/aura/[^/]+(/[^/]+\\.(cmp|xml|design|svg|auradoc|controller|helper|renderer))?$/)" } ], "commandPalette": [ @@ -448,6 +452,10 @@ "command": "sfdx.create.manifest", "when": "false" }, + { + "command": "sfdx.lightning.rename", + "when": "false" + }, { "command": "sfdx.force.apex.trigger.create", "when": "sfdx:project_opened" @@ -938,6 +946,10 @@ { "command": "sfdx.force.launch.apex.replay.debugger.with.current.file", "title": "%force_launch_apex_replay_debugger_with_current_file%" + }, + { + "command": "sfdx.lightning.rename", + "title": "%force_lightning_rename_component_text%" } ], "configuration": { diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index d978490eac..0d17657b24 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -27,6 +27,7 @@ "force_lightning_interface_create_text": "SFDX: Create Aura Interface", "force_lightning_lwc_create_text": "SFDX: Create Lightning Web Component", "force_lightning_lwc_test_create_text": "SFDX: Create Lightning Web Component Test", + "force_lightning_rename_component_text": "SFDX: Rename Component (Files Only)", "force_source_status_local_text": "SFDX: View Local Changes", "force_source_status_remote_text": "SFDX: View Changes in Default Scratch Org", "force_debugger_stop_text": "SFDX: Stop Apex Debugger Session", diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts new file mode 100644 index 0000000000..01b630c9fd --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { ManifestResolver } from '@salesforce/source-deploy-retrieve'; +import * as fs from 'fs'; +import { join } from 'path'; +import { format } from 'sinon'; +import * as vscode from 'vscode'; +import { nls } from '../messages'; + +const RENAME_INPUT_PLACEHOLDER = 'rename_comp_input_placeholder'; +const REAME_INPUT_PROMPT = 'rename_comp_input_prompt'; +const REANME_INPUT_DUP_ERROR = 'rename_comp_input_dup_error'; + +// export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { + +// public async run() { + +// } +// } + +export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { + const inputOptions = { + placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), + promopt: nls.localize(REAME_INPUT_PROMPT) + } as vscode.InputBoxOptions; + + const responseText = await vscode.window.showInputBox(inputOptions); + const sourceFsPath = sourceUri.fsPath; + const stats = fs.statSync(sourceFsPath); + const isFile = stats.isFile(); + if (responseText) { + renameComponent(sourceFsPath, isFile, responseText); + // TODO: warning prompt + } +} + +function renameComponent(componentPath: string, isFile: boolean, newName: string) { + if (isFile) { + componentPath = componentPath.substring(0, componentPath.lastIndexOf('/') + 1); + } + const lwcOrAuraPath = componentPath.substring(0, componentPath.lastIndexOf('/') + 1); + const newComponentPath = join(lwcOrAuraPath, newName); + const componentName = componentPath.substring(componentPath.lastIndexOf('/') + 1); + if (!fs.existsSync(newComponentPath)) { + const items: string[] | undefined = readItemsFromDir(componentPath); + if (items) { + for (const item of items) { + // item can be file or folder(eg: _test_) + const baseAndExtension = item.split('.'); + const baseName = baseAndExtension[0]; + const extensionSuffix = baseAndExtension.length > 1 ? '.' + baseAndExtension[1] : undefined; + if (baseName === componentName) { + fs.rename( + componentPath + `/${item}`, + componentPath + `/${newName}` + extensionSuffix, + err => { + if (err) { + console.log(err); + } + }); + } + } + } + fs.rename( + componentPath, + newComponentPath, + err => { + if (err) { + console.log(err); + } + }); + } else { + vscode.window.showErrorMessage(nls.localize(REANME_INPUT_DUP_ERROR)); + throw new Error(format(nls.localize(REANME_INPUT_DUP_ERROR))); + } +} + +function readItemsFromDir(uri: string): string[] | undefined { + try { + const files: string[] = fs.readdirSync(uri); + return files; + } catch (err) { + console.error('Unable to scan directory: ', uri); + } +} diff --git a/packages/salesforcedx-vscode-core/src/commands/index.ts b/packages/salesforcedx-vscode-core/src/commands/index.ts index 98651a6ebd..78fff173e8 100644 --- a/packages/salesforcedx-vscode-core/src/commands/index.ts +++ b/packages/salesforcedx-vscode-core/src/commands/index.ts @@ -170,3 +170,6 @@ export { forceRefreshSObjects, initSObjectDefinitions } from './forceRefreshSObjects'; +export { + forceRenameLightningComponent +} from './forceRenameLightningComponent'; diff --git a/packages/salesforcedx-vscode-core/src/index.ts b/packages/salesforcedx-vscode-core/src/index.ts index 89480b9989..4ffefa726c 100644 --- a/packages/salesforcedx-vscode-core/src/index.ts +++ b/packages/salesforcedx-vscode-core/src/index.ts @@ -47,6 +47,7 @@ import { forcePackageInstall, forceProjectWithManifestCreate, forceRefreshSObjects, + forceRenameLightningComponent, forceSfdxProjectCreate, forceSourceDelete, forceSourceDeployManifest, @@ -426,6 +427,11 @@ function registerCommands( forceRefreshSObjects ); + const forceRenameComponentCmd = vscode.commands.registerCommand( + 'sfdx.lightning.rename', + forceRenameLightningComponent + ); + return vscode.Disposable.from( forceAuthAccessTokenCmd, forceAuthWebLoginCmd, diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 74ffdaec9d..0239f488ff 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -621,5 +621,8 @@ export const messages = { force_sobjects_refresh: 'SFDX: Refresh SObject Definitions', sobject_refresh_all: 'All SObjects', sobject_refresh_custom: 'Custom SObjects', - sobject_refresh_standard: 'Standard SObjects' + sobject_refresh_standard: 'Standard SObjects', + rename_comp_input_dup_error: 'Component name is already in use', + rename_comp_input_placeholder: 'Enter a unique component name', + rename_comp_input_prompt: 'Press Enter to confirm your input or Escape to cancel' }; From c441da1ec9e78b1c268a4a997382bda5d31dd82a Mon Sep 17 00:00:00 2001 From: flora lan Date: Mon, 7 Mar 2022 16:31:46 -0800 Subject: [PATCH 02/25] chore: utilize path --- .../salesforcedx-vscode-core/package.json | 2 +- .../commands/forceRenameLightningComponent.ts | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 5aa42c4c49..296ab904db 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -308,7 +308,7 @@ }, { "command": "sfdx.lightning.rename", - "when": "sfdx:project_opened && resource =~ /.*(/lwc/[^/]+(/[^/]+\\.(html|css|js|xml))?$/)|(/aura/[^/]+(/[^/]+\\.(cmp|xml|design|svg|auradoc|controller|helper|renderer))?$/)" + "when": "sfdx:project_opened && resource =~ /.*/lwc/[^/]+(/[^/]+\\.(html|css|js|xml))?$/" } ], "commandPalette": [ diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 01b630c9fd..e602b000ce 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -5,12 +5,12 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { ManifestResolver } from '@salesforce/source-deploy-retrieve'; import * as fs from 'fs'; -import { join } from 'path'; +import * as path from 'path'; import { format } from 'sinon'; import * as vscode from 'vscode'; import { nls } from '../messages'; +import { getRootWorkspacePath } from '../util'; const RENAME_INPUT_PLACEHOLDER = 'rename_comp_input_placeholder'; const REAME_INPUT_PROMPT = 'rename_comp_input_prompt'; @@ -31,33 +31,30 @@ export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { const responseText = await vscode.window.showInputBox(inputOptions); const sourceFsPath = sourceUri.fsPath; - const stats = fs.statSync(sourceFsPath); - const isFile = stats.isFile(); if (responseText) { - renameComponent(sourceFsPath, isFile, responseText); + renameComponent(sourceFsPath, responseText); // TODO: warning prompt } } -function renameComponent(componentPath: string, isFile: boolean, newName: string) { - if (isFile) { - componentPath = componentPath.substring(0, componentPath.lastIndexOf('/') + 1); - } - const lwcOrAuraPath = componentPath.substring(0, componentPath.lastIndexOf('/') + 1); - const newComponentPath = join(lwcOrAuraPath, newName); - const componentName = componentPath.substring(componentPath.lastIndexOf('/') + 1); +function renameComponent(sourceFsPath: string, newName: string) { + const stats = fs.statSync(sourceFsPath); + const componentPath = stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; + const newComponentPath = path.join(path.dirname(componentPath), newName); + const componentName = path.basename(componentPath); if (!fs.existsSync(newComponentPath)) { const items: string[] | undefined = readItemsFromDir(componentPath); if (items) { for (const item of items) { // item can be file or folder(eg: _test_) - const baseAndExtension = item.split('.'); + const baseAndExtension = getBaseNameAndExtension(item); const baseName = baseAndExtension[0]; - const extensionSuffix = baseAndExtension.length > 1 ? '.' + baseAndExtension[1] : undefined; + const extensionSuffix = baseAndExtension[1]; if (baseName === componentName) { + const newItem = newName + extensionSuffix; fs.rename( - componentPath + `/${item}`, - componentPath + `/${newName}` + extensionSuffix, + path.join(componentPath, item), + path.join(componentPath, newItem), err => { if (err) { console.log(err); @@ -80,6 +77,18 @@ function renameComponent(componentPath: string, isFile: boolean, newName: string } } +function getBaseNameAndExtension(item: string): string[] { + const splited = item.split('.'); + const baseName = splited[0]; + let extensionSuffix = ''; + if (splited.length > 1) { + for (let i = 1; i < splited.length; i++) { + extensionSuffix += '.' + splited[i]; + } + } + return [baseName, extensionSuffix]; +} + function readItemsFromDir(uri: string): string[] | undefined { try { const files: string[] = fs.readdirSync(uri); From 6b310491cd21663d60ade1baea7cebcb66b3fa43 Mon Sep 17 00:00:00 2001 From: flora lan Date: Tue, 8 Mar 2022 16:47:32 -0800 Subject: [PATCH 03/25] chore(forcerenamelightningcomponent): add telemetry --- .../commands/forceRenameLightningComponent.ts | 153 ++++++++++++------ .../src/messages/i18n.ts | 4 +- 2 files changed, 104 insertions(+), 53 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index e602b000ce..830639af0b 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -5,88 +5,125 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode/out/src'; +import { ContinueResponse } from '@salesforce/salesforcedx-utils-vscode/src/types'; +import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; import * as fs from 'fs'; import * as path from 'path'; -import { format } from 'sinon'; +import { format } from 'util'; import * as vscode from 'vscode'; +import { OUTPUT_CHANNEL } from '../channels'; import { nls } from '../messages'; -import { getRootWorkspacePath } from '../util'; +import { FilePathGatherer, SfdxCommandlet, SfdxWorkspaceChecker } from './util'; +const RENAME_LIGHTNING_COMPONENT_EXECUTOR = 'force_rename_lightning_component'; const RENAME_INPUT_PLACEHOLDER = 'rename_comp_input_placeholder'; const REAME_INPUT_PROMPT = 'rename_comp_input_prompt'; const REANME_INPUT_DUP_ERROR = 'rename_comp_input_dup_error'; +const RENAME_COMP_WARNING = 'rename_comp_warning'; -// export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { - -// public async run() { +export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { + private sourceFsPath: string; + private responseText: string | undefined; + constructor(sourceFsPath: string, responseText: string | undefined) { + super( + nls.localize(RENAME_LIGHTNING_COMPONENT_EXECUTOR), + RENAME_LIGHTNING_COMPONENT_EXECUTOR, + OUTPUT_CHANNEL + ); + this.sourceFsPath = sourceFsPath; + this.responseText = responseText; + } -// } -// } + public async run( + response: ContinueResponse, + progress?: vscode.Progress<{ message?: string | undefined; increment?: number | undefined; }>, + token?: vscode.CancellationToken + ): Promise { + if (this.sourceFsPath) { + if (this.responseText) { + renameComponent(this.sourceFsPath, this.responseText); + } + return true; + } + return false; + } +} export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { + const sourceFsPath = sourceUri.fsPath; const inputOptions = { placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), promopt: nls.localize(REAME_INPUT_PROMPT) } as vscode.InputBoxOptions; const responseText = await vscode.window.showInputBox(inputOptions); - const sourceFsPath = sourceUri.fsPath; - if (responseText) { - renameComponent(sourceFsPath, responseText); - // TODO: warning prompt + if (sourceFsPath) { + const commandlet = new SfdxCommandlet( + new SfdxWorkspaceChecker(), + new FilePathGatherer(sourceUri), + new RenameLwcComponentExecutor(sourceFsPath, responseText) + ); + await commandlet.run(); } } function renameComponent(sourceFsPath: string, newName: string) { - const stats = fs.statSync(sourceFsPath); - const componentPath = stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; - const newComponentPath = path.join(path.dirname(componentPath), newName); + const componentPath = getComponentPath(sourceFsPath); const componentName = path.basename(componentPath); - if (!fs.existsSync(newComponentPath)) { - const items: string[] | undefined = readItemsFromDir(componentPath); - if (items) { - for (const item of items) { - // item can be file or folder(eg: _test_) - const baseAndExtension = getBaseNameAndExtension(item); - const baseName = baseAndExtension[0]; - const extensionSuffix = baseAndExtension[1]; - if (baseName === componentName) { - const newItem = newName + extensionSuffix; - fs.rename( - path.join(componentPath, item), - path.join(componentPath, newItem), - err => { - if (err) { - console.log(err); - } - }); - } + checkForDuplicateName(componentPath, newName); + const items = readItemsFromDir(componentPath); + if (items) { + for (const item of items) { + // item can be file or folder(eg: _test_) + const baseAndExtension = getBaseNameAndExtension(item); + const baseName = baseAndExtension[0]; + const extensionSuffix = baseAndExtension[1]; + if (baseName === componentName) { + const newItem = newName + extensionSuffix; + fs.rename( + path.join(componentPath, item), + path.join(componentPath, newItem), + err => { + if (err) { + console.log(err); + } + }); } } - fs.rename( - componentPath, - newComponentPath, - err => { - if (err) { - console.log(err); - } - }); - } else { - vscode.window.showErrorMessage(nls.localize(REANME_INPUT_DUP_ERROR)); + } + const newComponentPath = path.join(path.dirname(componentPath), newName); + fs.rename( + componentPath, + newComponentPath, + err => { + if (err) { + console.log(err); + } + }); + notificationService.showWarningMessage(nls.localize(RENAME_COMP_WARNING)); +} + +function getComponentPath(sourceFsPath: string): string { + const stats = fs.statSync(sourceFsPath); + return stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; +} + +function checkForDuplicateName(componentPath: string, newName: string) { + if (isDuplicate(componentPath, newName)) { + notificationService.showErrorMessage(nls.localize(REANME_INPUT_DUP_ERROR)); throw new Error(format(nls.localize(REANME_INPUT_DUP_ERROR))); } } -function getBaseNameAndExtension(item: string): string[] { - const splited = item.split('.'); - const baseName = splited[0]; - let extensionSuffix = ''; - if (splited.length > 1) { - for (let i = 1; i < splited.length; i++) { - extensionSuffix += '.' + splited[i]; - } +function isDuplicate(componentPath: string, newName: string): boolean { + const isLwc = path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; + const lwcPath = isLwc ? path.dirname(componentPath) : path.join(path.dirname(path.dirname(componentPath)), 'lwc'); + const auraPath = isLwc ? path.join(path.dirname(path.dirname(componentPath)), 'aura') : path.dirname(componentPath); + if (fs.existsSync(path.join(lwcPath, newName)) || fs.existsSync(path.join(auraPath, newName))) { + return true; } - return [baseName, extensionSuffix]; + return false; } function readItemsFromDir(uri: string): string[] | undefined { @@ -97,3 +134,15 @@ function readItemsFromDir(uri: string): string[] | undefined { console.error('Unable to scan directory: ', uri); } } + +function getBaseNameAndExtension(item: string): string[] { + const splited = item.split('.'); + const baseName = splited[0]; + let extensionSuffix = ''; + if (splited.length > 1) { + for (let i = 1; i < splited.length; i++) { + extensionSuffix += '.' + splited[i]; + } + } + return [baseName, extensionSuffix]; +} diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 0239f488ff..3b16e89b45 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -622,7 +622,9 @@ export const messages = { sobject_refresh_all: 'All SObjects', sobject_refresh_custom: 'Custom SObjects', sobject_refresh_standard: 'Standard SObjects', + force_rename_lightning_component: 'SFDX: Rename Component (Files Only)', rename_comp_input_dup_error: 'Component name is already in use', rename_comp_input_placeholder: 'Enter a unique component name', - rename_comp_input_prompt: 'Press Enter to confirm your input or Escape to cancel' + rename_comp_input_prompt: 'Press Enter to confirm your input or Escape to cancel', + rename_comp_warning: 'Warning: References to the old name will not be updated. Update manually and redeploy once all changes have been made.' }; From 00f58f9a745117924cfddbced336170b7066deee Mon Sep 17 00:00:00 2001 From: flora lan Date: Tue, 8 Mar 2022 22:33:20 -0800 Subject: [PATCH 04/25] chore: add regular expression to check the file name --- .../salesforcedx-vscode-core/package.json | 2 +- .../commands/forceRenameLightningComponent.ts | 32 +++++++++---------- .../forceRenameLightningComponent.test.ts | 10 ++++++ 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 296ab904db..164a54368e 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -308,7 +308,7 @@ }, { "command": "sfdx.lightning.rename", - "when": "sfdx:project_opened && resource =~ /.*/lwc/[^/]+(/[^/]+\\.(html|css|js|xml))?$/" + "when": "sfdx:project_opened && resource =~ /.*/(lwc|aura)/[^/]+(/[^/]+\\.(html|css|js|xml|svg|cmp|app|design|auradoc))?$/" } ], "commandPalette": [ diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 830639af0b..eb4153d7b5 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -75,12 +75,9 @@ function renameComponent(sourceFsPath: string, newName: string) { const items = readItemsFromDir(componentPath); if (items) { for (const item of items) { - // item can be file or folder(eg: _test_) - const baseAndExtension = getBaseNameAndExtension(item); - const baseName = baseAndExtension[0]; - const extensionSuffix = baseAndExtension[1]; - if (baseName === componentName) { - const newItem = newName + extensionSuffix; + // only rename the file that has same name with component + if (isNameMatch(item, componentName, componentPath)) { + const newItem = item.replace(componentName, newName); fs.rename( path.join(componentPath, item), path.join(componentPath, newItem), @@ -117,7 +114,7 @@ function checkForDuplicateName(componentPath: string, newName: string) { } function isDuplicate(componentPath: string, newName: string): boolean { - const isLwc = path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; + const isLwc = isLwcComp(componentPath); const lwcPath = isLwc ? path.dirname(componentPath) : path.join(path.dirname(path.dirname(componentPath)), 'lwc'); const auraPath = isLwc ? path.join(path.dirname(path.dirname(componentPath)), 'aura') : path.dirname(componentPath); if (fs.existsSync(path.join(lwcPath, newName)) || fs.existsSync(path.join(auraPath, newName))) { @@ -135,14 +132,17 @@ function readItemsFromDir(uri: string): string[] | undefined { } } -function getBaseNameAndExtension(item: string): string[] { - const splited = item.split('.'); - const baseName = splited[0]; - let extensionSuffix = ''; - if (splited.length > 1) { - for (let i = 1; i < splited.length; i++) { - extensionSuffix += '.' + splited[i]; - } +function isNameMatch(item: string, componentName: string, componentPath: string) { + const isLwc = isLwcComp(componentPath); + let regularExp: RegExp; + if (isLwc) { + regularExp = new RegExp(`${componentName}\.(html|js|js-meta.xml|css|svg)`); + } else { + regularExp = new RegExp(`${componentName}(((Controller|Renderer|Helper)?\.js)|(\.(cmp|app|css|design|auradoc|svg)))`); } - return [baseName, extensionSuffix]; + return item.match(regularExp) ? true : false; +} + +function isLwcComp(componentPath: string): boolean { + return path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts new file mode 100644 index 0000000000..ce5f2f6994 --- /dev/null +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -0,0 +1,10 @@ +import { ComponentSet } from '@salesforce/source-deploy-retrieve'; +import { fail } from 'assert'; +import { expect } from 'chai'; +import * as fs from 'fs'; +import * as util from 'util'; +import * as sinon from 'sinon'; +import { createSandbox } from 'sinon'; +import * as vscode from 'vscode'; +import { RenameLwcComponentExecutor } from '../../../src/commands/forceRenameLightningComponent'; +import { nls } from '../../../src/messages'; \ No newline at end of file From e77dc200299f38e6bcb375b2cd696105cf5d7e5a Mon Sep 17 00:00:00 2001 From: flora lan Date: Tue, 15 Mar 2022 20:17:22 -0700 Subject: [PATCH 05/25] chore: add unit test --- .../commands/forceRenameLightningComponent.ts | 37 +--- .../forceRenameLightningComponent.test.ts | 163 +++++++++++++++++- 2 files changed, 166 insertions(+), 34 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index eb4153d7b5..467cdf5958 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -6,8 +6,8 @@ */ import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode/out/src'; -import { ContinueResponse } from '@salesforce/salesforcedx-utils-vscode/src/types'; import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; +import { ContinueResponse } from '@salesforce/salesforcedx-utils-vscode/src/types'; import * as fs from 'fs'; import * as path from 'path'; import { format } from 'util'; @@ -72,36 +72,26 @@ function renameComponent(sourceFsPath: string, newName: string) { const componentPath = getComponentPath(sourceFsPath); const componentName = path.basename(componentPath); checkForDuplicateName(componentPath, newName); - const items = readItemsFromDir(componentPath); + const items = fs.readdirSync(componentPath); if (items) { for (const item of items) { // only rename the file that has same name with component if (isNameMatch(item, componentName, componentPath)) { const newItem = item.replace(componentName, newName); - fs.rename( + fs.renameSync( path.join(componentPath, item), - path.join(componentPath, newItem), - err => { - if (err) { - console.log(err); - } - }); + path.join(componentPath, newItem)); } } } const newComponentPath = path.join(path.dirname(componentPath), newName); - fs.rename( + fs.renameSync( componentPath, - newComponentPath, - err => { - if (err) { - console.log(err); - } - }); + newComponentPath); notificationService.showWarningMessage(nls.localize(RENAME_COMP_WARNING)); } -function getComponentPath(sourceFsPath: string): string { +export function getComponentPath(sourceFsPath: string): string { const stats = fs.statSync(sourceFsPath); return stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; } @@ -113,7 +103,7 @@ function checkForDuplicateName(componentPath: string, newName: string) { } } -function isDuplicate(componentPath: string, newName: string): boolean { +export function isDuplicate(componentPath: string, newName: string): boolean { const isLwc = isLwcComp(componentPath); const lwcPath = isLwc ? path.dirname(componentPath) : path.join(path.dirname(path.dirname(componentPath)), 'lwc'); const auraPath = isLwc ? path.join(path.dirname(path.dirname(componentPath)), 'aura') : path.dirname(componentPath); @@ -123,15 +113,6 @@ function isDuplicate(componentPath: string, newName: string): boolean { return false; } -function readItemsFromDir(uri: string): string[] | undefined { - try { - const files: string[] = fs.readdirSync(uri); - return files; - } catch (err) { - console.error('Unable to scan directory: ', uri); - } -} - function isNameMatch(item: string, componentName: string, componentPath: string) { const isLwc = isLwcComp(componentPath); let regularExp: RegExp; @@ -143,6 +124,6 @@ function isNameMatch(item: string, componentName: string, componentPath: string) return item.match(regularExp) ? true : false; } -function isLwcComp(componentPath: string): boolean { +export function isLwcComp(componentPath: string): boolean { return path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index ce5f2f6994..017d4072bb 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -1,10 +1,161 @@ -import { ComponentSet } from '@salesforce/source-deploy-retrieve'; -import { fail } from 'assert'; +import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; import { expect } from 'chai'; import * as fs from 'fs'; -import * as util from 'util'; +import * as path from 'path'; import * as sinon from 'sinon'; -import { createSandbox } from 'sinon'; import * as vscode from 'vscode'; -import { RenameLwcComponentExecutor } from '../../../src/commands/forceRenameLightningComponent'; -import { nls } from '../../../src/messages'; \ No newline at end of file +import {RenameLwcComponentExecutor} from '../../../src/commands/forceRenameLightningComponent'; + +const lwcPath = vscode.Uri.parse('/force-app/main/default/lwc'); +const auraPath = vscode.Uri.parse('/force-app/main/default/aura/'); +const lwcComponent = 'hero'; +const auraComponent = 'page'; +const itemsInHero = ['_test_', 'hero.css', 'hero.html', 'hero.js', 'hero.js-meta.xml', 'templateOne.html']; +const itemsInPage = ['_test_', 'page.auradoc', 'page.cmp', 'page.cmp-meta.xml', 'page.css', 'page.design', 'page.svg', 'pageController.js', 'pageHelper.js', 'pageRenderer.js', 'templateOne.css']; + +const env = sinon.createSandbox(); +let renameStub: sinon.SinonStub; +let statStub: sinon.SinonStub; +let existStub: sinon.SinonStub; + +describe('Force Rename Lightning Component', () => { + describe('Happy Path Unit Test', () => { + beforeEach(() => { + renameStub = env.stub(fs, 'renameSync').returns(undefined); + statStub = env.stub(fs, 'statSync').returns({ + isFile: () => { + return false; + } + }); + existStub = env.stub(fs, 'existsSync').returns(false); + }); + + afterEach(() => { + env.restore(); + }); + + it('should rename the files and folder with new name under the same path and same file format', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + const oldFilePath = path.join(sourceUri.fsPath, 'hero.css'); + const newFilePath = path.join(sourceUri.fsPath, 'hero1.css'); + const newFolderPath = path.join(lwcPath.fsPath, 'hero1'); + expect(renameStub.callCount).to.equal(2); + expect(renameStub.calledWith(oldFilePath, newFilePath)).to.equal(true); + expect(renameStub.calledWith(sourceUri.fsPath, newFolderPath)).to.equal(true); + }); + + it('should only rename the files and folder that have same name with LWC component', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + env.stub(fs, 'readdirSync').returns(itemsInHero); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + expect(renameStub.callCount).to.equal(5); + }); + + it('should only rename the files and folder that have same name with Aura component', async () => { + const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); + env.stub(fs, 'readdirSync').returns(itemsInPage); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'page1'); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + expect(renameStub.callCount).to.equal(10); + }); + + it('should show the warning message once rename is done', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + const warningSpy = env.spy(notificationService, 'showWarningMessage'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + expect(warningSpy.callCount).to.equal(1); + }); + }); + + describe('Exception handling', () => { + beforeEach(() => { + renameStub = env.stub(fs, 'renameSync').returns(undefined); + statStub = env.stub(fs, 'statSync').returns({ + isFile: () => { + return false; + } + }); + }); + + afterEach(() => { + env.restore(); + }); + + it('should not rename when input text is empty', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + existStub = env.stub(fs, 'existsSync').returns(false); + env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + expect(renameStub.callCount).to.equal(0); + }); + + it('should not show warning message when input text is empty', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + existStub = env.stub(fs, 'existsSync').returns(false); + env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + const warningSpy = env.spy(notificationService, 'showWarningMessage'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + expect(warningSpy.callCount).to.equal(0); + }); + + it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + existStub = env.stub(fs, 'existsSync').returns(true); + let exceptionThrown = false; + try { + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero'); + await executor.run({ + type: 'CONTINUE', + data: '' + }); + } catch (e) { + exceptionThrown = true; + } + expect(exceptionThrown).to.equal(true); + expect(renameStub.callCount).to.equal(0); + }); + }); + + describe('getComponentPath', () => { + it('should return component path whatever file path or component path is given', () => { + }); + }); + + describe('isDuplicate', () => { + it('should return true when new name already existed in LWC or Aura', () => { + + }); + }); + + describe('isLwcComponent', () => { + it('should know if it is LWC component based on component path', () => { + + }); + }); +}); From 9946fd44a66be363083a61426e4ce7fbcb151f27 Mon Sep 17 00:00:00 2001 From: flora lan Date: Tue, 15 Mar 2022 20:21:31 -0700 Subject: [PATCH 06/25] feat: rename web component bundles provide a new command to rename lightning component bundles --- .../forceRenameLightningComponent.test.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 017d4072bb..3a78b6316f 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -34,7 +34,7 @@ describe('Force Rename Lightning Component', () => { env.restore(); }); - it('should rename the files and folder with new name under the same path and same file format', async () => { + it('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); @@ -141,21 +141,4 @@ describe('Force Rename Lightning Component', () => { expect(renameStub.callCount).to.equal(0); }); }); - - describe('getComponentPath', () => { - it('should return component path whatever file path or component path is given', () => { - }); - }); - - describe('isDuplicate', () => { - it('should return true when new name already existed in LWC or Aura', () => { - - }); - }); - - describe('isLwcComponent', () => { - it('should know if it is LWC component based on component path', () => { - - }); - }); }); From a73e9316d7e42cf424fbf1126c92126159621016 Mon Sep 17 00:00:00 2001 From: flora lan Date: Tue, 15 Mar 2022 20:40:34 -0700 Subject: [PATCH 07/25] chore: delete useless export --- .../src/commands/forceRenameLightningComponent.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 467cdf5958..fb1c93bbf5 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -91,7 +91,7 @@ function renameComponent(sourceFsPath: string, newName: string) { notificationService.showWarningMessage(nls.localize(RENAME_COMP_WARNING)); } -export function getComponentPath(sourceFsPath: string): string { +function getComponentPath(sourceFsPath: string): string { const stats = fs.statSync(sourceFsPath); return stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; } @@ -103,7 +103,7 @@ function checkForDuplicateName(componentPath: string, newName: string) { } } -export function isDuplicate(componentPath: string, newName: string): boolean { +function isDuplicate(componentPath: string, newName: string): boolean { const isLwc = isLwcComp(componentPath); const lwcPath = isLwc ? path.dirname(componentPath) : path.join(path.dirname(path.dirname(componentPath)), 'lwc'); const auraPath = isLwc ? path.join(path.dirname(path.dirname(componentPath)), 'aura') : path.dirname(componentPath); @@ -124,6 +124,6 @@ function isNameMatch(item: string, componentName: string, componentPath: string) return item.match(regularExp) ? true : false; } -export function isLwcComp(componentPath: string): boolean { +function isLwcComp(componentPath: string): boolean { return path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; } From eca0e570d53498ac475469e8fa1d5627eaf44867 Mon Sep 17 00:00:00 2001 From: flora lan Date: Wed, 16 Mar 2022 15:50:42 -0700 Subject: [PATCH 08/25] chore: update the dup error --- packages/salesforcedx-vscode-core/src/messages/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 3b16e89b45..61da7692b4 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -623,7 +623,7 @@ export const messages = { sobject_refresh_custom: 'Custom SObjects', sobject_refresh_standard: 'Standard SObjects', force_rename_lightning_component: 'SFDX: Rename Component (Files Only)', - rename_comp_input_dup_error: 'Component name is already in use', + rename_comp_input_dup_error: 'Component name is already in use in LWC or Aura', rename_comp_input_placeholder: 'Enter a unique component name', rename_comp_input_prompt: 'Press Enter to confirm your input or Escape to cancel', rename_comp_warning: 'Warning: References to the old name will not be updated. Update manually and redeploy once all changes have been made.' From 7677870c6d872374db8878b6f66a5ebb705ba857 Mon Sep 17 00:00:00 2001 From: flora lan Date: Wed, 16 Mar 2022 16:13:21 -0700 Subject: [PATCH 09/25] chore: show error when input is empty --- .../src/commands/forceRenameLightningComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index fb1c93bbf5..13156bdcfc 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -43,8 +43,8 @@ export class RenameLwcComponentExecutor extends LibraryCommandletExecutor Date: Thu, 17 Mar 2022 14:08:20 -0700 Subject: [PATCH 10/25] chore: revise code based on comments --- .../commands/forceRenameLightningComponent.ts | 36 +++++++++++-------- .../src/messages/i18n.ts | 8 ++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 13156bdcfc..3d0199ef13 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, salesforce.com, inc. + * Copyright (c) 2022, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause @@ -17,10 +17,10 @@ import { nls } from '../messages'; import { FilePathGatherer, SfdxCommandlet, SfdxWorkspaceChecker } from './util'; const RENAME_LIGHTNING_COMPONENT_EXECUTOR = 'force_rename_lightning_component'; -const RENAME_INPUT_PLACEHOLDER = 'rename_comp_input_placeholder'; -const REAME_INPUT_PROMPT = 'rename_comp_input_prompt'; -const REANME_INPUT_DUP_ERROR = 'rename_comp_input_dup_error'; -const RENAME_COMP_WARNING = 'rename_comp_warning'; +const RENAME_INPUT_PLACEHOLDER = 'rename_component_input_placeholder'; +const RENAME_INPUT_PROMPT = 'rename_component_input_prompt'; +const RENAME_INPUT_DUP_ERROR = 'rename_component_input_dup_error'; +const RENAME_COMPONENT_WARNING = 'rename_component_warning'; export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { private sourceFsPath: string; @@ -54,7 +54,7 @@ export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { const sourceFsPath = sourceUri.fsPath; const inputOptions = { placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), - promopt: nls.localize(REAME_INPUT_PROMPT) + promopt: nls.localize(RENAME_INPUT_PROMPT) } as vscode.InputBoxOptions; const responseText = await vscode.window.showInputBox(inputOptions); @@ -88,7 +88,7 @@ function renameComponent(sourceFsPath: string, newName: string) { fs.renameSync( componentPath, newComponentPath); - notificationService.showWarningMessage(nls.localize(RENAME_COMP_WARNING)); + notificationService.showWarningMessage(nls.localize(RENAME_COMPONENT_WARNING)); } function getComponentPath(sourceFsPath: string): string { @@ -98,15 +98,23 @@ function getComponentPath(sourceFsPath: string): string { function checkForDuplicateName(componentPath: string, newName: string) { if (isDuplicate(componentPath, newName)) { - notificationService.showErrorMessage(nls.localize(REANME_INPUT_DUP_ERROR)); - throw new Error(format(nls.localize(REANME_INPUT_DUP_ERROR))); + const errorMessage = nls.localize(RENAME_INPUT_DUP_ERROR); + notificationService.showErrorMessage(errorMessage); + throw new Error(format(errorMessage)); } } function isDuplicate(componentPath: string, newName: string): boolean { - const isLwc = isLwcComp(componentPath); - const lwcPath = isLwc ? path.dirname(componentPath) : path.join(path.dirname(path.dirname(componentPath)), 'lwc'); - const auraPath = isLwc ? path.join(path.dirname(path.dirname(componentPath)), 'aura') : path.dirname(componentPath); + const componentPathDirName = path.dirname(componentPath); + let lwcPath: string; + let auraPath: string; + if (isLwcComponent(componentPath)) { + lwcPath = componentPathDirName; + auraPath = path.join(path.dirname(componentPathDirName), 'aura'); + } else { + lwcPath = path.join(path.dirname(componentPathDirName), 'lwc'); + auraPath = componentPathDirName; + } if (fs.existsSync(path.join(lwcPath, newName)) || fs.existsSync(path.join(auraPath, newName))) { return true; } @@ -114,7 +122,7 @@ function isDuplicate(componentPath: string, newName: string): boolean { } function isNameMatch(item: string, componentName: string, componentPath: string) { - const isLwc = isLwcComp(componentPath); + const isLwc = isLwcComponent(componentPath); let regularExp: RegExp; if (isLwc) { regularExp = new RegExp(`${componentName}\.(html|js|js-meta.xml|css|svg)`); @@ -124,6 +132,6 @@ function isNameMatch(item: string, componentName: string, componentPath: string) return item.match(regularExp) ? true : false; } -function isLwcComp(componentPath: string): boolean { +function isLwcComponent(componentPath: string): boolean { return path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; } diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 61da7692b4..1e93e5b6ad 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -623,8 +623,8 @@ export const messages = { sobject_refresh_custom: 'Custom SObjects', sobject_refresh_standard: 'Standard SObjects', force_rename_lightning_component: 'SFDX: Rename Component (Files Only)', - rename_comp_input_dup_error: 'Component name is already in use in LWC or Aura', - rename_comp_input_placeholder: 'Enter a unique component name', - rename_comp_input_prompt: 'Press Enter to confirm your input or Escape to cancel', - rename_comp_warning: 'Warning: References to the old name will not be updated. Update manually and redeploy once all changes have been made.' + rename_component_input_dup_error: 'Component name is already in use in LWC or Aura', + rename_component_input_placeholder: 'Enter a unique component name', + rename_component_input_prompt: 'Press Enter to confirm your input or Escape to cancel', + rename_component_warning: 'Warning: References to the old name will not be updated. Update manually and redeploy once all changes have been made.' }; From 8033b289fdc42cb7bf2e218bd4e78cd00a677e80 Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 17 Mar 2022 14:43:27 -0700 Subject: [PATCH 11/25] chore: add comment --- .../src/commands/forceRenameLightningComponent.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 3d0199ef13..83da927ad4 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -105,6 +105,7 @@ function checkForDuplicateName(componentPath: string, newName: string) { } function isDuplicate(componentPath: string, newName: string): boolean { + // A LWC component can't share the same name as a Aura component const componentPathDirName = path.dirname(componentPath); let lwcPath: string; let auraPath: string; From c373a981cf1c3e222d8004bc9cde9f4ae3f4d2ef Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 17 Mar 2022 14:49:38 -0700 Subject: [PATCH 12/25] chore: revise the naming for a variable --- .../src/commands/forceRenameLightningComponent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 83da927ad4..4d533c56a9 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -20,7 +20,7 @@ const RENAME_LIGHTNING_COMPONENT_EXECUTOR = 'force_rename_lightning_component'; const RENAME_INPUT_PLACEHOLDER = 'rename_component_input_placeholder'; const RENAME_INPUT_PROMPT = 'rename_component_input_prompt'; const RENAME_INPUT_DUP_ERROR = 'rename_component_input_dup_error'; -const RENAME_COMPONENT_WARNING = 'rename_component_warning'; +const RENAME_WARNING = 'rename_component_warning'; export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { private sourceFsPath: string; @@ -88,7 +88,7 @@ function renameComponent(sourceFsPath: string, newName: string) { fs.renameSync( componentPath, newComponentPath); - notificationService.showWarningMessage(nls.localize(RENAME_COMPONENT_WARNING)); + notificationService.showWarningMessage(nls.localize(RENAME_WARNING)); } function getComponentPath(sourceFsPath: string): string { From 2a666f42b1d5145b183f8513f2cfe264ce4d451b Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 17 Mar 2022 17:12:13 -0700 Subject: [PATCH 13/25] chore: minor updates in forceRenameLightningComponent.ts --- .../commands/forceRenameLightningComponent.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 4d533c56a9..3a01713708 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -73,21 +73,21 @@ function renameComponent(sourceFsPath: string, newName: string) { const componentName = path.basename(componentPath); checkForDuplicateName(componentPath, newName); const items = fs.readdirSync(componentPath); - if (items) { - for (const item of items) { - // only rename the file that has same name with component - if (isNameMatch(item, componentName, componentPath)) { - const newItem = item.replace(componentName, newName); - fs.renameSync( - path.join(componentPath, item), - path.join(componentPath, newItem)); - } + for (const item of items) { + // only rename the file that has same name with component + if (isNameMatch(item, componentName, componentPath)) { + const newItem = item.replace(componentName, newName); + fs.renameSync( + path.join(componentPath, item), + path.join(componentPath, newItem) + ); } } const newComponentPath = path.join(path.dirname(componentPath), newName); fs.renameSync( componentPath, - newComponentPath); + newComponentPath + ); notificationService.showWarningMessage(nls.localize(RENAME_WARNING)); } From 8c1144e857dc42a1aff4df3c31d3cee47af9e7cc Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 17 Mar 2022 17:41:41 -0700 Subject: [PATCH 14/25] fix: isDuplicate should be case-sensitive --- .../commands/forceRenameLightningComponent.ts | 4 +- .../forceRenameLightningComponent.test.ts | 38 +++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 3a01713708..a2c6501ee5 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -116,7 +116,9 @@ function isDuplicate(componentPath: string, newName: string): boolean { lwcPath = path.join(path.dirname(componentPathDirName), 'lwc'); auraPath = componentPathDirName; } - if (fs.existsSync(path.join(lwcPath, newName)) || fs.existsSync(path.join(auraPath, newName))) { + const allLwcComponents = fs.readdirSync(lwcPath); + const allAuraComponents = fs.readdirSync(auraPath); + if (allLwcComponents.includes(newName) || allAuraComponents.includes(newName)) { return true; } return false; diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 3a78b6316f..033daf743d 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -16,7 +16,6 @@ const itemsInPage = ['_test_', 'page.auradoc', 'page.cmp', 'page.cmp-meta.xml', const env = sinon.createSandbox(); let renameStub: sinon.SinonStub; let statStub: sinon.SinonStub; -let existStub: sinon.SinonStub; describe('Force Rename Lightning Component', () => { describe('Happy Path Unit Test', () => { @@ -27,7 +26,6 @@ describe('Force Rename Lightning Component', () => { return false; } }); - existStub = env.stub(fs, 'existsSync').returns(false); }); afterEach(() => { @@ -36,7 +34,10 @@ describe('Force Rename Lightning Component', () => { it('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); await executor.run({ type: 'CONTINUE', @@ -52,7 +53,10 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with LWC component', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync').returns(itemsInHero); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns(itemsInHero); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); await executor.run({ type: 'CONTINUE', @@ -63,7 +67,10 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with Aura component', async () => { const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); - env.stub(fs, 'readdirSync').returns(itemsInPage); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns(itemsInPage); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'page1'); await executor.run({ type: 'CONTINUE', @@ -74,7 +81,10 @@ describe('Force Rename Lightning Component', () => { it('should show the warning message once rename is done', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); await executor.run({ @@ -101,8 +111,10 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - existStub = env.stub(fs, 'existsSync').returns(false); - env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); await executor.run({ type: 'CONTINUE', @@ -113,8 +125,10 @@ describe('Force Rename Lightning Component', () => { it('should not show warning message when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - existStub = env.stub(fs, 'existsSync').returns(false); - env.stub(fs, 'readdirSync').returns([itemsInHero[1]]); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); await executor.run({ @@ -126,7 +140,9 @@ describe('Force Rename Lightning Component', () => { it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - existStub = env.stub(fs, 'existsSync').returns(true); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([lwcComponent]) + .onSecondCall().returns([]); let exceptionThrown = false; try { const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero'); From 1b616ae1ddd7effbf116bc83de70ba9d52da0367 Mon Sep 17 00:00:00 2001 From: flora lan Date: Fri, 18 Mar 2022 16:05:09 -0700 Subject: [PATCH 15/25] chore: add parameterGatherer and refill component name --- .../commands/forceRenameLightningComponent.ts | 60 ++++++++++++------- .../forceRenameLightningComponent.test.ts | 28 ++++----- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index a2c6501ee5..3187548f6f 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -7,14 +7,14 @@ import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode/out/src'; import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; -import { ContinueResponse } from '@salesforce/salesforcedx-utils-vscode/src/types'; +import { CancelResponse, ContinueResponse, ParametersGatherer } from '@salesforce/salesforcedx-utils-vscode/src/types'; import * as fs from 'fs'; import * as path from 'path'; import { format } from 'util'; import * as vscode from 'vscode'; import { OUTPUT_CHANNEL } from '../channels'; import { nls } from '../messages'; -import { FilePathGatherer, SfdxCommandlet, SfdxWorkspaceChecker } from './util'; +import { SfdxCommandlet, SfdxWorkspaceChecker } from './util'; const RENAME_LIGHTNING_COMPONENT_EXECUTOR = 'force_rename_lightning_component'; const RENAME_INPUT_PLACEHOLDER = 'rename_component_input_placeholder'; @@ -22,47 +22,61 @@ const RENAME_INPUT_PROMPT = 'rename_component_input_prompt'; const RENAME_INPUT_DUP_ERROR = 'rename_component_input_dup_error'; const RENAME_WARNING = 'rename_component_warning'; -export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { +export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { private sourceFsPath: string; - private responseText: string | undefined; - constructor(sourceFsPath: string, responseText: string | undefined) { + constructor(sourceFsPath: string) { super( nls.localize(RENAME_LIGHTNING_COMPONENT_EXECUTOR), RENAME_LIGHTNING_COMPONENT_EXECUTOR, OUTPUT_CHANNEL ); this.sourceFsPath = sourceFsPath; - this.responseText = responseText; } public async run( - response: ContinueResponse, - progress?: vscode.Progress<{ message?: string | undefined; increment?: number | undefined; }>, - token?: vscode.CancellationToken + response: ContinueResponse ): Promise { - if (this.sourceFsPath) { - if (this.responseText) { - renameComponent(this.sourceFsPath, this.responseText); + const newComponentName = response.data.name; + if (newComponentName) { + if (this.sourceFsPath) { + renameComponent(this.sourceFsPath, newComponentName); return true; } } return false; } } +export interface ComponentName { + name?: string; +} +export class GetComponentName + implements ParametersGatherer { + private sourceFsPath: string; + constructor(sourceFsPath: string) { + this.sourceFsPath = sourceFsPath; + } + public async gather(): Promise< + CancelResponse | ContinueResponse + > { + const inputOptions = { + value: getComponentName(getComponentPath(this.sourceFsPath)), + placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), + promopt: nls.localize(RENAME_INPUT_PROMPT) + } as vscode.InputBoxOptions; + const inputResult = await vscode.window.showInputBox(inputOptions); + return inputResult + ? { type: 'CONTINUE', data: { name: inputResult } } + : { type: 'CANCEL' }; + } +} export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { const sourceFsPath = sourceUri.fsPath; - const inputOptions = { - placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), - promopt: nls.localize(RENAME_INPUT_PROMPT) - } as vscode.InputBoxOptions; - - const responseText = await vscode.window.showInputBox(inputOptions); if (sourceFsPath) { const commandlet = new SfdxCommandlet( new SfdxWorkspaceChecker(), - new FilePathGatherer(sourceUri), - new RenameLwcComponentExecutor(sourceFsPath, responseText) + new GetComponentName(sourceFsPath), + new RenameLwcComponentExecutor(sourceFsPath) ); await commandlet.run(); } @@ -70,7 +84,7 @@ export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { function renameComponent(sourceFsPath: string, newName: string) { const componentPath = getComponentPath(sourceFsPath); - const componentName = path.basename(componentPath); + const componentName = getComponentName(componentPath); checkForDuplicateName(componentPath, newName); const items = fs.readdirSync(componentPath); for (const item of items) { @@ -96,6 +110,10 @@ function getComponentPath(sourceFsPath: string): string { return stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; } +function getComponentName(componentPath: string): string { + return path.basename(componentPath); +} + function checkForDuplicateName(componentPath: string, newName: string) { if (isDuplicate(componentPath, newName)) { const errorMessage = nls.localize(RENAME_INPUT_DUP_ERROR); diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 033daf743d..e605e429b0 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -38,10 +38,10 @@ describe('Force Rename Lightning Component', () => { .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {name: 'hero1'} }); const oldFilePath = path.join(sourceUri.fsPath, 'hero.css'); const newFilePath = path.join(sourceUri.fsPath, 'hero1.css'); @@ -57,10 +57,10 @@ describe('Force Rename Lightning Component', () => { .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInHero); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {name: 'hero1'} }); expect(renameStub.callCount).to.equal(5); }); @@ -71,10 +71,10 @@ describe('Force Rename Lightning Component', () => { .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInPage); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'page1'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {name: 'page1'} }); expect(renameStub.callCount).to.equal(10); }); @@ -86,10 +86,10 @@ describe('Force Rename Lightning Component', () => { .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero1'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {name: 'hero1'} }); expect(warningSpy.callCount).to.equal(1); }); @@ -115,10 +115,10 @@ describe('Force Rename Lightning Component', () => { .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {} }); expect(renameStub.callCount).to.equal(0); }); @@ -130,10 +130,10 @@ describe('Force Rename Lightning Component', () => { .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, undefined); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {} }); expect(warningSpy.callCount).to.equal(0); }); @@ -145,10 +145,10 @@ describe('Force Rename Lightning Component', () => { .onSecondCall().returns([]); let exceptionThrown = false; try { - const executor = new RenameLwcComponentExecutor(sourceUri.fsPath, 'hero'); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', - data: '' + data: {name: 'hero'} }); } catch (e) { exceptionThrown = true; From c140c0aebbee0b974ae50d7d1a0424b5615eb9f4 Mon Sep 17 00:00:00 2001 From: flora lan Date: Fri, 18 Mar 2022 18:10:03 -0700 Subject: [PATCH 16/25] chore: convert sync function to async --- .../commands/forceRenameLightningComponent.ts | 62 +++++++++---------- .../forceRenameLightningComponent.test.ts | 26 ++++---- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 3187548f6f..ca8446234b 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -37,15 +37,25 @@ export class RenameLwcComponentExecutor extends LibraryCommandletExecutor ): Promise { const newComponentName = response.data.name; - if (newComponentName) { - if (this.sourceFsPath) { - renameComponent(this.sourceFsPath, newComponentName); - return true; - } + if (newComponentName && this.sourceFsPath) { + renameComponent(this.sourceFsPath, newComponentName); + return true; } return false; } } + +export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { + const sourceFsPath = sourceUri.fsPath; + if (sourceFsPath) { + const commandlet = new SfdxCommandlet( + new SfdxWorkspaceChecker(), + new GetComponentName(sourceFsPath), + new RenameLwcComponentExecutor(sourceFsPath) + ); + await commandlet.run(); + } +} export interface ComponentName { name?: string; } @@ -70,35 +80,23 @@ export class GetComponentName } } -export async function forceRenameLightningComponent(sourceUri: vscode.Uri) { - const sourceFsPath = sourceUri.fsPath; - if (sourceFsPath) { - const commandlet = new SfdxCommandlet( - new SfdxWorkspaceChecker(), - new GetComponentName(sourceFsPath), - new RenameLwcComponentExecutor(sourceFsPath) - ); - await commandlet.run(); - } -} - -function renameComponent(sourceFsPath: string, newName: string) { +async function renameComponent(sourceFsPath: string, newName: string) { const componentPath = getComponentPath(sourceFsPath); const componentName = getComponentName(componentPath); checkForDuplicateName(componentPath, newName); - const items = fs.readdirSync(componentPath); + const items = await fs.promises.readdir(componentPath); for (const item of items) { // only rename the file that has same name with component if (isNameMatch(item, componentName, componentPath)) { const newItem = item.replace(componentName, newName); - fs.renameSync( + await fs.promises.rename( path.join(componentPath, item), path.join(componentPath, newItem) ); } } const newComponentPath = path.join(path.dirname(componentPath), newName); - fs.renameSync( + await fs.promises.rename( componentPath, newComponentPath ); @@ -114,15 +112,16 @@ function getComponentName(componentPath: string): string { return path.basename(componentPath); } -function checkForDuplicateName(componentPath: string, newName: string) { - if (isDuplicate(componentPath, newName)) { +async function checkForDuplicateName(componentPath: string, newName: string) { + const isNameDuplicate = await isDuplicate(componentPath, newName); + if (isNameDuplicate) { const errorMessage = nls.localize(RENAME_INPUT_DUP_ERROR); notificationService.showErrorMessage(errorMessage); throw new Error(format(errorMessage)); } } -function isDuplicate(componentPath: string, newName: string): boolean { +async function isDuplicate(componentPath: string, newName: string): Promise { // A LWC component can't share the same name as a Aura component const componentPathDirName = path.dirname(componentPath); let lwcPath: string; @@ -134,15 +133,12 @@ function isDuplicate(componentPath: string, newName: string): boolean { lwcPath = path.join(path.dirname(componentPathDirName), 'lwc'); auraPath = componentPathDirName; } - const allLwcComponents = fs.readdirSync(lwcPath); - const allAuraComponents = fs.readdirSync(auraPath); - if (allLwcComponents.includes(newName) || allAuraComponents.includes(newName)) { - return true; - } - return false; + const allLwcComponents = await fs.promises.readdir(lwcPath); + const allAuraComponents = await fs.promises.readdir(auraPath); + return allLwcComponents.includes(newName) || allAuraComponents.includes(newName) ? true : false; } -function isNameMatch(item: string, componentName: string, componentPath: string) { +export function isNameMatch(item: string, componentName: string, componentPath: string) { const isLwc = isLwcComponent(componentPath); let regularExp: RegExp; if (isLwc) { @@ -150,9 +146,9 @@ function isNameMatch(item: string, componentName: string, componentPath: string) } else { regularExp = new RegExp(`${componentName}(((Controller|Renderer|Helper)?\.js)|(\.(cmp|app|css|design|auradoc|svg)))`); } - return item.match(regularExp) ? true : false; + return item.match(regularExp); } function isLwcComponent(componentPath: string): boolean { - return path.basename(path.dirname(componentPath)) === 'lwc' ? true : false; + return path.basename(path.dirname(componentPath)) === 'lwc'; } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index e605e429b0..0752f4b042 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -20,7 +20,7 @@ let statStub: sinon.SinonStub; describe('Force Rename Lightning Component', () => { describe('Happy Path Unit Test', () => { beforeEach(() => { - renameStub = env.stub(fs, 'renameSync').returns(undefined); + renameStub = env.stub(fs.promises, 'rename').resolves(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -32,12 +32,12 @@ describe('Force Rename Lightning Component', () => { env.restore(); }); - it('should rename the files and folder with new name under the same path', async () => { + it.only('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns([itemsInHero[1]]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -53,7 +53,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with LWC component', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInHero); @@ -67,7 +67,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with Aura component', async () => { const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInPage); @@ -81,7 +81,7 @@ describe('Force Rename Lightning Component', () => { it('should show the warning message once rename is done', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -97,7 +97,7 @@ describe('Force Rename Lightning Component', () => { describe('Exception handling', () => { beforeEach(() => { - renameStub = env.stub(fs, 'renameSync').returns(undefined); + renameStub = env.stub(fs.promises, 'rename').returns(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -111,7 +111,7 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -125,7 +125,7 @@ describe('Force Rename Lightning Component', () => { it('should not show warning message when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -140,7 +140,7 @@ describe('Force Rename Lightning Component', () => { it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') + env.stub(fs.promises, 'readdir') .onFirstCall().returns([lwcComponent]) .onSecondCall().returns([]); let exceptionThrown = false; From 8c9003d6df05fa07bde45af4d7e5a4b982571368 Mon Sep 17 00:00:00 2001 From: flora lan Date: Fri, 18 Mar 2022 18:50:39 -0700 Subject: [PATCH 17/25] chore: add test for isNameMatch & keep sync functions --- .../commands/forceRenameLightningComponent.ts | 22 +++---- .../forceRenameLightningComponent.test.ts | 62 ++++++++++++++----- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index ca8446234b..07b0dde7a2 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -80,23 +80,23 @@ export class GetComponentName } } -async function renameComponent(sourceFsPath: string, newName: string) { +function renameComponent(sourceFsPath: string, newName: string) { const componentPath = getComponentPath(sourceFsPath); const componentName = getComponentName(componentPath); checkForDuplicateName(componentPath, newName); - const items = await fs.promises.readdir(componentPath); + const items = fs.readdirSync(componentPath); for (const item of items) { // only rename the file that has same name with component if (isNameMatch(item, componentName, componentPath)) { const newItem = item.replace(componentName, newName); - await fs.promises.rename( + fs.renameSync( path.join(componentPath, item), path.join(componentPath, newItem) ); } } const newComponentPath = path.join(path.dirname(componentPath), newName); - await fs.promises.rename( + fs.renameSync( componentPath, newComponentPath ); @@ -112,8 +112,8 @@ function getComponentName(componentPath: string): string { return path.basename(componentPath); } -async function checkForDuplicateName(componentPath: string, newName: string) { - const isNameDuplicate = await isDuplicate(componentPath, newName); +function checkForDuplicateName(componentPath: string, newName: string) { + const isNameDuplicate = isDuplicate(componentPath, newName); if (isNameDuplicate) { const errorMessage = nls.localize(RENAME_INPUT_DUP_ERROR); notificationService.showErrorMessage(errorMessage); @@ -121,7 +121,7 @@ async function checkForDuplicateName(componentPath: string, newName: string) { } } -async function isDuplicate(componentPath: string, newName: string): Promise { +function isDuplicate(componentPath: string, newName: string): boolean { // A LWC component can't share the same name as a Aura component const componentPathDirName = path.dirname(componentPath); let lwcPath: string; @@ -133,12 +133,12 @@ async function isDuplicate(componentPath: string, newName: string): Promise { describe('Happy Path Unit Test', () => { beforeEach(() => { - renameStub = env.stub(fs.promises, 'rename').resolves(undefined); + renameStub = env.stub(fs, 'renameSync').returns(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -32,12 +32,12 @@ describe('Force Rename Lightning Component', () => { env.restore(); }); - it.only('should rename the files and folder with new name under the same path', async () => { + it('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') - .onFirstCall().resolves([]) - .onSecondCall().resolves([]) - .onThirdCall().resolves([itemsInHero[1]]); + env.stub(fs, 'readdirSync') + .onFirstCall().returns([]) + .onSecondCall().returns([]) + .onThirdCall().returns([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -53,7 +53,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with LWC component', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInHero); @@ -67,7 +67,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with Aura component', async () => { const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns(itemsInPage); @@ -81,7 +81,7 @@ describe('Force Rename Lightning Component', () => { it('should show the warning message once rename is done', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -97,7 +97,7 @@ describe('Force Rename Lightning Component', () => { describe('Exception handling', () => { beforeEach(() => { - renameStub = env.stub(fs.promises, 'rename').returns(undefined); + renameStub = env.stub(fs, 'rename').returns(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -111,7 +111,7 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -125,7 +125,7 @@ describe('Force Rename Lightning Component', () => { it('should not show warning message when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([]) .onSecondCall().returns([]) .onThirdCall().returns([itemsInHero[1]]); @@ -140,7 +140,7 @@ describe('Force Rename Lightning Component', () => { it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + env.stub(fs, 'readdirSync') .onFirstCall().returns([lwcComponent]) .onSecondCall().returns([]); let exceptionThrown = false; @@ -158,3 +158,37 @@ describe('Force Rename Lightning Component', () => { }); }); }); + +describe ('#isNameMatch', () => { + it('should return true if file name and component name match for essential LWC files', () => { + const componentName = 'hero'; + const componentPath = path.join(lwcPath.fsPath, lwcComponent); + expect(isNameMatch(itemsInHero[1], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[2], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[3], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[4], componentName, componentPath)).to.equal(true); + }); + + it('should return true of file name and component name match for essential Aura files', () => { + const componentName = 'page'; + const componentPath = path.join(auraPath.fsPath, auraComponent); + expect(isNameMatch(itemsInPage[1], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[2], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[3], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[4], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[5], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[6], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[7], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[8], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[9], componentName, componentPath)).to.equal(true); + }); + + it('should return false if file type is not in LWC or Aura or file name and component name do not match', () => { + const lwcComponentPath = path.join(lwcPath.fsPath, lwcComponent); + const auraComponentPath = path.join(auraPath.fsPath, auraComponent); + expect(isNameMatch('hero.jpg', 'hero', lwcComponentPath)).to.equal(false); + expect(isNameMatch('hero1.css', 'hero', lwcComponentPath)).to.equal(false); + expect(isNameMatch('page.jpg', 'page', auraComponentPath)).to.equal(false); + expect(isNameMatch('page1.css', 'hero', auraComponentPath)).to.equal(false); + }); +}); From 7759f04a20977df9216de566a8d8cdc3a2d043b2 Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 10:05:46 -0700 Subject: [PATCH 18/25] fix: change command text --- packages/salesforcedx-vscode-core/package.nls.json | 2 +- .../src/commands/forceRenameLightningComponent.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 0d17657b24..5d824234fd 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -27,7 +27,7 @@ "force_lightning_interface_create_text": "SFDX: Create Aura Interface", "force_lightning_lwc_create_text": "SFDX: Create Lightning Web Component", "force_lightning_lwc_test_create_text": "SFDX: Create Lightning Web Component Test", - "force_lightning_rename_component_text": "SFDX: Rename Component (Files Only)", + "force_lightning_rename_component_text": "SFDX: Rename Component", "force_source_status_local_text": "SFDX: View Local Changes", "force_source_status_remote_text": "SFDX: View Changes in Default Scratch Org", "force_debugger_stop_text": "SFDX: Stop Apex Debugger Session", diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 07b0dde7a2..b700acd3c7 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -21,6 +21,8 @@ const RENAME_INPUT_PLACEHOLDER = 'rename_component_input_placeholder'; const RENAME_INPUT_PROMPT = 'rename_component_input_prompt'; const RENAME_INPUT_DUP_ERROR = 'rename_component_input_dup_error'; const RENAME_WARNING = 'rename_component_warning'; +const LWC = 'lwc'; +const AURA = 'aura'; export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { private sourceFsPath: string; @@ -128,9 +130,9 @@ function isDuplicate(componentPath: string, newName: string): boolean { let auraPath: string; if (isLwcComponent(componentPath)) { lwcPath = componentPathDirName; - auraPath = path.join(path.dirname(componentPathDirName), 'aura'); + auraPath = path.join(path.dirname(componentPathDirName), AURA); } else { - lwcPath = path.join(path.dirname(componentPathDirName), 'lwc'); + lwcPath = path.join(path.dirname(componentPathDirName), LWC); auraPath = componentPathDirName; } const allLwcComponents = fs.readdirSync(lwcPath); @@ -146,9 +148,9 @@ export function isNameMatch(item: string, componentName: string, componentPath: } else { regularExp = new RegExp(`${componentName}(((Controller|Renderer|Helper)?\.js)|(\.(cmp|app|css|design|auradoc|svg)))`); } - return item.match(regularExp) ? true : false; + return Boolean(item.match(regularExp)); } function isLwcComponent(componentPath: string): boolean { - return path.basename(path.dirname(componentPath)) === 'lwc'; + return path.basename(path.dirname(componentPath)) === LWC; } From 14100bfaf708e89c675de30cdfd33ce5a1767bcd Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 13:11:51 -0700 Subject: [PATCH 19/25] fix: use async functions --- .../commands/forceRenameLightningComponent.ts | 22 +++---- .../forceRenameLightningComponent.test.ts | 58 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index b700acd3c7..adf7e4e70c 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -40,7 +40,7 @@ export class RenameLwcComponentExecutor extends LibraryCommandletExecutor { const newComponentName = response.data.name; if (newComponentName && this.sourceFsPath) { - renameComponent(this.sourceFsPath, newComponentName); + await renameComponent(this.sourceFsPath, newComponentName); return true; } return false; @@ -82,23 +82,23 @@ export class GetComponentName } } -function renameComponent(sourceFsPath: string, newName: string) { +async function renameComponent(sourceFsPath: string, newName: string) { const componentPath = getComponentPath(sourceFsPath); const componentName = getComponentName(componentPath); - checkForDuplicateName(componentPath, newName); - const items = fs.readdirSync(componentPath); + await checkForDuplicateName(componentPath, newName); + const items = await fs.promises.readdir(componentPath); for (const item of items) { // only rename the file that has same name with component if (isNameMatch(item, componentName, componentPath)) { const newItem = item.replace(componentName, newName); - fs.renameSync( + await fs.promises.rename( path.join(componentPath, item), path.join(componentPath, newItem) ); } } const newComponentPath = path.join(path.dirname(componentPath), newName); - fs.renameSync( + await fs.promises.rename( componentPath, newComponentPath ); @@ -114,8 +114,8 @@ function getComponentName(componentPath: string): string { return path.basename(componentPath); } -function checkForDuplicateName(componentPath: string, newName: string) { - const isNameDuplicate = isDuplicate(componentPath, newName); +async function checkForDuplicateName(componentPath: string, newName: string) { + const isNameDuplicate = await isDuplicate(componentPath, newName); if (isNameDuplicate) { const errorMessage = nls.localize(RENAME_INPUT_DUP_ERROR); notificationService.showErrorMessage(errorMessage); @@ -123,7 +123,7 @@ function checkForDuplicateName(componentPath: string, newName: string) { } } -function isDuplicate(componentPath: string, newName: string): boolean { +async function isDuplicate(componentPath: string, newName: string): Promise { // A LWC component can't share the same name as a Aura component const componentPathDirName = path.dirname(componentPath); let lwcPath: string; @@ -135,8 +135,8 @@ function isDuplicate(componentPath: string, newName: string): boolean { lwcPath = path.join(path.dirname(componentPathDirName), LWC); auraPath = componentPathDirName; } - const allLwcComponents = fs.readdirSync(lwcPath); - const allAuraComponents = fs.readdirSync(auraPath); + const allLwcComponents = await fs.promises.readdir(lwcPath); + const allAuraComponents = await fs.promises.readdir(auraPath); return allLwcComponents.includes(newName) || allAuraComponents.includes(newName) ? true : false; } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 665144b44c..400a84cb68 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -20,7 +20,7 @@ let statStub: sinon.SinonStub; describe('Force Rename Lightning Component', () => { describe('Happy Path Unit Test', () => { beforeEach(() => { - renameStub = env.stub(fs, 'renameSync').returns(undefined); + renameStub = env.stub(fs.promises, 'rename').resolves(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -34,10 +34,10 @@ describe('Force Rename Lightning Component', () => { it('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns([itemsInHero[1]]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -53,10 +53,10 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with LWC component', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns(itemsInHero); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves(itemsInHero); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -67,10 +67,10 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with Aura component', async () => { const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns(itemsInPage); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves(itemsInPage); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -81,10 +81,10 @@ describe('Force Rename Lightning Component', () => { it('should show the warning message once rename is done', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns([itemsInHero[1]]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ @@ -97,7 +97,7 @@ describe('Force Rename Lightning Component', () => { describe('Exception handling', () => { beforeEach(() => { - renameStub = env.stub(fs, 'rename').returns(undefined); + renameStub = env.stub(fs.promises, 'rename').resolves(undefined); statStub = env.stub(fs, 'statSync').returns({ isFile: () => { return false; @@ -111,10 +111,10 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns([itemsInHero[1]]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', @@ -125,10 +125,10 @@ describe('Force Rename Lightning Component', () => { it('should not show warning message when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([]) - .onSecondCall().returns([]) - .onThirdCall().returns([itemsInHero[1]]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); const warningSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ @@ -140,9 +140,9 @@ describe('Force Rename Lightning Component', () => { it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs, 'readdirSync') - .onFirstCall().returns([lwcComponent]) - .onSecondCall().returns([]); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([lwcComponent]) + .onSecondCall().resolves([]); let exceptionThrown = false; try { const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); From 7a1c779919c78497f2d0ca2977fead2b2ce42c35 Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 13:29:20 -0700 Subject: [PATCH 20/25] fix: edit i18n for command text --- packages/salesforcedx-vscode-core/src/messages/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 1e93e5b6ad..4e6487dec8 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -622,7 +622,7 @@ export const messages = { sobject_refresh_all: 'All SObjects', sobject_refresh_custom: 'Custom SObjects', sobject_refresh_standard: 'Standard SObjects', - force_rename_lightning_component: 'SFDX: Rename Component (Files Only)', + force_rename_lightning_component: 'SFDX: Rename Component', rename_component_input_dup_error: 'Component name is already in use in LWC or Aura', rename_component_input_placeholder: 'Enter a unique component name', rename_component_input_prompt: 'Press Enter to confirm your input or Escape to cancel', From 33290330550afcb7c6f150c94d68354ba371974b Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 14:39:39 -0700 Subject: [PATCH 21/25] fix: trim user input --- .../src/commands/forceRenameLightningComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index adf7e4e70c..9a49229576 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -38,7 +38,7 @@ export class RenameLwcComponentExecutor extends LibraryCommandletExecutor ): Promise { - const newComponentName = response.data.name; + const newComponentName = response.data.name?.trim(); if (newComponentName && this.sourceFsPath) { await renameComponent(this.sourceFsPath, newComponentName); return true; From 8027eae41fd6b0d4b14428c033391b4027b29931 Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 14:54:17 -0700 Subject: [PATCH 22/25] test: add test for trim input --- .../forceRenameLightningComponent.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 400a84cb68..8c77d0f574 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -109,6 +109,37 @@ describe('Force Rename Lightning Component', () => { env.restore(); }); + it('should get trimmed component name if new component input has leading or trailing spaces', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); + await executor.run({ + type: 'CONTINUE', + data: {name: ' hero1 '} + }); + const oldFilePath = path.join(sourceUri.fsPath, 'hero.css'); + const newFilePath = path.join(sourceUri.fsPath, 'hero1.css'); + expect(renameStub.callCount).to.equal(2); + expect(renameStub.calledWith(oldFilePath, newFilePath)).to.equal(true); + }); + + it('should not rename when input text only contains white spaces', async () => { + const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); + env.stub(fs.promises, 'readdir') + .onFirstCall().resolves([]) + .onSecondCall().resolves([]) + .onThirdCall().resolves([itemsInHero[1]]); + const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); + await executor.run({ + type: 'CONTINUE', + data: {name: ' '} + }); + expect(renameStub.callCount).to.equal(0); + }); + it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); env.stub(fs.promises, 'readdir') From 1bc9904ae7b3d243d8974d69abb04776c23aea34 Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 14:58:52 -0700 Subject: [PATCH 23/25] chore: return logic --- .../src/commands/forceRenameLightningComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 9a49229576..113db3305d 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -137,7 +137,7 @@ async function isDuplicate(componentPath: string, newName: string): Promise Date: Thu, 24 Mar 2022 15:55:39 -0700 Subject: [PATCH 24/25] chore: existSync to async --- .../commands/forceRenameLightningComponent.ts | 8 +- .../forceRenameLightningComponent.test.ts | 91 ++++++++++--------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts index 113db3305d..7c6bee0c1e 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts +++ b/packages/salesforcedx-vscode-core/src/commands/forceRenameLightningComponent.ts @@ -71,7 +71,7 @@ export class GetComponentName CancelResponse | ContinueResponse > { const inputOptions = { - value: getComponentName(getComponentPath(this.sourceFsPath)), + value: getComponentName(await getComponentPath(this.sourceFsPath)), placeHolder: nls.localize(RENAME_INPUT_PLACEHOLDER), promopt: nls.localize(RENAME_INPUT_PROMPT) } as vscode.InputBoxOptions; @@ -83,7 +83,7 @@ export class GetComponentName } async function renameComponent(sourceFsPath: string, newName: string) { - const componentPath = getComponentPath(sourceFsPath); + const componentPath = await getComponentPath(sourceFsPath); const componentName = getComponentName(componentPath); await checkForDuplicateName(componentPath, newName); const items = await fs.promises.readdir(componentPath); @@ -105,8 +105,8 @@ async function renameComponent(sourceFsPath: string, newName: string) { notificationService.showWarningMessage(nls.localize(RENAME_WARNING)); } -function getComponentPath(sourceFsPath: string): string { - const stats = fs.statSync(sourceFsPath); +async function getComponentPath(sourceFsPath: string): Promise { + const stats = await fs.promises.stat(sourceFsPath); return stats.isFile() ? path.dirname(sourceFsPath) : sourceFsPath; } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 8c77d0f574..013e4bbbf0 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -16,16 +16,18 @@ const itemsInPage = ['_test_', 'page.auradoc', 'page.cmp', 'page.cmp-meta.xml', const env = sinon.createSandbox(); let renameStub: sinon.SinonStub; let statStub: sinon.SinonStub; +let readdirStub: sinon.SinonStub; describe('Force Rename Lightning Component', () => { describe('Happy Path Unit Test', () => { beforeEach(() => { renameStub = env.stub(fs.promises, 'rename').resolves(undefined); - statStub = env.stub(fs, 'statSync').returns({ + statStub = env.stub(fs.promises, 'stat').resolves({ isFile: () => { return false; } }); + readdirStub = env.stub(fs.promises, 'readdir'); }); afterEach(() => { @@ -34,7 +36,7 @@ describe('Force Rename Lightning Component', () => { it('should rename the files and folder with new name under the same path', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); @@ -53,7 +55,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with LWC component', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves(itemsInHero); @@ -67,7 +69,7 @@ describe('Force Rename Lightning Component', () => { it('should only rename the files and folder that have same name with Aura component', async () => { const sourceUri = vscode.Uri.joinPath(auraPath, auraComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves(itemsInPage); @@ -81,7 +83,7 @@ describe('Force Rename Lightning Component', () => { it('should show the warning message once rename is done', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); @@ -95,14 +97,15 @@ describe('Force Rename Lightning Component', () => { }); }); - describe('Exception handling', () => { + describe('Exception and corner cases handling', () => { beforeEach(() => { renameStub = env.stub(fs.promises, 'rename').resolves(undefined); - statStub = env.stub(fs, 'statSync').returns({ + statStub = env.stub(fs.promises, 'stat').resolves({ isFile: () => { return false; } }); + readdirStub = env.stub(fs.promises, 'readdir'); }); afterEach(() => { @@ -111,7 +114,7 @@ describe('Force Rename Lightning Component', () => { it('should get trimmed component name if new component input has leading or trailing spaces', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); @@ -128,7 +131,7 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text only contains white spaces', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); @@ -142,7 +145,7 @@ describe('Force Rename Lightning Component', () => { it('should not rename when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); @@ -156,22 +159,22 @@ describe('Force Rename Lightning Component', () => { it('should not show warning message when input text is empty', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); - const warningSpy = env.spy(notificationService, 'showWarningMessage'); + const showWarningMessageSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', data: {} }); - expect(warningSpy.callCount).to.equal(0); + expect(showWarningMessageSpy.callCount).to.equal(0); }); it('should enforce unique component name under LWC and Aura and show error message for duplicate name', async () => { const sourceUri = vscode.Uri.joinPath(lwcPath, lwcComponent); - env.stub(fs.promises, 'readdir') + readdirStub .onFirstCall().resolves([lwcComponent]) .onSecondCall().resolves([]); let exceptionThrown = false; @@ -188,38 +191,38 @@ describe('Force Rename Lightning Component', () => { expect(renameStub.callCount).to.equal(0); }); }); -}); -describe ('#isNameMatch', () => { - it('should return true if file name and component name match for essential LWC files', () => { - const componentName = 'hero'; - const componentPath = path.join(lwcPath.fsPath, lwcComponent); - expect(isNameMatch(itemsInHero[1], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInHero[2], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInHero[3], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInHero[4], componentName, componentPath)).to.equal(true); - }); + describe ('#isNameMatch', () => { + it('should return true if file name and component name match for essential LWC files', () => { + const componentName = 'hero'; + const componentPath = path.join(lwcPath.fsPath, lwcComponent); + expect(isNameMatch(itemsInHero[1], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[2], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[3], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInHero[4], componentName, componentPath)).to.equal(true); + }); - it('should return true of file name and component name match for essential Aura files', () => { - const componentName = 'page'; - const componentPath = path.join(auraPath.fsPath, auraComponent); - expect(isNameMatch(itemsInPage[1], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[2], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[3], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[4], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[5], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[6], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[7], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[8], componentName, componentPath)).to.equal(true); - expect(isNameMatch(itemsInPage[9], componentName, componentPath)).to.equal(true); - }); + it('should return true of file name and component name match for essential Aura files', () => { + const componentName = 'page'; + const componentPath = path.join(auraPath.fsPath, auraComponent); + expect(isNameMatch(itemsInPage[1], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[2], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[3], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[4], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[5], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[6], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[7], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[8], componentName, componentPath)).to.equal(true); + expect(isNameMatch(itemsInPage[9], componentName, componentPath)).to.equal(true); + }); - it('should return false if file type is not in LWC or Aura or file name and component name do not match', () => { - const lwcComponentPath = path.join(lwcPath.fsPath, lwcComponent); - const auraComponentPath = path.join(auraPath.fsPath, auraComponent); - expect(isNameMatch('hero.jpg', 'hero', lwcComponentPath)).to.equal(false); - expect(isNameMatch('hero1.css', 'hero', lwcComponentPath)).to.equal(false); - expect(isNameMatch('page.jpg', 'page', auraComponentPath)).to.equal(false); - expect(isNameMatch('page1.css', 'hero', auraComponentPath)).to.equal(false); + it('should return false if file type is not in LWC or Aura or file name and component name do not match', () => { + const lwcComponentPath = path.join(lwcPath.fsPath, lwcComponent); + const auraComponentPath = path.join(auraPath.fsPath, auraComponent); + expect(isNameMatch('hero.jpg', 'hero', lwcComponentPath)).to.equal(false); + expect(isNameMatch('hero1.css', 'hero', lwcComponentPath)).to.equal(false); + expect(isNameMatch('page.jpg', 'page', auraComponentPath)).to.equal(false); + expect(isNameMatch('page1.css', 'hero', auraComponentPath)).to.equal(false); + }); }); }); From 64d44164623d351b30c6d143476950ebd5c63e7c Mon Sep 17 00:00:00 2001 From: flora lan Date: Thu, 24 Mar 2022 16:43:21 -0700 Subject: [PATCH 25/25] fix: showWarningMessageSpy --- .../commands/forceRenameLightningComponent.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts index 013e4bbbf0..f5953e7a18 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceRenameLightningComponent.test.ts @@ -87,13 +87,13 @@ describe('Force Rename Lightning Component', () => { .onFirstCall().resolves([]) .onSecondCall().resolves([]) .onThirdCall().resolves([itemsInHero[1]]); - const warningSpy = env.spy(notificationService, 'showWarningMessage'); + const showWarningMessageSpy = env.spy(notificationService, 'showWarningMessage'); const executor = new RenameLwcComponentExecutor(sourceUri.fsPath); await executor.run({ type: 'CONTINUE', data: {name: 'hero1'} }); - expect(warningSpy.callCount).to.equal(1); + expect(showWarningMessageSpy.callCount).to.equal(1); }); });