Skip to content

Commit

Permalink
Merge pull request #495 from usethesource/feature/rename-refactoring/has
Browse files Browse the repository at this point in the history
Support `has` in rename refactoring
  • Loading branch information
toinehartman authored Nov 8, 2024
2 parents 12c303a + d964b20 commit b7c9012
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.exceptions.FactTypeUseException;
Expand Down Expand Up @@ -204,15 +205,15 @@ public InterruptibleFuture<IList> getDocumentSymbols(IConstructor module) {
}


public InterruptibleFuture<IList> getRename(ITree module, Position cursor, Set<ISourceLocation> workspaceFolders, Function<ISourceLocation, PathConfig> getPathConfig, String newName, ColumnMaps columns) {
public InterruptibleFuture<ITuple> getRename(ITree module, Position cursor, Set<ISourceLocation> workspaceFolders, Function<ISourceLocation, PathConfig> getPathConfig, String newName, ColumnMaps columns) {
var moduleLocation = TreeAdapter.getLocation(module);
Position pos = Locations.toRascalPosition(moduleLocation, cursor, columns);
var cursorTree = TreeAdapter.locateLexical(module, pos.getLine(), pos.getCharacter());

return runEvaluator("Rascal rename", semanticEvaluator, eval -> {
try {
IFunction rascalGetPathConfig = eval.getFunctionValueFactory().function(getPathConfigType, (t, u) -> addResources(getPathConfig.apply((ISourceLocation) t[0])));
return (IList) eval.call("rascalRenameSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig);
return (ITuple) eval.call("rascalRenameSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig);
} catch (Throw e) {
if (e.getException() instanceof IConstructor) {
var exception = (IConstructor)e.getException();
Expand All @@ -231,7 +232,7 @@ public InterruptibleFuture<IList> getRename(ITree module, Position cursor, Set<I
}
throw e;
}
}, VF.list(), exec, false, client);
}, VF.tuple(VF.list(), VF.map()), exec, false, client);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Expand Down Expand Up @@ -83,7 +82,6 @@
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
Expand All @@ -94,7 +92,6 @@
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.BaseWorkspaceService;
import org.rascalmpl.vscode.lsp.IBaseLanguageClient;
Expand All @@ -107,8 +104,8 @@
import org.rascalmpl.vscode.lsp.util.CodeActions;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.DocumentChanges;
import org.rascalmpl.vscode.lsp.util.FoldingRanges;
import org.rascalmpl.vscode.lsp.util.DocumentSymbols;
import org.rascalmpl.vscode.lsp.util.FoldingRanges;
import org.rascalmpl.vscode.lsp.util.SemanticTokenizer;
import org.rascalmpl.vscode.lsp.util.Versioned;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;
Expand All @@ -117,6 +114,7 @@
import org.rascalmpl.vscode.lsp.util.locations.impl.TreeSearch;

import io.usethesource.vallang.IList;
import io.usethesource.vallang.IMap;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;

Expand Down Expand Up @@ -301,8 +299,12 @@ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
.thenApply(Versioned::get)
.handle((t, r) -> (t == null ? (file.getMostRecentTree().get()) : t))
.thenCompose(tr -> rascalServices.getRename(tr, params.getPosition(), workspaceFolders, facts::getPathConfig, params.getNewName(), columns).get())
.thenApply(c -> new WorkspaceEdit(DocumentChanges.translateDocumentChanges(this, c)))
;
.thenApply(t -> {
WorkspaceEdit wsEdit = new WorkspaceEdit();
wsEdit.setDocumentChanges(DocumentChanges.translateDocumentChanges(this, (IList) t.get(0)));
wsEdit.setChangeAnnotations(DocumentChanges.translateChangeAnnotations((IMap) t.get(1)));
return wsEdit;
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.AnnotatedTextEdit;
import org.eclipse.lsp4j.ChangeAnnotation;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.DeleteFile;
import org.eclipse.lsp4j.Range;
Expand All @@ -43,10 +46,13 @@
import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap;
import org.rascalmpl.vscode.lsp.util.locations.Locations;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.IMap;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;

/**
Expand Down Expand Up @@ -89,8 +95,13 @@ public static List<Either<TextDocumentEdit, ResourceOperation>> translateDocumen

private static List<TextEdit> translateTextEdits(final IBaseTextDocumentService docService, IList edits) {
return edits.stream()
.map(e -> (IConstructor) e)
.map(c -> new TextEdit(locationToRange(docService, (ISourceLocation) c.get("range")), ((IString) c.get("replacement")).getValue()))
.map(IConstructor.class::cast)
.map(c -> {
var kw = c.asWithKeywordParameters();
return kw.hasParameter("annotation")
? new AnnotatedTextEdit(locationToRange(docService, (ISourceLocation) c.get("range")), ((IString) c.get("replacement")).getValue(), ((IString) kw.getParameter("annotation")).getValue())
: new TextEdit(locationToRange(docService, (ISourceLocation) c.get("range")), ((IString) c.get("replacement")).getValue());
})
.collect(Collectors.toList());
}

Expand All @@ -102,4 +113,19 @@ private static Range locationToRange(final IBaseTextDocumentService docService,
private static String getFileURI(IConstructor edit, String label) {
return ((ISourceLocation) edit.get(label)).getURI().toString();
}

public static Map<String, ChangeAnnotation> translateChangeAnnotations(IMap annos) {
return annos.stream()
.map(ITuple.class::cast)
.map(entry -> {
String annoId = ((IString) entry.get(0)).getValue();
ChangeAnnotation anno = new ChangeAnnotation();
IConstructor c = (IConstructor) entry.get(1);
anno.setLabel(((IString) c.get("label")).getValue());
anno.setDescription(((IString) c.get("description")).getValue());
anno.setNeedsConfirmation(((IBool) c.get("needsConfirmation")).getValue());
return Map.entry(annoId, anno);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}
58 changes: 44 additions & 14 deletions rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ extend lang::rascal::lsp::refactor::Exception;
import lang::rascal::lsp::refactor::Util;
import lang::rascal::lsp::refactor::WorkspaceInfo;

import analysis::diff::edits::TextEdits;
import lang::rascal::lsp::refactor::TextEdits;

import util::FileSystem;
import util::Maybe;
import util::Monitor;
import util::Reflective;

alias Edits = tuple[list[DocumentEdit], map[ChangeAnnotationId, ChangeAnnotation]];

// Rascal compiler-specific extension
void throwAnyErrors(list[ModuleMessages] mmsgs) {
for (mmsg <- mmsgs) {
Expand Down Expand Up @@ -190,7 +192,7 @@ private set[IllegalRenameReason] rascalCollectIllegalRenames(WorkspaceInfo ws, s
private str rascalEscapeName(str name) = name in getRascalReservedIdentifiers() ? "\\<name>" : name;
// Find the smallest trees of defined non-terminal type with a source location in `useDefs`
private set[loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs) {
private map[loc, loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs) {
map[loc, loc] useDefNameAt = ();
useDefsToDo = useDefs;
visit(m.top) {
Expand All @@ -206,7 +208,7 @@ private set[loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs) {
throw unsupportedRename("Rename unsupported", issues={<l, "Cannot find the name for this definition in <m.src.top>."> | l <- useDefsToDo});
}
return range(useDefNameAt);
return useDefNameAt;
}
Maybe[loc] rascalLocationOfName(Name n) = just(n.src);
Expand All @@ -227,16 +229,24 @@ Maybe[loc] rascalLocationOfName(Nonterminal nt) = just(nt.src);
Maybe[loc] rascalLocationOfName(NonterminalLabel l) = just(l.src);
default Maybe[loc] rascalLocationOfName(Tree t) = nothing();
private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) {
if (reasons := rascalCollectIllegalRenames(ws, m, defs, uses, name), reasons != {}) {
private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[RenameLocation] defs, set[RenameLocation] uses, str name) {
if (reasons := rascalCollectIllegalRenames(ws, m, defs.l, uses.l, name), reasons != {}) {
return <reasons, []>;
}
replaceName = rascalEscapeName(name);
return <{}, [replace(l, replaceName) | l <- rascalFindNamesInUseDefs(m, defs + uses)]>;
set[RenameLocation] renames = defs + uses;
set[loc] renameLocs = renames.l;
map[loc, loc] namesAt = rascalFindNamesInUseDefs(m, renameLocs);
return <{}, [{just(annotation), *_} := renames[l]
? replace(namesAt[l], replaceName, annotation = annotation)
: replace(namesAt[l], replaceName)
| l <- renameLocs]>;
}
private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[loc] defs, set[loc] uses, str name) =
private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[RenameLocation] defs, set[RenameLocation] uses, str name) =
computeTextEdits(ws, parseModuleWithSpacesCached(moduleLoc), defs, uses, name);
private bool rascalIsFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) {
Expand Down Expand Up @@ -382,7 +392,7 @@ private Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) {
rel[loc field, loc container] fields = {<fieldLoc, containerLoc>
| /Tree t := parseModuleWithSpacesCached(cursorLoc.top)
, just(<containerLoc, fieldLocs, _>) := rascalGetFieldLocs(cursorName, t)
, just(<containerLoc, fieldLocs, _>) := rascalGetFieldLocs(cursorName, t) || just(<containerLoc, fieldLocs>) := rascalGetHasLocs(cursorName, t)
, loc fieldLoc <- fieldLocs
};
Expand Down Expand Up @@ -508,8 +518,8 @@ private bool rascalContainsName(loc l, str name) {
2. It does not change the semantics of the application.
3. It does not change definitions outside of the current workspace.
}
list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig)
= job("renaming <cursorT> to <newName>", list[DocumentEdit](void(str, int) step) {
Edits rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig)
= job("renaming <cursorT> to <newName>", Edits(void(str, int) step) {
loc cursorLoc = cursorT.src;
str cursorName = "<cursorT>";
Expand Down Expand Up @@ -572,14 +582,34 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s
}
step("collecting uses of \'<cursorName>\'", 1);
<defs, uses, getRenames> = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName);
rel[loc file, loc defines] defsPerFile = {<d.top, d> | d <- defs};
rel[loc file, loc uses] usesPerFile = {<u.top, u> | u <- uses};
map[ChangeAnnotationId, ChangeAnnotation] changeAnnotations = ();
ChangeAnnotationRegister registerChangeAnnotation = ChangeAnnotationId(str label, str description, bool needsConfirmation) {
ChangeAnnotationId makeKey(str label, int suffix) = "<label>_<suffix>";
int suffix = 1;
while (makeKey(label, suffix) in changeAnnotations) {
suffix += 1;
}
ChangeAnnotationId id = makeKey(label, suffix);
changeAnnotations[id] = changeAnnotation(label, description, needsConfirmation);
return id;
};
set[RenameLocation] registerChangeAnnotations(set[RenameLocation] locs, str label, str description = "These changes are required for a correct renaming. They can be previewed here, but it is not advised to disable them.", bool needsConfirmation = false) =
{<r, nothing() := maybeAnn ? just(registerChangeAnnotation(label, description, needsConfirmation)) : maybeAnn> | <r, maybeAnn> <- locs};
<defs, uses, getRenames> = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, registerChangeAnnotation);
rel[loc file, RenameLocation defines] defsPerFile = {<d.l.top, d> | d <- registerChangeAnnotations(defs, "Definitions")};
rel[loc file, RenameLocation uses] usesPerFile = {<u.l.top, u> | u <- registerChangeAnnotations(uses, "References")};
set[loc] \files = defsPerFile.file + usesPerFile.file;
step("checking rename validity", 1);
map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults =
(file: <reasons, edits> | file <- \files, <reasons, edits> := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName));
Expand All @@ -591,7 +621,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s
list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults];
list[DocumentEdit] renames = [renamed(from, to) | <from, to> <- getRenames(newName)];
return changes + renames;
return <changes + renames, changeAnnotations>;
}, totalWork = 6);
//// WORKAROUNDS
Expand Down
40 changes: 40 additions & 0 deletions rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/TextEdits.rsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@license{
Copyright (c) 2018-2023, NWO-I CWI and Swat.engineering
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
}
module lang::rascal::lsp::refactor::TextEdits

extend analysis::diff::edits::TextEdits;

alias ChangeAnnotationId = str;

data ChangeAnnotation
= changeAnnotation(str label, str description, bool needsConfirmation)
;

data TextEdit(ChangeAnnotationId annotation = "");

alias ChangeAnnotationRegister =
ChangeAnnotationId(str label, str description, bool needsConfirmation);
Loading

0 comments on commit b7c9012

Please sign in to comment.