From 00e3225301f60d4b1b2527e708a27ca1595c2937 Mon Sep 17 00:00:00 2001 From: Frotty Date: Mon, 10 Aug 2026 11:55:09 +0200 Subject: [PATCH] Fix module inlay hint source positions --- .../requests/InlayHintsRequest.java | 15 +++++++ .../tests/LspNativeFeaturesTests.java | 40 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/InlayHintsRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/InlayHintsRequest.java index 0929ea536..7e0f588c2 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/InlayHintsRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/InlayHintsRequest.java @@ -20,6 +20,7 @@ import de.peeeq.wurstscript.ast.ExprRealVal; import de.peeeq.wurstscript.ast.ExprStringVal; import de.peeeq.wurstscript.ast.FuncRef; +import de.peeeq.wurstscript.ast.ModuleInstanciation; import de.peeeq.wurstscript.ast.NameRef; import de.peeeq.wurstscript.attributes.names.FuncLink; import org.eclipse.lsp4j.InlayHint; @@ -68,6 +69,12 @@ public List execute(ModelManager modelManager) { todo.push(cu); while (!todo.isEmpty()) { Element e = todo.pop(); + // Module instantiations are compiler-generated copies of module bodies. Their children + // retain the source positions of the module definition, which are not positions in the + // document containing the `use` statement. + if (e instanceof ModuleInstanciation) { + continue; + } collectHints(hints, e); for (int i = e.size() - 1; i >= 0; i--) { todo.push(e.get(i)); @@ -125,6 +132,9 @@ private void addParameterHints(List hints, Arguments args, List typeFrequencies = typeFrequencies(paramTypes); for (int i = 0; i < count; i++) { Expr arg = args.get(i); + if (!isConcreteSourceInRequestedFile(arg)) { + continue; + } String paramName = paramNames.get(i); if (paramName == null || paramName.isEmpty()) { continue; @@ -143,6 +153,11 @@ private void addParameterHints(List hints, Arguments args, List typeFrequencies) { int score = 0; String argName = argumentName(arg); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LspNativeFeaturesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LspNativeFeaturesTests.java index 788729cec..0b0c3d169 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LspNativeFeaturesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LspNativeFeaturesTests.java @@ -47,6 +47,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -549,6 +550,37 @@ public void inlayHintForMemberAccessStartsAtReceiver() throws IOException { assertEquals(idHint.getPosition().getCharacter(), expectedStart); } + @Test + public void inlayHintsIgnoreCopiedModuleBodyFromAnotherFile() throws IOException { + CompletionTestData data = input( + "package test", + "import HintModule", + "class Mover", + " use HintModule", + "", + "", + "endpackage" + ); + String module = String.join("\n", + "package HintModule", + "public module HintModule", + " function target(int amount)", + " skip", + " function callTarget()", + " target(1)", + "endpackage", + ""); + TestContext ctx = createContext(data, data.buffer, Map.of("HintModule.wurst", module)); + + InlayHintParams params = new InlayHintParams( + new TextDocumentIdentifier(ctx.uri), + new Range(new Position(0, 0), new Position(100, 0)) + ); + List hints = new InlayHintsRequest(params, ctx.bufferManager).execute(ctx.modelManager); + + assertTrue(hints.isEmpty(), "Hints from a copied module body must not appear in the using file: " + hints); + } + @Test public void inlayHintsStayStableWhileTemporarilyUnparsable() throws IOException { CompletionTestData valid = input( @@ -870,6 +902,11 @@ private int compare(Position a, Position b) { } private TestContext createContext(CompletionTestData data, String diskContent) throws IOException { + return createContext(data, diskContent, Collections.emptyMap()); + } + + private TestContext createContext(CompletionTestData data, String diskContent, + Map additionalFiles) throws IOException { File projectFolder = new File("./temp/lspNative/" + System.nanoTime()); File wurstFolder = new File(projectFolder, "wurst"); Files.createDirectories(wurstFolder.toPath()); @@ -878,6 +915,9 @@ private TestContext createContext(CompletionTestData data, String diskContent) t File wurstFile = new File(wurstFolder, "Wurst.wurst"); Files.writeString(testFile.toPath(), diskContent); Files.writeString(wurstFile.toPath(), "package Wurst\n"); + for (Map.Entry additionalFile : additionalFiles.entrySet()) { + Files.writeString(new File(wurstFolder, additionalFile.getKey()).toPath(), additionalFile.getValue()); + } BufferManager bufferManager = new BufferManager(); ModelManagerImpl modelManager = new ModelManagerImpl(projectFolder.getAbsoluteFile(), bufferManager);