Skip to content

Commit 5922491

Browse files
authored
(feat) support TS resolution mode node16/nodenext (#1526)
#1522 - only force module/resolutionmode if not set to something valid by user - TS plugin: add new param to ts resolver to fix diagnostics not appearing
1 parent 83d92c5 commit 5922491

File tree

15 files changed

+198
-30
lines changed

15 files changed

+198
-30
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"lint": "prettier --check . && eslint \"packages/**/*.{ts,js}\""
1717
},
1818
"dependencies": {
19-
"typescript": "^4.7.2"
19+
"typescript": "^4.7.3"
2020
},
2121
"devDependencies": {
2222
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.2.0",

packages/language-server/src/plugins/typescript/module-loader.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { createSvelteSys } from './svelte-sys';
55
import {
66
ensureRealSvelteFilePath,
77
getExtensionFromScriptKind,
8-
isVirtualSvelteFilePath
8+
isSvelteFilePath,
9+
isVirtualSvelteFilePath,
10+
toVirtualSvelteFilePath
911
} from './utils';
1012

1113
/**
@@ -67,6 +69,38 @@ class ModuleResolutionCache {
6769
}
6870
}
6971

72+
class ImpliedNodeFormatResolver {
73+
private alreadyResolved = new Map<string, ReturnType<typeof ts.getModeForResolutionAtIndex>>();
74+
75+
resolve(
76+
importIdxInFile: number,
77+
sourceFile: ts.SourceFile | undefined,
78+
compilerOptions: ts.CompilerOptions
79+
) {
80+
let mode = undefined;
81+
if (sourceFile) {
82+
if (!sourceFile.impliedNodeFormat && isSvelteFilePath(sourceFile.fileName)) {
83+
// impliedNodeFormat is not set for Svelte files, because the TS function which
84+
// calculates this works with a fixed set of file extensions,
85+
// which .svelte is obv not part of. Make it work by faking a TS file.
86+
if (!this.alreadyResolved.has(sourceFile.fileName)) {
87+
sourceFile.impliedNodeFormat = ts.getImpliedNodeFormatForFile(
88+
toVirtualSvelteFilePath(sourceFile.fileName) as any,
89+
undefined,
90+
ts.sys,
91+
compilerOptions
92+
);
93+
this.alreadyResolved.set(sourceFile.fileName, sourceFile.impliedNodeFormat);
94+
} else {
95+
sourceFile.impliedNodeFormat = this.alreadyResolved.get(sourceFile.fileName);
96+
}
97+
}
98+
mode = ts.getModeForResolutionAtIndex(sourceFile, importIdxInFile);
99+
}
100+
return mode;
101+
}
102+
}
103+
70104
/**
71105
* Creates a module loader specifically for `.svelte` files.
72106
*
@@ -85,6 +119,7 @@ export function createSvelteModuleLoader(
85119
) {
86120
const svelteSys = createSvelteSys(getSnapshot);
87121
const moduleCache = new ModuleResolutionCache();
122+
const impliedNodeFormatResolver = new ImpliedNodeFormatResolver();
88123

89124
return {
90125
fileExists: svelteSys.fileExists,
@@ -103,31 +138,50 @@ export function createSvelteModuleLoader(
103138

104139
function resolveModuleNames(
105140
moduleNames: string[],
106-
containingFile: string
141+
containingFile: string,
142+
_reusedNames: string[] | undefined,
143+
_redirectedReference: ts.ResolvedProjectReference | undefined,
144+
_options: ts.CompilerOptions,
145+
containingSourceFile?: ts.SourceFile | undefined
107146
): Array<ts.ResolvedModule | undefined> {
108-
return moduleNames.map((moduleName) => {
147+
return moduleNames.map((moduleName, index) => {
109148
if (moduleCache.has(moduleName, containingFile)) {
110149
return moduleCache.get(moduleName, containingFile);
111150
}
112151

113-
const resolvedModule = resolveModuleName(moduleName, containingFile);
152+
const resolvedModule = resolveModuleName(
153+
moduleName,
154+
containingFile,
155+
containingSourceFile,
156+
index
157+
);
114158
moduleCache.set(moduleName, containingFile, resolvedModule);
115159
return resolvedModule;
116160
});
117161
}
118162

119163
function resolveModuleName(
120164
name: string,
121-
containingFile: string
165+
containingFile: string,
166+
containingSourceFile: ts.SourceFile | undefined,
167+
index: number
122168
): ts.ResolvedModule | undefined {
169+
const mode = impliedNodeFormatResolver.resolve(
170+
index,
171+
containingSourceFile,
172+
compilerOptions
173+
);
123174
// Delegate to the TS resolver first.
124175
// If that does not bring up anything, try the Svelte Module loader
125176
// which is able to deal with .svelte files.
126177
const tsResolvedModule = ts.resolveModuleName(
127178
name,
128179
containingFile,
129180
compilerOptions,
130-
ts.sys
181+
ts.sys,
182+
undefined,
183+
undefined,
184+
mode
131185
).resolvedModule;
132186
if (tsResolvedModule && !isVirtualSvelteFilePath(tsResolvedModule.resolvedFileName)) {
133187
return tsResolvedModule;
@@ -137,7 +191,10 @@ export function createSvelteModuleLoader(
137191
name,
138192
containingFile,
139193
compilerOptions,
140-
svelteSys
194+
svelteSys,
195+
undefined,
196+
undefined,
197+
mode
141198
).resolvedModule;
142199
if (
143200
!svelteResolvedModule ||

packages/language-server/src/plugins/typescript/service.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,6 @@ async function createLanguageService(
292292
const forcedCompilerOptions: ts.CompilerOptions = {
293293
allowNonTsExtensions: true,
294294
target: ts.ScriptTarget.Latest,
295-
module: ts.ModuleKind.ESNext,
296-
moduleResolution: ts.ModuleResolutionKind.NodeJs,
297295
allowJs: true,
298296
noEmit: true,
299297
declaration: false,
@@ -344,6 +342,25 @@ async function createLanguageService(
344342
...parsedConfig.options,
345343
...forcedCompilerOptions
346344
};
345+
if (
346+
!compilerOptions.moduleResolution ||
347+
compilerOptions.moduleResolution === ts.ModuleResolutionKind.Classic
348+
) {
349+
compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs;
350+
}
351+
if (
352+
!compilerOptions.module ||
353+
[
354+
ts.ModuleKind.AMD,
355+
ts.ModuleKind.CommonJS,
356+
ts.ModuleKind.ES2015,
357+
ts.ModuleKind.None,
358+
ts.ModuleKind.System,
359+
ts.ModuleKind.UMD
360+
].includes(compilerOptions.module)
361+
) {
362+
compilerOptions.module = ts.ModuleKind.ESNext;
363+
}
347364

348365
// detect which JSX namespace to use (svelte | svelteNative) if not specified or not compatible
349366
if (!compilerOptions.jsxFactory || !compilerOptions.jsxFactory.startsWith('svelte')) {

packages/language-server/src/plugins/typescript/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export function toRealSvelteFilePath(filePath: string) {
7676
return filePath.slice(0, -'.ts'.length);
7777
}
7878

79+
export function toVirtualSvelteFilePath(filePath: string) {
80+
return filePath.endsWith('.ts') ? filePath : filePath + '.ts';
81+
}
82+
7983
export function ensureRealSvelteFilePath(filePath: string) {
8084
return isVirtualSvelteFilePath(filePath) ? toRealSvelteFilePath(filePath) : filePath;
8185
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const foo = true;
2+
export const baz = true;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"range": { "start": { "line": 4, "character": 22 }, "end": { "line": 4, "character": 29 } },
4+
"severity": 1,
5+
"source": "ts",
6+
"message": "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './bar.js'?",
7+
"code": 2835,
8+
"tags": []
9+
}
10+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"range": { "start": { "line": 4, "character": 22 }, "end": { "line": 4, "character": 29 } },
4+
"severity": 1,
5+
"source": "ts",
6+
"message": "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './bar.js'?",
7+
"code": 2835,
8+
"tags": []
9+
}
10+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
// valid
3+
import { foo } from './bar.js';
4+
// invalid
5+
import { baz } from './bar';
6+
7+
foo;baz;
8+
</script>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"allowJs": true,
4+
"module": "Node16",
5+
"moduleResolution": "Node16",
6+
/**
7+
This is actually not needed, but makes the tests faster
8+
because TS does not look up other types.
9+
*/
10+
"types": ["svelte"]
11+
}
12+
}

packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
*/
88
"types": ["svelte"]
99
},
10-
"exclude": ["./svelte-native/**/*"]
10+
"exclude": ["./svelte-native/**/*", "./node16/**/*"]
1111
}

packages/language-server/test/plugins/typescript/module-loader.test.ts

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,21 @@ describe('createSvelteModuleLoader', () => {
4747
const { resolveStub, moduleResolver, compilerOptions } = setup(resolvedModule);
4848
const result = moduleResolver.resolveModuleNames(
4949
['./normal.ts'],
50-
'C:/somerepo/somefile.svelte'
50+
'C:/somerepo/somefile.svelte',
51+
undefined,
52+
undefined,
53+
undefined as any
5154
);
5255

5356
assert.deepStrictEqual(result, [resolvedModule]);
5457
assert.deepStrictEqual(lastCall(resolveStub).args, [
5558
'./normal.ts',
5659
'C:/somerepo/somefile.svelte',
5760
compilerOptions,
58-
ts.sys
61+
ts.sys,
62+
undefined,
63+
undefined,
64+
undefined
5965
]);
6066
});
6167

@@ -67,15 +73,21 @@ describe('createSvelteModuleLoader', () => {
6773
const { resolveStub, moduleResolver, compilerOptions } = setup(resolvedModule);
6874
const result = moduleResolver.resolveModuleNames(
6975
['/@/normal'],
70-
'C:/somerepo/somefile.svelte'
76+
'C:/somerepo/somefile.svelte',
77+
undefined,
78+
undefined,
79+
undefined as any
7180
);
7281

7382
assert.deepStrictEqual(result, [resolvedModule]);
7483
assert.deepStrictEqual(lastCall(resolveStub).args, [
7584
'/@/normal',
7685
'C:/somerepo/somefile.svelte',
7786
compilerOptions,
78-
ts.sys
87+
ts.sys,
88+
undefined,
89+
undefined,
90+
undefined
7991
]);
8092
});
8193

@@ -87,15 +99,21 @@ describe('createSvelteModuleLoader', () => {
8799
const { resolveStub, moduleResolver, compilerOptions } = setup(resolvedModule);
88100
const result = moduleResolver.resolveModuleNames(
89101
['./normal.ts'],
90-
'C:/somerepo/somefile.svelte'
102+
'C:/somerepo/somefile.svelte',
103+
undefined,
104+
undefined,
105+
undefined as any
91106
);
92107

93108
assert.deepStrictEqual(result, [resolvedModule]);
94109
assert.deepStrictEqual(lastCall(resolveStub).args, [
95110
'./normal.ts',
96111
'C:/somerepo/somefile.svelte',
97112
compilerOptions,
98-
ts.sys
113+
ts.sys,
114+
undefined,
115+
undefined,
116+
undefined
99117
]);
100118
});
101119

@@ -108,7 +126,10 @@ describe('createSvelteModuleLoader', () => {
108126
setup(resolvedModule);
109127
const result = moduleResolver.resolveModuleNames(
110128
['./svelte.svelte'],
111-
'C:/somerepo/somefile.svelte'
129+
'C:/somerepo/somefile.svelte',
130+
undefined,
131+
undefined,
132+
undefined as any
112133
);
113134

114135
assert.deepStrictEqual(result, [
@@ -122,7 +143,10 @@ describe('createSvelteModuleLoader', () => {
122143
'./svelte.svelte',
123144
'C:/somerepo/somefile.svelte',
124145
compilerOptions,
125-
svelteSys
146+
svelteSys,
147+
undefined,
148+
undefined,
149+
undefined
126150
]);
127151
assert.deepStrictEqual(lastCall(getSvelteSnapshotStub).args, ['filename.svelte']);
128152
});
@@ -136,7 +160,10 @@ describe('createSvelteModuleLoader', () => {
136160
setup(resolvedModule);
137161
const result = moduleResolver.resolveModuleNames(
138162
['/@/svelte.svelte'],
139-
'C:/somerepo/somefile.svelte'
163+
'C:/somerepo/somefile.svelte',
164+
undefined,
165+
undefined,
166+
undefined as any
140167
);
141168

142169
assert.deepStrictEqual(result, [
@@ -150,7 +177,10 @@ describe('createSvelteModuleLoader', () => {
150177
'/@/svelte.svelte',
151178
'C:/somerepo/somefile.svelte',
152179
compilerOptions,
153-
svelteSys
180+
svelteSys,
181+
undefined,
182+
undefined,
183+
undefined
154184
]);
155185
assert.deepStrictEqual(lastCall(getSvelteSnapshotStub).args, ['filename.svelte']);
156186
});
@@ -162,11 +192,20 @@ describe('createSvelteModuleLoader', () => {
162192
};
163193
const { resolveStub, moduleResolver } = setup(resolvedModule);
164194
// first call
165-
moduleResolver.resolveModuleNames(['./normal.ts'], 'C:/somerepo/somefile.svelte');
195+
moduleResolver.resolveModuleNames(
196+
['./normal.ts'],
197+
'C:/somerepo/somefile.svelte',
198+
undefined,
199+
undefined,
200+
undefined as any
201+
);
166202
// second call, which should be from cache
167203
const result = moduleResolver.resolveModuleNames(
168204
['./normal.ts'],
169-
'C:/somerepo/somefile.svelte'
205+
'C:/somerepo/somefile.svelte',
206+
undefined,
207+
undefined,
208+
undefined as any
170209
);
171210

172211
assert.deepStrictEqual(result, [resolvedModule]);

packages/svelte2tsx/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"svelte": "~3.48.0",
3838
"tiny-glob": "^0.2.6",
3939
"tslib": "^1.10.0",
40-
"typescript": "^4.7.2"
40+
"typescript": "^4.7.3"
4141
},
4242
"peerDependencies": {
4343
"svelte": "^3.24",

0 commit comments

Comments
 (0)