Skip to content

Commit 2bdbf6d

Browse files
authored
fix: invalidate module resolution when higher priority file is added (#2754)
part of #2738
1 parent 0c4e103 commit 2bdbf6d

File tree

3 files changed

+63
-28
lines changed

3 files changed

+63
-28
lines changed

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

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,13 @@ export function createSvelteModuleLoader(
164164
>();
165165

166166
const impliedNodeFormatResolver = new ImpliedNodeFormatResolver(tsSystem);
167-
const failedPathToContainingFile = new FileMap<FileSet>();
168-
const failedLocationInvalidated = new FileSet();
167+
const resolutionWithFailedLookup = new Set<
168+
ts.ResolvedModuleWithFailedLookupLocations & {
169+
files?: Set<string>;
170+
}
171+
>();
172+
const failedLocationInvalidated = new FileSet(tsSystem.useCaseSensitiveFileNames);
173+
const pendingFailedLocationCheck = new FileSet(tsSystem.useCaseSensitiveFileNames);
169174

170175
return {
171176
svelteFileExists: svelteSys.svelteFileExists,
@@ -179,16 +184,7 @@ export function createSvelteModuleLoader(
179184
deleteUnresolvedResolutionsFromCache: (path: string) => {
180185
svelteSys.deleteFromCache(path);
181186
moduleCache.deleteUnresolvedResolutionsFromCache(path);
182-
183-
const previousTriedButFailed = failedPathToContainingFile.get(path);
184-
185-
if (!previousTriedButFailed) {
186-
return;
187-
}
188-
189-
for (const containingFile of previousTriedButFailed) {
190-
failedLocationInvalidated.add(containingFile);
191-
}
187+
pendingFailedLocationCheck.add(path);
192188

193189
tsModuleCache.clear();
194190
typeReferenceCache.clear();
@@ -197,7 +193,8 @@ export function createSvelteModuleLoader(
197193
resolveTypeReferenceDirectiveReferences,
198194
mightHaveInvalidatedResolutions,
199195
clearPendingInvalidations,
200-
getModuleResolutionCache: () => tsModuleCache
196+
getModuleResolutionCache: () => tsModuleCache,
197+
invalidateFailedLocationResolution
201198
};
202199

203200
function resolveModuleNames(
@@ -222,11 +219,7 @@ export function createSvelteModuleLoader(
222219
options
223220
);
224221

225-
resolvedModule?.failedLookupLocations?.forEach((failedLocation) => {
226-
const failedPaths = failedPathToContainingFile.get(failedLocation) ?? new FileSet();
227-
failedPaths.add(containingFile);
228-
failedPathToContainingFile.set(failedLocation, failedPaths);
229-
});
222+
cacheResolutionWithFailedLookup(resolvedModule, containingFile);
230223

231224
moduleCache.set(moduleName, containingFile, resolvedModule?.resolvedModule);
232225
return resolvedModule?.resolvedModule;
@@ -333,5 +326,46 @@ export function createSvelteModuleLoader(
333326
function clearPendingInvalidations() {
334327
moduleCache.clearPendingInvalidations();
335328
failedLocationInvalidated.clear();
329+
pendingFailedLocationCheck.clear();
330+
}
331+
332+
function cacheResolutionWithFailedLookup(
333+
resolvedModule: ts.ResolvedModuleWithFailedLookupLocations & {
334+
files?: Set<string>;
335+
},
336+
containingFile: string
337+
) {
338+
if (!resolvedModule.failedLookupLocations?.length) {
339+
return;
340+
}
341+
342+
// The resolvedModule object will be reused in different files. A bit hacky, but TypeScript also does this.
343+
// https://github.com/microsoft/TypeScript/blob/11e79327598db412a161616849041487673fadab/src/compiler/resolutionCache.ts#L1103
344+
resolvedModule.files ??= new Set();
345+
resolvedModule.files.add(containingFile);
346+
resolutionWithFailedLookup.add(resolvedModule);
347+
}
348+
349+
function invalidateFailedLocationResolution() {
350+
resolutionWithFailedLookup.forEach((resolvedModule) => {
351+
if (
352+
!resolvedModule.resolvedModule ||
353+
!resolvedModule.files ||
354+
!resolvedModule.failedLookupLocations
355+
) {
356+
return;
357+
}
358+
for (const location of resolvedModule.failedLookupLocations) {
359+
if (pendingFailedLocationCheck.has(location)) {
360+
moduleCache.delete(resolvedModule.resolvedModule.resolvedFileName);
361+
resolvedModule.files?.forEach((file) => {
362+
failedLocationInvalidated.add(file);
363+
});
364+
break;
365+
}
366+
}
367+
});
368+
369+
pendingFailedLocationCheck.clear();
336370
}
337371
}

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ declare module 'typescript' {
8989
resolutionDiagnostics?: ts.Diagnostic[];
9090
/**
9191
* @internal
92-
* Used to issue a diagnostic if typings for a non-relative import couldn't be found
93-
* while respecting package.json `exports`, but were found when disabling `exports`.
92+
* Used to issue a better diagnostic when an unresolvable module may
93+
* have been resolvable under different module resolution settings.
9494
*/
95-
node10Result?: string;
95+
alternateResult?: string;
9696
}
9797
}
9898

@@ -527,10 +527,11 @@ async function createLanguageService(
527527

528528
function invalidateModuleCache(filePaths: string[]) {
529529
for (const filePath of filePaths) {
530-
svelteModuleLoader.deleteFromModuleCache(filePath);
531-
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(filePath);
530+
const normalizedPath = normalizePath(filePath);
531+
svelteModuleLoader.deleteFromModuleCache(normalizedPath);
532+
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(normalizedPath);
532533

533-
scheduleUpdate(filePath);
534+
scheduleUpdate(normalizedPath);
534535
}
535536
}
536537

@@ -974,22 +975,22 @@ async function createLanguageService(
974975
return;
975976
}
976977

978+
svelteModuleLoader.invalidateFailedLocationResolution();
977979
const oldProgram = project?.program;
978980
let program: ts.Program | undefined;
979981
try {
980982
program = languageService.getProgram();
981983
} finally {
982984
// mark as clean even if the update fails, at least we can still try again next time there is a change
983985
dirty = false;
986+
compilerHost = undefined;
987+
svelteModuleLoader.clearPendingInvalidations();
984988
}
985-
svelteModuleLoader.clearPendingInvalidations();
986989

987990
if (project) {
988991
project.program = program;
989992
}
990993

991-
compilerHost = undefined;
992-
993994
if (!skipSvelteInputCheck) {
994995
const svelteConfigDiagnostics = checkSvelteInput(program, projectConfig);
995996
const codes = svelteConfigDiagnostics.map((d) => d.code);

packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('DiagnosticsProvider', function () {
8484

8585
try {
8686
const diagnostics3 = await plugin.getDiagnostics(document);
87-
assert.deepStrictEqual(diagnostics3.length, 1);
87+
assert.deepStrictEqual(diagnostics3.length, 0);
8888
await lsAndTsDocResolver.deleteSnapshot(newTsFilePath);
8989
} finally {
9090
unlinkSync(newTsFilePath);

0 commit comments

Comments
 (0)