From 7349ae125a8aba83469cedf342b47432c32871bb Mon Sep 17 00:00:00 2001 From: vedant Date: Fri, 5 Sep 2025 23:02:06 +0530 Subject: [PATCH 1/2] Fix 'Changes overlap' error in move to file refactoring - Add updateImportsInTargetFile() function to handle import cleanup - Use ImportAdder API to coordinate import changes and prevent overlapping edits - Resolves issue where moving code to files with existing imports failed Fixes #62378 --- src/services/refactors/moveToFile.ts | 52 +++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index 603f049a0ddc7..5eb0f4991da1b 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -257,7 +257,10 @@ export function getNewStatementsAndRemoveFromOldFile( deleteUnusedOldImports(oldFile, toMove.all, usage.unusedImportsFromOldFile, importAdderForOldFile); importAdderForOldFile.writeFixes(changes, quotePreference); deleteMovedStatements(oldFile, toMove.ranges, changes); - updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, targetFile.fileName, quotePreference); + if (isFullSourceFile(targetFile)) { + updateImportsInTargetFile(program, oldFile, targetFile, usage.movedSymbols, importAdderForNewFile); + } + updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, targetFile.fileName, quotePreference, isFullSourceFile(targetFile) ? targetFile : undefined); addExportsInOldFile(oldFile, usage.targetFileImportsFromOldFile, changes, useEsModuleSyntax); addTargetFileImports(oldFile, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, checker, program, importAdderForNewFile); if (!isFullSourceFile(targetFile) && prologueDirectives.length) { @@ -331,6 +334,37 @@ export function addExportsInOldFile(oldFile: SourceFile, targetFileImportsFromOl }); } +function updateImportsInTargetFile( + program: Program, + oldFile: SourceFile, + targetFile: SourceFile, + movedSymbols: Set, + importAdder: codefix.ImportAdder, +): void { + const checker = program.getTypeChecker(); + for (const statement of targetFile.statements) { + forEachImportInStatement(statement, importNode => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)); + if (moduleSymbol !== oldFile.symbol) return; + + forEachAliasDeclarationInImportOrRequire(importNode, decl => { + if (!decl.name || !isIdentifier(decl.name)) return; + + const symbol = isBindingElement(decl) + ? getPropertySymbolFromBindingElement(checker, decl as ObjectBindingElementWithoutPropertyName) + : checker.getSymbolAtLocation(decl.name); + + if (symbol) { + const resolvedSymbol = skipAlias(symbol, checker); + if (movedSymbols.has(resolvedSymbol)) { + importAdder.removeExistingImport(decl); + } + } + }); + }); + } +} + function updateImportsInOtherFiles( changes: textChanges.ChangeTracker, program: Program, @@ -339,10 +373,11 @@ function updateImportsInOtherFiles( movedSymbols: Set, targetFileName: string, quotePreference: QuotePreference, + targetSourceFile?: SourceFile, ): void { const checker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { - if (sourceFile === oldFile) continue; + if (sourceFile === oldFile || (targetSourceFile && sourceFile.fileName === targetSourceFile.fileName)) continue; for (const statement of sourceFile.statements) { forEachImportInStatement(statement, importNode => { if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; @@ -736,6 +771,8 @@ export interface UsageInfo { readonly oldImportsNeededByTargetFile: Map; /** Subset of oldImportsNeededByTargetFile that are will no longer be used in the old file. */ readonly unusedImportsFromOldFile: Set; + /** Imports in the target file that should be deleted */ + readonly importsToDeleteFromTargetFile: Set; } /** @internal */ @@ -870,6 +907,7 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], const movedSymbols = new Set(); const oldImportsNeededByTargetFile = new Map(); const targetFileImportsFromOldFile = new Map(); + const importsToDeleteFromTargetFile = new Set(); const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx(toMove)); @@ -921,12 +959,18 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], } forEachReference(statement, checker, enclosingRange, (symbol, isValidTypeOnlyUseSite) => { - if (movedSymbols.has(symbol)) oldFileImportsFromTargetFile.set(symbol, isValidTypeOnlyUseSite); + if (movedSymbols.has(symbol)) { + oldFileImportsFromTargetFile.set(symbol, isValidTypeOnlyUseSite); + const importStatement = find(symbol.declarations, d => isTopLevelDeclaration(d) && getSourceFileOfNode(d) === oldFile && isPureImport(d.parent)); + if (importStatement) { + importsToDeleteFromTargetFile.add(importStatement.parent as SupportedImport); + } + } unusedImportsFromOldFile.delete(symbol); }); } - return { movedSymbols, targetFileImportsFromOldFile, oldFileImportsFromTargetFile, oldImportsNeededByTargetFile, unusedImportsFromOldFile }; + return { movedSymbols, targetFileImportsFromOldFile, oldFileImportsFromTargetFile, oldImportsNeededByTargetFile, unusedImportsFromOldFile, importsToDeleteFromTargetFile }; function getJsxNamespaceSymbol(containsJsx: Node | undefined) { if (containsJsx === undefined) { From c433d13553dd8aefb2f3dcf7e21e54f454b020ab Mon Sep 17 00:00:00 2001 From: vedant Date: Sat, 6 Sep 2025 01:37:21 +0530 Subject: [PATCH 2/2] Fix formatting in updateImportsInTargetFile function --- src/services/refactors/moveToFile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index 5eb0f4991da1b..46c083ea29a3d 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -349,7 +349,7 @@ function updateImportsInTargetFile( forEachAliasDeclarationInImportOrRequire(importNode, decl => { if (!decl.name || !isIdentifier(decl.name)) return; - + const symbol = isBindingElement(decl) ? getPropertySymbolFromBindingElement(checker, decl as ObjectBindingElementWithoutPropertyName) : checker.getSymbolAtLocation(decl.name);