Skip to content

Commit c473e95

Browse files
shiersaclaude
andcommitted
feat: add Deno language server support
Add a Deno language server backed by the Deno CLI's built-in `deno lsp`, serving TypeScript/JavaScript in Deno projects. Unlike the plain typescript-language-server it understands Deno module resolution (npm:/jsr:/https: imports) and the `Deno.*` global namespace. It is experimental and must be selected explicitly via `language_servers: [deno]`; it overlaps the TypeScript server on file extensions, so it is not auto-detected. Requires the `deno` CLI on PATH. Tests run in CI: the `deno` marker joins the other-langs batch, which now installs Deno via denoland/setup-deno. Off-CI and wherever the CLI is absent they skip through the central conftest guard, following the existing pattern for toolchain-gated language servers. Hover is covered explicitly, including hover on a `Deno.*` global, which is the capability this server adds over the TypeScript one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 29d07d4 commit c473e95

13 files changed

Lines changed: 328 additions & 14 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
# other-langs: grab-bag + scripting languages + haskell + qml. Haskell (GHC/HLS) and qml (qmlls) are
7272
# installed only on Linux -- too slow/expensive to build on the macOS runner (Haskell), or not worth
7373
# the cost of a cross-OS Qt pull (qmlls) -- and skip on Windows+macOS via the central conftest guard.
74-
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
74+
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or deno or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
7575
# niche: slow, mostly-cached toolchains. ocaml + haskell are the two slowest -- kept in separate
7676
# batches (ocaml here, haskell in other-langs). nix + perl skip on Windows via the guard.
7777
MARKERS_NICHE: "julia or r or perl or lean4 or nix or ocaml"
@@ -219,6 +219,13 @@ jobs:
219219
with:
220220
terraform_version: "1.5.0"
221221
terraform_wrapper: false
222+
# Deno ships its language server with the CLI (`deno lsp`), so installing the
223+
# runtime is all that is needed for the deno-marked tests.
224+
- name: Install Deno
225+
if: matrix.batch == 'other-langs'
226+
uses: denoland/setup-deno@v2
227+
with:
228+
deno-version: v2.x
222229
# - name: Install swift
223230
# if: runner.os != 'Windows'
224231
# uses: swift-actions/setup-swift@v2

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ Status of the `main` branch. Changes prior to the next official version change w
4343
now separated by `#` instead (e.g. `create_user#4`), so the reported name path round-trips and
4444
`find_referencing_symbols`/`replace_symbol_body`/`insert_after_symbol` work on Erlang
4545
functions #1797
46+
- Add Deno support (experimental; language server `deno`, backed by the Deno CLI's built-in
47+
`deno lsp`). Understands Deno module resolution (`npm:` / `jsr:` / `https:` imports) and the
48+
`Deno.*` globals, which the plain TypeScript language server does not. Overlaps TypeScript on
49+
file extensions, so it is not auto-detected and must be selected explicitly; requires the
50+
`deno` CLI on PATH
4651

4752
* JetBrains:
4853
- `jet_brains_find_symbol`: Disallow wildcard-only search, delegating to overview tool if request is for file

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language
110110
The underlying language servers are typically open-source projects or at least freely available for use.
111111

112112
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
113-
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Elixir, Elm, Erlang, Fortran, F#, GDScript, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
113+
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Deno, Elixir, Elm, Erlang, Fortran, F#, GDScript, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
114114

115115
### The Serena JetBrains Plugin
116116

docs/01-about/020_programming-languages.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ Some languages require additional installations or setup steps, as noted.
164164
* **TOML**
165165
(experimental; uses Taplo 0.10.0, taken from PATH if present, otherwise downloaded automatically)
166166
* **TypeScript**
167+
* **Deno**
168+
(experimental; requires the `deno` CLI on PATH — it bundles the language server used here;
169+
serves Deno TypeScript/JavaScript and understands `npm:` / `jsr:` / `https:` imports and the `Deno.*`
170+
globals, which the plain TypeScript language server does not; overlaps `typescript` on file extensions,
171+
so it is not auto-detected and must be set as the language explicitly — do not also enable `typescript`
172+
for the same files)
167173
* **Vue**
168174
(3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
169175
* **YAML**

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ markers = [
329329
"groovy: language server running for Groovy",
330330
"rust: language server running for Rust",
331331
"typescript: language server running for TypeScript",
332+
"deno: language server running for Deno (uses the built-in deno lsp)",
332333
"svelte: language server running for Svelte (uses svelte-language-server)",
333334
"vue: language server running for Vue (uses TypeScript LSP)",
334335
"php: language server running for PHP",

src/serena/resources/project.template.yml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ project_name: "project_name"
44
# list of language servers to start when using the LSP backend; choose from:
55
# ada al angular ansible bash
66
# bsl clojure cpp cpp_ccls crystal
7-
# csharp csharp_omnisharp cue dart elixir
8-
# elm erlang fortran fsharp gdscript
9-
# go groovy haskell haxe hlsl
10-
# html java json julia kotlin
11-
# latex lean4 lua luau markdown
12-
# matlab msl nix ocaml pascal
13-
# perl php php_phpactor php_phpantom powershell
14-
# python python_basedpyright python_jedi python_pyrefly python_ty
15-
# qml r rego ruby ruby_solargraph
16-
# rust scala scss solidity svelte
17-
# swift systemverilog terraform toml typescript
18-
# typescript_vts vue yaml zig
7+
# csharp csharp_omnisharp cue dart deno
8+
# elixir elm erlang fortran fsharp
9+
# gdscript go groovy haskell haxe
10+
# hlsl html java json julia
11+
# kotlin latex lean4 lua luau
12+
# markdown matlab msl nix ocaml
13+
# pascal perl php php_phpactor php_phpantom
14+
# powershell python python_basedpyright python_jedi python_pyrefly
15+
# python_ty qml r rego ruby
16+
# ruby_solargraph rust scala scss solidity
17+
# svelte swift systemverilog terraform toml
18+
# typescript typescript_vts vue yaml zig
1919
# (This list may be outdated; generated with scripts/print_language_list.py;
2020
# For the current list, see values of the LanguageServerId enum here:
2121
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
@@ -25,6 +25,7 @@ project_name: "project_name"
2525
# - For JavaScript, use typescript
2626
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
2727
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
28+
# - For Deno projects, use deno (serves the same .ts/.js files as typescript; requires the deno CLI on PATH)
2829
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
2930
# - For Free Pascal/Lazarus, use pascal
3031
# Special requirements:
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
Provides Deno-specific instantiation of the LanguageServer class, using the
3+
language server built into the Deno CLI (``deno lsp``).
4+
"""
5+
6+
import logging
7+
import shutil
8+
9+
from overrides import override
10+
11+
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
12+
from solidlsp.ls_config import LanguageServerConfig
13+
from solidlsp.settings import SolidLSPSettings
14+
15+
log = logging.getLogger(__name__)
16+
17+
18+
class DenoLanguageServer(SolidLanguageServer):
19+
"""
20+
Deno instantiation of the LanguageServer class, backed by ``deno lsp``.
21+
22+
Serves TypeScript/JavaScript in Deno projects. Unlike the plain
23+
typescript-language-server, ``deno lsp`` understands Deno-specific module
24+
resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the ``Deno.*``
25+
global namespace.
26+
27+
This server overlaps the TypeScript server on file extensions and is therefore
28+
marked experimental: it is not auto-detected and must be selected explicitly via
29+
``languages: [deno]`` in ``project.yml``.
30+
"""
31+
32+
@classmethod
33+
def supports_implementation_request(cls) -> bool:
34+
return True
35+
36+
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
37+
super().__init__(
38+
config,
39+
repository_root_path,
40+
None,
41+
"typescript",
42+
solidlsp_settings,
43+
)
44+
45+
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
46+
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
47+
48+
@override
49+
def is_ignored_dirname(self, dirname: str) -> bool:
50+
# node_modules appears in Deno projects using npm compatibility; vendor/ holds
51+
# vendored remote dependencies. Neither should be indexed as project sources.
52+
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "dist", "build"]
53+
54+
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
55+
def _get_or_install_core_dependency(self) -> str:
56+
"""Return the path to the ``deno`` executable (the language server ships with it)."""
57+
deno_path = shutil.which("deno")
58+
if deno_path is None:
59+
raise FileNotFoundError(
60+
"The 'deno' executable was not found on PATH. Install Deno "
61+
"(https://docs.deno.com/runtime/getting_started/installation/) — it bundles "
62+
"the language server used here — and ensure 'deno' is on your PATH."
63+
)
64+
return deno_path
65+
66+
def _create_launch_command(self, core_path: str) -> list[str]:
67+
return [core_path, "lsp"]
68+
69+
def _get_language_id_for_file(self, relative_file_path: str) -> str:
70+
# deno lsp relies on the correct languageId; .tsx/.jsx in particular must not
71+
# be sent as plain "typescript" or symbol ranges get truncated at JSX expressions.
72+
if relative_file_path.endswith(".tsx"):
73+
return "typescriptreact"
74+
if relative_file_path.endswith(".jsx"):
75+
return "javascriptreact"
76+
if relative_file_path.endswith((".js", ".mjs", ".cjs")):
77+
return "javascript"
78+
return "typescript"
79+
80+
def _create_base_initialize_params(self) -> dict:
81+
return {
82+
"locale": "en",
83+
"capabilities": {
84+
"textDocument": {
85+
"synchronization": {"didSave": True, "dynamicRegistration": True},
86+
"definition": {"dynamicRegistration": True},
87+
"references": {"dynamicRegistration": True},
88+
"documentSymbol": {
89+
"dynamicRegistration": True,
90+
"hierarchicalDocumentSymbolSupport": True,
91+
"symbolKind": {"valueSet": list(range(1, 27))},
92+
},
93+
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
94+
"completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
95+
"rename": {"dynamicRegistration": True, "prepareSupport": True},
96+
"publishDiagnostics": {"relatedInformation": True},
97+
},
98+
"workspace": {
99+
"workspaceFolders": True,
100+
"configuration": True,
101+
"didChangeConfiguration": {"dynamicRegistration": True},
102+
"symbol": {"dynamicRegistration": True},
103+
},
104+
},
105+
# deno lsp reads its settings from initializationOptions; enabling the server
106+
# and the linter mirrors the defaults of the official VS Code Deno extension.
107+
"initializationOptions": {
108+
"enable": True,
109+
"lint": True,
110+
"unstable": False,
111+
},
112+
}
113+
114+
def _start_server(self) -> None:
115+
"""Start the ``deno lsp`` process and drive the LSP initialize handshake."""
116+
117+
def register_capability_handler(params: dict) -> None:
118+
return
119+
120+
def window_log_message(msg: dict) -> None:
121+
log.info(f"LSP: window/logMessage: {msg}")
122+
123+
def do_nothing(params: dict) -> None:
124+
return
125+
126+
def configuration_handler(params: dict) -> list:
127+
# deno lsp requests workspace/configuration during startup; return an empty
128+
# settings object per requested item so it proceeds with its defaults.
129+
return [{} for _ in params.get("items", [])]
130+
131+
self.server.on_request("client/registerCapability", register_capability_handler)
132+
self.server.on_request("workspace/configuration", configuration_handler)
133+
self.server.on_notification("window/logMessage", window_log_message)
134+
self.server.on_notification("$/progress", do_nothing)
135+
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
136+
# Deno-specific notifications emitted after config discovery; no action needed.
137+
self.server.on_notification("deno/didRefreshDenoConfigurationTree", do_nothing)
138+
self.server.on_notification("deno/didChangeDenoConfiguration", do_nothing)
139+
140+
log.info("Starting deno lsp server process")
141+
self.server.start()
142+
initialize_params = self._create_initialize_params()
143+
144+
log.info("Sending initialize request from LSP client to deno lsp and awaiting response")
145+
init_response = self.server.send.initialize(initialize_params)
146+
147+
assert "textDocumentSync" in init_response["capabilities"]
148+
assert "definitionProvider" in init_response["capabilities"]
149+
assert "documentSymbolProvider" in init_response["capabilities"]
150+
assert "referencesProvider" in init_response["capabilities"]
151+
152+
self.server.notify.initialized({})

src/solidlsp/ls_config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,14 @@ class LanguageServerId(str, Enum):
286286
project.yml — Angular LS supersedes both for Angular projects.
287287
Must be explicitly specified in project.yml.
288288
"""
289+
DENO = "deno"
290+
"""Deno's built-in language server (``deno lsp``) for Deno TypeScript/JavaScript projects.
291+
Understands Deno module resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the
292+
``Deno.*`` globals, which the plain typescript-language-server does not. Overlaps the
293+
TypeScript server on file extensions (.ts/.tsx/.js/.jsx/.mts/.cts/.mjs/.cjs), so it is
294+
experimental and must be explicitly specified via ``languages: [deno]`` in project.yml;
295+
do not also enable typescript for the same files. Requires the ``deno`` CLI on PATH.
296+
"""
289297

290298
@classmethod
291299
def iter_all(cls, include_experimental: bool = True, include_non_programming_languages: bool = True) -> Iterable[Self]:
@@ -321,6 +329,7 @@ def is_experimental(self) -> bool:
321329
self.HTML,
322330
self.SCSS,
323331
self.ANGULAR,
332+
self.DENO,
324333
}
325334

326335
def is_programming_language(self) -> bool:
@@ -588,6 +597,14 @@ def get_source_fn_matcher(self) -> FilenameMatcher:
588597
for postfix in ["x", ""]:
589598
path_patterns.append(f".{prefix}ts{postfix}")
590599
return FilenameMatcher(*path_patterns)
600+
case self.DENO:
601+
# Deno serves the same TS/JS family as the TypeScript server.
602+
path_patterns = []
603+
for prefix in ["c", "m", ""]:
604+
for postfix in ["x", ""]:
605+
for base_pattern in ["ts", "js"]:
606+
path_patterns.append(f".{prefix}{base_pattern}{postfix}")
607+
return FilenameMatcher(*path_patterns)
591608
case _:
592609
raise ValueError(f"Unhandled language: {self}")
593610

@@ -871,6 +888,10 @@ def get_ls_class(self) -> type["SolidLanguageServer"]:
871888
from solidlsp.language_servers.angular_language_server import AngularLanguageServer
872889

873890
return AngularLanguageServer
891+
case self.DENO:
892+
from solidlsp.language_servers.deno_language_server import DenoLanguageServer
893+
894+
return DenoLanguageServer
874895
case _:
875896
raise ValueError(f"Unhandled language: {self}")
876897

test/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ def project_with_ls(request: LanguageParamRequest) -> Iterator[Project]:
280280
LanguageServerId.CPP_CCLS: [pytest.mark.cpp],
281281
LanguageServerId.CUE: [pytest.mark.cue],
282282
LanguageServerId.CSHARP: [pytest.mark.csharp],
283+
LanguageServerId.DENO: [pytest.mark.deno],
283284
LanguageServerId.FSHARP: [pytest.mark.fsharp],
284285
LanguageServerId.GO: [pytest.mark.go],
285286
LanguageServerId.HAXE: [pytest.mark.haxe],
@@ -471,6 +472,8 @@ def _determine_disabled_language_servers() -> list[LanguageServerId]:
471472
result.append(LanguageServerId.OCAML)
472473
if not _is_perl_language_server_available(): # perl ships with the OS; the LS module is the real signal
473474
result.append(LanguageServerId.PERL)
475+
if _sh.which("deno") is None: # deno bundles the language server (`deno lsp`); skip where the CLI is absent
476+
result.append(LanguageServerId.DENO)
474477

475478
# === 4. Enabled everywhere: every language NOT listed in this function (python, go, java, ...) ===
476479

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"lint": {
3+
"rules": {
4+
"tags": ["recommended"]
5+
}
6+
}
7+
}

0 commit comments

Comments
 (0)