fix(check): include .astro files in checked project references - #17715
Conversation
🦋 Changeset detectedLatest commit: 1117575 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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
left a comment
There was a problem hiding this comment.
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.mjsruns esbuild withbundle: trueand only externalisesvscode/@astrojs/compiler/prettier*, so the patched@volar/kitis inlined), - but everyone running
astro checkfrom npm still gets unpatched@volar/kitand still sees #17478.
That also makes the changeset inaccurate (see inline comment).
Suggested alternatives, roughly in order of preference:
- Land volarjs/volar.js#315 upstream and bump the dep — the real fix.
- Implement the equivalent in this repo's own source so it actually ships.
createTypeScriptChecker'ssetupcallback already receivesproject.typescript(configFileName+languageServiceHost), andcheck.tsalready wraps it foraddAstroTypes. Re-parsing the referencedconfigFileNamewithextraFileExtensionsand augmentinggetScriptFileNamesthere would be contained, testable, and present in the published package. - 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:16asts.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, sots.sysusage, discarded.errors, and argument positions are consistent with existing behaviour rather than a new inconsistency.tsandpathare both in scope in that module. - Transitive / nested references are handled.
visitstill recurses viaref.references?.forEach(visit), and every visited ref now re-parses its own tsconfig withextraFileExtensions, so a reference chain A -> B -> C is covered at each level. Cycles are still guarded by the pre-existingtsconfigsSet. (Pre-existing, not introduced here: the Set is seeded withasPosix(configFileName)for the root but stores rawref.sourceFile.fileNamefor refs, so the normalisation is asymmetric — worth an upstream note, not a blocker.) - No hot-path perf regression. The
getCommandLinethunk is invoked once eagerly atcreateTypeScriptCheckerLanguageService(line 225) and thereafter only fromcheckRootFilesUpdate()when a watched file is created/deleted. It is not called pergetScriptFileNames(), so re-parsing from disk inside it is bounded. - Blast radius is small (item 4).
@volar/kithas exactly one importer in the monorepo —packages/language-tools/language-server/src/check.ts— and one entry inpnpm-lock.yaml. No transitive consumers. One knock-on worth knowing:check.tsregisters the Svelte and Vue language plugins alongside Astro, so this patch also newly pulls.vue/.sveltefiles out of referenced projects into the program. They are filtered out of linting atcheck.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, andallowUnusedPatchesdefaults tofalse, so bumping@volar/kitto2.4.29leaves the patch unused andpnpm installfails withERR_PNPM_PATCH_NOT_APPLIED. Separately, pnpm v11 removedignorePatchFailures— patch application failures now always throw. And the lockfile stores the patch content hash, so--frozen-lockfileCI 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 firstpatchedDependenciesentry (nopatches/directory existed before), which is a new maintenance concept maintainers may not want; and becausepackage.jsondeclares~2.4.28, a perfectly routinepnpm updateinside the tilde range will hard-breakpnpm installfor 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.
| + // 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.
This comment was marked as spam.
Sorry, something went wrong.
| assert.strictEqual(result.errors, 2); | ||
| }); | ||
|
|
||
| it('Includes .astro files from referenced projects', async () => { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| '@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.
This comment was marked as spam.
Sorry, something went wrong.
| workerd: false | ||
|
|
||
| patchedDependencies: | ||
| '@volar/kit@2.4.28': patches/@volar__kit@2.4.28.patch |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
…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.
This comment was marked as spam.
This comment was marked as spam.
|
Thanks @wakqasahmed, can you fix the lint errors? Thank you. |
This comment was marked as spam.
This comment was marked as spam.
|
Before merging, could you simplify the implementation?
The current invalidation compares TypeScript’s original file list, which excludes 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 |
…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.
This comment was marked as spam.
This comment was marked as spam.
|
@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. |
Fixes #17478
Problem
When a project uses TypeScript project references (
referencesintsconfig.json),astro check(and the editor language server) silently drops.astrofiles that live inside a referenced tsconfig's project. Only the root tsconfig's files were checked with Astro'sextraFileExtensions; files pulled in through a reference were not.Root cause
In
@volar/kit'screateChecker.js, the root tsconfig is parsed withts.parseJsonSourceFileConfigFileContent(...), explicitly passing the language plugins'extraFileExtensions(so.astrois included). The project-reference walker (visit()) instead reuses TypeScript's own internally-resolvedref.commandLinefor each referenced project, which was resolved without knowledge ofextraFileExtensions. As a result,.astrofiles 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.28directly via pnpm'spatchedDependencies. A cold-start review caught a blocking problem with that approach:@astrojs/language-serverand@astrojs/checkboth build withtsc -b(no bundling) and declare@volar/kitas a normal runtime dependency. pnpm'spatchedDependenciesonly rewritesnode_modulesinside this monorepo for local dev/CI — it is never encoded into the published npm tarballs. Anyone runningnpm 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'ssetupcallback runs once per project — the root project and each project reference — and receives that project'sconfigFileNameand a mutablelanguageServiceHost.extraFileExtensions(the same call shape@volar/kitalready uses for the root tsconfig) and merge any newly-found files intolanguageServiceHost.getScriptFileNames(). This affects the actual TypeScriptProgramused for diagnostics.getRootFileNames()(used byAstroCheck.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 fromsetup, so it gets its own equivalent patch onthis.linter.getRootFileNames.@volar/kitdependency at all —patches/@volar__kit@2.4.28.patchand thepatchedDependenciesentries inpnpm-workspace.yaml/pnpm-lock.yamlhave 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/kitpin is bumped past it, our in-source workaround incheck.tsbecomes redundant (harmless, since it re-derives the same file list) and can be removed.Test plan
packages/language-tools/language-server/test/check/check.test.ts: a.astrofile with a type error added to the project-references fixture, asserting viachecker.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.@volar/kit@2.4.28from npm (not a pnpm patch) — confirms the fix is present in code that will actually reach published packages.node --test test/check/check.test.tsinpackages/language-tools/language-server: 10/10 passing, including a clean rebuild ofastro,@astrojs/markdown-satteri,@astrojs/svelte, and@astrojs/vueto confirm the two previously-"pre-existing" failures were an artifact of an unbuilt local environment, not a real repo-level issue.@astrojs/language-serverdescribing the in-source fix.