UNPKG

svelte-language-server

Version:
306 lines 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SveltePlugin = void 0; const path_1 = require("path"); const vscode_languageserver_1 = require("vscode-languageserver"); const importPackage_1 = require("../../importPackage"); const logger_1 = require("../../logger"); const utils_1 = require("../../utils"); const getCodeActions_1 = require("./features/getCodeActions"); const getCompletions_1 = require("./features/getCompletions"); const getDiagnostics_1 = require("./features/getDiagnostics"); const getHoverInfo_1 = require("./features/getHoverInfo"); const getSelectionRanges_1 = require("./features/getSelectionRanges"); const SvelteDocument_1 = require("./SvelteDocument"); class SveltePlugin { constructor(configManager) { this.configManager = configManager; this.__name = 'svelte'; this.docManager = new Map(); } async getCodeLens(document) { if (!this.featureEnabled('runesLegacyModeCodeLens')) return null; if (!document.isSvelte5) return null; const doc = await this.getSvelteDoc(document); try { const result = await doc.getCompiled(); // @ts-ignore const runes = result.metadata.runes; return [ { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, command: { title: runes ? 'Runes mode' : 'Legacy mode', command: 'svelte.openLink', arguments: ['https://svelte.dev/docs/svelte/legacy-overview'] } } ]; } catch (e) { // show an empty code lens in case of a compilation error to prevent code from jumping around return [ { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, command: { title: '', command: '' } } ]; } } async getDiagnostics(document, cancellationToken) { if (!this.featureEnabled('diagnostics') || !this.configManager.getIsTrusted()) { return []; } return (0, getDiagnostics_1.getDiagnostics)(document, await this.getSvelteDoc(document), this.configManager.getConfig().svelte.compilerWarnings, cancellationToken); } async getCompiledResult(document) { try { const svelteDoc = await this.getSvelteDoc(document); const options = { generate: 'dom' }; // 'client' in Svelte 5 // @ts-ignore Svelte 5 only; we gotta write it like this else Svelte 4 fails on unknown key if (document.config?.compilerOptions?.experimental) { // @ts-ignore Svelte 5 only options.experimental = document.config.compilerOptions.experimental; } return await svelteDoc.getCompiledWith(options); } catch (error) { return null; } } async formatDocument(document, options) { if (!this.featureEnabled('format')) { return []; } const filePath = document.getFilePath(); /** * Prettier v2 can't use v3 plugins and vice versa. Therefore, we need to check * which version of prettier is used in the workspace and import the correct * version of the Svelte plugin. If user uses Prettier < 3 and has no Svelte plugin * then fall back to our built-in versions which are both v3 and compatible with * each other. */ const importFittingPrettier = async () => { const getConfig = async (p) => { // Try resolving the config through prettier and fall back to possible editor config return this.configManager.getMergedPrettierConfig(await p.resolveConfig(filePath, { editorconfig: true }), // Be defensive here because IDEs other than VSCode might not have these settings options && { tabWidth: options.tabSize, useTabs: !options.insertSpaces }); }; const prettier1 = (0, importPackage_1.importPrettier)(filePath); const config1 = await getConfig(prettier1); const resolvedPlugins1 = resolvePlugins(config1.plugins); const pluginLoaded = await hasSveltePluginLoaded(prettier1, resolvedPlugins1); if (Number(prettier1.version[0]) >= 3 || pluginLoaded) { // plugin loaded, or referenced in user config as a plugin, or same version as our fallback version -> ok return { prettier: prettier1, config: config1, isFallback: false, resolvedPlugins: resolvedPlugins1 }; } // User either only has Plugin or incompatible Prettier major version installed or none // -> load our fallback version const prettier2 = (0, importPackage_1.importPrettier)(__dirname); const config2 = await getConfig(prettier2); const resolvedPlugins2 = resolvePlugins(config2.plugins); return { prettier: prettier2, config: config2, isFallback: true, resolvedPlugins: resolvedPlugins2 }; }; const { prettier, config, isFallback, resolvedPlugins } = await importFittingPrettier(); // If user has prettier-plugin-svelte 1.x, then remove `options` from the sort // order or else it will throw a config error (`options` was not present back then). if (config?.svelteSortOrder && (0, importPackage_1.getPackageInfo)('prettier-plugin-svelte', filePath)?.version.major < 2) { config.svelteSortOrder = config.svelteSortOrder .replace('-options', '') .replace('options-', ''); } // If user has prettier-plugin-svelte 3.x, then add `options` from the sort // order or else it will throw a config error (now required). if (config?.svelteSortOrder && !config.svelteSortOrder.includes('options') && config.svelteSortOrder !== 'none' && (0, importPackage_1.getPackageInfo)('prettier-plugin-svelte', filePath)?.version.major >= 3) { config.svelteSortOrder = 'options-' + config.svelteSortOrder; } // Take .prettierignore into account const fileInfo = await prettier.getFileInfo(filePath, { ignorePath: this.configManager.getPrettierConfig()?.ignorePath ?? '.prettierignore', // Sapper places stuff within src/node_modules, we want to format that, too withNodeModules: true }); if (fileInfo.ignored) { logger_1.Logger.debug('File is ignored, formatting skipped'); return []; } if (isFallback || !(await hasSveltePluginLoaded(prettier, resolvedPlugins))) { // If the user uses Svelte 5 but doesn't have prettier installed, we need to provide // the compiler path to the plugin so it can use its parser method; else it will crash. const svelteCompilerInfo = (0, importPackage_1.getPackageInfo)('svelte', filePath); if (svelteCompilerInfo.version.major >= 5) { config.svelte5CompilerPath = svelteCompilerInfo.path + '/compiler'; } } // Prettier v3 format is async, v2 is not const formattedCode = await prettier.format(document.getText(), { ...config, plugins: Array.from(new Set([...resolvedPlugins, ...(await getSveltePlugin(resolvedPlugins))])), parser: 'svelte' }); return document.getText() === formattedCode ? [] : [ vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(document.positionAt(0), document.positionAt(document.getTextLength())), formattedCode) ]; async function getSveltePlugin(plugins = []) { // Only provide our version of the svelte plugin if the user doesn't have one in // the workspace already. If we did it, Prettier would - for some reason - use // the workspace version for parsing and the extension version for printing, // which could crash if the contract of the parser output changed. return !isFallback && (await hasSveltePluginLoaded(prettier, plugins)) ? [] : [require.resolve('prettier-plugin-svelte')]; } async function hasSveltePluginLoaded(p, plugins = []) { if (plugins.some(SveltePlugin.isPrettierPluginSvelte)) return true; if (Number(p.version[0]) >= 3) return false; // Prettier version 3 has removed the "search plugins" feature // Prettier v3 getSupportInfo is async, v2 is not const info = await p.getSupportInfo(); return info.languages.some((l) => l.name === 'svelte'); } function resolvePlugins(plugins) { return (plugins ?? []).map(resolvePlugin).filter(utils_1.isNotNullOrUndefined); } function resolvePlugin(plugin) { // https://github.com/prettier/prettier-vscode/blob/160b0e92d88fa19003dce2745d5ab8c67e886a04/src/ModuleResolver.ts#L373 if (typeof plugin != 'string' || (0, path_1.isAbsolute)(plugin) || plugin.startsWith('.')) { return plugin; } try { return require.resolve(plugin, { paths: [filePath] }); } catch (error) { logger_1.Logger.error(`failed to resolve plugin ${plugin} with error:\n`, error); } } } static isPrettierPluginSvelte(plugin) { if (typeof plugin === 'string') { return plugin.includes('prettier-plugin-svelte'); } return !!plugin?.languages?.find((l) => l.name === 'svelte'); } async getCompletions(document, position, _, cancellationToken) { if (!this.featureEnabled('completions')) { return null; } const svelteDoc = await this.getSvelteDoc(document); if (cancellationToken?.isCancellationRequested) { return null; } return (0, getCompletions_1.getCompletions)(document, svelteDoc, position); } async doHover(document, position) { if (!this.featureEnabled('hover')) { return null; } return (0, getHoverInfo_1.getHoverInfo)(document, await this.getSvelteDoc(document), position); } async getCodeActions(document, range, context, cancellationToken) { if (!this.featureEnabled('codeActions')) { return []; } const svelteDoc = await this.getSvelteDoc(document); if (cancellationToken?.isCancellationRequested) { return []; } try { return (0, getCodeActions_1.getCodeActions)(svelteDoc, range, context); } catch (error) { return []; } } async executeCommand(document, command, args) { if (command === 'migrate_to_svelte_5') { return this.migrate(document); } if (!this.featureEnabled('codeActions')) { return null; } const svelteDoc = await this.getSvelteDoc(document); try { return (0, getCodeActions_1.executeCommand)(svelteDoc, command, args); } catch (error) { return null; } } migrate(document) { try { const compiler = document.compiler; if (!compiler.migrate) { return 'Your installed Svelte version does not support migration'; } const migrated = compiler.migrate(document.getText(), { filename: document.getFilePath() ?? undefined }); return { changes: { [document.uri]: [ vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(document.positionAt(0), document.positionAt(document.getTextLength())), migrated.code) ] } }; } catch (error) { logger_1.Logger.error('Failed to migrate Svelte file', error); return error?.message ?? 'Failed to migrate Svelte file'; } } async getSelectionRange(document, position) { if (!this.featureEnabled('selectionRange')) { return null; } const svelteDoc = await this.getSvelteDoc(document); return (0, getSelectionRanges_1.getSelectionRange)(svelteDoc, position); } featureEnabled(feature) { return (this.configManager.enabled('svelte.enable') && this.configManager.enabled(`svelte.${feature}.enable`)); } async getSvelteDoc(document) { let svelteDoc = this.docManager.get(document); if (!svelteDoc || svelteDoc.version !== document.version) { svelteDoc = new SvelteDocument_1.SvelteDocument(document); this.docManager.set(document, svelteDoc); } return svelteDoc; } } exports.SveltePlugin = SveltePlugin; //# sourceMappingURL=SveltePlugin.js.map