Skip to content

Commit c5ddd64

Browse files
committed
fix(language-server): follow project references when resolving the owning tsconfig
A solution-style tsconfig (`files: []` plus `references`) includes nothing itself. Nuxt 3 generates exactly that shape at the project root, pointing at `.nuxt/tsconfig.*.json` — without following the references those projects fell back to the empty simple project, losing their aliases and compiler options. `searchConfig` now recurses into `projectReferences` (with a visited set) before walking further up, and a solution-style fixture pins the behaviour.
1 parent 4d90811 commit c5ddd64

6 files changed

Lines changed: 56 additions & 4 deletions

File tree

‎packages/language-server/lib/project.ts‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,18 @@ export function createProjectResolver(ts: typeof import('typescript')): ProjectR
3131
}
3232

3333
function search(fileName: string) {
34+
const visited = new Set<string>();
3435
let dir = path.dirname(fileName);
3536
while (true) {
3637
const configFileName = ts.findConfigFile(dir, ts.sys.fileExists);
3738
if (!configFileName) {
3839
return undefined;
3940
}
40-
if (isOwned(configFileName, fileName)) {
41-
return configFileName;
41+
const owned = searchConfig(configFileName, fileName, visited);
42+
if (owned) {
43+
return owned;
4244
}
43-
// The nearest tsconfig does not include the file (e.g. a solution-style config that only
44-
// holds references); keep looking in the parent directory.
45+
// The nearest tsconfig does not include the file; keep looking in the parent directory.
4546
const parent = path.dirname(path.dirname(configFileName));
4647
if (parent === path.dirname(configFileName)) {
4748
return undefined;
@@ -50,6 +51,28 @@ export function createProjectResolver(ts: typeof import('typescript')): ProjectR
5051
}
5152
}
5253

54+
/**
55+
* A solution-style tsconfig (`files: []` plus `references`) does not include anything itself —
56+
* Nuxt 3 generates exactly this shape at the project root, pointing at `.nuxt/tsconfig.*.json`.
57+
* Follow the references to find the config that actually owns the file.
58+
*/
59+
function searchConfig(configFileName: string, fileName: string, visited: Set<string>): string | undefined {
60+
const key = configFileName.replace(/\\/g, '/').toLowerCase();
61+
if (visited.has(key)) {
62+
return undefined;
63+
}
64+
visited.add(key);
65+
if (isOwned(configFileName, fileName)) {
66+
return configFileName;
67+
}
68+
for (const reference of getCommandLine(configFileName)?.projectReferences ?? []) {
69+
const owned = searchConfig(reference.path, fileName, visited);
70+
if (owned) {
71+
return owned;
72+
}
73+
}
74+
}
75+
5376
const resolver: ProjectResolver = {
5477
getConfigFileName(fileName) {
5578
if (!fileToConfig.has(fileName)) {

‎packages/language-server/tests/project.spec.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ test('resolves the tsconfig that owns a file', () => {
1818
resolver.dispose();
1919
});
2020

21+
test('follows project references of a solution-style tsconfig', () => {
22+
const resolver = createProjectResolver(ts);
23+
// Nuxt 3 shape: the root tsconfig only holds references, the generated config owns the files.
24+
const appFile = path.join(testWorkspacePath, 'tsconfigSolution/src/App.vue');
25+
26+
expect(normalize(resolver.getConfigFileName(appFile)!)).toMatch(/tsconfigSolution\/generated\/tsconfig\.app\.json$/);
27+
expect(resolver.getCommandLine(appFile)!.options.paths).toEqual({ '@solution/*': ['../src/*'] });
28+
29+
resolver.dispose();
30+
});
31+
2132
test('resolves module names with the owning project options', () => {
2233
const resolver = createProjectResolver(ts);
2334
const firstOptions = resolver.getCommandLine(firstProjectFile)!.options;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"include": ["../**/*"],
3+
"compilerOptions": {
4+
"paths": {
5+
"@solution/*": ["../src/*"]
6+
}
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script setup lang="ts">
2+
const msg = "hi";
3+
</script>
4+
5+
<template><div>{{ msg }}</div></template>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const helper = 1;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"files": [],
3+
"references": [{ "path": "./generated/tsconfig.app.json" }]
4+
}

0 commit comments

Comments
 (0)