Skip to content

Commit d53a37e

Browse files
authored
feat: fallback resolution mode for createResolveModuleName (#293)
1 parent e08f2f4 commit d53a37e

File tree

4 files changed

+137
-69
lines changed

4 files changed

+137
-69
lines changed

packages/typescript/lib/node/decorateLanguageServiceHost.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Language } from '@volar/language-core';
22
import type * as ts from 'typescript';
33
import { createResolveModuleName } from '../resolveModuleName';
4+
import { fixupImpliedNodeFormatForFile } from './utils';
45

56
export function decorateLanguageServiceHost(
67
ts: typeof import('typescript'),
@@ -65,27 +66,40 @@ export function decorateLanguageServiceHost(
6566
containingSourceFile,
6667
...rest
6768
) => {
68-
if (moduleLiterals.every(name => !pluginExtensions.some(ext => name.text.endsWith(ext)))) {
69-
return resolveModuleNameLiterals(
70-
moduleLiterals,
71-
containingFile,
72-
redirectedReference,
73-
options,
74-
containingSourceFile,
75-
...rest,
76-
);
69+
const disposeFixup = fixupImpliedNodeFormatForFile(
70+
ts,
71+
pluginExtensions,
72+
containingSourceFile,
73+
moduleResolutionCache.getPackageJsonInfoCache(),
74+
languageServiceHost,
75+
options,
76+
);
77+
try {
78+
if (moduleLiterals.every(name => !pluginExtensions.some(ext => name.text.endsWith(ext)))) {
79+
return resolveModuleNameLiterals(
80+
moduleLiterals,
81+
containingFile,
82+
redirectedReference,
83+
options,
84+
containingSourceFile,
85+
...rest,
86+
);
87+
}
88+
return moduleLiterals.map(moduleLiteral => {
89+
const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, options);
90+
return resolveModuleName(
91+
moduleLiteral.text,
92+
containingFile,
93+
options,
94+
moduleResolutionCache,
95+
redirectedReference,
96+
mode,
97+
);
98+
});
99+
}
100+
finally {
101+
disposeFixup?.();
77102
}
78-
return moduleLiterals.map(moduleLiteral => {
79-
const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, options);
80-
return resolveModuleName(
81-
moduleLiteral.text,
82-
containingFile,
83-
options,
84-
moduleResolutionCache,
85-
redirectedReference,
86-
mode,
87-
);
88-
});
89103
};
90104
}
91105
if (resolveModuleNames) {

packages/typescript/lib/node/proxyCreateProgram.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type * as ts from 'typescript';
33
import { resolveFileLanguageId } from '../common';
44
import { createResolveModuleName } from '../resolveModuleName';
55
import { decorateProgram } from './decorateProgram';
6+
import { fixupImpliedNodeFormatForFile } from './utils';
67

78
const arrayEqual = (a: readonly any[], b: readonly any[]) => {
89
if (a.length !== b.length) {
@@ -113,7 +114,7 @@ export function proxyCreateProgram(
113114
}
114115

115116
const originalHost = options.host;
116-
const extensions = languagePlugins
117+
const pluginExtensions = languagePlugins
117118
.map(plugin => plugin.typescript?.extraFileExtensions.map(({ extension }) => `.${extension}`) ?? [])
118119
.flat();
119120

@@ -189,7 +190,7 @@ export function proxyCreateProgram(
189190
return parsedSourceFiles.get(originalSourceFile) ?? originalSourceFile;
190191
};
191192

192-
if (extensions.length) {
193+
if (pluginExtensions.length) {
193194
options.options.allowArbitraryExtensions = true;
194195

195196
const resolveModuleName = createResolveModuleName(
@@ -210,29 +211,43 @@ export function proxyCreateProgram(
210211
containingSourceFile,
211212
...rest
212213
) => {
213-
if (
214-
resolveModuleNameLiterals && moduleLiterals.every(name => !extensions.some(ext => name.text.endsWith(ext)))
215-
) {
216-
return resolveModuleNameLiterals(
217-
moduleLiterals,
218-
containingFile,
219-
redirectedReference,
220-
compilerOptions,
221-
containingSourceFile,
222-
...rest,
223-
);
214+
const disposeFixup = fixupImpliedNodeFormatForFile(
215+
ts,
216+
pluginExtensions,
217+
containingSourceFile,
218+
moduleResolutionCache.getPackageJsonInfoCache(),
219+
originalHost,
220+
compilerOptions,
221+
);
222+
try {
223+
if (
224+
resolveModuleNameLiterals
225+
&& moduleLiterals.every(name => !pluginExtensions.some(ext => name.text.endsWith(ext)))
226+
) {
227+
return resolveModuleNameLiterals(
228+
moduleLiterals,
229+
containingFile,
230+
redirectedReference,
231+
compilerOptions,
232+
containingSourceFile,
233+
...rest,
234+
);
235+
}
236+
return moduleLiterals.map(moduleLiteral => {
237+
const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, compilerOptions);
238+
return resolveModuleName(
239+
moduleLiteral.text,
240+
containingFile,
241+
compilerOptions,
242+
moduleResolutionCache,
243+
redirectedReference,
244+
mode,
245+
);
246+
});
247+
}
248+
finally {
249+
disposeFixup?.();
224250
}
225-
return moduleLiterals.map(moduleLiteral => {
226-
const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, compilerOptions);
227-
return resolveModuleName(
228-
moduleLiteral.text,
229-
containingFile,
230-
compilerOptions,
231-
moduleResolutionCache,
232-
redirectedReference,
233-
mode,
234-
);
235-
});
236251
};
237252
options.host.resolveModuleNames = (
238253
moduleNames,
@@ -242,7 +257,7 @@ export function proxyCreateProgram(
242257
compilerOptions,
243258
containingSourceFile,
244259
) => {
245-
if (resolveModuleNames && moduleNames.every(name => !extensions.some(ext => name.endsWith(ext)))) {
260+
if (resolveModuleNames && moduleNames.every(name => !pluginExtensions.some(ext => name.endsWith(ext)))) {
246261
return resolveModuleNames(
247262
moduleNames,
248263
containingFile,

packages/typescript/lib/node/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Language, SourceScript } from '@volar/language-core';
2+
import type * as ts from 'typescript';
23
import type { TypeScriptServiceScript } from '../..';
34

45
export function getServiceScript(language: Language<string>, fileName: string):
@@ -33,3 +34,24 @@ export function getServiceScript(language: Language<string>, fileName: string):
3334
}
3435
return [undefined, undefined, undefined];
3536
}
37+
38+
export function fixupImpliedNodeFormatForFile(
39+
ts: typeof import('typescript'),
40+
pluginExtensions: string[],
41+
sourceFile: ts.SourceFile,
42+
packageJsonInfoCache: ts.PackageJsonInfoCache,
43+
host: ts.ModuleResolutionHost,
44+
options: ts.CompilerOptions,
45+
) {
46+
if (sourceFile.impliedNodeFormat !== undefined || !pluginExtensions.some(ext => sourceFile.fileName.endsWith(ext))) {
47+
return;
48+
}
49+
// https://github.com/microsoft/TypeScript/blob/669c25c091ad4d32298d0f33b0e4e681d46de3ea/src/compiler/program.ts#L1354
50+
const validExts = [ts.Extension.Dts, ts.Extension.Ts, ts.Extension.Tsx, ts.Extension.Js, ts.Extension.Jsx];
51+
if (validExts.some(ext => sourceFile.fileName.endsWith(ext))) {
52+
return;
53+
}
54+
const asTs = sourceFile.fileName + ts.Extension.Ts;
55+
sourceFile.impliedNodeFormat = ts.getImpliedNodeFormatForFile?.(asTs, packageJsonInfoCache, host, options);
56+
return () => sourceFile.impliedNodeFormat = undefined;
57+
}

packages/typescript/lib/protocol/createProject.ts

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FileMap, forEachEmbeddedCode, type Language } from '@volar/language-cor
22
import * as path from 'path-browserify';
33
import type * as ts from 'typescript';
44
import type { TypeScriptExtraServiceScript } from '../..';
5+
import { fixupImpliedNodeFormatForFile } from '../node/utils';
56
import { createResolveModuleName } from '../resolveModuleName';
67
import type { createSys } from './createSys';
78

@@ -33,6 +34,9 @@ export function createLanguageServiceHost<T>(
3334
asScriptId: (fileName: string) => T,
3435
projectHost: TypeScriptProjectHost,
3536
) {
37+
const pluginExtensions = language.plugins
38+
.map(plugin => plugin.typescript?.extraFileExtensions.map(ext => '.' + ext.extension) ?? [])
39+
.flat();
3640
const scriptVersions = new FileMap<{ lastVersion: number; map: WeakMap<ts.IScriptSnapshot, number> }>(
3741
sys.useCaseSensitiveFileNames,
3842
);
@@ -63,17 +67,15 @@ export function createLanguageServiceHost<T>(
6367
},
6468
readDirectory(dirName, extensions, excludes, includes, depth) {
6569
const exts = new Set(extensions);
66-
for (const languagePlugin of language.plugins) {
67-
for (const ext of languagePlugin.typescript?.extraFileExtensions ?? []) {
68-
exts.add('.' + ext.extension);
69-
}
70+
for (const ext of pluginExtensions) {
71+
exts.add(ext);
7072
}
7173
extensions = [...exts];
7274
return sys.readDirectory(dirName, extensions, excludes, includes, depth);
7375
},
7476
getCompilationSettings() {
7577
const options = projectHost.getCompilationSettings();
76-
if (language.plugins.some(language => language.typescript?.extraFileExtensions.length)) {
78+
if (pluginExtensions.length) {
7779
options.allowNonTsExtensions ??= true;
7880
if (!options.allowNonTsExtensions) {
7981
console.warn('`allowNonTsExtensions` must be `true`.');
@@ -161,9 +163,9 @@ export function createLanguageServiceHost<T>(
161163
}
162164
}
163165

164-
if (language.plugins.some(plugin => plugin.typescript?.extraFileExtensions.length)) {
166+
if (pluginExtensions.length) {
165167
// TODO: can this share between monorepo packages?
166-
const moduleCache = ts.createModuleResolutionCache(
168+
const moduleResolutionCache = ts.createModuleResolutionCache(
167169
languageServiceHost.getCurrentDirectory(),
168170
languageServiceHost.useCaseSensitiveFileNames?.() ? s => s : s => s.toLowerCase(),
169171
languageServiceHost.getCompilationSettings(),
@@ -183,22 +185,36 @@ export function createLanguageServiceHost<T>(
183185
containingFile,
184186
redirectedReference,
185187
options,
186-
sourceFile,
188+
containingSourceFile,
187189
) => {
188-
if ('version' in sys && lastSysVersion !== sys.version) {
189-
lastSysVersion = sys.version;
190-
moduleCache.clear();
190+
const disposeFixup = fixupImpliedNodeFormatForFile(
191+
ts,
192+
pluginExtensions,
193+
containingSourceFile,
194+
moduleResolutionCache.getPackageJsonInfoCache(),
195+
languageServiceHost,
196+
options,
197+
);
198+
try {
199+
if ('version' in sys && lastSysVersion !== sys.version) {
200+
lastSysVersion = sys.version;
201+
moduleResolutionCache.clear();
202+
}
203+
return moduleLiterals.map(moduleLiteral => {
204+
const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, options);
205+
return resolveModuleName(
206+
moduleLiteral.text,
207+
containingFile,
208+
options,
209+
moduleResolutionCache,
210+
redirectedReference,
211+
mode,
212+
);
213+
});
214+
}
215+
finally {
216+
disposeFixup?.();
191217
}
192-
return moduleLiterals.map(moduleLiteral => {
193-
return resolveModuleName(
194-
moduleLiteral.text,
195-
containingFile,
196-
options,
197-
moduleCache,
198-
redirectedReference,
199-
sourceFile.impliedNodeFormat,
200-
);
201-
});
202218
};
203219
languageServiceHost.resolveModuleNames = (
204220
moduleNames,
@@ -209,14 +225,15 @@ export function createLanguageServiceHost<T>(
209225
) => {
210226
if ('version' in sys && lastSysVersion !== sys.version) {
211227
lastSysVersion = sys.version;
212-
moduleCache.clear();
228+
moduleResolutionCache.clear();
213229
}
214230
return moduleNames.map(moduleName => {
215-
return resolveModuleName(moduleName, containingFile, options, moduleCache, redirectedReference).resolvedModule;
231+
return resolveModuleName(moduleName, containingFile, options, moduleResolutionCache, redirectedReference)
232+
.resolvedModule;
216233
});
217234
};
218235

219-
languageServiceHost.getModuleResolutionCache = () => moduleCache;
236+
languageServiceHost.getModuleResolutionCache = () => moduleResolutionCache;
220237
}
221238

222239
return {

0 commit comments

Comments
 (0)