UNPKG

svelte-language-server

Version:
287 lines 13.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LSAndTSDocResolver = void 0; const path_1 = require("path"); const typescript_1 = __importDefault(require("typescript")); const utils_1 = require("../../utils"); const service_1 = require("./service"); const serviceCache_1 = require("./serviceCache"); const SnapshotManager_1 = require("./SnapshotManager"); const utils_2 = require("./utils"); const fileCollection_1 = require("../../lib/documents/fileCollection"); class LSAndTSDocResolver { constructor(docManager, workspaceUris, configManager, options) { this.docManager = docManager; this.workspaceUris = workspaceUris; this.configManager = configManager; this.options = options; /** * Create a svelte document -> should only be invoked with svelte files. */ this.createDocument = (fileName, content) => { const uri = (0, utils_1.pathToUrl)(fileName); const document = this.docManager.openDocument({ text: content, uri }, /* openedByClient */ false); this.docManager.lockDocument(uri); return document; }; this.extendedConfigCache = new Map(); docManager.on('documentChange', (0, utils_1.debounceSameArg)(this.updateSnapshot.bind(this), (newDoc, prevDoc) => newDoc.uri === prevDoc?.uri, 1000)); // New files would cause typescript to rebuild its type-checker. // Open it immediately to reduce rebuilds in the startup // where multiple files and their dependencies // being loaded in a short period of times docManager.on('documentOpen', (document) => { if (document.openedByClient) { this.getOrCreateSnapshot(document); } else { this.updateSnapshot(document); } docManager.lockDocument(document.uri); }); this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)((options?.tsSystem ?? typescript_1.default.sys).useCaseSensitiveFileNames); this.tsSystem = this.wrapWithPackageJsonMonitoring(this.options?.tsSystem ?? typescript_1.default.sys); this.globalSnapshotsManager = new SnapshotManager_1.GlobalSnapshotsManager(this.tsSystem); this.userPreferencesAccessor = { preferences: this.getTsUserPreferences() }; const projectService = (0, serviceCache_1.createProjectService)(this.tsSystem, this.userPreferencesAccessor); configManager.onChange(() => { const newPreferences = this.getTsUserPreferences(); const autoImportConfigChanged = newPreferences.includePackageJsonAutoImports !== this.userPreferencesAccessor.preferences.includePackageJsonAutoImports; this.userPreferencesAccessor.preferences = newPreferences; if (autoImportConfigChanged) { (0, service_1.forAllServices)((service) => { service.onAutoImportProviderSettingsChanged(); }); } }); this.packageJsonWatchers = new fileCollection_1.FileMap(this.tsSystem.useCaseSensitiveFileNames); this.watchedDirectories = new fileCollection_1.FileSet(this.tsSystem.useCaseSensitiveFileNames); // workspaceUris are already watched during initialization for (const root of this.workspaceUris) { const rootPath = (0, utils_1.urlToPath)(root); if (rootPath) { this.watchedDirectories.add(rootPath); } } this.lsDocumentContext = { isSvelteCheck: !!this.options?.isSvelteCheck, ambientTypesSource: this.options?.isSvelteCheck ? 'svelte-check' : 'svelte2tsx', createDocument: this.createDocument, transformOnTemplateError: !this.options?.isSvelteCheck, globalSnapshotsManager: this.globalSnapshotsManager, notifyExceedSizeLimit: this.options?.notifyExceedSizeLimit, extendedConfigCache: this.extendedConfigCache, onProjectReloaded: this.options?.onProjectReloaded, watchTsConfig: !!this.options?.watch, tsSystem: this.tsSystem, projectService, watchDirectory: this.options?.watchDirectory ? this.watchDirectory.bind(this) : undefined, nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern, reportConfigError: this.options?.reportConfigError }; } async getLSAndTSDoc(document) { const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document); return { tsDoc, lang: lsContainer.getService(), userPreferences, lsContainer }; } /** * Retrieves the LS for operations that don't need cross-files information. * can save some time by not synchronizing languageService program */ async getLsForSyntheticOperations(document) { const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document); return { tsDoc, userPreferences, lang: lsContainer.getService(/* skipSynchronize */ true) }; } async getLSAndTSDocWorker(document) { const lsContainer = await this.getTSService(document.getFilePath() || ''); const tsDoc = await this.getOrCreateSnapshot(document); const userPreferences = this.getUserPreferences(tsDoc); return { tsDoc, lsContainer, userPreferences }; } async getOrCreateSnapshot(pathOrDoc) { const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || ''; const tsService = await this.getTSService(filePath); return tsService.updateSnapshot(pathOrDoc); } async updateSnapshot(document) { const filePath = document.getFilePath(); if (!filePath) { return; } // ensure no new service is created await this.updateExistingFile(filePath, (service) => service.updateSnapshot(document)); } /** * Updates snapshot path in all existing ts services and retrieves snapshot */ async updateSnapshotPath(oldPath, newPath) { const document = this.docManager.get((0, utils_1.pathToUrl)(oldPath)); const isOpenedInClient = document?.openedByClient; for (const snapshot of this.globalSnapshotsManager.getByPrefix(oldPath)) { await this.deleteSnapshot(snapshot.filePath); } if (isOpenedInClient) { this.docManager.openClientDocument({ uri: (0, utils_1.pathToUrl)(newPath), text: document.getText() }); } else { // This may not be a file but a directory, still try await this.getOrCreateSnapshot(newPath); } } /** * Deletes snapshot in all existing ts services */ async deleteSnapshot(filePath) { await (0, service_1.forAllServices)((service) => service.deleteSnapshot(filePath)); const uri = (0, utils_1.pathToUrl)(filePath); if (this.docManager.get(uri)) { // Guard this call, due to race conditions it may already have been closed; // also this may not be a Svelte file this.docManager.closeDocument(uri); } this.docManager.releaseDocument(uri); } async invalidateModuleCache(filePaths) { await (0, service_1.forAllServices)((service) => service.invalidateModuleCache(filePaths)); } /** * Updates project files in all existing ts services */ async updateProjectFiles(watcherNewFiles) { await (0, service_1.forAllServices)((service) => service.scheduleProjectFileUpdate(watcherNewFiles)); } /** * Updates file in all ts services where it exists */ async updateExistingTsOrJsFile(path, changes) { await this.updateExistingFile(path, (service) => service.updateTsOrJsFile(path, changes)); } async updateExistingSvelteFile(path) { const newDocument = this.createDocument(path, this.tsSystem.readFile(path) ?? ''); await this.updateExistingFile(path, (service) => { service.updateSnapshot(newDocument); }); } async updateExistingFile(path, cb) { path = (0, utils_1.normalizePath)(path); // Only update once because all snapshots are shared between // services. Since we don't have a current version of TS/JS // files, the operation wouldn't be idempotent. let didUpdate = false; await (0, service_1.forAllServices)((service) => { if (service.hasFile(path) && !didUpdate) { didUpdate = true; cb(service); } }); } async getTSService(filePath) { if (this.options?.tsconfigPath) { return this.getTSServiceByConfigPath(this.options.tsconfigPath, (0, path_1.dirname)(this.options.tsconfigPath)); } if (!filePath) { throw new Error('Cannot call getTSService without filePath and without tsconfigPath'); } return (0, service_1.getService)(filePath, this.workspaceUris, this.lsDocumentContext); } async getTSServiceByConfigPath(tsconfigPath, workspacePath) { return (0, service_1.getServiceForTsconfig)(tsconfigPath, workspacePath, this.lsDocumentContext); } getUserPreferences(tsDoc) { const configLang = tsDoc.scriptKind === typescript_1.default.ScriptKind.TS || tsDoc.scriptKind === typescript_1.default.ScriptKind.TSX ? 'typescript' : 'javascript'; const nearestWorkspaceUri = this.workspaceUris.find((workspaceUri) => (0, utils_2.isSubPath)(workspaceUri, tsDoc.filePath, this.getCanonicalFileName)); return this.configManager.getTsUserPreferences(configLang, nearestWorkspaceUri ? (0, utils_1.urlToPath)(nearestWorkspaceUri) : null); } getTsUserPreferences() { return this.configManager.getTsUserPreferences('typescript', null); } wrapWithPackageJsonMonitoring(sys) { if (!sys.watchFile || !this.options?.watch) { return sys; } const watchFile = sys.watchFile; return { ...sys, readFile: (path, encoding) => { if (path.endsWith('package.json') && !this.packageJsonWatchers.has(path)) { this.packageJsonWatchers.set(path, watchFile(path, this.onPackageJsonWatchChange.bind(this), 3_000)); } return sys.readFile(path, encoding); } }; } onPackageJsonWatchChange(path, onWatchChange) { const dir = (0, path_1.dirname)(path); const projectService = this.lsDocumentContext.projectService; const packageJsonCache = projectService?.packageJsonCache; const normalizedPath = projectService?.toPath(path); if (onWatchChange === typescript_1.default.FileWatcherEventKind.Deleted) { this.packageJsonWatchers.get(path)?.close(); this.packageJsonWatchers.delete(path); packageJsonCache?.delete(normalizedPath); } else { packageJsonCache?.addOrUpdate(normalizedPath); } (0, service_1.forAllServices)((service) => { service.onPackageJsonChange(path); }); if (!path.includes('node_modules')) { return; } setTimeout(() => { this.updateSnapshotsInDirectory(dir); const realPath = this.tsSystem.realpath && this.getCanonicalFileName((0, utils_1.normalizePath)(this.tsSystem.realpath?.(dir))); // pnpm if (realPath && realPath !== dir) { this.updateSnapshotsInDirectory(realPath); const realPkgPath = (0, path_1.join)(realPath, 'package.json'); (0, service_1.forAllServices)((service) => { service.onPackageJsonChange(realPkgPath); }); } }, 500); } updateSnapshotsInDirectory(dir) { this.globalSnapshotsManager.getByPrefix(dir).forEach((snapshot) => { this.globalSnapshotsManager.updateTsOrJsFile(snapshot.filePath); }); } watchDirectory(patterns) { if (!this.options?.watchDirectory || patterns.length === 0) { return; } for (const pattern of patterns) { const uri = typeof pattern.baseUri === 'string' ? pattern.baseUri : pattern.baseUri.uri; for (const watched of this.watchedDirectories) { if ((0, utils_2.isSubPath)(watched, uri, this.getCanonicalFileName)) { return; } } } this.options.watchDirectory(patterns); } } exports.LSAndTSDocResolver = LSAndTSDocResolver; //# sourceMappingURL=LSAndTSDocResolver.js.map