[
https://issues.apache.org/jira/browse/GROOVY-12411?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18119628#comment-18119628
]
ASF GitHub Bot commented on GROOVY-12411:
-----------------------------------------
daniellansun commented on PR #2936:
URL: https://github.com/apache/groovy/pull/2936#issuecomment-5846117566
> @daniellansun if you want to make this a configuration extension then I
would limit the extension to it. I do not see GDSL being possible with this
extension mechanism, forced-STC is imho configuration. The problem though
persists. How are multiple configuration extensions handled? Assume for example
one wants to force STC strict and another wants to force STC non-strict. Which
one will be taken now? LSP4J supports `workspace/configuration` and
`workspace/didChangeConfiguration` to handle configuration. If you truly want
to support custom configuration then defining a format for that and supporting
it would be what actually should be done. Also, if you still want to define
extensions, then instead of an serviceloader mechanism, you can make those part
of the configuration and thus have a configuration order that controls the
order in which they would be applied. Just as an example... though I would not
use that extension mechanism for configuration. A GDSL support for example
could then be mentioned there. But I would try to solve as much as possible by
configuration first. Configuration is normally more easy to extend as well.
Thank you, Jochen. Agreed — configuration first, and not a
ServiceLoader.
The extension SPI is removed. Settings live in the `groovy` section
of `workspace/didChangeConfiguration`. After `initialized`, if the
client advertised `workspace.configuration`, the server also pulls
that section via LSP4J `workspace/configuration`. The pull is
asynchronous so the JSON-RPC reader is not blocked waiting for the
reply.
The format is:
* `classpath` — extra jars and directories
* `sourcePaths` — extra source roots
* `grapeEnabled` / `astTestEnabled` — default `false`
* any other `groovy.*` key — stored as a string; changing it forces
a recompile. The core does not download or interpret those keys.
There is no extension order. Forced STC would be another key on
this map, not an extension; it is not in this cut. GDSL is not
possible through a compile hook, and we are not using one for it.
> Add a first-party Language Server Protocol implementation (groovy-lsp)
> ----------------------------------------------------------------------
>
> Key: GROOVY-12411
> URL: https://issues.apache.org/jira/browse/GROOVY-12411
> Project: Groovy
> Issue Type: Improvement
> Reporter: Daniel Sun
> Priority: Major
>
> Ship an optional first-party language server ({{groovy-lsp}}) so editors and
> AI tools can offer Groovy completion, navigation, diagnostics, and
> conservative rename against the *in-tree* compiler.
> h2. Why
> Community servers lag language changes because they compile against a Groovy
> snapshot, not this repository. {{groovy-lsp}} uses the same
> {{CompilationUnit}} as the rest of Groovy, so parser and type-system work
> appear in editors automatically.
> It is a Groovy *language core* for the Language Server Protocol, not a
> workspace or IDE. The differentiator is the same compiler, a small surface,
> and edits that refuse to guess, not more IDE features.
> h2. Intended use
> * *Editor / IDE:* start {{groovylsp}} and speak stdio JSON-RPC. The client
> supplies {{groovy.classpath}} / {{groovy.sourcePaths}}.
> * *AI tools:* the same process is the semantic backend. A later
> {{groovy-mcp}} module is expected to wrap it so a model can ask what an
> identifier is without embedding the compiler.
> * *Other Apache-2.0 Groovy LSPs:* extend the compile and protocol loop
> through the incubating {{GroovyLspExtension}} SPI instead of forking it.
> Eclipse JDT / groovy-eclipse servers remain a parallel stack for a full Java
> IDE; they cannot replace this compiler through the SPI.
> h2. Out of scope
> These are strengths of other implementations and stay out on purpose:
> * A full Java language server (Java completion, Java format, Java-only
> rename). Joint *type resolution* is in scope (see below)
> * GDSL / Jenkins pipeline descriptors (a plugin may add them; this module
> does not load GDSL)
> * Invoking Gradle or Maven (no Tooling API)
> * Debugger (DAP) and test runner
> * Identifier-text rename fallbacks (rename every {{foo}} in the file)
> * Extra name-span fields on {{AnnotatedNode}} (identifier selection is
> {{Positions.toNameRange}} in groovy-lsp)
> h2. Joint Groovy and Java sources
> When the workspace contains {{.java}} files, groovy-lsp joint-compiles them
> with Groovy through in-tree {{JavaAwareCompilationUnit}} ({{memStub}}):
> Parrot to {{CONVERSION}}, in-memory Java stubs, {{javax.tools}} javac with
> annotation processing off, then Groovy {{SEMANTIC_ANALYSIS}} (no Groovy class
> generation). Open Java buffers overlay the filesystem. Javac diagnostics and
> go-to-definition cross the language boundary via {{JavaSymbolIndex}}
> ({{com.sun.source}} {{Trees}}, captured before {{generate()}}).
> That is type resolution and navigation, not a Java language server. Java
> completion, Java formatting, and Java-only rename stay with a Java LS or
> [groovy-language-server-edt|https://github.com/tomaszrup/groovy-language-server-edt].
> h2. Comparison with other Groovy language servers
> Four implementations, four jobs. *groovy-lsp* is the in-tree language core.
> The others keep the IDE-shaped work this module refuses.
> *
> [groovy-language-server|https://github.com/GroovyLanguageServer/groovy-language-server]
> (Prominic): community server for Moonshine IDE. Apache 2.0 on a Groovy
> 4.0.26 dependency.
> * [groovyls|https://github.com/trustytrojan/groovyls] (trustytrojan): Apache
> 2.0 fork of Prominic. Jenkins GDSL, forced STC on every class, Aether
> downloads. Those features are hard-wired, not a plugin.
> *
> [groovy-language-server-edt|https://github.com/tomaszrup/groovy-language-server-edt]:
> EPL 2.0, Equinox + JDT + groovy-eclipse. Full Java LS and joint IDE
> features. Complementary stack; none of its source is copied.
> ||Aspect||groovy-lsp||Prominic||groovyls||edt||
> |License|Apache 2.0|Apache 2.0|Apache 2.0|EPL 2.0|
> |Compiler|in-tree {{CompilationUnit}}|Groovy 4.0.26 jar|Groovy 5.1.1 jar|JDT
> + groovy-eclipse|
> |Phase|{{SEMANTIC_ANALYSIS}}, no Groovy
> classgen|{{CANONICALIZATION}}|{{CANONICALIZATION}} + forced STC|JDT compile|
> |Classpath|client {{groovy.classpath}}|client {{groovy.classpath}}|client +
> Aether downloads|Red Hat Java / Gradle wrapper|
> |GDSL / Jenkins|plugin, not core|no|yes|no|
> |Joint Groovy+Java|{{JavaAwareCompilationUnit}} + {{memStub}};
> def/hover|no|no|yes (JDT IDE)|
> |Rename|declarations and bound calls only|AST references + name regex|same as
> Prominic|JDT; identifier-scan fallback|
> |Extension point|incubating {{GroovyLspExtension}}|none|none (baked in)|OSGi
> / Equinox|
> h2. Extensibility as a reusable base
> {{groovy-lsp}} is meant to be the compile + protocol loop other *Apache-2.0*
> Groovy language servers sit on, not a fork point. Plugins implement
> incubating {{GroovyLspExtension}} ({{ServiceLoader}}; tests use
> {{ExtensionHost}}). There is no second hover/completion SPI: inject members
> in {{afterCompile}} so core hover, completion, navigation, and rename see
> them.
> ||Hook||For||
> |{{configure(config, session)}}|extra classpath, extra {{groovy.*}} settings
> (for example {{groovy.dependencies}})|
> |{{initialized}} / {{shutdown}}|load or release catalogs|
> |{{afterCompile(CompilationUnit)}}|GDSL injection, a forced STC pass; must
> not generate class files|
> |{{extraDiagnostics}} / {{extraCodeActions}}|plugin diagnostics and actions
> ({{ModuleNode}} is groovy-core AST)|
> |{{commands}} / {{executeCommand}}|extra workspace commands via
> {{GroovyLspSession.applyEdit}}|
> {{GroovyLspSession}} is apply-edit, document text, folders, classpath, source
> paths, extra settings, and position encoding. It is not a compile handle. A
> throwing plugin is isolated and must not compile or publish on its own. The
> built-in {{groovy.lsp.stc}} extension is the example: annotated
> {{@TypeChecked}} / {{@CompileStatic}} only.
> * *groovyls* can sit on this SPI. GDSL inject and forced-on-all-classes STC
> belong in {{afterCompile}}; Aether downloads belong in {{configure}} from
> extra settings. Semantic tokens, conservative navigation, and joint type
> resolution are already in core. That is a plugin, not a paste of groovyls
> sources.
> * *Prominic groovy-language-server* has no unique language feature left.
> Moonshine can run {{groovylsp}} and supply {{groovy.classpath}}. A
> Moonshine-only command would be a tiny plugin.
> * *groovy-language-server-edt* cannot replace {{CompilationUnit}} with JDT
> through the SPI. Joint *type resolution* is now in groovy-lsp core; edt
> remains the complementary full Java IDE (library sources, file-move imports).
> Its identifier-scan rename fallback is not adopted.
> The SPI is incubating: design may still settle. It is a seam for Apache-2.0
> reuse, not a stability promise and not a slot for an Equinox compiler.
> h2. Rename and dynamically typed Groovy
> {{MethodCallExpression.methodTarget}} is often empty in dynamic Groovy. A
> name-and-arity match is a heuristic: good enough for hover, not for rewriting
> source.
> ||Feature||When {{methodTarget}} is set||When it is not (typical dynamic
> Groovy)||
> |Hover|Bound signature|Unique arity match on a resolved receiver, labelled
> {{_inferred_}}; otherwise {{name(...)}}|
> |Definition|That method|Every arity-matching candidate with a source
> location; never a random first match|
> |Rename / prepareRename|Allowed|Refused. A heuristic must not rewrite the
> wrong overload.|
> Declarations (class, method, field, parameter, bound local) rename by
> identity (owner + name + descriptor), not the unqualified identifier. Class
> rename also renames the file when the class name matches the file stem.
> h2. AST columns (not a breaking change)
> Compiler AST columns were already 1-based. The parser writes
> {{setColumnNumber(token.getCharPositionInLine() + 1)}}; ANTLR's
> {{getCharPositionInLine()}} is 0-based, Groovy's AST is not. Authored source
> starts at column 1, the end column is exclusive, and missing positions are
> {{-1}}.
> GROOVY-12085 changed the *unit* (Unicode code points instead of UTF-16
> units). It did not change 0-based vs 1-based.
> The patch also corrects {{ASTNode}} javadoc that had said "0-indexed" (added
> 2026-05-03, never shipped in a release). Getters, setters, and stored values
> are unchanged. That is a documentation fix, not a runtime ABI change.
> LSP positions stay 0-based in the encoding negotiated at {{initialize}}
> ({{utf-16}} by default; {{utf-8}} / {{utf-32}} optional). The server
> converts; clients never see AST columns. Existing LSP clients do not need to
> change how they send positions.
> h2. What lands
> * Optional subproject {{groovy-lsp}} ({{optionalModule()}}, not
> {{groovy-all}})
> * {{groovylsp}} / {{groovylsp.bat}} on the binary distribution (stdio
> JSON-RPC)
> * LSP 3.18 over Eclipse LSP4J 1.0; unknown 3.19 fields ignored; advertise
> only implemented capabilities
> * LSP4J is EPL 2.0 Category B *binary*; its source is not mixed into Groovy
> * Compile open Groovy buffers through {{SEMANTIC_ANALYSIS}} (no Groovy class
> generation)
> * Joint compile of workspace {{.java}} via {{JavaAwareCompilationUnit}} +
> {{memStub}} and {{javax.tools}} javac
> * Incubating {{GroovyLspExtension}} ({{ServiceLoader}}). Built-in
> {{groovy.lsp.stc}} runs STC only on {{@TypeChecked}} / {{@CompileStatic}}
> members
> * {{LocatedMessage}}: {{toDiagnostic()}} / {{write()}} no longer NPE when the
> CST context is null
> * Docs: {{groovy-lsp.adoc}}, {{subprojects/groovy-lsp/ARCHITECTURE.md}}
> h2. Language features
> File types: {{.groovy}}, {{.gvy}}, {{.gy}}, {{.gsh}}, or
> {{languageId=groovy}}. Workspace {{.java}} participates in joint compile
> (diagnostics, definition, hover), not as a Java language server.
> * *Editing:* incremental sync; completion (keywords, snippets, members,
> workspace types with auto-import, GDK) and {{completionItem/resolve}}; format
> / range format / on-type indent; organize imports; unused-import hints;
> generate accessors / {{toString}} / {{equals}}/{{hashCode}} / constructor;
> add import, package, {{@Override}}, missing class stub
> * *Navigation:* definition, type definition, implementation, declaration,
> references (by identity); call and type hierarchy; document / workspace
> symbols; folding; selection range; document highlight; import document links;
> Groovy to Java and Java to Groovy definition on jointly compiled types
> * *Presentation:* push/pull diagnostics (compiler + unused import + STC on
> annotated members + javac); semantic tokens; inlay hints; reference-count
> code lenses
> * *Workspace:* folders, watched files, configuration,
> {{window/workDoneProgress}}, {{workspace/willRenameFiles}} for Groovy sources
> * *Commands:* {{groovy.lsp.organizeImports}}, {{groovy.lsp.showReferences}},
> {{groovy.lsp.gotoSuperMethod}}
> h2. Safety and classpath
> * {{@Grab}} and {{@ASTTest}} off unless the client sets {{grapeEnabled}} /
> {{astTestEnabled}}
> * Diagnostics scoped to the owning source unit (shared {{ErrorCollector}})
> * No Gradle or Maven Tooling API. If {{classpath}} / {{sourcePaths}} are
> empty, fall back to {{src/main/groovy}}, {{src/main/java}} (and friends) and
> jars under {{lib}} / {{libs}}
> * Unchanged inputs skip recompilation; {{GroovyClassLoader}} is reused and
> closed on shutdown
> * Joint javac output is a per-compile directory under {{.groovy/lsp-javac}},
> owner-only where POSIX allows, deleted on the next compile
> * A throwing plugin must not abort compile or a request
> h2. How to run
> {code}
> groovylsp
> groovylsp --stdio
> groovylsp --socket <port>
> groovylsp --help
> groovylsp --version
> {code}
> Do not write to stdout from Groovy code running inside the server; the
> launcher redirects {{System.out}} to {{System.err}} so stray prints cannot
> break the protocol stream.
> h2. See also
> * GROOVY-12085 (code-point columns; not an origin change)
--
This message was sent by Atlassian Jira
(v8.20.10#820010)