Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/services/refactorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
Refactor,
RefactorContext,
RefactorEditInfo,
FileTextChanges,
} from "./_namespaces/ts.js";
import { refactorKindBeginsWith } from "./_namespaces/ts.refactor.js";
import { getEditsForFileRename } from '../services/refactors/autoRenamePlugin.js';

// A map with the refactor code as key, the refactor itself as value
// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want
Expand Down Expand Up @@ -35,3 +37,34 @@ export function getEditsForRefactor(context: RefactorContext, refactorName: stri
const refactor = refactors.get(refactorName);
return refactor && refactor.getEditsForAction(context, actionName, interactiveRefactorArguments);
}

/** @internal */
export function autoRenameRefactor(
fileName: string,
newFileName: string,
program: ts.Program
): RefactorEditInfo | undefined {
const edits: FileTextChanges[] = getEditsForFileRename(fileName, newFileName, program);
if (edits.length === 0) return;
return { edits };
}

registerRefactor("autoRename", {
kinds: ["refactor.autoRename"],
getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] {
const { fileName, newFileName } = context;
if (!fileName || !newFileName) return [];

return [{
name: "autoRenameFunction",
description: "Rename function to match the file name",
actions: [{
name: "autoRenameFunctionAction",
description: "Automatically rename the function to match the file name",
}],
}];
},
getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined {
return autoRenameRefactor(context.fileName, context.newFileName!, context.program);
},
});
41 changes: 41 additions & 0 deletions src/services/refactors/autoRenamePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as ts from "../_namespaces/ts";

export function getEditsForFileRename(
oldFilePath: string,
newFilePath: string,
sourceFile: ts.SourceFile
): ts.FileTextChanges[] {
const edits: ts.FileTextChanges[] = [];

// Extract function name from old and new file paths
const oldFileName = ts.getBaseFileName(oldFilePath).replace(/\.[jt]sx?$/, "");
const newFileName = ts.getBaseFileName(newFilePath).replace(/\.[jt]sx?$/, "");

if (!oldFileName || !newFileName) return edits;

// Traverse the source file to find a matching function
function visit(node: ts.Node) {
if (
ts.isFunctionDeclaration(node) &&
node.name?.text === oldFileName
) {
edits.push({
fileName: sourceFile.fileName,
textChanges: [
{
newText: newFileName,
span: {
start: node.name.getStart(),
length: node.name.getWidth(),
},
},
],
});
}

ts.forEachChild(node, visit);
}

visit(sourceFile);
return edits;
}
83 changes: 83 additions & 0 deletions tests/cases/fourslash/autoRenamePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/// <reference path="fourslash.ts" />

// Test Case: Rename Function and Update Imports

// Step 1: Create an initial file "myFunction.ts" with named, default exports and arrow function
// @filename: myFunction.ts
//// export function myFunction() {}
//// export const myFunc = () => {};
//// export default function defaultFunction() {}

// Step 2: Perform file renaming from "myFunction.ts" to "myNewFunction.ts"
goTo.file("myFunction.ts");
renameFile("myFunction.ts", "myNewFunction.ts");

// Verify that the content of the current file reflects the correct function name change
verify.currentFileContentIs(`export function myNewFunction() {}
export const myFunc = () => {};
export default function defaultFunction() {}`);

// Step 3: Verify the imported path in other files

// @filename: anotherFile.ts
//// import { myFunction } from './myFunction';
//// import myDefaultFunction from './myFunction';

goTo.file("anotherFile.ts");

// After renaming, the import paths and bindings should be updated correctly
verify.currentFileContentIs(`import { myFunction } from './myNewFunction';
import myDefaultFunction from './myNewFunction';`);

verify.importBindingChange("myFunction", "myNewFunction"); // Check that the named function is renamed correctly
verify.importBindingChange("myDefaultFunction", "defaultFunction"); // Default function name should not change

// Step 4: Edge Case - Renaming a class

// Initial file: myClass.ts
//// export class MyClass {}
//// export default class MyClassDefault {}

goTo.file("myClass.ts");
renameFile("myClass.ts", "myNewClass.ts");

// Verify that the class name and file name are updated correctly
verify.currentFileContentIs(`export class MyNewClass {}
export default class MyClassDefault {}`);

// Step 5: Verify imports in another file for the class

// @filename: anotherFileWithClass.ts
//// import { MyClass } from './myClass';
//// import MyClassDefault from './myClass';

goTo.file("anotherFileWithClass.ts");

// Verify that the import paths and bindings are updated correctly after renaming
verify.currentFileContentIs(`import { MyClass } from './myNewClass';
import MyClassDefault from './myNewClass';`);

verify.importBindingChange("MyClass", "MyNewClass"); // Check that the class name is renamed correctly
verify.importBindingChange("MyClassDefault", "MyClassDefault"); // Default class name should remain the same

// Step 6: Edge Case - Renaming an arrow function

// Initial file: myArrowFunction.ts
//// export const myArrowFunc = () => {};

goTo.file("myArrowFunction.ts");
renameFile("myArrowFunction.ts", "myNewArrowFunction.ts");

// Verify the renamed arrow function is updated correctly
verify.currentFileContentIs(`export const myNewArrowFunc = () => {};`);

// Step 7: Verify imports for the renamed arrow function

// @filename: anotherFileWithArrow.ts
//// import { myArrowFunc } from './myArrowFunction';

goTo.file("anotherFileWithArrow.ts");

// Verify the import path and binding name after renaming the arrow function
verify.currentFileContentIs(`import { myArrowFunc } from './myNewArrowFunction';`);
verify.importBindingChange("myArrowFunc", "myNewArrowFunc"); // Verify the arrow function name change