UNPKG

svelte-language-server

Version:
246 lines 12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSvelteModuleLoader = createSvelteModuleLoader; const typescript_1 = __importDefault(require("typescript")); const fileCollection_1 = require("../../lib/documents/fileCollection"); const utils_1 = require("../../utils"); const svelte_sys_1 = require("./svelte-sys"); const utils_2 = require("./utils"); const CACHE_KEY_SEPARATOR = ':::'; /** * Caches resolved modules. */ class ModuleResolutionCache { constructor() { this.cache = new fileCollection_1.FileMap(); this.pendingInvalidations = new fileCollection_1.FileSet(); this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(typescript_1.default.sys.useCaseSensitiveFileNames); } /** * Tries to get a cached module. * Careful: `undefined` can mean either there's no match found, or that the result resolved to `undefined`. */ get(moduleName, containingFile) { return this.cache.get(this.getKey(moduleName, containingFile)); } /** * Checks if has cached module. */ has(moduleName, containingFile) { return this.cache.has(this.getKey(moduleName, containingFile)); } /** * Caches resolved module (or undefined). */ set(moduleName, containingFile, resolvedModule) { this.cache.set(this.getKey(moduleName, containingFile), resolvedModule); } /** * Deletes module from cache. Call this if a file was deleted. * @param resolvedModuleName full path of the module */ delete(resolvedModuleName) { resolvedModuleName = this.getCanonicalFileName(resolvedModuleName); this.cache.forEach((val, key) => { if (val && this.getCanonicalFileName(val.resolvedFileName) === resolvedModuleName) { this.cache.delete(key); this.pendingInvalidations.add(key.split(CACHE_KEY_SEPARATOR).shift() || ''); } }); } /** * Deletes everything from cache that resolved to `undefined` * and which might match the path. */ deleteUnresolvedResolutionsFromCache(path) { const fileNameWithoutEnding = (0, utils_1.getLastPartOfPath)(this.getCanonicalFileName(path)).split('.').shift() || ''; this.cache.forEach((val, key) => { if (val) { return; } const [containingFile, moduleName = ''] = key.split(CACHE_KEY_SEPARATOR); if (moduleName.includes(fileNameWithoutEnding)) { this.cache.delete(key); this.pendingInvalidations.add(containingFile); } }); } getKey(moduleName, containingFile) { return containingFile + CACHE_KEY_SEPARATOR + (0, utils_2.ensureRealSvelteFilePath)(moduleName); } clearPendingInvalidations() { this.pendingInvalidations.clear(); } oneOfResolvedModuleChanged(path) { return this.pendingInvalidations.has(path); } } class ImpliedNodeFormatResolver { constructor(tsSystem) { this.tsSystem = tsSystem; } resolve(importPath, importIdxInFile, sourceFile, compilerOptions) { if ((0, utils_2.isSvelteFilePath)(importPath)) { // Svelte imports should use the old resolution algorithm, else they are not found return undefined; } let mode = undefined; if (sourceFile) { mode = typescript_1.default.getModeForResolutionAtIndex(sourceFile, importIdxInFile, compilerOptions); } return mode; } resolveForTypeReference(entry, sourceFile) { let mode = undefined; if (sourceFile) { mode = typescript_1.default.getModeForFileReference(entry, sourceFile?.impliedNodeFormat); } return mode; } } // https://github.com/microsoft/TypeScript/blob/dddd0667f012c51582c2ac92c08b8e57f2456587/src/compiler/program.ts#L989 function getTypeReferenceResolutionName(entry) { return typeof entry !== 'string' ? (0, utils_1.toFileNameLowerCase)(entry.fileName) : entry; } /** * Creates a module loader specifically for `.svelte` files. * * The typescript language service tries to look up other files that are referenced in the currently open svelte file. * For `.ts`/`.js` files this works, for `.svelte` files it does not by default. * Reason: The typescript language service does not know about the `.svelte` file ending, * so it assumes it's a normal typescript file and searches for files like `../Component.svelte.ts`, which is wrong. * In order to fix this, we need to wrap typescript's module resolution and reroute all `.svelte.ts` file lookups to .svelte. * * @param getSnapshot A function which returns a (in case of svelte file fully preprocessed) typescript/javascript snapshot * @param compilerOptions The typescript compiler options */ function createSvelteModuleLoader(getSnapshot, compilerOptions, tsSystem, tsModule, getModuleResolutionHost) { const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames); const svelteSys = (0, svelte_sys_1.createSvelteSys)(tsSystem); // tsModuleCache caches package.json parsing and module resolution for directory const tsModuleCache = tsModule.createModuleResolutionCache(tsSystem.getCurrentDirectory(), (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames)); const tsTypeReferenceDirectiveCache = tsModule.createTypeReferenceDirectiveResolutionCache(tsSystem.getCurrentDirectory(), getCanonicalFileName, undefined, tsModuleCache.getPackageJsonInfoCache()); const moduleCache = new ModuleResolutionCache(); const typeReferenceCache = new Map(); const impliedNodeFormatResolver = new ImpliedNodeFormatResolver(tsSystem); const resolutionWithFailedLookup = new Set(); const failedLocationInvalidated = new fileCollection_1.FileSet(tsSystem.useCaseSensitiveFileNames); const pendingFailedLocationCheck = new fileCollection_1.FileSet(tsSystem.useCaseSensitiveFileNames); return { svelteFileExists: svelteSys.svelteFileExists, fileExists: svelteSys.fileExists, readFile: svelteSys.readFile, readDirectory: svelteSys.readDirectory, deleteFromModuleCache: (path) => { svelteSys.deleteFromCache(path); moduleCache.delete(path); }, deleteUnresolvedResolutionsFromCache: (path) => { svelteSys.deleteFromCache(path); moduleCache.deleteUnresolvedResolutionsFromCache(path); pendingFailedLocationCheck.add(path); tsModuleCache.clear(); typeReferenceCache.clear(); }, resolveModuleNames, resolveTypeReferenceDirectiveReferences, mightHaveInvalidatedResolutions, clearPendingInvalidations, getModuleResolutionCache: () => tsModuleCache, invalidateFailedLocationResolution }; function resolveModuleNames(moduleNames, containingFile, _reusedNames, redirectedReference, options, containingSourceFile) { return moduleNames.map((moduleName, index) => { if (moduleCache.has(moduleName, containingFile)) { return moduleCache.get(moduleName, containingFile); } const resolvedModule = resolveModuleName(moduleName, containingFile, containingSourceFile, index, redirectedReference, options); cacheResolutionWithFailedLookup(resolvedModule, containingFile); moduleCache.set(moduleName, containingFile, resolvedModule?.resolvedModule); return resolvedModule?.resolvedModule; }); } function resolveModuleName(name, containingFile, containingSourceFile, index, redirectedReference, option) { const mode = impliedNodeFormatResolver.resolve(name, index, containingSourceFile, // use the same compiler options as resolveModuleName // otherwise it might not find the module because of inconsistent module resolution strategy redirectedReference?.commandLine.options ?? option); const resolvedModuleWithFailedLookup = tsModule.resolveModuleName(name, containingFile, compilerOptions, getModuleResolutionHost() ?? svelteSys, tsModuleCache, redirectedReference, mode); const resolvedModule = resolvedModuleWithFailedLookup.resolvedModule; if (!resolvedModule || !(0, utils_2.isVirtualSvelteFilePath)(resolvedModule.resolvedFileName)) { return resolvedModuleWithFailedLookup; } const resolvedFileName = svelteSys.getRealSveltePathIfExists(resolvedModule.resolvedFileName); if (!(0, utils_2.isSvelteFilePath)(resolvedFileName)) { return resolvedModuleWithFailedLookup; } const snapshot = getSnapshot(resolvedFileName); const resolvedSvelteModule = { extension: (0, utils_2.getExtensionFromScriptKind)(snapshot && snapshot.scriptKind), resolvedFileName, isExternalLibraryImport: resolvedModule.isExternalLibraryImport }; return { ...resolvedModuleWithFailedLookup, resolvedModule: resolvedSvelteModule }; } function resolveTypeReferenceDirectiveReferences(typeDirectiveNames, containingFile, redirectedReference, options, containingSourceFile) { return typeDirectiveNames.map((typeDirectiveName) => { const entry = getTypeReferenceResolutionName(typeDirectiveName); const mode = impliedNodeFormatResolver.resolveForTypeReference(entry, containingSourceFile); const key = `${entry}|${mode}`; let result = typeReferenceCache.get(key); if (!result) { result = typescript_1.default.resolveTypeReferenceDirective(entry, containingFile, options, { ...tsSystem }, redirectedReference, tsTypeReferenceDirectiveCache, mode); typeReferenceCache.set(key, result); } return result; }); } function mightHaveInvalidatedResolutions(path) { return (moduleCache.oneOfResolvedModuleChanged(path) || // tried but failed file might now exist failedLocationInvalidated.has(path)); } function clearPendingInvalidations() { moduleCache.clearPendingInvalidations(); failedLocationInvalidated.clear(); pendingFailedLocationCheck.clear(); } function cacheResolutionWithFailedLookup(resolvedModule, containingFile) { if (!resolvedModule.failedLookupLocations?.length) { return; } // The resolvedModule object will be reused in different files. A bit hacky, but TypeScript also does this. // https://github.com/microsoft/TypeScript/blob/11e79327598db412a161616849041487673fadab/src/compiler/resolutionCache.ts#L1103 resolvedModule.files ??= new Set(); resolvedModule.files.add(containingFile); resolutionWithFailedLookup.add(resolvedModule); } function invalidateFailedLocationResolution() { resolutionWithFailedLookup.forEach((resolvedModule) => { if (!resolvedModule.resolvedModule || !resolvedModule.files || !resolvedModule.failedLookupLocations) { return; } for (const location of resolvedModule.failedLookupLocations) { if (pendingFailedLocationCheck.has(location)) { moduleCache.delete(resolvedModule.resolvedModule.resolvedFileName); resolvedModule.files?.forEach((file) => { failedLocationInvalidated.add(file); }); break; } } }); pendingFailedLocationCheck.clear(); } } //# sourceMappingURL=module-loader.js.map