svelte-language-server
Version:
A language server for Svelte
425 lines • 22.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
const vscode_languageserver_1 = require("vscode-languageserver");
const node_1 = require("vscode-languageserver/node");
const DiagnosticsManager_1 = require("./lib/DiagnosticsManager");
const documents_1 = require("./lib/documents");
const semanticTokenLegend_1 = require("./lib/semanticToken/semanticTokenLegend");
const logger_1 = require("./logger");
const ls_config_1 = require("./ls-config");
const plugins_1 = require("./plugins");
const utils_1 = require("./utils");
const FallbackWatcher_1 = require("./lib/FallbackWatcher");
const configLoader_1 = require("./lib/documents/configLoader");
const importPackage_1 = require("./importPackage");
const CodeActionsProvider_1 = require("./plugins/typescript/features/CodeActionsProvider");
const service_1 = require("./plugins/css/service");
const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider");
var TagCloseRequest;
(function (TagCloseRequest) {
TagCloseRequest.type = new vscode_languageserver_1.RequestType('html/tag');
})(TagCloseRequest || (TagCloseRequest = {}));
/**
* Starts the language server.
*
* @param options Options to customize behavior
*/
function startServer(options) {
let connection = options?.connection;
if (!connection) {
if (process.argv.includes('--stdio')) {
console.log = (...args) => {
console.warn(...args);
};
connection = (0, node_1.createConnection)(process.stdin, process.stdout);
}
else {
connection = (0, node_1.createConnection)(new node_1.IPCMessageReader(process), new node_1.IPCMessageWriter(process));
}
}
if (options?.logErrorsOnly !== undefined) {
logger_1.Logger.setLogErrorsOnly(options.logErrorsOnly);
}
const docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text));
const configManager = new ls_config_1.LSConfigManager();
const pluginHost = new plugins_1.PluginHost(docManager);
let sveltePlugin = undefined;
let watcher;
let pendingWatchPatterns = [];
let watchDirectory = (patterns) => {
pendingWatchPatterns = patterns;
};
// Include Svelte files to better deal with scenarios such as switching git branches
// where files that are not opened in the client could change
const watchExtensions = ['.ts', '.js', '.mts', '.mjs', '.cjs', '.cts', '.json', '.svelte'];
const nonRecursiveWatchPattern = '*.{' + watchExtensions.map((ext) => ext.slice(1)).join(',') + '}';
const recursiveWatchPattern = '**/' + nonRecursiveWatchPattern;
connection.onInitialize((evt) => {
const workspaceUris = evt.workspaceFolders?.map((folder) => folder.uri.toString()) ?? [
evt.rootUri ?? ''
];
logger_1.Logger.log('Initialize language server at ', workspaceUris.join(', '));
if (workspaceUris.length === 0) {
logger_1.Logger.error('No workspace path set');
}
if (!evt.capabilities.workspace?.didChangeWatchedFiles) {
const workspacePaths = workspaceUris.map(utils_1.urlToPath).filter(utils_1.isNotNullOrUndefined);
watcher = new FallbackWatcher_1.FallbackWatcher(watchExtensions, workspacePaths);
watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
watchDirectory = (patterns) => {
watcher?.watchDirectory(patterns);
};
}
const isTrusted = evt.initializationOptions?.isTrusted ?? true;
configLoader_1.configLoader.setDisabled(!isTrusted);
(0, importPackage_1.setIsTrusted)(isTrusted);
configManager.updateIsTrusted(isTrusted);
if (!isTrusted) {
logger_1.Logger.log('Workspace is not trusted, running with reduced capabilities.');
}
logger_1.Logger.setDebug((evt.initializationOptions?.configuration?.svelte ||
evt.initializationOptions?.config)?.['language-server']?.debug);
// Backwards-compatible way of setting initialization options (first `||` is the old style)
configManager.update(evt.initializationOptions?.configuration?.svelte?.plugin ||
evt.initializationOptions?.config ||
{});
configManager.updateTsJsUserPreferences(evt.initializationOptions?.configuration ||
evt.initializationOptions?.typescriptConfig ||
{});
configManager.updateTsJsFormateConfig(evt.initializationOptions?.configuration ||
evt.initializationOptions?.typescriptConfig ||
{});
configManager.updateEmmetConfig(evt.initializationOptions?.configuration?.emmet ||
evt.initializationOptions?.emmetConfig ||
{});
configManager.updatePrettierConfig(evt.initializationOptions?.configuration?.prettier ||
evt.initializationOptions?.prettierConfig ||
{});
// no old style as these were added later
configManager.updateCssConfig(evt.initializationOptions?.configuration?.css);
configManager.updateScssConfig(evt.initializationOptions?.configuration?.scss);
configManager.updateLessConfig(evt.initializationOptions?.configuration?.less);
configManager.updateHTMLConfig(evt.initializationOptions?.configuration?.html);
configManager.updateClientCapabilities(evt.capabilities);
pluginHost.initialize({
filterIncompleteCompletions: !evt.initializationOptions?.dontFilterIncompleteCompletions,
definitionLinkSupport: !!evt.capabilities.textDocument?.definition?.linkSupport
});
// Order of plugin registration matters for FirstNonNull, which affects for example hover info
pluginHost.register((sveltePlugin = new plugins_1.SveltePlugin(configManager)));
pluginHost.register(new plugins_1.HTMLPlugin(docManager, configManager));
const cssLanguageServices = (0, service_1.createLanguageServices)({
clientCapabilities: evt.capabilities,
fileSystemProvider: new FileSystemProvider_1.FileSystemProvider()
});
const workspaceFolders = evt.workspaceFolders ?? [{ name: '', uri: evt.rootUri ?? '' }];
pluginHost.register(new plugins_1.CSSPlugin(docManager, configManager, workspaceFolders, cssLanguageServices));
const normalizedWorkspaceUris = workspaceUris.map(utils_1.normalizeUri);
pluginHost.register(new plugins_1.TypeScriptPlugin(configManager, new plugins_1.LSAndTSDocResolver(docManager, normalizedWorkspaceUris, configManager, {
notifyExceedSizeLimit: notifyTsServiceExceedSizeLimit,
onProjectReloaded: refreshCrossFilesSemanticFeatures,
watch: true,
nonRecursiveWatchPattern,
watchDirectory: (patterns) => watchDirectory(patterns),
reportConfigError(diagnostic) {
connection?.sendDiagnostics(diagnostic);
}
}), normalizedWorkspaceUris, docManager));
const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit;
const clientCodeActionCapabilities = evt.capabilities.textDocument?.codeAction;
const clientSupportedCodeActionKinds = clientCodeActionCapabilities?.codeActionLiteralSupport?.codeActionKind.valueSet;
return {
capabilities: {
textDocumentSync: {
openClose: true,
change: vscode_languageserver_1.TextDocumentSyncKind.Incremental,
save: {
includeText: false
}
},
hoverProvider: true,
completionProvider: {
resolveProvider: true,
triggerCharacters: [
'.',
'"',
"'",
'`',
'/',
'@',
'<',
// Emmet
'>',
'*',
'#',
'$',
'+',
'^',
'(',
'[',
'@',
'-',
// No whitespace because
// it makes for weird/too many completions
// of other completion providers
// Svelte
':',
'|'
],
completionItem: {
labelDetailsSupport: true
}
},
documentFormattingProvider: true,
colorProvider: true,
documentSymbolProvider: true,
definitionProvider: true,
codeActionProvider: clientCodeActionCapabilities?.codeActionLiteralSupport
? {
codeActionKinds: [
vscode_languageserver_1.CodeActionKind.QuickFix,
vscode_languageserver_1.CodeActionKind.SourceOrganizeImports,
CodeActionsProvider_1.SORT_IMPORT_CODE_ACTION_KIND,
CodeActionsProvider_1.ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
...(clientSupportApplyEditCommand ? [vscode_languageserver_1.CodeActionKind.Refactor] : [])
].filter(clientSupportedCodeActionKinds &&
evt.initializationOptions?.shouldFilterCodeActionKind
? (kind) => clientSupportedCodeActionKinds.includes(kind)
: () => true),
resolveProvider: true
}
: true,
executeCommandProvider: clientSupportApplyEditCommand
? {
commands: [
'function_scope_0',
'function_scope_1',
'function_scope_2',
'function_scope_3',
'constant_scope_0',
'constant_scope_1',
'constant_scope_2',
'constant_scope_3',
'extract_to_svelte_component',
'migrate_to_svelte_5',
'Infer function return type'
]
}
: undefined,
renameProvider: evt.capabilities.textDocument?.rename?.prepareSupport
? { prepareProvider: true }
: true,
referencesProvider: true,
selectionRangeProvider: true,
signatureHelpProvider: {
triggerCharacters: ['(', ',', '<'],
retriggerCharacters: [')']
},
semanticTokensProvider: {
legend: (0, semanticTokenLegend_1.getSemanticTokenLegends)(),
range: true,
full: true
},
linkedEditingRangeProvider: true,
implementationProvider: true,
typeDefinitionProvider: true,
inlayHintProvider: true,
callHierarchyProvider: true,
foldingRangeProvider: true,
codeLensProvider: {
resolveProvider: true
},
documentHighlightProvider: evt.initializationOptions?.configuration?.svelte?.plugin?.svelte
?.documentHighlight?.enable ?? true,
workspaceSymbolProvider: true
}
};
});
connection.onInitialized(() => {
if (watcher) {
return;
}
const didChangeWatchedFiles = configManager.getClientCapabilities()?.workspace?.didChangeWatchedFiles;
if (!didChangeWatchedFiles?.dynamicRegistration) {
return;
}
// still watch the roots since some files might be referenced but not included in the project
connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
watchers: [
{
// Editors have exclude configs, such as VSCode with `files.watcherExclude`,
// which means it's safe to watch recursively here
globPattern: recursiveWatchPattern
}
]
});
if (didChangeWatchedFiles.relativePatternSupport) {
watchDirectory = (patterns) => {
connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
watchers: patterns.map((pattern) => ({
globPattern: pattern
}))
});
};
if (pendingWatchPatterns.length) {
watchDirectory(pendingWatchPatterns);
pendingWatchPatterns = [];
}
}
});
function notifyTsServiceExceedSizeLimit() {
connection?.sendNotification(vscode_languageserver_1.ShowMessageNotification.type, {
message: 'Svelte language server detected a large amount of JS/Svelte files. ' +
'To enable project-wide JavaScript/TypeScript language features for Svelte files, ' +
'exclude large folders in the tsconfig.json or jsconfig.json with source files that you do not work on.',
type: vscode_languageserver_1.MessageType.Warning
});
}
connection.onExit(() => {
watcher?.dispose();
});
connection.onRenameRequest((req) => pluginHost.rename(req.textDocument, req.position, req.newName));
connection.onPrepareRename((req) => pluginHost.prepareRename(req.textDocument, req.position));
connection.onDidChangeConfiguration(({ settings }) => {
configManager.update(settings.svelte?.plugin);
configManager.updateTsJsUserPreferences(settings);
configManager.updateTsJsFormateConfig(settings);
configManager.updateEmmetConfig(settings.emmet);
configManager.updatePrettierConfig(settings.prettier);
configManager.updateCssConfig(settings.css);
configManager.updateScssConfig(settings.scss);
configManager.updateLessConfig(settings.less);
configManager.updateHTMLConfig(settings.html);
logger_1.Logger.setDebug(settings.svelte?.['language-server']?.debug);
});
connection.onDidOpenTextDocument((evt) => {
const document = docManager.openClientDocument(evt.textDocument);
diagnosticsManager.scheduleUpdate(document);
});
connection.onDidCloseTextDocument((evt) => docManager.closeDocument(evt.textDocument.uri));
connection.onDidChangeTextDocument((evt) => {
diagnosticsManager.cancelStarted(evt.textDocument.uri);
docManager.updateDocument(evt.textDocument, evt.contentChanges);
pluginHost.didUpdateDocument();
});
connection.onHover((evt) => pluginHost.doHover(evt.textDocument, evt.position));
connection.onCompletion((evt, cancellationToken) => pluginHost.getCompletions(evt.textDocument, evt.position, evt.context, cancellationToken));
connection.onDocumentFormatting((evt) => pluginHost.formatDocument(evt.textDocument, evt.options));
connection.onRequest(TagCloseRequest.type, (evt) => pluginHost.doTagComplete(evt.textDocument, evt.position));
connection.onDocumentColor((evt) => pluginHost.getDocumentColors(evt.textDocument));
connection.onColorPresentation((evt) => pluginHost.getColorPresentations(evt.textDocument, evt.range, evt.color));
connection.onDocumentSymbol((evt, cancellationToken) => pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken));
connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position));
connection.onReferences((evt, cancellationToken) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context, cancellationToken));
connection.onCodeAction((evt, cancellationToken) => pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context, cancellationToken));
connection.onExecuteCommand(async (evt) => {
const result = await pluginHost.executeCommand({ uri: evt.arguments?.[0] }, evt.command, evt.arguments);
if (vscode_languageserver_1.WorkspaceEdit.is(result)) {
const edit = { edit: result };
connection?.sendRequest(vscode_languageserver_1.ApplyWorkspaceEditRequest.type.method, edit);
}
else if (result) {
connection?.sendNotification(vscode_languageserver_1.ShowMessageNotification.type.method, {
message: result,
type: vscode_languageserver_1.MessageType.Error
});
}
});
connection.onCodeActionResolve((codeAction, cancellationToken) => {
const data = codeAction.data;
return pluginHost.resolveCodeAction(data, codeAction, cancellationToken);
});
connection.onCompletionResolve((completionItem, cancellationToken) => {
const data = completionItem.data;
if (!data) {
return completionItem;
}
return pluginHost.resolveCompletion(data, completionItem, cancellationToken);
});
connection.onSignatureHelp((evt, cancellationToken) => pluginHost.getSignatureHelp(evt.textDocument, evt.position, evt.context, cancellationToken));
connection.onSelectionRanges((evt) => pluginHost.getSelectionRanges(evt.textDocument, evt.positions));
connection.onImplementation((evt, cancellationToken) => pluginHost.getImplementation(evt.textDocument, evt.position, cancellationToken));
connection.onTypeDefinition((evt) => pluginHost.getTypeDefinition(evt.textDocument, evt.position));
connection.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument));
connection.onCodeLens((evt) => pluginHost.getCodeLens(evt.textDocument));
connection.onCodeLensResolve((codeLens, token) => {
const data = codeLens.data;
if (!data) {
return codeLens;
}
return pluginHost.resolveCodeLens(data, codeLens, token);
});
connection.onDocumentHighlight((evt) => pluginHost.findDocumentHighlight(evt.textDocument, evt.position));
connection.onWorkspaceSymbol((evt, token) => pluginHost.getWorkspaceSymbols(evt.query, token));
const diagnosticsManager = new DiagnosticsManager_1.DiagnosticsManager(connection.sendDiagnostics, docManager, pluginHost.getDiagnostics.bind(pluginHost));
const refreshSemanticTokens = (0, utils_1.debounceThrottle)(() => {
if (configManager?.getClientCapabilities()?.workspace?.semanticTokens?.refreshSupport) {
connection?.sendRequest(vscode_languageserver_1.SemanticTokensRefreshRequest.method);
}
}, 1500);
const refreshInlayHints = (0, utils_1.debounceThrottle)(() => {
if (configManager?.getClientCapabilities()?.workspace?.inlayHint?.refreshSupport) {
connection?.sendRequest(vscode_languageserver_1.InlayHintRefreshRequest.method);
}
}, 1500);
const refreshCrossFilesSemanticFeatures = () => {
diagnosticsManager.scheduleUpdateAll();
refreshInlayHints();
refreshSemanticTokens();
};
connection.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
function onDidChangeWatchedFiles(para) {
const onWatchFileChangesParas = para.changes
.map((change) => ({
fileName: (0, utils_1.urlToPath)(change.uri),
changeType: change.type
}))
.filter((change) => !!change.fileName);
pluginHost.onWatchFileChanges(onWatchFileChangesParas);
refreshCrossFilesSemanticFeatures();
}
connection.onDidSaveTextDocument(diagnosticsManager.scheduleUpdateAll.bind(diagnosticsManager));
connection.onNotification('$/onDidChangeTsOrJsFile', async (e) => {
const path = (0, utils_1.urlToPath)(e.uri);
if (path) {
pluginHost.updateTsOrJsFile(path, e.changes);
}
refreshCrossFilesSemanticFeatures();
});
connection.onRequest(vscode_languageserver_1.SemanticTokensRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, undefined, cancellationToken));
connection.onRequest(vscode_languageserver_1.SemanticTokensRangeRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, evt.range, cancellationToken));
connection.onRequest(vscode_languageserver_1.LinkedEditingRangeRequest.type, async (evt) => await pluginHost.getLinkedEditingRanges(evt.textDocument, evt.position));
connection.onRequest(vscode_languageserver_1.InlayHintRequest.type, (evt, cancellationToken) => pluginHost.getInlayHints(evt.textDocument, evt.range, cancellationToken));
connection.onRequest(vscode_languageserver_1.CallHierarchyPrepareRequest.type, async (evt, token) => await pluginHost.prepareCallHierarchy(evt.textDocument, evt.position, token));
connection.onRequest(vscode_languageserver_1.CallHierarchyIncomingCallsRequest.type, async (evt, token) => await pluginHost.getIncomingCalls(evt.item, token));
connection.onRequest(vscode_languageserver_1.CallHierarchyOutgoingCallsRequest.type, async (evt, token) => await pluginHost.getOutgoingCalls(evt.item, token));
docManager.on('documentChange', diagnosticsManager.scheduleUpdate.bind(diagnosticsManager));
docManager.on('documentClose', (document) => diagnosticsManager.removeDiagnostics(document));
// The language server protocol does not have a specific "did rename/move files" event,
// so we create our own in the extension client and handle it here
connection.onRequest('$/getEditsForFileRename', async (fileRename) => pluginHost.updateImports(fileRename));
connection.onRequest('$/getFileReferences', async (uri) => {
return pluginHost.fileReferences(uri);
});
connection.onRequest('$/getComponentReferences', async (uri) => {
return pluginHost.findComponentReferences(uri);
});
connection.onRequest('$/getCompiledCode', async (uri) => {
const doc = docManager.get(uri);
if (!doc) {
return null;
}
const compiled = await sveltePlugin.getCompiledResult(doc);
if (compiled) {
const js = compiled.js;
const css = compiled.css;
return { js, css };
}
else {
return null;
}
});
connection.listen();
}
//# sourceMappingURL=server.js.map