Skip to content

Commit 7d9cca3

Browse files
authored
Merge pull request #759 from usethesource/fix/715-robust-module-rename
More robust module renames
2 parents 8c8dd7c + 9de3225 commit 7d9cca3

File tree

3 files changed

+161
-75
lines changed

3 files changed

+161
-75
lines changed

rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/refactor/rename/Modules.rsc

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,31 +184,55 @@ list[TextEdit] getChanges(loc f, PathConfig wsProject, lrel[str oldName, str new
184184
return [];
185185
}
186186
187-
set[tuple[str, str, PathConfig]] getQualifiedNameChanges(loc old, loc new, PathConfig(loc) getPathConfig) {
187+
set[tuple[str, str, PathConfig]] getQualifiedNameChanges(loc old, loc new, PathConfig(loc) getPathConfig, void(Message) msg) {
188188
PathConfig oldPcfg = getPathConfig(old);
189189
PathConfig newPcfg = getPathConfig(new);
190-
if (isFile(new) && endsWith(new.file, ".rsc")) {
191-
return {<safeRelativeModuleName(old, oldPcfg), safeRelativeModuleName(new, newPcfg), newPcfg>};
190+
// Moved a single file
191+
if (isFile(new)) {
192+
if(new.extension == "rsc") {
193+
// Moved a single Rascal module
194+
try {
195+
return {<safeRelativeModuleName(old, oldPcfg), safeRelativeModuleName(new, newPcfg), newPcfg>};
196+
} catch PathNotFound(loc f): {
197+
msg(error("Cannot rename references to this file, since it was moved outside of the project\'s source directories.", f));
198+
return {};
199+
}
200+
} else {
201+
// Renamed from .rsc to a non-Rascal extension
202+
str reason = new.extension == ""
203+
? "its extension was removed"
204+
: "it was renamed to the non-Rascal extension \'<new.extension>\'"
205+
;
206+
207+
msg(error("Cannot rename references to thie file, since <reason>.", new));
208+
return {};
209+
}
210+
}
211+
212+
// Moved directories
213+
set[tuple[str, str, PathConfig]] moves = {};
214+
for (loc newFile <- find(new, "rsc")
215+
, loc relFilePath := relativize(new, newFile)
216+
, loc oldFile := old + relFilePath.path) {
217+
try {
218+
moves += <safeRelativeModuleName(oldFile, oldPcfg), safeRelativeModuleName(newFile, newPcfg), newPcfg>;
219+
} catch PathNotFound(loc f): {
220+
msg(error("Cannot rename references to this file, since it was moved outside of the project\'s source directories.", f));
221+
}
192222
}
193223
194-
return {
195-
<safeRelativeModuleName(oldFile, oldPcfg), safeRelativeModuleName(newFile, newPcfg), newPcfg>
196-
| loc newFile <- find(new, "rsc")
197-
, loc relFilePath := relativize(new, newFile)
198-
, loc oldFile := old + relFilePath.path
199-
};
224+
return moves;
200225
}
201226
202227
tuple[list[DocumentEdit], set[Message]] propagateModuleRenames(lrel[loc old, loc new] renames, set[loc] workspaceFolders, PathConfig(loc) getPathConfig) {
228+
set[Message] messages = {};
229+
void registerMessage(Message msg) { messages += msg; }
203230
lrel[str oldName, str newName, PathConfig pcfg] qualifiedNameChanges = [
204231
rename
205232
| <oldLoc, newLoc> <- renames
206-
, tuple[str, str, PathConfig] rename <- getQualifiedNameChanges(oldLoc, newLoc, getPathConfig)
233+
, tuple[str, str, PathConfig] rename <- getQualifiedNameChanges(oldLoc, newLoc, getPathConfig, registerMessage)
207234
];
208235
209-
set[Message] messages = {};
210-
void registerMessage(Message msg) { messages += msg; }
211-
212236
list[PathConfig] projectWithRenamedModule = qualifiedNameChanges.pcfg;
213237
set[DocumentEdit] edits = flatMap(workspaceFolders, set[DocumentEdit](loc wsFolder) {
214238
PathConfig wsFolderPcfg = getPathConfig(wsFolder);

rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/Modules.rsc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ POSSIBILITY OF SUCH DAMAGE.
2727
@bootstrapParser
2828
module lang::rascal::tests::rename::Modules
2929

30+
import IO;
31+
import List;
32+
import Set;
33+
34+
import lang::rascal::lsp::refactor::Rename;
3035
import lang::rascal::tests::rename::TestUtils;
36+
import lang::rascalcore::check::Checker;
3137

3238
test bool deepModule() = testRenameOccurrences({
3339
byText("some::path::to::Foo", "
@@ -157,3 +163,53 @@ test bool moduleExists() = testRenameOccurrences({
157163
byText("Foo", "", {0}),
158164
byText("foo::Foo", "", {})
159165
}, oldName = "Foo", newName = "foo::Foo");
166+
167+
test bool moduleRenameProducesEdits()
168+
= testProject({byText("Foo", "", {})},
169+
"moduleRenameProducesEdits",
170+
bool({TestModule foo}, loc testDir, PathConfig pcfg) {
171+
loc oldLoc = foo.file;
172+
loc newLoc = |<oldLoc.scheme>:///<oldLoc.parent.path>/nested/<oldLoc.file>|;
173+
174+
// VS Code moves first, and informs us afterwards
175+
move(foo.file, newLoc);
176+
177+
<edits, msgs> = rascalRenameModule([<foo.file, newLoc>], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; });
178+
throwMessagesIfError(msgs);
179+
return [changed(newLoc, [replace(_, "nested::Foo")])] := edits;
180+
}
181+
);
182+
183+
@expected{illegalRename}
184+
test bool moduleRenameWithoutExtension()
185+
= testProject({byText("Foo", "", {})},
186+
"moduleRenameWithoutExtension",
187+
bool({TestModule foo}, loc testDir, PathConfig pcfg) {
188+
loc oldLoc = foo.file;
189+
loc newLoc = |<oldLoc.scheme>:///<oldLoc.path[..-4]>|; // remove .rsc extension
190+
191+
// VS Code moves first, and informs us afterwards
192+
move(foo.file, newLoc);
193+
194+
<edits, msgs> = rascalRenameModule([<foo.file, newLoc>], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; });
195+
throwMessagesIfError(msgs);
196+
return [] := edits;
197+
}
198+
);
199+
200+
@expected{illegalRename}
201+
test bool moduleRenameOutsideSources()
202+
= testProject({byText("Foo", "", {})},
203+
"moduleRenameOutsideSources",
204+
bool({TestModule foo}, loc testDir, PathConfig pcfg) {
205+
loc oldLoc = foo.file;
206+
loc newLoc = |<oldLoc.scheme>:///<oldLoc.parent.parent.path>/<oldLoc.file>|;
207+
208+
// VS Code moves first, and informs us afterwards
209+
move(foo.file, newLoc);
210+
211+
<edits, msgs> = rascalRenameModule([<foo.file, newLoc>], toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; });
212+
throwMessagesIfError(msgs);
213+
return false;
214+
}
215+
);

rascal-lsp/src/main/rascal/lsp/lang/rascal/tests/rename/TestUtils.rsc

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC
8383
list[ModuleMessages] checkBefore = checkAll(root, ccfg);
8484
8585
// Back-up sources
86-
loc backupLoc = |memory://tests/backup|;
86+
loc backupLoc = |memory:///tests/backup|;
8787
remove(backupLoc, recursive = true);
8888
copy(root, backupLoc, recursive = true);
8989
@@ -118,76 +118,82 @@ bool expectEq(&T expected, &T actual, str epilogue = "") {
118118
return true;
119119
}
120120
121-
bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") {
122-
bool success = true;
123-
121+
bool testProject(set[TestModule] modules, str testName, bool(set[TestModule] mods, loc testDir, PathConfig pcfg) doCheck) {
122+
loc testDir = |unknown:///|;
124123
bool moduleExistsOnDisk = any(mmm <- modules, mmm is byLoc);
125-
for (mm <- modules, cursorOcc <- (mm.nameOccs - mm.skipCursors)) {
126-
loc testDir = |unknown:///|;
127-
if (moduleExistsOnDisk){
128-
testDir = cover([m.file.parent | m <- modules, m is byLoc]).parent;
129-
} else {
130-
// If none of the modules refers to an existing file, clear the test directory before writing files.
131-
str testName = "Test_<mm.name>_<cursorOcc>";
132-
testDir = |memory://tests/rename/<testName>|;
133-
remove(testDir);
134-
}
124+
if (moduleExistsOnDisk){
125+
testDir = cover([m.file.parent | m <- modules, m is byLoc]).parent;
126+
} else {
127+
// If none of the modules refers to an existing file, clear the test directory before writing files.
128+
testDir = |memory:///tests/rename/<testName>|;
129+
remove(testDir);
130+
}
135131
136-
pcfg = getTestPathConfig(testDir);
137-
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))};
132+
pcfg = getTestPathConfig(testDir);
133+
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))};
138134
139-
for (m <- modulesByLocation) {
140-
try {
141-
parse(#start[Module], m.file);
142-
} catch ParseError(l): {
143-
throw "Parse error in test module <m.file>: <l>";
144-
}
135+
for (m <- modulesByLocation) {
136+
try {
137+
parse(#start[Module], m.file);
138+
} catch ParseError(l): {
139+
throw "Parse error in test module <m.file>: <l>";
145140
}
141+
}
146142
147-
<cursor, focus> = findCursor([m.file | m <- modulesByLocation, m.name == mm.name][0], oldName, cursorOcc);
143+
// Do the actual work here
144+
bool result = doCheck(modulesByLocation, testDir, pcfg);
148145
149-
println("Renaming \'<oldName>\' from <focus[0].src>");
150-
<edits, msgs> = rascalRenameSymbol(cursor, focus, newName, toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; });
146+
if (!moduleExistsOnDisk) {
147+
remove(testDir);
148+
}
151149
152-
throwMessagesIfError(msgs);
150+
return result;
151+
}
153152
154-
renamesPerModule = (
155-
beforeRename: afterRename
156-
| renamed(oldLoc, newLoc) <- edits
157-
, beforeRename := safeRelativeModuleName(oldLoc, pcfg)
158-
, afterRename := safeRelativeModuleName(newLoc, pcfg)
159-
);
153+
bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") {
154+
bool success = true;
155+
for (mm <- modules, cursorOcc <- (mm.nameOccs - mm.skipCursors)) {
156+
success = success && testProject(modules, "Test_<mm.name>_<cursorOcc>", bool(set[TestModule] modulesByLocation, loc testDir, PathConfig pcfg) {
157+
<cursor, focus> = findCursor([m.file | m <- modulesByLocation, m.name == mm.name][0], oldName, cursorOcc);
158+
159+
println("Renaming \'<oldName>\' from <focus[0].src>");
160+
<edits, msgs> = rascalRenameSymbol(cursor, focus, newName, toSet(pcfg.srcs), PathConfig(loc _) { return pcfg; });
161+
162+
throwMessagesIfError(msgs);
163+
164+
renamesPerModule = (
165+
beforeRename: afterRename
166+
| renamed(oldLoc, newLoc) <- edits
167+
, beforeRename := safeRelativeModuleName(oldLoc, pcfg)
168+
, afterRename := safeRelativeModuleName(newLoc, pcfg)
169+
);
170+
171+
replacesPerModule = toMap({
172+
<name, occ>
173+
| changed(file, changes) <- edits
174+
, name := safeRelativeModuleName(file, pcfg)
175+
, locs := {c.range | c <- changes}
176+
, occ <- locsToOccs(parseModuleWithSpaces(file), oldName, locs)
177+
});
178+
179+
editsPerModule = (
180+
name : <occs, nameAfterRename>
181+
| srcDir <- pcfg.srcs
182+
, file <- find(srcDir, "rsc")
183+
, name := safeRelativeModuleName(file, pcfg)
184+
, occs := replacesPerModule[name] ? {}
185+
, nameAfterRename := renamesPerModule[name] ? name
186+
);
187+
188+
expectedEditsPerModule = (name: <m.nameOccs, m.newName> | m <- modulesByLocation, name := safeRelativeModuleName(m.file, pcfg));
189+
190+
if (expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor <focus[0].src> failed:")) {
191+
verifyTypeCorrectRenaming(testDir, edits, pcfg);
192+
return true;
193+
}
160194
161-
replacesPerModule = toMap({
162-
<name, occ>
163-
| changed(file, changes) <- edits
164-
, name := safeRelativeModuleName(file, pcfg)
165-
, locs := {c.range | c <- changes}
166-
, occ <- locsToOccs(parseModuleWithSpaces(file), oldName, locs)
195+
return false;
167196
});
168-
169-
editsPerModule = (
170-
name : <occs, nameAfterRename>
171-
| srcDir <- pcfg.srcs
172-
, file <- find(srcDir, "rsc")
173-
, name := safeRelativeModuleName(file, pcfg)
174-
, occs := replacesPerModule[name] ? {}
175-
, nameAfterRename := renamesPerModule[name] ? name
176-
);
177-
178-
expectedEditsPerModule = (name: <m.nameOccs, m.newName> | m <- modulesByLocation, name := safeRelativeModuleName(m.file, pcfg));
179-
180-
if (!expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor <focus[0].src> failed:")) {
181-
success = false;
182-
}
183-
184-
if (success) {
185-
verifyTypeCorrectRenaming(testDir, edits, pcfg);
186-
}
187-
188-
if (!moduleExistsOnDisk) {
189-
remove(testDir);
190-
}
191197
}
192198
193199
return success;
@@ -308,7 +314,7 @@ private tuple[Edits, set[int]] getEditsAndModule(str stmtsStr, int cursorAtOldNa
308314
'}";
309315
310316
// Write the file to disk (and clean up later) to easily emulate typical editor behaviour
311-
loc testDir = |memory://tests/rename/<moduleName>|;
317+
loc testDir = |memory:///tests/rename/<moduleName>|;
312318
remove(testDir);
313319
loc moduleFileName = testDir + "rascal" + "<moduleName>.rsc";
314320
writeFile(moduleFileName, moduleStr);

0 commit comments

Comments
 (0)