From ab1d0402f1cecdc89d9113c230099774d9278786 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Sep 2025 13:54:31 +0200 Subject: [PATCH 1/3] Factor out testing on projects in memory. --- .../lang/rascal/tests/rename/TestUtils.rsc | 126 +++++++++--------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc index 9f87d6976..16051b8dc 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc @@ -118,76 +118,82 @@ bool expectEq(&T expected, &T actual, str epilogue = "") { return true; } -bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") { - bool success = true; - +bool testProject(set[TestModule] modules, str testName, bool(set[TestModule] mods, loc testDir, PathConfig pcfg) doCheck) { + loc testDir = |unknown:///|; bool moduleExistsOnDisk = any(mmm <- modules, mmm is byLoc); - for (mm <- modules, cursorOcc <- (mm.nameOccs - mm.skipCursors)) { - loc testDir = |unknown:///|; - if (moduleExistsOnDisk){ - testDir = cover([m.file.parent | m <- modules, m is byLoc]).parent; - } else { - // If none of the modules refers to an existing file, clear the test directory before writing files. - str testName = "Test__"; - testDir = |memory://tests/rename/|; - remove(testDir); - } + if (moduleExistsOnDisk){ + testDir = cover([m.file.parent | m <- modules, m is byLoc]).parent; + } else { + // If none of the modules refers to an existing file, clear the test directory before writing files. + testDir = |memory://tests/rename/|; + remove(testDir); + } - pcfg = getTestPathConfig(testDir); - modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(m.name, storeTestModule(testDir, m.name, m.body), m.nameOccs, newName = m.newName, skipCursors = m.skipCursors))}; + pcfg = getTestPathConfig(testDir); + modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(m.name, storeTestModule(testDir, m.name, m.body), m.nameOccs, newName = m.newName, skipCursors = m.skipCursors))}; - for (m <- modulesByLocation) { - try { - parse(#start[Module], m.file); - } catch ParseError(l): { - throw "Parse error in test module : "; - } + for (m <- modulesByLocation) { + try { + parse(#start[Module], m.file); + } catch ParseError(l): { + throw "Parse error in test module : "; } + } - = findCursor([m.file | m <- modulesByLocation, m.name == mm.name][0], oldName, cursorOcc); + // Do the actual work here + bool result = doCheck(modulesByLocation, testDir, pcfg); - println("Renaming \'\' from "); - = rascalRenameSymbol(cursor, focus, newName, toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; }); + if (!moduleExistsOnDisk) { + remove(testDir); + } - throwMessagesIfError(msgs); + return result; +} - renamesPerModule = ( - beforeRename: afterRename - | renamed(oldLoc, newLoc) <- edits - , beforeRename := safeRelativeModuleName(oldLoc, pcfg) - , afterRename := safeRelativeModuleName(newLoc, pcfg) - ); +bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") { + bool success = true; + for (mm <- modules, cursorOcc <- (mm.nameOccs - mm.skipCursors)) { + success = success && testProject(modules, "Test__", bool(set[TestModule] modulesByLocation, loc testDir, PathConfig pcfg) { + = findCursor([m.file | m <- modulesByLocation, m.name == mm.name][0], oldName, cursorOcc); + + println("Renaming \'\' from "); + = rascalRenameSymbol(cursor, focus, newName, toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; }); + + throwMessagesIfError(msgs); + + renamesPerModule = ( + beforeRename: afterRename + | renamed(oldLoc, newLoc) <- edits + , beforeRename := safeRelativeModuleName(oldLoc, pcfg) + , afterRename := safeRelativeModuleName(newLoc, pcfg) + ); + + replacesPerModule = toMap({ + + | changed(file, changes) <- edits + , name := safeRelativeModuleName(file, pcfg) + , locs := {c.range | c <- changes} + , occ <- locsToOccs(parseModuleWithSpaces(file), oldName, locs) + }); + + editsPerModule = ( + name : + | srcDir <- pcfg.srcs + , file <- find(srcDir, "rsc") + , name := safeRelativeModuleName(file, pcfg) + , occs := replacesPerModule[name] ? {} + , nameAfterRename := renamesPerModule[name] ? name + ); + + expectedEditsPerModule = (name: | m <- modulesByLocation, name := safeRelativeModuleName(m.file, pcfg)); + + if (expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor failed:")) { + verifyTypeCorrectRenaming(testDir, edits, pcfg); + return true; + } - replacesPerModule = toMap({ - - | changed(file, changes) <- edits - , name := safeRelativeModuleName(file, pcfg) - , locs := {c.range | c <- changes} - , occ <- locsToOccs(parseModuleWithSpaces(file), oldName, locs) + return false; }); - - editsPerModule = ( - name : - | srcDir <- pcfg.srcs - , file <- find(srcDir, "rsc") - , name := safeRelativeModuleName(file, pcfg) - , occs := replacesPerModule[name] ? {} - , nameAfterRename := renamesPerModule[name] ? name - ); - - expectedEditsPerModule = (name: | m <- modulesByLocation, name := safeRelativeModuleName(m.file, pcfg)); - - if (!expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor failed:")) { - success = false; - } - - if (success) { - verifyTypeCorrectRenaming(testDir, edits, pcfg); - } - - if (!moduleExistsOnDisk) { - remove(testDir); - } } return success; From 689990f92f1a4760e702ddb15882d57301e9a331 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Sep 2025 15:03:03 +0200 Subject: [PATCH 2/3] Test some module renaming edge cases. --- .../rascal/lsp/refactor/rename/Modules.rsc | 50 ++++++++++++----- .../lsp/lang/rascal/tests/rename/Modules.rsc | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/refactor/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/refactor/rename/Modules.rsc index 702bf0bc3..d9c1e1805 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/refactor/rename/Modules.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/refactor/rename/Modules.rsc @@ -184,31 +184,55 @@ list[TextEdit] getChanges(loc f, PathConfig wsProject, lrel[str oldName, str new return []; } -set[tuple[str, str, PathConfig]] getQualifiedNameChanges(loc old, loc new, PathConfig(loc) getPathConfig) { +set[tuple[str, str, PathConfig]] getQualifiedNameChanges(loc old, loc new, PathConfig(loc) getPathConfig, void(Message) msg) { PathConfig oldPcfg = getPathConfig(old); PathConfig newPcfg = getPathConfig(new); - if (isFile(new) && endsWith(new.file, ".rsc")) { - return {}; + // Moved a single file + if (isFile(new)) { + if(new.extension == "rsc") { + // Moved a single Rascal module + try { + return {}; + } catch PathNotFound(loc f): { + msg(error("Cannot rename references to this file, since it was moved outside of the project\'s source directories.", f)); + return {}; + } + } else { + // Renamed from .rsc to a non-Rascal extension + str reason = new.extension == "" + ? "its extension was removed" + : "it was renamed to the non-Rascal extension \'\'" + ; + + msg(error("Cannot rename references to thie file, since .", new)); + return {}; + } + } + + // Moved directories + set[tuple[str, str, PathConfig]] moves = {}; + for (loc newFile <- find(new, "rsc") + , loc relFilePath := relativize(new, newFile) + , loc oldFile := old + relFilePath.path) { + try { + moves += ; + } catch PathNotFound(loc f): { + msg(error("Cannot rename references to this file, since it was moved outside of the project\'s source directories.", f)); + } } - return { - - | loc newFile <- find(new, "rsc") - , loc relFilePath := relativize(new, newFile) - , loc oldFile := old + relFilePath.path - }; + return moves; } tuple[list[DocumentEdit], set[Message]] propagateModuleRenames(lrel[loc old, loc new] renames, set[loc] workspaceFolders, PathConfig(loc) getPathConfig) { + set[Message] messages = {}; + void registerMessage(Message msg) { messages += msg; } lrel[str oldName, str newName, PathConfig pcfg] qualifiedNameChanges = [ rename | <- renames - , tuple[str, str, PathConfig] rename <- getQualifiedNameChanges(oldLoc, newLoc, getPathConfig) + , tuple[str, str, PathConfig] rename <- getQualifiedNameChanges(oldLoc, newLoc, getPathConfig, registerMessage) ]; - set[Message] messages = {}; - void registerMessage(Message msg) { messages += msg; } - list[PathConfig] projectWithRenamedModule = qualifiedNameChanges.pcfg; set[DocumentEdit] edits = flatMap(workspaceFolders, set[DocumentEdit](loc wsFolder) { PathConfig wsFolderPcfg = getPathConfig(wsFolder); diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/Modules.rsc index 1796575dc..39b9d5193 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/Modules.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/Modules.rsc @@ -27,7 +27,13 @@ POSSIBILITY OF SUCH DAMAGE. @bootstrapParser module lang::rascal::tests::rename::Modules +import IO; +import List; +import Set; + +import lang::rascal::lsp::refactor::Rename; import lang::rascal::tests::rename::TestUtils; +import lang::rascalcore::check::Checker; test bool deepModule() = testRenameOccurrences({ byText("some::path::to::Foo", " @@ -157,3 +163,53 @@ test bool moduleExists() = testRenameOccurrences({ byText("Foo", "", {0}), byText("foo::Foo", "", {}) }, oldName = "Foo", newName = "foo::Foo"); + +test bool moduleRenameProducesEdits() + = testProject({byText("Foo", "", {})}, + "moduleRenameProducesEdits", + bool({TestModule foo}, loc testDir, PathConfig pcfg) { + loc oldLoc = foo.file; + loc newLoc = |:////nested/|; + + // VS Code moves first, and informs us afterwards + move(foo.file, newLoc); + + = rascalRenameModule([], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; }); + throwMessagesIfError(msgs); + return [changed(newLoc, [replace(_, "nested::Foo")])] := edits; + } + ); + +@expected{illegalRename} +test bool moduleRenameWithoutExtension() + = testProject({byText("Foo", "", {})}, + "moduleRenameWithoutExtension", + bool({TestModule foo}, loc testDir, PathConfig pcfg) { + loc oldLoc = foo.file; + loc newLoc = |:///|; // remove .rsc extension + + // VS Code moves first, and informs us afterwards + move(foo.file, newLoc); + + = rascalRenameModule([], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; }); + throwMessagesIfError(msgs); + return [] := edits; + } + ); + +@expected{illegalRename} +test bool moduleRenameOutsideSources() + = testProject({byText("Foo", "", {})}, + "moduleRenameOutsideSources", + bool({TestModule foo}, loc testDir, PathConfig pcfg) { + loc oldLoc = foo.file; + loc newLoc = |:////|; + + // VS Code moves first, and informs us afterwards + move(foo.file, newLoc); + + = rascalRenameModule([], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; }); + throwMessagesIfError(msgs); + return false; + } + ); From 9de32258aae36f81b8f2bc91e2ad52ac1dfe1fa0 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Sep 2025 15:03:19 +0200 Subject: [PATCH 3/3] Fix test path literals. --- .../main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc index 16051b8dc..eab328d77 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc @@ -83,7 +83,7 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC list[ModuleMessages] checkBefore = checkAll(root, ccfg); // Back-up sources - loc backupLoc = |memory://tests/backup|; + loc backupLoc = |memory:///tests/backup|; remove(backupLoc, recursive = true); copy(root, backupLoc, recursive = true); @@ -125,7 +125,7 @@ bool testProject(set[TestModule] modules, str testName, bool(set[TestModule] mod testDir = cover([m.file.parent | m <- modules, m is byLoc]).parent; } else { // If none of the modules refers to an existing file, clear the test directory before writing files. - testDir = |memory://tests/rename/|; + testDir = |memory:///tests/rename/|; remove(testDir); } @@ -314,7 +314,7 @@ private tuple[Edits, set[int]] getEditsAndModule(str stmtsStr, int cursorAtOldNa '}"; // Write the file to disk (and clean up later) to easily emulate typical editor behaviour - loc testDir = |memory://tests/rename/|; + loc testDir = |memory:///tests/rename/|; remove(testDir); loc moduleFileName = testDir + "rascal" + ".rsc"; writeFile(moduleFileName, moduleStr);