svelte-language-server
Version:
A language server for Svelte
386 lines • 16.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LSConfigManager = void 0;
const lodash_1 = require("lodash");
const typescript_1 = __importDefault(require("typescript"));
const importPackage_1 = require("./importPackage");
const utils_1 = require("./utils");
const path_1 = __importDefault(require("path"));
const fileCollection_1 = require("./lib/documents/fileCollection");
/**
* Default config for the language server.
*/
const defaultLSConfig = {
typescript: {
enable: true,
diagnostics: { enable: true },
hover: { enable: true },
completions: { enable: true },
documentSymbols: { enable: true },
codeActions: { enable: true },
selectionRange: { enable: true },
signatureHelp: { enable: true },
semanticTokens: { enable: true },
workspaceSymbols: { enable: true }
},
css: {
enable: true,
globals: '',
diagnostics: { enable: true },
hover: { enable: true },
completions: { enable: true, emmet: true },
documentColors: { enable: true },
colorPresentations: { enable: true },
documentSymbols: { enable: true },
selectionRange: { enable: true }
},
html: {
enable: true,
hover: { enable: true },
completions: { enable: true, emmet: true },
tagComplete: { enable: true },
documentSymbols: { enable: true },
linkedEditing: { enable: true }
},
svelte: {
enable: true,
compilerWarnings: {},
diagnostics: { enable: true },
rename: { enable: true },
format: {
enable: true,
config: {
svelteSortOrder: 'options-scripts-markup-styles',
svelteStrictMode: false,
svelteAllowShorthand: true,
svelteBracketNewLine: true,
svelteIndentScriptAndStyle: true,
printWidth: 80,
singleQuote: false
}
},
completions: { enable: true },
hover: { enable: true },
codeActions: { enable: true },
selectionRange: { enable: true },
runesLegacyModeCodeLens: { enable: true },
defaultScriptLanguage: 'none'
}
};
class LSConfigManager {
constructor() {
this.config = defaultLSConfig;
this.listeners = [];
this.tsUserPreferences = {
// populate default with _updateTsUserPreferences
typescript: {},
javascript: {}
};
this.rawTsUserConfig = {
typescript: {},
javascript: {}
};
this.resolvedAutoImportExcludeCache = new fileCollection_1.FileMap();
this.tsFormatCodeOptions = {
typescript: this.getDefaultFormatCodeOptions(),
javascript: this.getDefaultFormatCodeOptions()
};
this.prettierConfig = {};
this.emmetConfig = {};
this.isTrusted = true;
this._updateTsUserPreferences('javascript', {});
this._updateTsUserPreferences('typescript', {});
}
/**
* Updates config.
*/
update(config) {
// Ideally we shouldn't need the merge here because all updates should be valid and complete configs.
// But since those configs come from the client they might be out of synch with the valid config:
// We might at some point in the future forget to synch config settings in all packages after updating the config.
this.config = (0, lodash_1.merge)({}, defaultLSConfig, this.config, config);
// Merge will keep arrays/objects if the new one is empty/has less entries,
// therefore we need some extra checks if there are new settings
if (config?.svelte?.compilerWarnings) {
this.config.svelte.compilerWarnings = config.svelte.compilerWarnings;
}
this.notifyListeners();
}
/**
* Whether or not specified config is enabled
* @param key a string which is a path. Example: 'svelte.diagnostics.enable'.
*/
enabled(key) {
return !!this.get(key);
}
/**
* Get specific config
* @param key a string which is a path. Example: 'svelte.diagnostics.enable'.
*/
get(key) {
return (0, lodash_1.get)(this.config, key);
}
/**
* Get the whole config
*/
getConfig() {
return this.config;
}
/**
* Register a listener which is invoked when the config changed.
*/
onChange(callback) {
this.listeners.push(callback);
}
updateEmmetConfig(config) {
this.emmetConfig = config || {};
this.notifyListeners();
}
getEmmetConfig() {
return this.emmetConfig;
}
updatePrettierConfig(config) {
this.prettierConfig = config || {};
this.notifyListeners();
}
getPrettierConfig() {
return this.prettierConfig;
}
/**
* Returns a merged Prettier config following these rules:
* - If `prettierFromFileConfig` exists, that one is returned
* - Else the Svelte extension's Prettier config is used as a starting point,
* and overridden by a possible Prettier config from the Prettier extension,
* or, if that doesn't exist, a possible fallback override.
*/
getMergedPrettierConfig(prettierFromFileConfig, overridesWhenNoPrettierConfig = {}) {
return ((0, utils_1.returnObjectIfHasKeys)(prettierFromFileConfig) ||
(0, lodash_1.merge)({}, // merge into empty obj to not manipulate own config
this.get('svelte.format.config'), (0, utils_1.returnObjectIfHasKeys)(this.getPrettierConfig()) ||
overridesWhenNoPrettierConfig ||
{}));
}
updateTsJsUserPreferences(config) {
const shared = config['js/ts'];
['typescript', 'javascript'].forEach((lang) => {
if (config[lang]) {
this._updateTsUserPreferences(lang, config[lang], shared);
this.rawTsUserConfig[lang] = config[lang];
}
});
this.notifyListeners();
this.resolvedAutoImportExcludeCache.clear();
}
/**
* Whether or not the current workspace can be trusted.
* If not, certain operations should be disabled.
*/
getIsTrusted() {
return this.isTrusted;
}
updateIsTrusted(isTrusted) {
this.isTrusted = isTrusted;
this.notifyListeners();
}
_updateTsUserPreferences(lang, config, shared) {
const { inlayHints } = config;
this.tsUserPreferences[lang] = {
...this.tsUserPreferences[lang],
importModuleSpecifierPreference: config.preferences?.importModuleSpecifier,
importModuleSpecifierEnding: config.preferences?.importModuleSpecifierEnding,
includePackageJsonAutoImports: config.preferences?.includePackageJsonAutoImports,
quotePreference: config.preferences?.quoteStyle,
includeCompletionsForModuleExports: config.suggest?.autoImports ?? true,
includeCompletionsForImportStatements: config.suggest?.includeCompletionsForImportStatements ?? true,
includeAutomaticOptionalChainCompletions: config.suggest?.includeAutomaticOptionalChainCompletions ?? true,
includeCompletionsWithInsertText: true,
autoImportFileExcludePatterns: config.preferences?.autoImportFileExcludePatterns,
useLabelDetailsInCompletionEntries: true,
includeCompletionsWithSnippetText: config.suggest?.includeCompletionsWithSnippetText ?? true,
includeCompletionsWithClassMemberSnippets: config.suggest?.classMemberSnippets?.enabled ?? true,
includeCompletionsWithObjectLiteralMethodSnippets: config.suggest?.objectLiteralMethodSnippets?.enabled ?? true,
preferTypeOnlyAutoImports: config.preferences?.preferTypeOnlyAutoImports,
maximumHoverLength: shared?.hover?.maximumLength,
// Although we don't support incompletion cache.
// But this will make ts resolve the module specifier more aggressively
// Which also makes the completion label detail show up in more cases
allowIncompleteCompletions: true,
includeInlayEnumMemberValueHints: inlayHints?.enumMemberValues?.enabled,
includeInlayFunctionLikeReturnTypeHints: inlayHints?.functionLikeReturnTypes?.enabled,
includeInlayParameterNameHints: inlayHints?.parameterNames?.enabled,
includeInlayParameterNameHintsWhenArgumentMatchesName: inlayHints?.parameterNames?.suppressWhenArgumentMatchesName === false,
includeInlayFunctionParameterTypeHints: inlayHints?.parameterTypes?.enabled,
includeInlayVariableTypeHints: inlayHints?.variableTypes?.enabled,
includeInlayPropertyDeclarationTypeHints: inlayHints?.propertyDeclarationTypes?.enabled,
includeInlayVariableTypeHintsWhenTypeMatchesName: inlayHints?.variableTypes?.suppressWhenTypeMatchesName === false,
interactiveInlayHints: true,
autoImportSpecifierExcludeRegexes: config.preferences?.autoImportSpecifierExcludeRegexes,
organizeImportsAccentCollation: config.preferences?.organizeImports?.accentCollation,
organizeImportsCollation: config.preferences?.organizeImports?.collation,
organizeImportsCaseFirst: this.withDefaultAsUndefined(config.preferences?.organizeImports?.caseFirst, 'default'),
organizeImportsIgnoreCase: this.withDefaultAsUndefined(config.preferences?.organizeImports?.caseSensitivity, 'auto'),
organizeImportsLocale: config.preferences?.organizeImports?.locale,
organizeImportsNumericCollation: config.preferences?.organizeImports?.numericCollation,
organizeImportsTypeOrder: this.withDefaultAsUndefined(config.preferences?.organizeImports?.typeOrder, 'auto'),
excludeLibrarySymbolsInNavTo: config.workspaceSymbols?.excludeLibrarySymbols ?? true
};
}
withDefaultAsUndefined(value, def) {
return value === def ? undefined : value;
}
getTsUserPreferences(lang, normalizedWorkspacePath) {
const userPreferences = this.tsUserPreferences[lang];
if (!normalizedWorkspacePath || !userPreferences.autoImportFileExcludePatterns) {
return userPreferences;
}
let autoImportFileExcludePatterns = this.resolvedAutoImportExcludeCache.get(normalizedWorkspacePath);
if (!autoImportFileExcludePatterns) {
const version = typescript_1.default.version.split('.');
const major = parseInt(version[0]);
const minor = parseInt(version[1]);
const gte5_4 = major > 5 || (major === 5 && minor >= 4);
autoImportFileExcludePatterns = userPreferences.autoImportFileExcludePatterns.map((p) => {
// Normalization rules: https://github.com/microsoft/TypeScript/pull/49578
const slashNormalized = p.replace(/\\/g, '/');
const isRelative = /^\.\.?($|\/)/.test(slashNormalized);
if (path_1.default.isAbsolute(p)) {
return p;
}
// https://github.com/microsoft/vscode/pull/202762
// ts 5.4+ supports leading wildcards
const wildcardPrefix = gte5_4 ? '' : path_1.default.parse(normalizedWorkspacePath).root;
return p.startsWith('*')
? wildcardPrefix + slashNormalized
: isRelative
? path_1.default.join(normalizedWorkspacePath, p)
: wildcardPrefix + '**/' + slashNormalized;
});
this.resolvedAutoImportExcludeCache.set(normalizedWorkspacePath, autoImportFileExcludePatterns);
}
return {
...userPreferences,
autoImportFileExcludePatterns
};
}
getClientTsUserConfig(lang) {
return this.rawTsUserConfig[lang];
}
updateCssConfig(config) {
this.cssConfig = config;
this.notifyListeners();
}
getCssConfig() {
return this.cssConfig;
}
updateScssConfig(config) {
this.scssConfig = config;
this.notifyListeners();
}
getScssConfig() {
return this.scssConfig;
}
updateLessConfig(config) {
this.lessConfig = config;
this.notifyListeners();
}
getLessConfig() {
return this.lessConfig;
}
updateHTMLConfig(config) {
this.htmlConfig = config;
this.notifyListeners();
}
getHTMLConfig() {
return this.htmlConfig;
}
updateTsJsFormateConfig(config) {
['typescript', 'javascript'].forEach((lang) => {
if (config[lang]) {
this._updateTsFormatConfig(lang, config[lang]);
}
});
this.notifyListeners();
}
getDefaultFormatCodeOptions() {
// https://github.com/microsoft/TypeScript/blob/394f51aeed80788dca72c6f6a90d1d27886b6972/src/services/types.ts#L1014
return {
indentSize: 4,
tabSize: 4,
convertTabsToSpaces: true,
indentStyle: typescript_1.default.IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
trimTrailingWhitespace: true,
semicolons: typescript_1.default.SemicolonPreference.Ignore,
// Override TypeScript's default because VSCode default to true
// Also this matches the style of prettier
insertSpaceAfterFunctionKeywordForAnonymousFunctions: true
};
}
_updateTsFormatConfig(lang, config) {
this.tsFormatCodeOptions[lang] = {
...this.tsFormatCodeOptions[lang],
...(config.format ?? {})
};
}
async getFormatCodeSettingsForFile(document, scriptKind) {
const filePath = document.getFilePath();
const configLang = scriptKind === typescript_1.default.ScriptKind.TS || scriptKind === typescript_1.default.ScriptKind.TSX
? 'typescript'
: 'javascript';
const tsFormatCodeOptions = this.tsFormatCodeOptions[configLang];
if (!filePath) {
return tsFormatCodeOptions;
}
const prettierConfig = this.getMergedPrettierConfig(await (0, importPackage_1.importPrettier)(filePath).resolveConfig(filePath, {
editorconfig: true
}));
const useSemicolons = prettierConfig.semi ?? true;
const documentUseLf = document.getText().includes('\n') && !document.getText().includes('\r\n');
const indentSize = (typeof prettierConfig.tabWidth === 'number' ? prettierConfig.tabWidth : null) ??
tsFormatCodeOptions.tabSize;
return {
...tsFormatCodeOptions,
newLineCharacter: documentUseLf ? '\n' : typescript_1.default.sys.newLine,
baseIndentSize: prettierConfig.svelteIndentScriptAndStyle === false ? 0 : indentSize,
indentSize,
convertTabsToSpaces: !prettierConfig.useTabs,
semicolons: useSemicolons
? typescript_1.default.SemicolonPreference.Insert
: typescript_1.default.SemicolonPreference.Remove,
tabSize: indentSize
};
}
notifyListeners() {
if (this.scheduledUpdate) {
clearTimeout(this.scheduledUpdate);
}
this.scheduledUpdate = setTimeout(() => {
this.scheduledUpdate = undefined;
this.listeners.forEach((listener) => listener(this));
});
}
updateClientCapabilities(clientCapabilities) {
this.clientCapabilities = clientCapabilities;
this.notifyListeners();
}
getClientCapabilities() {
return this.clientCapabilities;
}
}
exports.LSConfigManager = LSConfigManager;
//# sourceMappingURL=ls-config.js.map