UNPKG

svelte-language-server

Version:
288 lines 13.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SvelteCheck = void 0; const path_1 = require("path"); const typescript_1 = __importDefault(require("typescript")); const vscode_languageserver_1 = require("vscode-languageserver"); const documents_1 = require("./lib/documents"); const logger_1 = require("./logger"); const ls_config_1 = require("./ls-config"); const plugins_1 = require("./plugins"); const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider"); const service_1 = require("./plugins/css/service"); const utils_1 = require("./plugins/typescript/features/utils"); const utils_2 = require("./plugins/typescript/utils"); const utils_3 = require("./utils"); const lodash_1 = require("lodash"); /** * Small wrapper around PluginHost's Diagnostic Capabilities * for svelte-check, without the overhead of the lsp. */ class SvelteCheck { constructor(workspacePath, options = {}) { this.options = options; this.docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text)); this.configManager = new ls_config_1.LSConfigManager(); this.pluginHost = new plugins_1.PluginHost(this.docManager); logger_1.Logger.setLogErrorsOnly(true); this.initialize(workspacePath, options); } async initialize(workspacePath, options) { if (options.tsconfig && !(0, path_1.isAbsolute)(options.tsconfig)) { throw new Error('tsconfigPath needs to be absolute, got ' + options.tsconfig); } this.configManager.update({ svelte: { compilerWarnings: options.compilerWarnings } }); // No HTMLPlugin, it does not provide diagnostics if (shouldRegister('svelte')) { this.pluginHost.register(new plugins_1.SveltePlugin(this.configManager)); } if (shouldRegister('css')) { const services = (0, service_1.createLanguageServices)({ fileSystemProvider: new FileSystemProvider_1.FileSystemProvider() }); const workspaceFolders = [ { name: '', uri: (0, utils_3.pathToUrl)(workspacePath) } ]; this.pluginHost.register(new plugins_1.CSSPlugin(this.docManager, this.configManager, workspaceFolders, services)); } if (shouldRegister('js') || options.tsconfig) { const workspaceUris = [(0, utils_3.pathToUrl)(workspacePath)]; this.lsAndTSDocResolver = new plugins_1.LSAndTSDocResolver(this.docManager, workspaceUris, this.configManager, { tsconfigPath: options.tsconfig, isSvelteCheck: true, onProjectReloaded: options.onProjectReload, watch: options.watch, onFileSnapshotCreated: options.onFileSnapshotCreated }); this.pluginHost.register(new plugins_1.TypeScriptPlugin(this.configManager, this.lsAndTSDocResolver, workspaceUris, this.docManager)); } function shouldRegister(source) { return !options.diagnosticSources || options.diagnosticSources.includes(source); } } /** * Creates/updates given document * * @param doc Text and Uri of the document * @param isNew Whether or not this is the creation of the document */ async upsertDocument(doc, isNew) { const filePath = (0, utils_3.urlToPath)(doc.uri) || ''; if (this.options.tsconfig) { const lsContainer = await this.getLSContainer(this.options.tsconfig); if (!lsContainer.fileBelongsToProject(filePath, isNew)) { return; } } if (doc.uri.endsWith('.ts') || doc.uri.endsWith('.js') || doc.uri.endsWith('.tsx') || doc.uri.endsWith('.jsx') || doc.uri.endsWith('.mjs') || doc.uri.endsWith('.cjs') || doc.uri.endsWith('.mts') || doc.uri.endsWith('.cts')) { this.pluginHost.updateTsOrJsFile(filePath, [ { range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(Number.MAX_VALUE, Number.MAX_VALUE)), text: doc.text } ]); } else { this.docManager.openClientDocument({ text: doc.text, uri: doc.uri }); } } /** * Removes/closes document * * @param uri Uri of the document */ async removeDocument(uri) { if (!this.docManager.get(uri)) { return; } this.docManager.closeDocument(uri); this.docManager.releaseDocument(uri); if (this.options.tsconfig) { const lsContainer = await this.getLSContainer(this.options.tsconfig); lsContainer.deleteSnapshot((0, utils_3.urlToPath)(uri) || ''); } } /** * Gets the diagnostics for all currently open files. */ async getDiagnostics() { if (this.options.tsconfig) { return this.getDiagnosticsForTsconfig(this.options.tsconfig); } return await Promise.all(this.docManager.getAllOpenedByClient().map(async (doc) => { const uri = doc[1].uri; return await this.getDiagnosticsForFile(uri); })); } async getDiagnosticsForTsconfig(tsconfigPath) { const lsContainer = await this.getLSContainer(tsconfigPath); const map = (diagnostic, range) => { const file = diagnostic.file; range ??= file ? (0, utils_2.convertRange)({ positionAt: file.getLineAndCharacterOfPosition.bind(file) }, diagnostic) : { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }; return { range: range, severity: (0, utils_2.mapSeverity)(diagnostic.category), source: diagnostic.source, message: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), code: diagnostic.code, tags: (0, utils_2.getDiagnosticTag)(diagnostic) }; }; if (lsContainer.configErrors.some((error) => error.category === typescript_1.default.DiagnosticCategory.Error)) { return reportConfigError(); } const lang = lsContainer.getService(); if (lsContainer.configErrors.some((error) => error.category === typescript_1.default.DiagnosticCategory.Error)) { return reportConfigError(); } const files = lang.getProgram()?.getSourceFiles() || []; const options = lang.getProgram()?.getCompilerOptions() || {}; const diagnostics = await Promise.all(files.map((file) => { const uri = (0, utils_3.pathToUrl)(file.fileName); const doc = this.docManager.get(uri); if (doc) { this.docManager.markAsOpenedInClient(uri); return this.getDiagnosticsForFile(uri); } else { // This check is done inside TS mostly, too, but for some diagnostics like suggestions it // doesn't apply to all code paths. That's why we do it here, too. const skipDiagnosticsForFile = (options.skipLibCheck && file.isDeclarationFile) || (options.skipDefaultLibCheck && file.hasNoDefaultLib) || lsContainer.isShimFiles(file.fileName) || // ignore JS files in node_modules /\/node_modules\/.+\.(c|m)?js$/.test(file.fileName); const snapshot = lsContainer.snapshotManager.get(file.fileName); const isKitFile = snapshot?.kitFile ?? false; const diagnostics = []; if (!skipDiagnosticsForFile) { const diagnosticSources = [ 'getSyntacticDiagnostics', 'getSuggestionDiagnostics', 'getSemanticDiagnostics' ]; for (const diagnosticSource of diagnosticSources) { for (let diagnostic of lang[diagnosticSource](file.fileName)) { if (!diagnostic.start || !diagnostic.length || !isKitFile) { diagnostics.push(map(diagnostic)); continue; } let range = undefined; const inGenerated = (0, utils_1.isInGeneratedCode)(file.text, diagnostic.start, diagnostic.start + diagnostic.length); if (inGenerated && snapshot) { const pos = snapshot.getOriginalPosition(snapshot.positionAt(diagnostic.start)); range = { start: pos, end: { line: pos.line, // adjust length so it doesn't spill over to the next line character: pos.character + 1 } }; // If not one of the specific error messages then filter out if (diagnostic.code === 2307) { diagnostic = { ...diagnostic, messageText: typeof diagnostic.messageText === 'string' && diagnostic.messageText.includes('./$types') ? diagnostic.messageText + ` (this likely means that SvelteKit's type generation didn't run yet - try running it by executing 'npm run dev' or 'npm run build')` : diagnostic.messageText }; } else if (diagnostic.code === 2694) { diagnostic = { ...diagnostic, messageText: typeof diagnostic.messageText === 'string' && diagnostic.messageText.includes('/$types') ? diagnostic.messageText + ` (this likely means that SvelteKit's generated types are out of date - try rerunning it by executing 'npm run dev' or 'npm run build')` : diagnostic.messageText }; } else if (diagnostic.code !== 2355 /* A function whose declared type is neither 'void' nor 'any' must return a value */) { continue; } } diagnostics.push(map(diagnostic, range)); } } } return { filePath: file.fileName, text: snapshot?.originalText ?? file.text, diagnostics }; } })); if (lsContainer.configErrors.length) { diagnostics.push(...reportConfigError()); } return diagnostics; function reportConfigError() { const grouped = (0, lodash_1.groupBy)(lsContainer.configErrors, (error) => error.file?.fileName ?? tsconfigPath); return Object.entries(grouped).map(([filePath, errors]) => ({ filePath, text: '', diagnostics: errors.map((diagnostic) => map(diagnostic)) })); } } async getDiagnosticsForFile(uri) { const diagnostics = await this.pluginHost.getDiagnostics({ uri }); return { filePath: (0, utils_3.urlToPath)(uri) || '', text: this.docManager.get(uri)?.getText() || '', diagnostics }; } getLSContainer(tsconfigPath) { if (!this.lsAndTSDocResolver) { throw new Error('Cannot run with tsconfig path without LS/TSdoc resolver'); } return this.lsAndTSDocResolver.getTSService(tsconfigPath); } /** * Gets the watch directories based on the tsconfig include patterns. * Returns null if no tsconfig is specified. */ async getWatchDirectories() { if (!this.options.tsconfig) { return null; } const lsContainer = await this.getLSContainer(this.options.tsconfig); const projectConfig = lsContainer.getProjectConfig(); if (!projectConfig.wildcardDirectories) { return null; } return Object.entries(projectConfig.wildcardDirectories).map(([dir, flags]) => ({ path: dir, recursive: !!(flags & typescript_1.default.WatchDirectoryFlags.Recursive) })); } } exports.SvelteCheck = SvelteCheck; //# sourceMappingURL=svelte-check.js.map