UNPKG

svelte-language-server

Version:
411 lines 20 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; exports.mapSvelteCheckDiagnostics = mapSvelteCheckDiagnostics; 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 configLoader_1 = require("./lib/documents/configLoader"); const logger_1 = require("./logger"); const ls_config_1 = require("./ls-config"); const plugins_1 = require("./plugins"); const FileSystemProvider_1 = require("./lib/FileSystemProvider"); const service_1 = require("./plugins/css/service"); const DocumentSnapshot_1 = require("./plugins/typescript/DocumentSnapshot"); const utils_1 = require("./plugins/typescript/features/utils"); const DiagnosticsProvider_1 = require("./plugins/typescript/features/DiagnosticsProvider"); const utils_2 = require("./plugins/typescript/utils"); const utils_3 = require("./utils"); const DiagnosticsProvider_2 = require("./plugins/typescript-go/features/DiagnosticsProvider"); function mapSvelteCheckDiagnostics(sourcePath, sourceText, tsDiagnostics, options) { logger_1.Logger.setLogErrorsOnly(true); const document = new documents_1.Document((0, utils_3.pathToUrl)(sourcePath), sourceText, /* skipConfigLoading */ true); const snapshot = DocumentSnapshot_1.DocumentSnapshot.fromDocument(document, { parse: document.compiler?.parse, version: document.compiler?.VERSION, transformOnTemplateError: false, typingsNamespace: 'svelteHTML', emitJsDoc: true, rewriteExternalImports: options?.rewriteExternalImports }); return (0, DiagnosticsProvider_1.mapAndFilterDiagnostics)(tsDiagnostics, document, snapshot); } /** * 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); } if (options.configPath && !(0, path_1.isAbsolute)(options.configPath)) { throw new Error('configPath needs to be absolute, got ' + options.configPath); } configLoader_1.configLoader.setExplicitConfigScope(options.configPath ? { configPath: options.configPath, rootDirectory: options.tsconfig ? (0, path_1.dirname)(options.tsconfig) : workspacePath } : undefined); 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)]; if (options.experimental?.tsgo && options.tsconfig) { const { apiModule, astModule } = options.experimental.tsgo; if (!apiModule.API || !('ScriptKind' in astModule)) { throw new Error('Unsupported typescript-go version'); } this.tsGoDiagnosticsProvider = new DiagnosticsProvider_2.SvelteCheckTSGoDiagnosticsProvider(apiModule, astModule, options.tsconfig, 'svelte-check', (filePath, text) => this.docManager.openDocument({ text: text, uri: (0, utils_3.pathToUrl)(filePath) }, /* openedByClient */ true)); } else { 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) || ''; // in tsgo mode, let typescript check whether the file belongs to the project if (this.tsGoDiagnosticsProvider) { this.tsGoDiagnosticsProvider.watchUpdate(doc, isNew ? 'created' : 'changed'); return; } 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) { if (this.tsGoDiagnosticsProvider) { this.tsGoDiagnosticsProvider.watchUpdate({ text: '', uri }, 'deleted'); } else { 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) { if (this.tsGoDiagnosticsProvider) { return this.getDiagnosticsForTsconfigTsGo(); } 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 normalizedTsconfigPath = (0, utils_3.normalizePath)(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), data: { positionUnknown: !diagnostic.start || !diagnostic.length } }; }; const isErrorCategory = (diagnostic) => diagnostic.category === typescript_1.default.DiagnosticCategory.Error; if (lsContainer.configErrors.some(isErrorCategory)) { return reportConfigError(lsContainer.configErrors); } const lang = lsContainer.getService(); if (lsContainer.configErrors.some(isErrorCategory)) { return reportConfigError(lsContainer.configErrors); } const program = lang.getProgram(); const globalOrConfigFileDiagnostics = program ? [...program.getGlobalDiagnostics(), ...program.getOptionsDiagnostics()] : []; // TODO: enable this in svelte-check v5. For now, we report these as warnings along with other diagnostics. // if (globalOrConfigFileDiagnostics.some(isErrorCategory)) { // return reportConfigError(globalOrConfigFileDiagnostics); // } 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 }; } })); const configErrors = lsContainer.configErrors // TODO: remove this in svelte-check v5. .concat(globalOrConfigFileDiagnostics.map((diagnostic) => ({ ...diagnostic, category: diagnostic.category === typescript_1.default.DiagnosticCategory.Error ? typescript_1.default.DiagnosticCategory.Warning : diagnostic.category }))); if (configErrors.length) { diagnostics.push(...reportConfigError(configErrors)); } return diagnostics; function reportConfigError(errors) { const grouped = (0, utils_3.groupBy)(errors, (error) => error.file?.fileName ?? normalizedTsconfigPath); const lspDiagnostics = errors.map((diagnostic) => map(diagnostic)); return Object.entries(grouped).map(([filePath, errors]) => ({ filePath, text: lspDiagnostics.some((diagnostic) => diagnostic.data?.positionUnknown) ? (typescript_1.default.sys?.readFile(filePath) ?? '') : '', diagnostics: lspDiagnostics })); } } async getDiagnosticsForTsconfigTsGo() { if (!this.tsGoDiagnosticsProvider) { throw new Error('Cannot get diagnostics for tsconfig without TSGo diagnostics provider'); } const project = await this.tsGoDiagnosticsProvider.getProject(); if (!project) { throw new Error('Expected to have api project'); } let allTsDiagnostics = Array.from(project.program.getConfigFileParsingDiagnostics()); const configFileParsingDiagnosticsLength = allTsDiagnostics?.length ?? 0; allTsDiagnostics = allTsDiagnostics.concat(project.program.getSyntacticDiagnostics()); // doesn't exist in the API yet // allDiagnostics = allDiagnostics.concat(project.program.getProgramDiagnostics()); if (allTsDiagnostics.length == configFileParsingDiagnosticsLength) { allTsDiagnostics = allTsDiagnostics.concat(project.program.getSemanticDiagnostics()); } const result = this.tsGoDiagnosticsProvider.mapAndFilterDiagnostics(project, allTsDiagnostics); const map = new Map(); for (const diag of result) { map.set(diag.filePath, diag); } for (const filePath of this.tsGoDiagnosticsProvider.getAllSvelteFiles()) { const uri = (0, utils_3.pathToUrl)(filePath); if (!uri) { continue; } const doc = this.docManager.get(uri); if (!doc) { continue; } const nonTsDiagnostics = await this.getDiagnosticsForFile(uri); let existing = map.get(filePath); if (existing) { existing.diagnostics = existing.diagnostics.concat(nonTsDiagnostics.diagnostics); } else { map.set(filePath, nonTsDiagnostics); } } return Array.from(map.values()); } 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; } let projectConfig; if (this.tsGoDiagnosticsProvider) { projectConfig = this.tsGoDiagnosticsProvider.getProjectConfig(); } else { const lsContainer = await this.getLSContainer(this.options.tsconfig); 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