Skip to content

Commit 9ee7e9c

Browse files
authored
fix: prevent error when the script tag is removed in nodenext projects (#2635)
* fix error when script tag is removed in node16/nodenext * need to have syntax error * two more cases tracked it in git so we can reenable it later * cleanup * patch update as well * Mark it as clean first so at least it doesn't stuck in an old version even if lang="ts" is added back * try finally instead
1 parent bcd6dd0 commit 9ee7e9c

File tree

3 files changed

+138
-52
lines changed

3 files changed

+138
-52
lines changed

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

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import {
77
ensureRealSvelteFilePath,
88
getExtensionFromScriptKind,
99
isSvelteFilePath,
10-
isVirtualSvelteFilePath,
11-
toVirtualSvelteFilePath
10+
isVirtualSvelteFilePath
1211
} from './utils';
1312

1413
const CACHE_KEY_SEPARATOR = ':::';
@@ -89,8 +88,6 @@ class ModuleResolutionCache {
8988
}
9089

9190
class ImpliedNodeFormatResolver {
92-
private alreadyResolved = new FileMap<ReturnType<typeof ts.getModeForResolutionAtIndex>>();
93-
9491
constructor(private readonly tsSystem: ts.System) {}
9592

9693
resolve(
@@ -106,39 +103,17 @@ class ImpliedNodeFormatResolver {
106103

107104
let mode: ReturnType<typeof ts.getModeForResolutionAtIndex> = undefined;
108105
if (sourceFile) {
109-
this.cacheImpliedNodeFormat(sourceFile, compilerOptions);
110106
mode = ts.getModeForResolutionAtIndex(sourceFile, importIdxInFile, compilerOptions);
111107
}
112108
return mode;
113109
}
114110

115-
private cacheImpliedNodeFormat(sourceFile: ts.SourceFile, compilerOptions: ts.CompilerOptions) {
116-
if (!sourceFile.impliedNodeFormat && isSvelteFilePath(sourceFile.fileName)) {
117-
// impliedNodeFormat is not set for Svelte files, because the TS function which
118-
// calculates this works with a fixed set of file extensions,
119-
// which .svelte is obv not part of. Make it work by faking a TS file.
120-
if (!this.alreadyResolved.has(sourceFile.fileName)) {
121-
sourceFile.impliedNodeFormat = ts.getImpliedNodeFormatForFile(
122-
toVirtualSvelteFilePath(sourceFile.fileName) as any,
123-
undefined,
124-
this.tsSystem,
125-
compilerOptions
126-
);
127-
this.alreadyResolved.set(sourceFile.fileName, sourceFile.impliedNodeFormat);
128-
} else {
129-
sourceFile.impliedNodeFormat = this.alreadyResolved.get(sourceFile.fileName);
130-
}
131-
}
132-
}
133-
134111
resolveForTypeReference(
135112
entry: string | ts.FileReference,
136-
sourceFile: ts.SourceFile | undefined,
137-
compilerOptions: ts.CompilerOptions
113+
sourceFile: ts.SourceFile | undefined
138114
) {
139115
let mode = undefined;
140116
if (sourceFile) {
141-
this.cacheImpliedNodeFormat(sourceFile, compilerOptions);
142117
mode = ts.getModeForFileReference(entry, sourceFile?.impliedNodeFormat);
143118
}
144119
return mode;
@@ -315,8 +290,7 @@ export function createSvelteModuleLoader(
315290
const entry = getTypeReferenceResolutionName(typeDirectiveName);
316291
const mode = impliedNodeFormatResolver.resolveForTypeReference(
317292
entry,
318-
containingSourceFile,
319-
options
293+
containingSourceFile
320294
);
321295

322296
const key = `${entry}|${mode}`;

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

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import {
2626
findTsConfigPath,
2727
getNearestWorkspaceUri,
2828
hasTsExtensions,
29-
isSvelteFilePath
29+
isSvelteFilePath,
30+
toVirtualSvelteFilePath
3031
} from './utils';
3132
import { createProject, ProjectService } from './serviceCache';
3233
import { internalHelpers } from 'svelte2tsx';
@@ -974,14 +975,19 @@ async function createLanguageService(
974975
}
975976

976977
const oldProgram = project?.program;
977-
const program = languageService.getProgram();
978+
let program: ts.Program | undefined;
979+
try {
980+
program = languageService.getProgram();
981+
} finally {
982+
// mark as clean even if the update fails, at least we can still try again next time there is a change
983+
dirty = false;
984+
}
978985
svelteModuleLoader.clearPendingInvalidations();
979986

980987
if (project) {
981988
project.program = program;
982989
}
983990

984-
dirty = false;
985991
compilerHost = undefined;
986992

987993
if (!skipSvelteInputCheck) {
@@ -1376,38 +1382,93 @@ function getOrCreateDocumentRegistry(
13761382

13771383
registry = ts.createDocumentRegistry(useCaseSensitiveFileNames, currentDirectory);
13781384

1379-
// impliedNodeFormat is always undefined when the svelte source file is created
1380-
// We might patched it later but the registry doesn't know about it
1381-
const releaseDocumentWithKey = registry.releaseDocumentWithKey;
1382-
registry.releaseDocumentWithKey = (
1385+
const acquireDocumentWithKey = registry.acquireDocumentWithKey;
1386+
registry.acquireDocumentWithKey = (
1387+
fileName: string,
13831388
path: ts.Path,
1389+
compilationSettingsOrHost: ts.CompilerOptions | ts.MinimalResolutionCacheHost,
13841390
key: ts.DocumentRegistryBucketKey,
1385-
scriptKind: ts.ScriptKind,
1386-
impliedNodeFormat?: ts.ResolutionMode
1391+
scriptSnapshot: ts.IScriptSnapshot,
1392+
version: string,
1393+
scriptKind?: ts.ScriptKind,
1394+
sourceFileOptions?: ts.CreateSourceFileOptions | ts.ScriptTarget
13871395
) => {
1388-
if (isSvelteFilePath(path)) {
1389-
releaseDocumentWithKey(path, key, scriptKind, undefined);
1390-
return;
1391-
}
1396+
ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
13921397

1393-
releaseDocumentWithKey(path, key, scriptKind, impliedNodeFormat);
1398+
return acquireDocumentWithKey(
1399+
fileName,
1400+
path,
1401+
compilationSettingsOrHost,
1402+
key,
1403+
scriptSnapshot,
1404+
version,
1405+
scriptKind,
1406+
sourceFileOptions
1407+
);
13941408
};
13951409

1396-
registry.releaseDocument = (
1410+
const updateDocumentWithKey = registry.updateDocumentWithKey;
1411+
registry.updateDocumentWithKey = (
13971412
fileName: string,
1398-
compilationSettings: ts.CompilerOptions,
1399-
scriptKind: ts.ScriptKind,
1400-
impliedNodeFormat?: ts.ResolutionMode
1413+
path: ts.Path,
1414+
compilationSettingsOrHost: ts.CompilerOptions | ts.MinimalResolutionCacheHost,
1415+
key: ts.DocumentRegistryBucketKey,
1416+
scriptSnapshot: ts.IScriptSnapshot,
1417+
version: string,
1418+
scriptKind?: ts.ScriptKind,
1419+
sourceFileOptions?: ts.CreateSourceFileOptions | ts.ScriptTarget
14011420
) => {
1402-
if (isSvelteFilePath(fileName)) {
1403-
registry?.releaseDocument(fileName, compilationSettings, scriptKind, undefined);
1404-
return;
1405-
}
1421+
ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
14061422

1407-
registry?.releaseDocument(fileName, compilationSettings, scriptKind, impliedNodeFormat);
1423+
return updateDocumentWithKey(
1424+
fileName,
1425+
path,
1426+
compilationSettingsOrHost,
1427+
key,
1428+
scriptSnapshot,
1429+
version,
1430+
scriptKind,
1431+
sourceFileOptions
1432+
);
14081433
};
14091434

14101435
documentRegistries.set(key, registry);
14111436

14121437
return registry;
1438+
1439+
function ensureImpliedNodeFormat(
1440+
compilationSettingsOrHost: ts.CompilerOptions | ts.MinimalResolutionCacheHost,
1441+
fileName: string,
1442+
sourceFileOptions: ts.CreateSourceFileOptions | ts.ScriptTarget | undefined
1443+
) {
1444+
const compilationSettings = getCompilationSettings(compilationSettingsOrHost);
1445+
const host: ts.MinimalResolutionCacheHost | undefined =
1446+
compilationSettingsOrHost === compilationSettings
1447+
? undefined
1448+
: (compilationSettingsOrHost as ts.MinimalResolutionCacheHost);
1449+
if (
1450+
host &&
1451+
isSvelteFilePath(fileName) &&
1452+
typeof sourceFileOptions === 'object' &&
1453+
!sourceFileOptions.impliedNodeFormat
1454+
) {
1455+
const format = ts.getImpliedNodeFormatForFile(
1456+
toVirtualSvelteFilePath(fileName),
1457+
host?.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(),
1458+
host,
1459+
compilationSettings
1460+
);
1461+
1462+
sourceFileOptions.impliedNodeFormat = format;
1463+
}
1464+
}
1465+
1466+
function getCompilationSettings(
1467+
settingsOrHost: ts.CompilerOptions | ts.MinimalResolutionCacheHost
1468+
) {
1469+
if (typeof settingsOrHost.getCompilationSettings === 'function') {
1470+
return (settingsOrHost as ts.MinimalResolutionCacheHost).getCompilationSettings();
1471+
}
1472+
return settingsOrHost as ts.CompilerOptions;
1473+
}
14131474
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,57 @@ describe('service', () => {
281281
});
282282
});
283283

284+
it('do not throw when script tag is nuked', async () => {
285+
// testing this because the patch rely on ts implementation details
286+
// and we want to be aware of the changes
287+
288+
const dirPath = getRandomVirtualDirPath(testDir);
289+
const { virtualSystem, lsDocumentContext, rootUris } = setup();
290+
291+
virtualSystem.writeFile(
292+
path.join(dirPath, 'tsconfig.json'),
293+
JSON.stringify({
294+
compilerOptions: {
295+
module: 'NodeNext',
296+
moduleResolution: 'NodeNext'
297+
}
298+
})
299+
);
300+
301+
virtualSystem.writeFile(
302+
path.join(dirPath, 'random.svelte'),
303+
'<script>const a: number = null;</script>'
304+
);
305+
virtualSystem.writeFile(
306+
path.join(dirPath, 'random2.svelte'),
307+
'<script lang="ts">import Random from "./random.svelte";</script>'
308+
);
309+
310+
const ls = await getService(
311+
path.join(dirPath, 'random.svelte'),
312+
rootUris,
313+
lsDocumentContext
314+
);
315+
316+
const document = new Document(pathToUrl(path.join(dirPath, 'random.svelte')), '');
317+
document.openedByClient = true;
318+
ls.updateSnapshot(document);
319+
320+
const document2 = new Document(
321+
pathToUrl(path.join(dirPath, 'random2.svelte')),
322+
virtualSystem.readFile(path.join(dirPath, 'random2.svelte'))!
323+
);
324+
document.openedByClient = true;
325+
ls.updateSnapshot(document2);
326+
327+
const lang = ls.getService();
328+
lang.getProgram();
329+
330+
document2.update('<script', 0, document2.getTextLength());
331+
ls.updateSnapshot(document2);
332+
ls.getService();
333+
});
334+
284335
function createReloadTester(
285336
docContext: LanguageServiceDocumentContext,
286337
testAfterReload: (reloadingConfigs: string[]) => Promise<boolean>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy