svelte-language-server
Version:
A language server for Svelte
301 lines • 13.4 kB
JavaScript
"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);
// @ts-ignore is 'client' in Svelte 5
return svelteDoc.getCompiledWith({ generate: 'dom' });
}
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