From 649514f563d73d00dc3164f438980184b8908a83 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Tue, 26 May 2026 13:14:08 -0600 Subject: [PATCH] Implement go-to-definition for implicit script class `ClassExpression`s and `ImportNode`s where otherwise scripts were required to return an expression (last statement is an expression) Fix `NullPointerException` when requesting references for a `ClassExpression` of an implicit script class within the same file Add tests to `GroovyServicesDefinitionTests` for implicit script class definition requests --- .../providers/DefinitionProvider.java | 14 +++++- .../groovyls/providers/ReferenceProvider.java | 2 + .../GroovyServicesDefinitionTests.java | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java b/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java index df0e0cfd..502089d2 100644 --- a/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java +++ b/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java @@ -25,9 +25,12 @@ import java.util.concurrent.CompletableFuture; import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -56,7 +59,7 @@ public CompletableFuture, List, List> provideReferences(TextDocumen List references = GroovyASTUtils.getReferences(offsetNode, ast); List locations = references.stream().map(node -> { URI uri = ast.getURI(node); + if (uri == null) + return null; return GroovyLanguageServerUtils.astNodeToLocation(node, uri); }).filter(location -> location != null).collect(Collectors.toList()); diff --git a/src/test/java/net/prominic/groovyls/GroovyServicesDefinitionTests.java b/src/test/java/net/prominic/groovyls/GroovyServicesDefinitionTests.java index 93ce8f1a..83d53255 100644 --- a/src/test/java/net/prominic/groovyls/GroovyServicesDefinitionTests.java +++ b/src/test/java/net/prominic/groovyls/GroovyServicesDefinitionTests.java @@ -438,6 +438,50 @@ void testClassDefinitionFromImport() throws Exception { Assertions.assertEquals(1, location.getRange().getEnd().getCharacter()); } + @Test + void testImplicitClassDefinitionFromConstructorCall() throws Exception { + Path filePath = srcRoot.resolve("Definitions.groovy"); + String uri = filePath.toUri().toString(); + TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, "new Definitions()"); + services.didOpen(new DidOpenTextDocumentParams(textDocumentItem)); + TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri); + Position position = new Position(0, 10); + List locations = services.definition(new DefinitionParams(textDocument, position)).get() + .getLeft(); + Assertions.assertEquals(1, locations.size()); + Location location = locations.get(0); + Assertions.assertEquals(uri, location.getUri()); + Assertions.assertEquals(0, location.getRange().getStart().getLine()); + Assertions.assertEquals(0, location.getRange().getStart().getCharacter()); + Assertions.assertEquals(1, location.getRange().getEnd().getLine()); + Assertions.assertEquals(1, location.getRange().getEnd().getCharacter()); + } + + @Test + void testImplicitClassDefinitionFromImport() throws Exception { + Path filePath = srcRoot.resolve("Definitions.groovy"); + String uri = filePath.toUri().toString(); + TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, ""); + services.didOpen(new DidOpenTextDocumentParams(textDocumentItem)); + + Path filePath2 = srcRoot.resolve("Definitions2.groovy"); + String uri2 = filePath2.toUri().toString(); + TextDocumentItem textDocumentItem2 = new TextDocumentItem(uri2, LANGUAGE_GROOVY, 1, "import Definitions"); + services.didOpen(new DidOpenTextDocumentParams(textDocumentItem2)); + + TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri2); + Position position = new Position(0, 10); + List locations = services.definition(new DefinitionParams(textDocument, position)).get() + .getLeft(); + Assertions.assertEquals(1, locations.size()); + Location location = locations.get(0); + Assertions.assertEquals(uri, location.getUri()); + Assertions.assertEquals(0, location.getRange().getStart().getLine()); + Assertions.assertEquals(0, location.getRange().getStart().getCharacter()); + Assertions.assertEquals(1, location.getRange().getEnd().getLine()); + Assertions.assertEquals(1, location.getRange().getEnd().getCharacter()); + } + // --- parameters @Test