Adopt unified js/ts setting for preferences

For #292934
This commit is contained in:
Matt Bierner
2026-02-13 15:33:01 -08:00
parent 552db6b5ba
commit 5ded895db4
7 changed files with 330 additions and 57 deletions

View File

@@ -5,6 +5,7 @@
import * as vscode from 'vscode';
import * as Proto from '../tsServer/protocol/protocol';
import { readUnifiedConfig } from '../utils/configuration';
import * as objects from '../utils/objects';
export enum TsServerLogLevel {
@@ -268,8 +269,8 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu
return { ...(watchOptions ?? {}) };
}
protected readIncludePackageJsonAutoImports(configuration: vscode.WorkspaceConfiguration): 'auto' | 'on' | 'off' | undefined {
return configuration.get<'auto' | 'on' | 'off'>('typescript.preferences.includePackageJsonAutoImports');
protected readIncludePackageJsonAutoImports(_configuration: vscode.WorkspaceConfiguration): 'auto' | 'on' | 'off' | undefined {
return readUnifiedConfig<'auto' | 'on' | 'off' | undefined>('preferences.includePackageJsonAutoImports', undefined, { fallbackSection: 'typescript' });
}
protected readMaxTsServerMemory(configuration: vscode.WorkspaceConfiguration): number {

View File

@@ -178,20 +178,16 @@ export default class FileConfigurationManager extends Disposable {
isTypeScriptDocument(document) ? 'typescript' : 'javascript',
document);
const preferencesConfig = vscode.workspace.getConfiguration(
isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences',
document);
const fallbackSection = isTypeScriptDocument(document) ? 'typescript' : 'javascript';
const preferences: Proto.UserPreferences = {
...config.get('unstable'),
quotePreference: this.getQuoteStylePreference(preferencesConfig),
importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig),
importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig),
jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(preferencesConfig),
quotePreference: getQuoteStylePreference(document, fallbackSection),
importModuleSpecifierPreference: getImportModuleSpecifierPreference(document, fallbackSection),
importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(document, fallbackSection),
jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(document, fallbackSection),
allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file,
providePrefixAndSuffixTextForRename: preferencesConfig.get<boolean>('useAliasesForRenames', true),
providePrefixAndSuffixTextForRename: readUnifiedConfig<boolean>('preferences.useAliasesForRenames', true, { scope: document, fallbackSection }),
allowRenameOfImportPath: true,
includeAutomaticOptionalChainCompletions: readUnifiedConfig<boolean>('suggest.includeAutomaticOptionalChainCompletions', true, { scope: document, fallbackSection }),
provideRefactorNotApplicableReason: true,
@@ -200,9 +196,9 @@ export default class FileConfigurationManager extends Disposable {
includeCompletionsWithSnippetText: true,
includeCompletionsWithClassMemberSnippets: readUnifiedConfig<boolean>('suggest.classMemberSnippets.enabled', true, { scope: document, fallbackSection }),
includeCompletionsWithObjectLiteralMethodSnippets: readUnifiedConfig<boolean>('suggest.objectLiteralMethodSnippets.enabled', true, { scope: document, fallbackSection }),
autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri),
autoImportSpecifierExcludeRegexes: preferencesConfig.get<string[]>('autoImportSpecifierExcludeRegexes'),
preferTypeOnlyAutoImports: preferencesConfig.get<boolean>('preferTypeOnlyAutoImports', false),
autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(document, fallbackSection, vscode.workspace.getWorkspaceFolder(document.uri)?.uri),
autoImportSpecifierExcludeRegexes: readUnifiedConfig<string[] | undefined>('preferences.autoImportSpecifierExcludeRegexes', undefined, { scope: document, fallbackSection }),
preferTypeOnlyAutoImports: readUnifiedConfig<boolean>('preferences.preferTypeOnlyAutoImports', false, { scope: document, fallbackSection }),
useLabelDetailsInCompletionEntries: true,
allowIncompleteCompletions: true,
displayPartsForJSDoc: true,
@@ -210,23 +206,16 @@ export default class FileConfigurationManager extends Disposable {
interactiveInlayHints: true,
includeCompletionsForModuleExports: readUnifiedConfig<boolean>('suggest.autoImports', true, { scope: document, fallbackSection }),
...getInlayHintsPreferences(document, fallbackSection),
...this.getOrganizeImportsPreferences(preferencesConfig),
...getOrganizeImportsPreferences(document, fallbackSection),
maximumHoverLength: this.getMaximumHoverLength(document),
};
return preferences;
}
private getQuoteStylePreference(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('quoteStyle')) {
case 'single': return 'single';
case 'double': return 'double';
default: return 'auto';
}
}
private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined {
return workspaceFolder && config.get<string[]>('autoImportFileExcludePatterns')?.map(p => {
private getAutoImportFileExcludePatternsPreference(scope: vscode.ConfigurationScope, fallbackSection: string, workspaceFolder: vscode.Uri | undefined): string[] | undefined {
const patterns = readUnifiedConfig<string[] | undefined>('preferences.autoImportFileExcludePatterns', undefined, { scope, fallbackSection });
return workspaceFolder && patterns?.map(p => {
// Normalization rules: https://github.com/microsoft/TypeScript/pull/49578
const isRelative = /^\.\.?($|[\/\\])/.test(p);
// In TypeScript < 5.3, the first path component cannot be a wildcard, so we need to prefix
@@ -241,27 +230,6 @@ export default class FileConfigurationManager extends Disposable {
});
}
private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences {
const organizeImportsCollation = config.get<'ordinal' | 'unicode'>('organizeImports.unicodeCollation');
const organizeImportsCaseSensitivity = config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity');
return {
// More specific settings
organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'),
organizeImportsIgnoreCase: organizeImportsCaseSensitivity === 'caseInsensitive' ? true
: organizeImportsCaseSensitivity === 'caseSensitive' ? false
: 'auto',
organizeImportsCollation,
// The rest of the settings are only applicable when using unicode collation
...(organizeImportsCollation === 'unicode' ? {
organizeImportsCaseFirst: organizeImportsCaseSensitivity === 'caseInsensitive' ? undefined : withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower' | false>('organizeImports.caseFirst', false), 'default'),
organizeImportsAccentCollation: config.get<boolean>('organizeImports.accentCollation'),
organizeImportsLocale: config.get<string>('organizeImports.locale'),
organizeImportsNumericCollation: config.get<boolean>('organizeImports.numericCollation'),
} : {}),
};
}
private getMaximumHoverLength(document: vscode.TextDocument): number {
const defaultMaxLength = 500;
@@ -310,8 +278,16 @@ function getInlayParameterNameHintsPreference(scope: vscode.ConfigurationScope,
}
}
function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('importModuleSpecifier')) {
function getQuoteStylePreference(scope: vscode.ConfigurationScope, fallbackSection: string) {
switch (readUnifiedConfig<string>('preferences.quoteStyle', 'auto', { scope, fallbackSection })) {
case 'single': return 'single';
case 'double': return 'double';
default: return 'auto';
}
}
function getImportModuleSpecifierPreference(scope: vscode.ConfigurationScope, fallbackSection: string) {
switch (readUnifiedConfig<string>('preferences.importModuleSpecifier', 'shortest', { scope, fallbackSection })) {
case 'project-relative': return 'project-relative';
case 'relative': return 'relative';
case 'non-relative': return 'non-relative';
@@ -319,8 +295,8 @@ function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguratio
}
}
function getImportModuleSpecifierEndingPreference(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('importModuleSpecifierEnding')) {
function getImportModuleSpecifierEndingPreference(scope: vscode.ConfigurationScope, fallbackSection: string) {
switch (readUnifiedConfig<string>('preferences.importModuleSpecifierEnding', 'auto', { scope, fallbackSection })) {
case 'minimal': return 'minimal';
case 'index': return 'index';
case 'js': return 'js';
@@ -328,10 +304,31 @@ function getImportModuleSpecifierEndingPreference(config: vscode.WorkspaceConfig
}
}
function getJsxAttributeCompletionStyle(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('jsxAttributeCompletionStyle')) {
function getJsxAttributeCompletionStyle(scope: vscode.ConfigurationScope, fallbackSection: string) {
switch (readUnifiedConfig<string>('preferences.jsxAttributeCompletionStyle', 'auto', { scope, fallbackSection })) {
case 'braces': return 'braces';
case 'none': return 'none';
default: return 'auto';
}
}
function getOrganizeImportsPreferences(scope: vscode.ConfigurationScope, fallbackSection: string): Proto.UserPreferences {
const organizeImportsCollation = readUnifiedConfig<'ordinal' | 'unicode'>('preferences.organizeImports.unicodeCollation', 'ordinal', { scope, fallbackSection });
const organizeImportsCaseSensitivity = readUnifiedConfig<'auto' | 'caseInsensitive' | 'caseSensitive'>('preferences.organizeImports.caseSensitivity', 'auto', { scope, fallbackSection });
return {
// More specific settings
organizeImportsTypeOrder: withDefaultAsUndefined(readUnifiedConfig<'auto' | 'last' | 'inline' | 'first'>('preferences.organizeImports.typeOrder', 'auto', { scope, fallbackSection }), 'auto'),
organizeImportsIgnoreCase: organizeImportsCaseSensitivity === 'caseInsensitive' ? true
: organizeImportsCaseSensitivity === 'caseSensitive' ? false
: 'auto',
organizeImportsCollation,
// The rest of the settings are only applicable when using unicode collation
...(organizeImportsCollation === 'unicode' ? {
organizeImportsCaseFirst: organizeImportsCaseSensitivity === 'caseInsensitive' ? undefined : withDefaultAsUndefined(readUnifiedConfig<'default' | 'upper' | 'lower' | false>('preferences.organizeImports.caseFirst', false, { scope, fallbackSection }), 'default'),
organizeImportsAccentCollation: readUnifiedConfig<boolean | undefined>('preferences.organizeImports.accentCollation', undefined, { scope, fallbackSection }),
organizeImportsLocale: readUnifiedConfig<string | undefined>('preferences.organizeImports.locale', undefined, { scope, fallbackSection }),
organizeImportsNumericCollation: readUnifiedConfig<boolean | undefined>('preferences.organizeImports.numericCollation', undefined, { scope, fallbackSection }),
} : {}),
};
}

View File

@@ -11,6 +11,7 @@ import { API } from '../tsServer/api';
import type * as Proto from '../tsServer/protocol/protocol';
import * as typeConverters from '../typeConverters';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import { readUnifiedConfig } from '../utils/configuration';
import FileConfigurationManager from './fileConfigurationManager';
import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
import { LanguageDescription } from '../configuration/languageDescription';
@@ -112,7 +113,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
// Prefer renaming matching jsx tag when available
if (this.client.apiVersion.gte(API.v510) &&
vscode.workspace.getConfiguration(this.language.id).get('preferences.renameMatchingJsxTags', true) &&
readUnifiedConfig<boolean>('preferences.renameMatchingJsxTags', true, { scope: document, fallbackSection: this.language.id }) &&
this.looksLikePotentialJsxTagContext(document, position)
) {
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);

View File

@@ -20,8 +20,7 @@ suite.skip('TypeScript Completions', () => {
[Config.insertMode]: 'insert',
[Config.snippetSuggestions]: 'none',
[Config.suggestSelection]: 'first',
[Config.javascriptQuoteStyle]: 'double',
[Config.typescriptQuoteStyle]: 'double',
[Config.quoteStyle]: 'double',
});
const _disposables: vscode.Disposable[] = [];

View File

@@ -123,8 +123,7 @@ export const Config = Object.freeze({
insertMode: 'editor.suggest.insertMode',
snippetSuggestions: 'editor.snippetSuggestions',
suggestSelection: 'editor.suggestSelection',
javascriptQuoteStyle: 'javascript.preferences.quoteStyle',
typescriptQuoteStyle: 'typescript.preferences.quoteStyle',
quoteStyle: 'js/ts.preferences.quoteStyle',
} as const);
export const insertModesValues = Object.freeze(['insert', 'replace']);