UNPKG

svelte-language-server

Version:
386 lines 16.9 kB
"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