Skip to content

Commit 131f78a

Browse files
authored
fix: make sure snapshots are shared per ProjectService (#2614)
The ProjectService contains snapshots. Multiple TypeScript plugins may be instantiated for one projectservice. Ensure we share our internal Svelte snapshots between those instances. #2602
1 parent 89cc22b commit 131f78a

File tree

1 file changed

+76
-84
lines changed

1 file changed

+76
-84
lines changed

packages/typescript-plugin/src/svelte-snapshots.ts

Lines changed: 76 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ export class SvelteSnapshot {
269269
}
270270

271271
export class SvelteSnapshotManager {
272-
private snapshots = new Map<string, SvelteSnapshot>();
272+
private snapshots: Map<string, SvelteSnapshot>;
273273

274274
constructor(
275275
private typescript: typeof ts,
@@ -280,7 +280,9 @@ export class SvelteSnapshotManager {
280280
/** undefined if no node_modules with Svelte next to tsconfig.json */
281281
private svelteCompiler: typeof import('svelte/compiler') | undefined
282282
) {
283-
this.patchProjectServiceReadFile();
283+
this.patchProjectService();
284+
// @ts-expect-error
285+
this.snapshots = this.projectService[snapshots];
284286
}
285287

286288
get(fileName: string) {
@@ -321,92 +323,78 @@ export class SvelteSnapshotManager {
321323
return snapshot;
322324
}
323325

324-
private patchProjectServiceReadFile() {
325-
// @ts-ignore The projectService is shared across some instances, make sure we patch readFile only once
326-
if (!this.projectService.host[onReadSvelteFile]) {
327-
this.logger.log('patching projectService host readFile');
326+
private patchProjectService() {
327+
// @ts-expect-error The projectService is shared across some instances, make sure we patch only once
328+
if (this.projectService[snapshots]) return;
328329

329-
// @ts-ignore
330-
this.projectService.host[onReadSvelteFile] = [];
330+
this.logger.log('patching projectService');
331331

332-
const readFile = this.projectService.host.readFile;
333-
this.projectService.host.readFile = (path: string, encoding?: string | undefined) => {
334-
if (!this.configManager.getConfig().enable) {
335-
return readFile(path, encoding);
336-
}
332+
// @ts-expect-error Snapshots are stored on the projectService, so they are shared across all instances
333+
this.snapshots = this.projectService[snapshots] = new Map();
337334

338-
// The following (very hacky) first two checks make sure that the ambient module definitions
339-
// that tell TS "every import ending with .svelte is a valid module" are removed.
340-
// They exist in svelte2tsx and svelte to make sure that people don't
341-
// get errors in their TS files when importing Svelte files and not using our TS plugin.
342-
// If someone wants to get back the behavior they can add an ambient module definition
343-
// on their own.
344-
const normalizedPath = path.replace(/\\/g, '/');
345-
if (normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) {
346-
return '';
347-
} else if (normalizedPath.endsWith('svelte2tsx/svelte-jsx.d.ts')) {
348-
// Remove the dom lib reference to not load these ambient types in case
349-
// the user has a tsconfig.json with different lib settings like in
350-
// https://github.com/sveltejs/language-tools/issues/1733
351-
const originalText = readFile(path) || '';
352-
const toReplace = '/// <reference lib="dom" />';
353-
return originalText.replace(toReplace, ' '.repeat(toReplace.length));
354-
} else if (normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts')) {
355-
let originalText = readFile(path) || '';
356-
if (!originalText.includes('// -- start svelte-ls-remove --')) {
357-
return originalText; // uses an older version of svelte2tsx or is already patched
358-
}
359-
const startIdx = originalText.indexOf('// -- start svelte-ls-remove --');
360-
const endIdx = originalText.indexOf('// -- end svelte-ls-remove --');
361-
originalText =
362-
originalText.substring(0, startIdx) +
363-
' '.repeat(endIdx - startIdx) +
364-
originalText.substring(endIdx);
365-
return originalText;
366-
} else if (isSvelteFilePath(path)) {
367-
this.logger.debug('Read Svelte file:', path);
368-
const svelteCode = readFile(path) || '';
369-
const isTsFile = true; // TODO check file contents? TS might be okay with importing ts into js.
370-
let code: string;
371-
let mapper: SourceMapper;
372-
373-
try {
374-
const result = svelte2tsx(svelteCode, {
375-
filename: path.split('/').pop(),
376-
isTsFile,
377-
mode: 'ts',
378-
typingsNamespace: this.svelteOptions.namespace,
379-
// Don't search for compiler from current path - could be a different one from which we have loaded the svelte2tsx globals
380-
parse: this.svelteCompiler?.parse,
381-
version: this.svelteCompiler?.VERSION
382-
});
383-
code = result.code;
384-
mapper = new SourceMapper(result.map.mappings);
385-
this.logger.log('Successfully read Svelte file contents of', path);
386-
} catch (e) {
387-
this.logger.log('Error loading Svelte file:', path, ' Using fallback.');
388-
this.logger.debug('Error:', e);
389-
// Return something either way, else "X is not a module" errors will appear
390-
// in the TS files that use this file.
391-
code = 'export default class extends Svelte2TsxComponent<any,any,any> {}';
392-
mapper = new SourceMapper('');
393-
}
394-
395-
// @ts-ignore
396-
this.projectService.host[onReadSvelteFile].forEach((listener) =>
397-
listener(path, svelteCode, isTsFile, mapper)
398-
);
335+
const readFile = this.projectService.host.readFile;
336+
this.projectService.host.readFile = (path: string, encoding?: string | undefined) => {
337+
if (!this.configManager.getConfig().enable) {
338+
return readFile(path, encoding);
339+
}
399340

400-
return code;
401-
} else {
402-
return readFile(path, encoding);
341+
// The following (very hacky) first two checks make sure that the ambient module definitions
342+
// that tell TS "every import ending with .svelte is a valid module" are removed.
343+
// They exist in svelte2tsx and svelte to make sure that people don't
344+
// get errors in their TS files when importing Svelte files and not using our TS plugin.
345+
// If someone wants to get back the behavior they can add an ambient module definition
346+
// on their own.
347+
const normalizedPath = path.replace(/\\/g, '/');
348+
if (normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) {
349+
return '';
350+
} else if (normalizedPath.endsWith('svelte2tsx/svelte-jsx.d.ts')) {
351+
// Remove the dom lib reference to not load these ambient types in case
352+
// the user has a tsconfig.json with different lib settings like in
353+
// https://github.com/sveltejs/language-tools/issues/1733
354+
const originalText = readFile(path) || '';
355+
const toReplace = '/// <reference lib="dom" />';
356+
return originalText.replace(toReplace, ' '.repeat(toReplace.length));
357+
} else if (normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts')) {
358+
let originalText = readFile(path) || '';
359+
if (!originalText.includes('// -- start svelte-ls-remove --')) {
360+
return originalText; // uses an older version of svelte2tsx or is already patched
361+
}
362+
const startIdx = originalText.indexOf('// -- start svelte-ls-remove --');
363+
const endIdx = originalText.indexOf('// -- end svelte-ls-remove --');
364+
originalText =
365+
originalText.substring(0, startIdx) +
366+
' '.repeat(endIdx - startIdx) +
367+
originalText.substring(endIdx);
368+
return originalText;
369+
} else if (isSvelteFilePath(path)) {
370+
this.logger.debug('Read Svelte file:', path);
371+
const svelteCode = readFile(path) || '';
372+
const isTsFile = true; // TODO check file contents? TS might be okay with importing ts into js.
373+
let code: string;
374+
let mapper: SourceMapper;
375+
376+
try {
377+
const result = svelte2tsx(svelteCode, {
378+
filename: path.split('/').pop(),
379+
isTsFile,
380+
mode: 'ts',
381+
typingsNamespace: this.svelteOptions.namespace,
382+
// Don't search for compiler from current path - could be a different one from which we have loaded the svelte2tsx globals
383+
parse: this.svelteCompiler?.parse,
384+
version: this.svelteCompiler?.VERSION
385+
});
386+
code = result.code;
387+
mapper = new SourceMapper(result.map.mappings);
388+
this.logger.log('Successfully read Svelte file contents of', path);
389+
} catch (e) {
390+
this.logger.log('Error loading Svelte file:', path, ' Using fallback.');
391+
this.logger.debug('Error:', e);
392+
// Return something either way, else "X is not a module" errors will appear
393+
// in the TS files that use this file.
394+
code = 'export default class extends Svelte2TsxComponent<any,any,any> {}';
395+
mapper = new SourceMapper('');
403396
}
404-
};
405-
}
406397

407-
// @ts-ignore
408-
this.projectService.host[onReadSvelteFile].push(
409-
(path: string, svelteCode: string, isTsFile: boolean, mapper: SourceMapper) => {
410398
const canonicalFilePath = this.projectService.toCanonicalFileName(path);
411399
const existingSnapshot = this.snapshots.get(canonicalFilePath);
412400
if (existingSnapshot) {
@@ -424,9 +412,13 @@ export class SvelteSnapshotManager {
424412
)
425413
);
426414
}
415+
416+
return code;
417+
} else {
418+
return readFile(path, encoding);
427419
}
428-
);
420+
};
429421
}
430422
}
431423

432-
const onReadSvelteFile = Symbol('sveltePluginPatchSymbol');
424+
const snapshots = Symbol('sveltePluginPatchSymbol');

0 commit comments

Comments
 (0)