From 0493189e1898523d115cd62f8e0362c2b61667dc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:14:49 -0800 Subject: [PATCH] Start adopting unified js/ts config for code lenses For #292934 Testing this with a self contained area first: code lenses. Will keep support for the old setting values too to avoid breaking existing settings --- .../typescript-language-features/package.json | 121 +++++++++++++----- .../package.nls.json | 17 ++- .../codeLens/implementationsCodeLens.ts | 36 ++++-- .../codeLens/referencesCodeLens.ts | 27 +++- .../util/dependentRegistration.ts | 16 +++ .../src/utils/configuration.ts | 74 +++++++++++ 6 files changed, 240 insertions(+), 51 deletions(-) create mode 100644 extensions/typescript-language-features/src/utils/configuration.ts diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 667dc4e9ea7..4cd0263a1a0 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -173,36 +173,6 @@ "description": "%typescript.enablePromptUseWorkspaceTsdk%", "scope": "window" }, - "javascript.referencesCodeLens.enabled": { - "type": "boolean", - "default": false, - "description": "%javascript.referencesCodeLens.enabled%", - "scope": "window" - }, - "javascript.referencesCodeLens.showOnAllFunctions": { - "type": "boolean", - "default": false, - "description": "%javascript.referencesCodeLens.showOnAllFunctions%", - "scope": "window" - }, - "typescript.referencesCodeLens.enabled": { - "type": "boolean", - "default": false, - "description": "%typescript.referencesCodeLens.enabled%", - "scope": "window" - }, - "typescript.referencesCodeLens.showOnAllFunctions": { - "type": "boolean", - "default": false, - "description": "%typescript.referencesCodeLens.showOnAllFunctions%", - "scope": "window" - }, - "typescript.implementationsCodeLens.enabled": { - "type": "boolean", - "default": false, - "description": "%typescript.implementationsCodeLens.enabled%", - "scope": "window" - }, "typescript.experimental.useTsgo": { "type": "boolean", "default": false, @@ -212,16 +182,103 @@ "experimental" ] }, + "js/ts.referencesCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.enabled%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, + "javascript.referencesCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.enabled%", + "markdownDeprecationMessage": "%configuration.referencesCodeLens.enabled.unifiedDeprecationMessage%", + "scope": "window" + }, + "typescript.referencesCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.enabled%", + "markdownDeprecationMessage": "%configuration.referencesCodeLens.enabled.unifiedDeprecationMessage%", + "scope": "window" + }, + "js/ts.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.showOnAllFunctions%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, + "javascript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.showOnAllFunctions%", + "markdownDeprecationMessage": "%configuration.referencesCodeLens.showOnAllFunctions.unifiedDeprecationMessage%", + "scope": "window" + }, + "typescript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "%configuration.referencesCodeLens.showOnAllFunctions%", + "markdownDeprecationMessage": "%configuration.referencesCodeLens.showOnAllFunctions.unifiedDeprecationMessage%", + "scope": "window" + }, + "js/ts.implementationsCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.implementationsCodeLens.enabled%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, + "typescript.implementationsCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.implementationsCodeLens.enabled%", + "markdownDeprecationMessage": "%configuration.implementationsCodeLens.enabled.unifiedDeprecationMessage%", + "scope": "window" + }, + "js/ts.implementationsCodeLens.showOnInterfaceMethods": { + "type": "boolean", + "default": false, + "description": "%configuration.implementationsCodeLens.showOnInterfaceMethods%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "typescript.implementationsCodeLens.showOnInterfaceMethods": { "type": "boolean", "default": false, - "description": "%typescript.implementationsCodeLens.showOnInterfaceMethods%", + "description": "%configuration.implementationsCodeLens.showOnInterfaceMethods%", + "markdownDeprecationMessage": "%configuration.implementationsCodeLens.showOnInterfaceMethods.unifiedDeprecationMessage%", "scope": "window" }, + "js/ts.implementationsCodeLens.showOnAllClassMethods": { + "type": "boolean", + "default": false, + "description": "%configuration.implementationsCodeLens.showOnAllClassMethods%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "typescript.implementationsCodeLens.showOnAllClassMethods": { "type": "boolean", "default": false, - "description": "%typescript.implementationsCodeLens.showOnAllClassMethods%", + "description": "%configuration.implementationsCodeLens.showOnAllClassMethods%", + "markdownDeprecationMessage": "%configuration.implementationsCodeLens.showOnAllClassMethods.unifiedDeprecationMessage%", "scope": "window" }, "typescript.reportStyleChecksAsWarnings": { diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 43f62e918f3..ed640ef85f0 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -49,13 +49,16 @@ "javascript.validate.enable": "Enable/disable JavaScript validation.", "javascript.goToProjectConfig.title": "Go to Project Configuration (jsconfig / tsconfig)", "typescript.goToProjectConfig.title": "Go to Project Configuration (tsconfig)", - "javascript.referencesCodeLens.enabled": "Enable/disable references CodeLens in JavaScript files.", - "javascript.referencesCodeLens.showOnAllFunctions": "Enable/disable references CodeLens on all functions in JavaScript files.", - "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens in TypeScript files.", - "typescript.referencesCodeLens.showOnAllFunctions": "Enable/disable references CodeLens on all functions in TypeScript files.", - "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. This CodeLens shows the implementers of an interface.", - "typescript.implementationsCodeLens.showOnInterfaceMethods": "Enable/disable implementations CodeLens on interface methods.", - "typescript.implementationsCodeLens.showOnAllClassMethods": "Enable/disable showing implementations CodeLens above all class methods instead of only on abstract methods.", + "configuration.referencesCodeLens.enabled": "Enable/disable references CodeLens in JavaScript and TypeScript files. This CodeLens shows the number of references for classes and exported functions and allows you to peek or navigate to them.", + "configuration.referencesCodeLens.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.referencesCodeLens.enabled#` instead.", + "configuration.referencesCodeLens.showOnAllFunctions": "Enable/disable the references CodeLens on all functions in JavaScript and TypeScript files.", + "configuration.referencesCodeLens.showOnAllFunctions.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.referencesCodeLens.showOnAllFunctions#` instead.", + "configuration.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens in TypeScript files. This CodeLens shows the implementers of a TypeScript interface.", + "configuration.implementationsCodeLens.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.implementationsCodeLens.enabled#` instead.", + "configuration.implementationsCodeLens.showOnInterfaceMethods": "Enable/disable implementations CodeLens on TypeScript interface methods.", + "configuration.implementationsCodeLens.showOnInterfaceMethods.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.implementationsCodeLens.showOnInterfaceMethods#` instead.", + "configuration.implementationsCodeLens.showOnAllClassMethods": "Enable/disable showing implementations CodeLens above all TypeScript class methods instead of only on abstract methods.", + "configuration.implementationsCodeLens.showOnAllClassMethods.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.implementationsCodeLens.showOnAllClassMethods#` instead.", "typescript.openTsServerLog.title": "Open TS Server log", "typescript.restartTsServer": "Restart TS Server", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version...", diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index d012a6ab9d9..d32cfa129f8 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -11,10 +11,16 @@ import type * as Proto from '../../tsServer/protocol/protocol'; import * as PConst from '../../tsServer/protocol/protocol.const'; import * as typeConverters from '../../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService'; -import { conditionalRegistration, requireGlobalConfiguration, requireSomeCapability } from '../util/dependentRegistration'; +import { readUnifiedConfig, unifiedConfigSection } from '../../utils/configuration'; +import { conditionalRegistration, requireHasModifiedUnifiedConfig, requireSomeCapability } from '../util/dependentRegistration'; import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider'; import { ExecutionTarget } from '../../tsServer/server'; +const Config = Object.freeze({ + enabled: 'implementationsCodeLens.enabled', + showOnInterfaceMethods: 'implementationsCodeLens.showOnInterfaceMethods', + showOnAllClassMethods: 'implementationsCodeLens.showOnAllClassMethods', +}); export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { public constructor( @@ -25,14 +31,30 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip super(client, _cachedResponse); this._register( vscode.workspace.onDidChangeConfiguration(evt => { - if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`) || - evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnAllClassMethods`)) { + if ( + evt.affectsConfiguration(`${unifiedConfigSection}.${Config.enabled}`) || + evt.affectsConfiguration(`${language.id}.${Config.enabled}`) || + evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnInterfaceMethods}`) || + evt.affectsConfiguration(`${language.id}.${Config.showOnInterfaceMethods}`) || + evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnAllClassMethods}`) || + evt.affectsConfiguration(`${language.id}.${Config.showOnAllClassMethods}`) + ) { this.changeEmitter.fire(); } }) ); } + + override async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + const enabled = readUnifiedConfig(Config.enabled, false, { scope: document, fallbackSection: this.language.id }); + if (!enabled) { + return []; + } + + return super.provideCodeLenses(document, token); + } + public async resolveCodeLens( codeLens: ReferencesCodeLens, token: vscode.CancellationToken, @@ -88,8 +110,6 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined ): vscode.Range | undefined { - const cfg = vscode.workspace.getConfiguration(this.language.id); - // Always show on interfaces if (item.kind === PConst.Kind.interface) { return getSymbolRange(document, item); @@ -111,7 +131,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip if ( item.kind === PConst.Kind.method && parent?.kind === PConst.Kind.interface && - cfg.get('implementationsCodeLens.showOnInterfaceMethods', false) + readUnifiedConfig('implementationsCodeLens.showOnInterfaceMethods', false, { scope: document, fallbackSection: this.language.id }) ) { return getSymbolRange(document, item); } @@ -121,7 +141,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip if ( item.kind === PConst.Kind.method && parent?.kind === PConst.Kind.class && - cfg.get('implementationsCodeLens.showOnAllClassMethods', false) + readUnifiedConfig('implementationsCodeLens.showOnAllClassMethods', false, { scope: document, fallbackSection: this.language.id }) ) { // But not private ones as these can never be overridden if (/\bprivate\b/.test(item.kindModifiers ?? '')) { @@ -141,7 +161,7 @@ export function register( cachedResponse: CachedResponse, ) { return conditionalRegistration([ - requireGlobalConfiguration(language.id, 'implementationsCodeLens.enabled'), + requireHasModifiedUnifiedConfig(Config.enabled, language.id), requireSomeCapability(client, ClientCapability.Semantic), ], () => { return vscode.languages.registerCodeLensProvider(selector.semantic, diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts index de7d1f6d900..0942f7f8f3a 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts @@ -12,9 +12,14 @@ import * as PConst from '../../tsServer/protocol/protocol.const'; import { ExecutionTarget } from '../../tsServer/server'; import * as typeConverters from '../../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService'; -import { conditionalRegistration, requireGlobalConfiguration, requireSomeCapability } from '../util/dependentRegistration'; +import { readUnifiedConfig, unifiedConfigSection } from '../../utils/configuration'; +import { conditionalRegistration, requireHasModifiedUnifiedConfig, requireSomeCapability } from '../util/dependentRegistration'; import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider'; +const Config = Object.freeze({ + enabled: 'referencesCodeLens.enabled', + showOnAllFunctions: 'referencesCodeLens.showOnAllFunctions', +}); export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { public constructor( @@ -25,13 +30,27 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens super(client, _cachedResponse); this._register( vscode.workspace.onDidChangeConfiguration(evt => { - if (evt.affectsConfiguration(`${language.id}.referencesCodeLens.showOnAllFunctions`)) { + if ( + evt.affectsConfiguration(`${unifiedConfigSection}.${Config.enabled}`) || + evt.affectsConfiguration(`${language.id}.${Config.enabled}`) || + evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnAllFunctions}`) || + evt.affectsConfiguration(`${language.id}.${Config.showOnAllFunctions}`) + ) { this.changeEmitter.fire(); } }) ); } + override async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + const enabled = readUnifiedConfig(Config.enabled, false, { scope: document, fallbackSection: this.language.id }); + if (!enabled) { + return []; + } + + return super.provideCodeLenses(document, token); + } + public async resolveCodeLens(codeLens: ReferencesCodeLens, token: vscode.CancellationToken): Promise { const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); const response = await this.client.execute('references', args, token, { @@ -76,7 +95,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens switch (item.kind) { case PConst.Kind.function: { - const showOnAllFunctions = vscode.workspace.getConfiguration(this.language.id).get('referencesCodeLens.showOnAllFunctions'); + const showOnAllFunctions = readUnifiedConfig(Config.showOnAllFunctions, false, { scope: document, fallbackSection: this.language.id }); if (showOnAllFunctions && item.nameSpan) { return getSymbolRange(document, item); } @@ -137,7 +156,7 @@ export function register( cachedResponse: CachedResponse, ) { return conditionalRegistration([ - requireGlobalConfiguration(language.id, 'referencesCodeLens.enabled'), + requireHasModifiedUnifiedConfig(Config.enabled, language.id), requireSomeCapability(client, ClientCapability.Semantic), ], () => { return vscode.languages.registerCodeLensProvider(selector.semantic, diff --git a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts index 8371b470997..916bfd8f3ae 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { API } from '../../tsServer/api'; import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService'; +import { hasModifiedUnifiedConfig } from '../../utils/configuration'; import { Disposable } from '../../utils/dispose'; export class Condition extends Disposable { @@ -102,6 +103,21 @@ export function requireGlobalConfiguration( ); } +/** + * Requires that a configuration value has been modified from its default value in either the global or workspace scope + * + * Does not check the value, only that it has been modified from the default. + */ +export function requireHasModifiedUnifiedConfig( + configValue: string, + fallbackSection: string, +) { + return new Condition( + () => hasModifiedUnifiedConfig(configValue, { fallbackSection }), + vscode.workspace.onDidChangeConfiguration + ); +} + export function requireSomeCapability( client: ITypeScriptServiceClient, ...capabilities: readonly ClientCapability[] diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts new file mode 100644 index 00000000000..b10a70fd27a --- /dev/null +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +type ConfigurationScope = vscode.ConfigurationScope | null | undefined; + +export const unifiedConfigSection = 'js/ts'; + +/** + * Gets a configuration value, checking the unified `js/ts` setting first, + * then falling back to the language-specific setting. + */ +export function readUnifiedConfig( + subSectionName: string, + defaultValue: T, + options: { + readonly scope?: ConfigurationScope; + readonly fallbackSection: string; + } +): T { + // Check unified setting first + const unifiedConfig = vscode.workspace.getConfiguration(unifiedConfigSection, options.scope); + const unifiedInspect = unifiedConfig.inspect(subSectionName); + if (hasModifiedValue(unifiedInspect)) { + return unifiedConfig.get(subSectionName, defaultValue); + } + + // Fall back to language-specific setting + const languageConfig = vscode.workspace.getConfiguration(options.fallbackSection, options.scope); + return languageConfig.get(subSectionName, defaultValue); +} + +/** + * Checks if an inspected configuration value has any user-defined values set. + */ +function hasModifiedValue(inspect: ReturnType): boolean { + if (!inspect) { + return false; + } + + return ( + typeof inspect.globalValue !== 'undefined' + || typeof inspect.workspaceValue !== 'undefined' + || typeof inspect.workspaceFolderValue !== 'undefined' + || typeof inspect.globalLanguageValue !== 'undefined' + || typeof inspect.workspaceLanguageValue !== 'undefined' + || typeof inspect.workspaceFolderLanguageValue !== 'undefined' + || ((inspect.languageIds?.length ?? 0) > 0) + ); +} + +/** + * Checks if a unified configuration value has been modified from its default value. + */ +export function hasModifiedUnifiedConfig( + subSectionName: string, + options: { + readonly scope?: ConfigurationScope; + readonly fallbackSection: string; + } +): boolean { + // Check unified setting + const unifiedConfig = vscode.workspace.getConfiguration(unifiedConfigSection, options.scope); + if (hasModifiedValue(unifiedConfig.inspect(subSectionName))) { + return true; + } + + // Check language-specific setting + const languageConfig = vscode.workspace.getConfiguration(options.fallbackSection, options.scope); + return hasModifiedValue(languageConfig.inspect(subSectionName)); +}