Skip to content

fix(check): include .astro files in checked project references - #17715

Merged
matthewp merged 6 commits into
withastro:mainfrom
wakqasahmed:fix/volar-kit-astro-file-refs-17478
Aug 27, 2026
Merged

matthewp merged 6 commits into
withastro:mainfrom
wakqasahmed:fix/volar-kit-astro-file-refs-17478

Conversation

@wakqasahmed

@wakqasahmed wakqasahmed commented Aug 15, 2026

Copy link
Copy Markdown

Fixes #17478

Problem

When a project uses TypeScript project references (references in tsconfig.json), astro check (and the editor language server) silently drops .astro files that live inside a referenced tsconfig's project. Only the root tsconfig's files were checked with Astro's extraFileExtensions; files pulled in through a reference were not.

Root cause

In @volar/kit's createChecker.js, the root tsconfig is parsed with ts.parseJsonSourceFileConfigFileContent(...), explicitly passing the language plugins' extraFileExtensions (so .astro is included). The project-reference walker (visit()) instead reuses TypeScript's own internally-resolved ref.commandLine for each referenced project, which was resolved without knowledge of extraFileExtensions. As a result, .astro files inside a referenced project were never added to the referenced project's file list.

Revised approach (see review discussion)

The first version of this PR patched @volar/kit@2.4.28 directly via pnpm's patchedDependencies. A cold-start review caught a blocking problem with that approach: @astrojs/language-server and @astrojs/check both build with tsc -b (no bundling) and declare @volar/kit as a normal runtime dependency. pnpm's patchedDependencies only rewrites node_modules inside this monorepo for local dev/CI — it is never encoded into the published npm tarballs. Anyone running npm install @astrojs/check (or @astrojs/language-server) would still get the real, unpatched @volar/kit, so the original bug would remain for every CLI/CI user — the exact scenario in #17478. The only place that actually got the fix was the VS Code extension, because it bundles with esbuild and inlines the patched code at build time.

This PR now implements the fix directly in @astrojs/language-server's own source instead, so it ships in the real published packages:

  • createTypeScriptChecker's setup callback runs once per project — the root project and each project reference — and receives that project's configFileName and a mutable languageServiceHost.
  • For each project, we re-parse its own tsconfig with the language plugins' extraFileExtensions (the same call shape @volar/kit already uses for the root tsconfig) and merge any newly-found files into languageServiceHost.getScriptFileNames(). This affects the actual TypeScript Program used for diagnostics.
  • getRootFileNames() (used by AstroCheck.lint() to enumerate the whole project when no explicit file list is given) reads project-reference file lists from a separate internal host that isn't reachable from setup, so it gets its own equivalent patch on this.linter.getRootFileNames.
  • No changes to the @volar/kit dependency at all — patches/@volar__kit@2.4.28.patch and the patchedDependencies entries in pnpm-workspace.yaml/pnpm-lock.yaml have been removed.

There is still an open upstream fix for the same root cause in volar.js itself (volarjs/volar.js#315). If/when that lands and this repo's @volar/kit pin is bumped past it, our in-source workaround in check.ts becomes redundant (harmless, since it re-derives the same file list) and can be removed.

Test plan

  • Regression test in packages/language-tools/language-server/test/check/check.test.ts: a .astro file with a type error added to the project-references fixture, asserting via checker.linter.getRootFileNames() (per review feedback, independent of the diagnostics-count assertion) that the referenced project's file list includes it, plus asserting the error count and file-checked count.
  • Verified the assertions fail without the fix and pass with it, using the real, unpatched @volar/kit@2.4.28 from npm (not a pnpm patch) — confirms the fix is present in code that will actually reach published packages.
  • node --test test/check/check.test.ts in packages/language-tools/language-server: 10/10 passing, including a clean rebuild of astro, @astrojs/markdown-satteri, @astrojs/svelte, and @astrojs/vue to confirm the two previously-"pre-existing" failures were an artifact of an unbuilt local environment, not a real repo-level issue.
  • Updated changeset for @astrojs/language-server describing the in-source fix.

@changeset-bot

changeset-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1117575

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@astrojs/language-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@wakqasahmed wakqasahmed left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cold-start review (no prior context on this change). I verified the patch against the real @volar/kit@2.4.28 npm tarball, checked the upstream PR, the fixture/test structure, the blast radius across the monorepo, and pnpm's patch-mismatch semantics.

Verdict: request changes — one blocking design issue. The patch itself is correct and byte-identical to the upstream fix, but a patchedDependencies entry does not ship to npm consumers, so this does not actually fix astro check for users.

Blocking: the fix never reaches published packages

@astrojs/language-server builds with tsc -b (no bundling) and declares "@volar/kit": "~2.4.28" as a runtime dependency. @astrojs/check likewise builds with tsc -b and depends on @astrojs/language-server. pnpm's patchedDependencies only rewrites this monorepo's own node_modules; it is not encoded in the published tarballs and is not honoured by a consumer's npm/yarn/pnpm install. So after this lands and releases:

  • the repo's own tests go green,
  • the VS Code extension does get the fix (packages/language-tools/vscode/scripts/build.mjs runs esbuild with bundle: true and only externalises vscode/@astrojs/compiler/prettier*, so the patched @volar/kit is inlined),
  • but everyone running astro check from npm still gets unpatched @volar/kit and still sees #17478.

That also makes the changeset inaccurate (see inline comment).

Suggested alternatives, roughly in order of preference:

  1. Land volarjs/volar.js#315 upstream and bump the dep — the real fix.
  2. Implement the equivalent in this repo's own source so it actually ships. createTypeScriptChecker's setup callback already receives project.typescript (configFileName + languageServiceHost), and check.ts already wraps it for addAstroTypes. Re-parsing the referenced configFileName with extraFileExtensions and augmenting getScriptFileNames there would be contained, testable, and present in the published package.
  3. If a patch is kept as a stopgap, say so explicitly in the changeset and keep the issue open, because end users are not fixed.

Verified good

  • Patch fidelity. The patch is byte-for-byte the same hunk as upstream volarjs/volar.js#315 (including the comment text). I confirmed that PR is still open and unmerged — single commit 4691af3c (2026-07-22), no reviews, no comments. Zero divergence from upstream is the best possible property for a patch like this.
  • Correctness vs. the root-tsconfig path. In the real 2.4.28 tarball, the root config is parsed at lib/createChecker.js:16 as ts.parseJsonSourceFileConfigFileContent(ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile), ts.sys, path.dirname(...), undefined, tsconfigPath, undefined, <extraFileExtensions>). The patch reproduces that call shape exactly for each reference, so ts.sys usage, discarded .errors, and argument positions are consistent with existing behaviour rather than a new inconsistency. ts and path are both in scope in that module.
  • Transitive / nested references are handled. visit still recurses via ref.references?.forEach(visit), and every visited ref now re-parses its own tsconfig with extraFileExtensions, so a reference chain A -> B -> C is covered at each level. Cycles are still guarded by the pre-existing tsconfigs Set. (Pre-existing, not introduced here: the Set is seeded with asPosix(configFileName) for the root but stores raw ref.sourceFile.fileName for refs, so the normalisation is asymmetric — worth an upstream note, not a blocker.)
  • No hot-path perf regression. The getCommandLine thunk is invoked once eagerly at createTypeScriptCheckerLanguageService (line 225) and thereafter only from checkRootFilesUpdate() when a watched file is created/deleted. It is not called per getScriptFileNames(), so re-parsing from disk inside it is bounded.
  • Blast radius is small (item 4). @volar/kit has exactly one importer in the monorepo — packages/language-tools/language-server/src/check.ts — and one entry in pnpm-lock.yaml. No transitive consumers. One knock-on worth knowing: check.ts registers the Svelte and Vue language plugins alongside Astro, so this patch also newly pulls .vue/.svelte files out of referenced projects into the program. They are filtered out of linting at check.ts:~70, so no new diagnostics, but they will now be parsed and held in memory. That is parity with how the root tsconfig already behaves, so I read it as intended rather than a bug.
  • Future-upgrade risk is safe, loud, not silent (item 8). The repo pins pnpm@11.13.1. The patch key is an exact version, and allowUnusedPatches defaults to false, so bumping @volar/kit to 2.4.29 leaves the patch unused and pnpm install fails with ERR_PNPM_PATCH_NOT_APPLIED. Separately, pnpm v11 removed ignorePatchFailures — patch application failures now always throw. And the lockfile stores the patch content hash, so --frozen-lockfile CI also catches a hand-edited patch file. There is no path where the patch silently stops applying or silently applies wrong. The residual-risk note in the PR description is accurate. Two things worth calling out anyway: this would be the repo's first patchedDependencies entry (no patches/ directory existed before), which is a new maintenance concept maintainers may not want; and because package.json declares ~2.4.28, a perfectly routine pnpm update inside the tilde range will hard-break pnpm install for every contributor until someone re-diffs the patch.

Test review (item 6)

The fixture structure genuinely exercises cross-reference checking: fixture-references/tsconfig.json is {"files": [], "references": [{"path": "./tsconfig.app.json"}]} and tsconfig.app.json is {"include": ["src"]}, so src/hasError.astro is reachable only through the project reference. The errors: 1 -> 2 assertion is the load-bearing one and cannot pass by accident. The stated verification method (removing the patch, reinstalling, watching both new assertions fail) is credible — both assertions depend solely on the referenced project's file list. See the inline comment for a weakness in the second test.

On the two "unrelated pre-existing failures" (item 7)

They are unrelated to this change — @astrojs/svelte/@astrojs/vue only affect check/fixture/frameworks/Component.{svelte,vue}, which drive the fileResult.length === 4 and fileChecked === 6 assertions in the first describe, not the project-references block.

But they are not pre-existing in a correctly installed tree: both are declared as workspace:* in test/package.json and both packages exist in this repo at packages/integrations/svelte and packages/integrations/vue. So this looks like an incomplete/filtered local install rather than a repo-level breakage, which means part of the local verification ran against a degraded environment. Please confirm the full check.test.ts suite is green in CI before merge.

Comment thread patches/@volar__kit@2.4.28.patch Outdated
+ // Re-parse the referenced tsconfig with extraFileExtensions so that
+ // non-TS files (e.g. .astro) are included. TypeScript's resolved
+ // ref.commandLine does not include extra extensions.
+ return ts.parseJsonSourceFileConfigFileContent(ts.readJsonConfigFile(ref.sourceFile.fileName, ts.sys.readFile), ts.sys, path.dirname(ref.sourceFile.fileName), undefined, ref.sourceFile.fileName, undefined, extraFileExtensions);

This comment was marked as spam.

assert.strictEqual(result.errors, 2);
});

it('Includes .astro files from referenced projects', async () => {

This comment was marked as spam.

'@astrojs/language-server': patch
---

Fixes `astro check` (and the editor language server) silently skipping `.astro` files inside tsconfig project references. `@volar/kit` is patched via pnpm's `patchedDependencies` so referenced tsconfigs are re-parsed with the language plugins' `extraFileExtensions`, matching how the root tsconfig is already parsed.

This comment was marked as spam.

Comment thread pnpm-workspace.yaml Outdated
workerd: false

patchedDependencies:
'@volar/kit@2.4.28': patches/@volar__kit@2.4.28.patch

This comment was marked as spam.

…thastro#17478)

The previous approach patched @volar/kit via pnpm's patchedDependencies,
which only rewrites node_modules inside this monorepo and never reaches
published npm tarballs. astro check/@astrojs/language-server both build
with tsc -b (no bundling) and depend on @volar/kit as a runtime
dependency, so the patch never shipped to real npm consumers - only the
VS Code extension (which bundles with esbuild) benefited.

Reimplement the fix directly in AstroCheck's checker setup instead.
createTypeScriptChecker's setup callback runs once per project (root and
each reference) and exposes that project's configFileName and mutable
languageServiceHost, which is enough to re-parse each referenced
tsconfig with the language plugins' extraFileExtensions and merge the
result into that project's file list - without touching @volar/kit at
all. getRootFileNames() reads from a separate internal host, so it gets
its own merge on top.

Verified end to end: removed the @volar/kit patch, rebuilt
@astrojs/language-server against the real unpatched dependency, and
confirmed the project-references test suite (including a fileResult
assertion swapped for a direct getRootFileNames() check per review) is
fully green. Also rebuilt astro, @astrojs/markdown-satteri, and the
svelte/vue integrations from a clean state to confirm the two
previously-failing assertions were an artifact of an unbuilt local
environment, not a real pre-existing failure - full suite is 10/10 with
everything built.
@wakqasahmed

This comment was marked as spam.

@matthewp

Copy link
Copy Markdown
Contributor

Thanks @wakqasahmed, can you fix the lint errors? Thank you.

@wakqasahmed

This comment was marked as spam.

@matthewp

Copy link
Copy Markdown
Contributor

Before merging, could you simplify the implementation?

  • Parse each referenced config once during checker setup with extraFileExtensions.
  • Statically merge those file names into languageServiceHost.getScriptFileNames().
  • Collect the same names and merge them into linter.getRootFileNames().
  • Remove the cache, resolver callbacks, and file-list change tracking.

The current invalidation compares TypeScript’s original file list, which excludes .astro files. Consequently, adding or removing only an .astro file would not invalidate the cache, so the additional machinery does not provide reliable watch-mode behavior. The static approach covers the reported issue, including multiple and nested references present when the checker starts, with considerably less code.

If dynamic add/remove support is intentional, it should instead have a focused watch-mode regression test and invalidation that responds to extra-extension files directly.

Please also remove the claim that this fixes the editor language server from the PR description and changeset. This change affects AstroCheck; the editor uses the separate nodeServer.ts/createTypeScriptProject path. That claim would need a corresponding editor-path change and test.

…review

Per @matthewp: the previous version's dynamic caching/invalidation was
unreliable anyway — it only invalidated by comparing TypeScript's own
file list, which never includes .astro files, so adding/removing only
an .astro file never triggered a re-parse.

Replaces it with a static approach that matches what the root project
already does: each referenced project's tsconfig is parsed once with
extraFileExtensions during checker setup, and the result is merged once
into that project's languageServiceHost.getScriptFileNames() and into
linter.getRootFileNames() — no cache, no resolver callbacks, no
file-list change tracking.

Also removed the editor-language-server claim from the PR description
and changeset — this only fixes AstroCheck; the editor's language server
uses the separate nodeServer.ts/createTypeScriptProject path.

Verified: pnpm --filter @astrojs/language-server build (tsc -b) is clean,
and biome check on check.ts is clean. Could not run the check.test.ts
suite itself locally — it imports packages/astro/test/test-utils.ts,
which needs a full astro package build, and that build fails here on an
unrelated missing workspace dependency (@astrojs/markdown-satteri) not
present in this checkout. Happy to have CI or a maintainer confirm the
existing check.test.ts assertions (file count, error count, and the new
getRootFileNames() .astro-inclusion check) still pass — none of them
test dynamic add/remove behavior, so nothing in that suite should be
sensitive to removing the caching layer.
@wakqasahmed

This comment was marked as spam.

@matthewp

Copy link
Copy Markdown
Contributor

@wakqasahmed Last thing, the changeset explains implementation details. We word our changesets based on the how the changes affect the end user. Please see https://contribute.docs.astro.build/docs-for-code-changes/changesets/

Can you update it? Thanks.

@wakqasahmed

This comment was marked as spam.

@matthewp
matthewp merged commit a51c533 into withastro:main Aug 27, 2026
27 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Aug 27, 2026
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.

astro check references do not catch errors in .astro files

2 participants