svelte-language-server
Version:
A language server for Svelte
246 lines • 12 kB
JavaScript
;
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