Skip to content

Add Astro language server support#1669

Open
strategiert wants to merge 1 commit into
oraios:mainfrom
strategiert:astro-support
Open

Add Astro language server support#1669
strategiert wants to merge 1 commit into
oraios:mainfrom
strategiert:astro-support

Conversation

@strategiert

Copy link
Copy Markdown

Adds support for the Astro web framework using
@astrojs/language-server with a companion TypeScript language server for
cross-file type resolution.

Relationship to #886 / #884

This builds on @dansasser's Astro work in #886. #884 was closed with the
suggestion to base Astro on the companion-server abstraction from #829, and
#886 was written against that abstraction (CompanionLanguageServer in
companion_ls.py, plus embedded_language_config.py and
typescript_companion.py).

That abstraction is not present in main. The Vue and Svelte language servers
in the current tree each keep their companion TypeScript server in a
self-contained self._ts_server attribute instead of a shared base class. To
match what is actually in main, the adapter here was re-implemented against
that Vue/Svelte pattern rather than importing the abstraction from #886. As a
result this PR does not add the CompanionLanguageServer base class, does not
modify the Vue language server, and is smaller than #886.

Architecture

Dual-server setup, identical in shape to Vue/Svelte:

  • Astro LS (@astrojs/language-server, run via astro-ls --stdio) handles
    .astro parsing and document symbols.
  • Companion TypeScript LS (typescript-language-server configured with the
    @astrojs/ts-plugin initialization plugin) handles go-to-definition,
    find-references and rename for .ts/.js files and cross-file resolution.
    .astro files are indexed on the companion so cross-file references resolve.
    @astrojs/ts-plugin is installed explicitly (it is not a dependency of
    @astrojs/language-server), and its path is validated at startup with a clear
    warning if missing.

AstroLanguageServer subclasses SolidLanguageServer; the nested
AstroTypeScriptServer subclasses TypeScriptLanguageServer and reuses its
DependencyProvider, $/progress indexing tracking and
prefer_non_node_modules_definition helper. Initialization uses
_create_base_initialize_params (server-specific keys only; the common keys are
supplied by the central InitializeParamsBuilder).

Changes

  • src/solidlsp/language_servers/astro_language_server.py (new)
  • src/solidlsp/ls_config.py: Language.ASTRO, superset priority, .astro
    source matcher, factory wiring
  • src/serena/hooks.py: .astro added to the source-extension set
  • pyproject.toml: astro pytest marker
  • README.md, docs/01-about/020_programming-languages.md, CHANGELOG.md
  • test/solidlsp/astro/ (new): conftest.py, test_astro_basic.py,
    test_astro_symbol_retrieval.py
  • test/resources/repos/astro/test_repo/ (new): minimal Astro project

Runtime dependencies (@astrojs/language-server, @astrojs/ts-plugin,
typescript, typescript-language-server) are auto-installed via npm into
Serena's managed resource dir on first use, with a version marker file that
triggers reinstall on version change. Defaults: @astrojs/language-server
2.16.11 and @astrojs/ts-plugin 1.10.10 (latest at time of writing), both
overridable via ls_specific_settings.astro.

The test fixture's own npm dependencies (astro, needed by the ts-plugin to
resolve .astro modules) are installed by test/solidlsp/astro/conftest.py,
following the Svelte fixture: a session-scoped, filelock-guarded npm install
into the fixture's node_modules, cached across runs and skipped if npm is
unavailable. As with Svelte, package-lock.json is git-ignored rather than
committed.

Tests

Tests assert concrete symbol names and exact reference locations
(file:line), per the CONTRIBUTING guideline, not just non-empty results. In
particular, references to createCounter (defined in counter.ts) are asserted
to include its import at index.astro:4 and its call at index.astro:7 — this
exercises the dual-server cross-file path and would fail if @astrojs/ts-plugin
were missing from the companion. The tests are skipped only when npm is
unavailable (or the fixture install fails), matching the Svelte/Angular
fixtures. The astro marker is not in any named CI marker group, so the tests
run in the catch-all pytest batch, which already sets up Node.js on all OSes.

Verified locally on Windows (Python 3.14, Node 22):

uv run pytest test/solidlsp/astro test/solidlsp/vue -q
55 passed, 2 warnings in 108.67s (0:01:48)

(13 Astro tests + 42 Vue tests; Vue is included to show it is unchanged.)

To confirm the fixture install path and that nothing passes vacuously, the
Astro suite was also run after deleting both the fixture node_modules and the
managed language-server install:

uv run pytest test/solidlsp/astro -q
13 passed, 2 warnings in 70.84s (0:01:10)

uv run serena --help starts cleanly. ruff check and ruff format --check
pass on the changed files.

Notes

  • No standalone setup guide was added, following the Vue/Svelte precedent (they
    have none); the language is documented in
    docs/01-about/020_programming-languages.md.
  • request_references for .ts/.js symbols is served by the companion only.
    The sharpened cross-file test confirms this already returns references located
    inside .astro files, so no separate merge of primary-Astro-LS references is
    needed for symbol references. File-level component references (e.g. locating
    <Header/> tag usages), which the Vue/Svelte servers add via Volar-specific
    requests, are not implemented here and could be a follow-up.
  • .tsx/.jsx are opened with the typescriptreact/javascriptreact language
    ids (as the base TypeScriptLanguageServer does) so tsserver does not truncate
    symbol ranges at JSX expressions; Astro projects commonly include these via
    React/Preact/Solid integrations.
  • If the Astro LS fails to initialize after the companion has started,
    _start_server tears down the companion and the partial Astro process before
    re-raising, to avoid leaking Node processes (mirrors the Angular server).
  • The internal assert self._ts_server is not None invariants follow the
    current Vue/Svelte servers.

Add support for the Astro web framework via @astrojs/language-server with a
companion TypeScript language server (configured with @astrojs/ts-plugin) for
cross-file code intelligence. The Astro LS handles .astro parsing and document
symbols; the companion TypeScript server handles go-to-definition, references
and rename for .ts/.js files and cross-file resolution into .astro files.

The adapter mirrors the Vue and Svelte language servers, which keep their
companion server in a self-contained `self._ts_server` attribute rather than a
shared companion base class.

Details:
- @astrojs/ts-plugin is installed explicitly (it is not a dependency of
  @astrojs/language-server) and its path is validated at startup; without it the
  companion has no .astro awareness and cross-file resolution silently fails.
- .tsx/.jsx are opened as typescriptreact/javascriptreact to avoid tsserver
  truncating symbol ranges at JSX expressions.
- If the Astro LS fails to start after the companion is up, the companion and
  the partial Astro process are torn down before re-raising (as Angular does).
- Registration mirrors Vue/Svelte: Language.ASTRO enum, superset priority, a
  .astro source-file matcher, and get_ls_class wiring. Adds the .astro source
  extension to the hooks extension set and an `astro` pytest marker (astro tests
  run in the CI catch-all batch, which already provides Node.js).
- Documentation: README language list, docs/01-about/020_programming-languages.md,
  and CHANGELOG (under the existing "Language Servers" section).

Tests assert concrete symbol names and exact reference locations, including a
cross-file reference from counter.ts into index.astro. A session-scoped conftest
installs the fixture's npm dependencies (mirroring Svelte/Angular) so the tests
do not pass vacuously.

This builds on the Astro work in dansasser/serena PR oraios#886. That PR was written
against a companion-server base class abstraction that is not present in
upstream main, so the adapter here was implemented against the Vue/Svelte
companion pattern currently in main instead.

Co-authored-by: dansasser <sasserenterprises@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@strategiert

Copy link
Copy Markdown
Author

CI triage: the jvm (macos-latest) failure is a pre-existing Java test issue, unrelated to this PR — test_java_basic.py::test_bare_symbol_names[java] fails with FileNotFoundError: .../LombokModel$LombokModelBuilder.class not found. The same job fails on recent main runs (e.g. the merge commit of #1660 on 2026-07-07). The Astro tests added here run in the catch-all batch, which passes on all three OSes in this PR's run.

@MischaPanch MischaPanch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the contribution!

We never had time to properly review the companion server refactoring, so I agree that what you did is the reasonable step for adding Astro support. Overall looks good, only minor comments. Please have a look at the tests and remove the repo_path fixture if possible.

Also, please add the astro marker to the batch with other web frameworks in the pytest.yml github workflow (comment on top of the file explains everything)


log = logging.getLogger(__name__)

REPO_ROOT = Path(__file__).resolve().parents[2] / "resources" / "repos" / "astro" / "test_repo"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls use get_repo_path from conftest

INSTALL_LOCK = REPO_ROOT / ".astro-install.lock"


@pytest.fixture(scope="session", autouse=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since autouse is true, this should be marked with pytest.mark.astro, though I am not sure whether pytest fixtures support that


@pytest.mark.parametrize("language_server", [Language.ASTRO], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ASTRO], indirect=True)
def test_astro_document_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something is strange about these tests. We just need relative paths, so there's no need to use repo_path. No other tests use it, why did you have to do it here?

Comment thread CHANGELOG.md
Comment on lines +51 to +54
- Add support for **Astro** (`@astrojs/language-server`) with a companion `typescript-language-server`
(configured with `@astrojs/ts-plugin`) for cross-file go-to-definition, references and rename between
`.ts`/`.js` and `.astro` files. Requires Node.js v18+ and npm; dependencies auto-install on first use.
Use language `astro` instead of also enabling `typescript`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Add support for **Astro** (`@astrojs/language-server`) with a companion `typescript-language-server`
(configured with `@astrojs/ts-plugin`) for cross-file go-to-definition, references and rename between
`.ts`/`.js` and `.astro` files. Requires Node.js v18+ and npm; dependencies auto-install on first use.
Use language `astro` instead of also enabling `typescript`.
- Add support for **Astro**


# Get TypeScript version settings from TypeScript language server settings
typescript_config = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT)
typescript_version = typescript_config.get("typescript_version", "5.9.3")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls put the default versions of everything as constants at the top of the file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants