From 8d8d508c29e67fcd5dfb40200206cf4f24aacf23 Mon Sep 17 00:00:00 2001
From: Sysix <3897725+Sysix@users.noreply.github.com>
Date: Wed, 29 Oct 2025 09:36:32 +0000
Subject: [PATCH] refactor(editor): flatten `flags` options (#15006)
Now the configuration `oxc.flags` is deprecated on the editor side too.
It still sends the `flags` configuration to the language server, because it does not know which version is used. (defined with `oxc.path.server`).
Updated all test + included a deprecated flag test.
---
editors/vscode/README.md | 15 +++-
editors/vscode/client/WorkspaceConfig.ts | 82 +++++++++++++++++---
editors/vscode/package.json | 29 +++++++
editors/vscode/tests/WorkspaceConfig.spec.ts | 76 +++++++++---------
editors/vscode/tests/code_actions.spec.ts | 7 +-
editors/vscode/tests/e2e_server.spec.ts | 8 +-
6 files changed, 157 insertions(+), 60 deletions(-)
diff --git a/editors/vscode/README.md b/editors/vscode/README.md
index ac4e80db5e454..e4274ad729894 100644
--- a/editors/vscode/README.md
+++ b/editors/vscode/README.md
@@ -64,11 +64,22 @@ Following configuration are supported via `settings.json` and can be changed for
| `oxc.tsConfigPath` | `null` | `null` \| `` | Path to TypeScript configuration. If your `tsconfig.json` is not at the root, alias paths will not be resolve correctly for the `import` plugin. |
| `oxc.unusedDisableDirectives` | `allow` | `allow` \| `warn` \| `deny` | Define how directive comments like `// oxlint-disable-line` should be reported, when no errors would have been reported on that line anyway. |
| `oxc.typeAware` | `false` | `false` \| `true` | Enable type aware linting. |
-| `oxc.flags` | - | `Record` | Custom flags passed to the language server. |
+| `oxc.disableNestedConfig` | `false` | `false` \| `true` | Disable searching for nested configuration files. |
+| `oxc.fixKind` | `safe_fix` | [FixKind](#fixkind) | Specify the kind of fixes to suggest/apply. |
| `oxc.fmt.experimental` | `false` | `false` \| `true` | Enable experimental formatting support. This feature is experimental and might not work as expected. |
| `oxc.fmt.configPath` | `null` | `` \| `null` | Path to an oxfmt configuration file. When `null`, the server will use `.oxfmtrc.json` at the workspace root. |
+| `oxc.flags` | - | `Record` | (deprecated) Custom flags passed to the language server. |
-#### Flags
+#### FixKind
+
+- `"safe_fix"` (default)
+- `"safe_fix_or_suggestion"`
+- `"dangerous_fix"`
+- `"dangerous_fix_or_suggestion"`
+- `"none"`
+- `"all"`
+
+#### Flags (deprecated)
- `key: disable_nested_config`: Disabled nested configuration and searches only for `configPath`
- `key: fix_kind`: default: `"safe_fix"`, possible values `"safe_fix" | "safe_fix_or_suggestion" | "dangerous_fix" | "dangerous_fix_or_suggestion" | "none" | "all"`
diff --git a/editors/vscode/client/WorkspaceConfig.ts b/editors/vscode/client/WorkspaceConfig.ts
index ae53842396876..c2e6bf4310c29 100644
--- a/editors/vscode/client/WorkspaceConfig.ts
+++ b/editors/vscode/client/WorkspaceConfig.ts
@@ -7,6 +7,15 @@ export type Trigger = 'onSave' | 'onType';
type UnusedDisableDirectives = 'allow' | 'warn' | 'deny';
+export enum FixKind {
+ SafeFix = 'safe_fix',
+ SafeFixOrSuggestion = 'safe_fix_or_suggestion',
+ DangerousFix = 'dangerous_fix',
+ DangerousFixOrSuggestion = 'dangerous_fix_or_suggestion',
+ None = 'none',
+ All = 'all',
+}
+
/**
* See `"contributes.configuration"` in `package.json`
*/
@@ -54,6 +63,20 @@ export interface WorkspaceConfigInterface {
*/
typeAware: boolean;
+ /**
+ * Disable nested config files detection
+ * `oxc.disableNestedConfig`
+ * @default false
+ */
+ disableNestedConfig: boolean;
+
+ /**
+ * Fix kind to use when applying fixes
+ * `oxc.fixKind`
+ * @default 'safe_fix'
+ */
+ fixKind: FixKind;
+
/**
* Additional flags to pass to the LSP binary
* `oxc.flags`
@@ -83,7 +106,8 @@ export class WorkspaceConfig {
private _runTrigger: Trigger = 'onType';
private _unusedDisableDirectives: UnusedDisableDirectives = 'allow';
private _typeAware: boolean = false;
- private _flags: Record = {};
+ private _disableNestedConfig: boolean = false;
+ private _fixKind: FixKind = FixKind.SafeFix;
private _formattingExperimental: boolean = false;
private _formattingConfigPath: string | null = null;
@@ -97,16 +121,28 @@ export class WorkspaceConfig {
public refresh(): void {
const flags = this.configuration.get>('flags') ?? {};
- const useNestedConfigs = !('disable_nested_config' in flags);
+
+ // `configuration.get` takes the default value from the package.json, which is always `safe_fix`.
+ // We need to check the deprecated flags.fix_kind for the real default value.
+ let fixKind = this.configuration.get('fixKind');
+ if (fixKind === FixKind.SafeFix && flags.fix_kind !== undefined && flags.fix_kind !== 'safe_fix') {
+ fixKind = flags.fix_kind as FixKind;
+ }
+
+ // the same for disabledNestedConfig
+ let disableNestedConfig = this.configuration.get('disableNestedConfig');
+ if (disableNestedConfig === false && flags.disable_nested_config === 'true') {
+ disableNestedConfig = true;
+ }
this._runTrigger = this.configuration.get('lint.run') || 'onType';
- this._configPath =
- this.configuration.get('configPath') || (useNestedConfigs ? null : oxlintConfigFileName);
+ this._configPath = this.configuration.get('configPath') ?? null;
this._tsConfigPath = this.configuration.get('tsConfigPath') ?? null;
this._unusedDisableDirectives =
this.configuration.get('unusedDisableDirectives') ?? 'allow';
this._typeAware = this.configuration.get('typeAware') ?? false;
- this._flags = flags;
+ this._disableNestedConfig = disableNestedConfig ?? false;
+ this._fixKind = fixKind ?? FixKind.SafeFix;
this._formattingExperimental = this.configuration.get('fmt.experimental') ?? false;
this._formattingConfigPath = this.configuration.get('fmt.configPath') ?? null;
}
@@ -127,7 +163,10 @@ export class WorkspaceConfig {
if (event.affectsConfiguration(`${ConfigService.namespace}.typeAware`, this.workspace)) {
return true;
}
- if (event.affectsConfiguration(`${ConfigService.namespace}.flags`, this.workspace)) {
+ if (event.affectsConfiguration(`${ConfigService.namespace}.disableNestedConfig`, this.workspace)) {
+ return true;
+ }
+ if (event.affectsConfiguration(`${ConfigService.namespace}.fixKind`, this.workspace)) {
return true;
}
if (event.affectsConfiguration(`${ConfigService.namespace}.fmt.experimental`, this.workspace)) {
@@ -136,6 +175,10 @@ export class WorkspaceConfig {
if (event.affectsConfiguration(`${ConfigService.namespace}.fmt.configPath`, this.workspace)) {
return true;
}
+ // deprecated settings in flags
+ if (event.affectsConfiguration(`${ConfigService.namespace}.flags`, this.workspace)) {
+ return true;
+ }
return false;
}
@@ -188,13 +231,22 @@ export class WorkspaceConfig {
return this.configuration.update('typeAware', value, ConfigurationTarget.WorkspaceFolder);
}
- get flags(): Record {
- return this._flags;
+ get disableNestedConfig(): boolean {
+ return this._disableNestedConfig;
+ }
+
+ updateDisableNestedConfig(value: boolean): PromiseLike {
+ this._disableNestedConfig = value;
+ return this.configuration.update('disableNestedConfig', value, ConfigurationTarget.WorkspaceFolder);
+ }
+
+ get fixKind(): FixKind {
+ return this._fixKind;
}
- updateFlags(value: Record): PromiseLike {
- this._flags = value;
- return this.configuration.update('flags', value, ConfigurationTarget.WorkspaceFolder);
+ updateFixKind(value: FixKind): PromiseLike {
+ this._fixKind = value;
+ return this.configuration.update('fixKind', value, ConfigurationTarget.WorkspaceFolder);
}
get formattingExperimental(): boolean {
@@ -222,9 +274,15 @@ export class WorkspaceConfig {
tsConfigPath: this.tsConfigPath ?? null,
unusedDisableDirectives: this.unusedDisableDirectives,
typeAware: this.typeAware,
- flags: this.flags,
+ disableNestedConfig: this.disableNestedConfig,
+ fixKind: this.fixKind,
['fmt.experimental']: this.formattingExperimental,
['fmt.configPath']: this.formattingConfigPath ?? null,
+ // deprecated, kept for backward compatibility
+ flags: {
+ disable_nested_config: this.disableNestedConfig ? 'true' : 'false',
+ ...(this.fixKind ? { fix_kind: this.fixKind } : {}),
+ },
};
}
}
diff --git a/editors/vscode/package.json b/editors/vscode/package.json
index 83d11118c24bd..4499408f13fe5 100644
--- a/editors/vscode/package.json
+++ b/editors/vscode/package.json
@@ -147,10 +147,39 @@
"default": false,
"description": "Enable type-aware linting."
},
+ "oxc.disableNestedConfig": {
+ "type": "boolean",
+ "scope": "resource",
+ "default": false,
+ "description": "Disable searching for nested configuration files. When set to true, only the configuration file specified in `oxc.configPath` (if any) will be used."
+ },
+ "oxc.fixKind": {
+ "type": "string",
+ "scope": "resource",
+ "enum": [
+ "safe_fix",
+ "safe_fix_or_suggestion",
+ "dangerous_fix",
+ "dangerous_fix_or_suggestion",
+ "none",
+ "all"
+ ],
+ "enumDescriptions": [
+ "Only safe fixes will be applied",
+ "Safe fixes or suggestions will be applied",
+ "Safe and dangerous fixes will be applied",
+ "Safe and dangerous fixes or suggestions will be applied",
+ "No fixes will be applied",
+ "All fixes and suggestions will be applied"
+ ],
+ "default": "safe_fix",
+ "description": "Specify the kind of fixes to suggest/apply."
+ },
"oxc.flags": {
"type": "object",
"scope": "resource",
"default": {},
+ "deprecationMessage": "deprecated since 1.25.0, use `oxc.fixKind` or `oxc.disableNestedConfig` instead.",
"description": "Specific Oxlint flags to pass to the language server."
},
"oxc.fmt.experimental": {
diff --git a/editors/vscode/tests/WorkspaceConfig.spec.ts b/editors/vscode/tests/WorkspaceConfig.spec.ts
index 359be06462f4d..6919c1a12cd58 100644
--- a/editors/vscode/tests/WorkspaceConfig.spec.ts
+++ b/editors/vscode/tests/WorkspaceConfig.spec.ts
@@ -1,30 +1,42 @@
-import { deepStrictEqual, strictEqual } from 'assert';
+import { strictEqual } from 'assert';
import { ConfigurationTarget, workspace } from 'vscode';
-import { WorkspaceConfig } from '../client/WorkspaceConfig.js';
+import { FixKind, WorkspaceConfig } from '../client/WorkspaceConfig.js';
import { WORKSPACE_FOLDER } from './test-helpers.js';
-const keys = ['lint.run', 'configPath', 'tsConfigPath', 'flags', 'unusedDisableDirectives', 'typeAware', 'fmt.experimental', 'fmt.configPath'];
+const keys = [
+ 'lint.run',
+ 'configPath',
+ 'tsConfigPath',
+ 'unusedDisableDirectives',
+ 'typeAware',
+ 'disableNestedConfig',
+ 'fixKind',
+ 'fmt.experimental',
+ 'fmt.configPath',
+ // deprecated
+ 'flags'
+];
suite('WorkspaceConfig', () => {
- setup(async () => {
+
+ const updateConfiguration = async (key: string, value: unknown) => {
const workspaceConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER);
const globalConfig = workspace.getConfiguration('oxc');
+ await Promise.all([
+ workspaceConfig.update(key, value, ConfigurationTarget.WorkspaceFolder),
+ // VSCode will not save different workspace configuration inside a `.code-workspace` file.
+ // Do not fail, we will make sure the global config is empty too.
+ globalConfig.update(key, value)
+ ]);
+ };
- await Promise.all(keys.map(key => workspaceConfig.update(key, undefined, ConfigurationTarget.WorkspaceFolder)));
- // VSCode will not save different workspace configuration inside a `.code-workspace` file.
- // Do not fail, we will make sure the global config is empty too.
- await Promise.all(keys.map(key => globalConfig.update(key, undefined)));
+ setup(async () => {
+ await Promise.all(keys.map(key => updateConfiguration(key, undefined)));
});
teardown(async () => {
- const workspaceConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER);
- const globalConfig = workspace.getConfiguration('oxc');
-
- await Promise.all(keys.map(key => workspaceConfig.update(key, undefined, ConfigurationTarget.WorkspaceFolder)));
- // VSCode will not save different workspace configuration inside a `.code-workspace` file.
- // Do not fail, we will make sure the global config is empty too.
- await Promise.all(keys.map(key => globalConfig.update(key, undefined)));
+ await Promise.all(keys.map(key => updateConfiguration(key, undefined)));
});
test('default values on initialization', () => {
@@ -34,31 +46,21 @@ suite('WorkspaceConfig', () => {
strictEqual(config.tsConfigPath, null);
strictEqual(config.unusedDisableDirectives, 'allow');
strictEqual(config.typeAware, false);
- deepStrictEqual(config.flags, {});
+ strictEqual(config.disableNestedConfig, false);
+ strictEqual(config.fixKind, "safe_fix");
strictEqual(config.formattingExperimental, false);
strictEqual(config.formattingConfigPath, null);
});
- test('configPath defaults to null when using nested configs and configPath is empty', async () => {
- const wsConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER);
- await wsConfig.update('configPath', '', ConfigurationTarget.WorkspaceFolder);
- await wsConfig.update('flags', {}, ConfigurationTarget.WorkspaceFolder);
+ test('deprecated values are respected', async () => {
+ await updateConfiguration('flags', {
+ disable_nested_config: 'true',
+ fix_kind: 'dangerous_fix'
+ });
const config = new WorkspaceConfig(WORKSPACE_FOLDER);
-
- deepStrictEqual(config.flags, {});
- strictEqual(config.configPath, null);
- });
-
- test('configPath defaults to .oxlintrc.json when not using nested configs and configPath is empty', async () => {
- const wsConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER);
- await wsConfig.update('configPath', undefined, ConfigurationTarget.WorkspaceFolder);
- await wsConfig.update('flags', { disable_nested_config: '' }, ConfigurationTarget.WorkspaceFolder);
-
- const config = new WorkspaceConfig(WORKSPACE_FOLDER);
-
- deepStrictEqual(config.flags, { disable_nested_config: '' });
- strictEqual(config.configPath, '.oxlintrc.json');
+ strictEqual(config.disableNestedConfig, true);
+ strictEqual(config.fixKind, "dangerous_fix");
});
test('updating values updates the workspace configuration', async () => {
@@ -70,7 +72,8 @@ suite('WorkspaceConfig', () => {
config.updateTsConfigPath('./tsconfig.json'),
config.updateUnusedDisableDirectives('deny'),
config.updateTypeAware(true),
- config.updateFlags({ test: 'value' }),
+ config.updateDisableNestedConfig(true),
+ config.updateFixKind(FixKind.DangerousFix),
config.updateFormattingExperimental(true),
config.updateFormattingConfigPath('./oxfmt.json'),
]);
@@ -82,7 +85,8 @@ suite('WorkspaceConfig', () => {
strictEqual(wsConfig.get('tsConfigPath'), './tsconfig.json');
strictEqual(wsConfig.get('unusedDisableDirectives'), 'deny');
strictEqual(wsConfig.get('typeAware'), true);
- deepStrictEqual(wsConfig.get('flags'), { test: 'value' });
+ strictEqual(wsConfig.get('disableNestedConfig'), true);
+ strictEqual(wsConfig.get('fixKind'), 'dangerous_fix');
strictEqual(wsConfig.get('fmt.experimental'), true);
strictEqual(wsConfig.get('fmt.configPath'), './oxfmt.json');
});
diff --git a/editors/vscode/tests/code_actions.spec.ts b/editors/vscode/tests/code_actions.spec.ts
index c01c806cc92cf..fc6694df401fc 100644
--- a/editors/vscode/tests/code_actions.spec.ts
+++ b/editors/vscode/tests/code_actions.spec.ts
@@ -29,8 +29,7 @@ teardown(async () => {
const vsConfig = workspace.getConfiguration('oxc');
const wsConfig = workspace.getConfiguration('oxc', fixturesWorkspaceUri());
await vsConfig.update('unusedDisableDirectives', undefined);
- await wsConfig.update('flags', undefined, ConfigurationTarget.WorkspaceFolder);
-
+ await wsConfig.update('fixKind', undefined, ConfigurationTarget.WorkspaceFolder);
await workspace.getConfiguration('editor').update('codeActionsOnSave', undefined);
await workspace.saveAll();
});
@@ -154,9 +153,7 @@ suite('code actions', () => {
);
strictEqual(quickFixesNoFix.length, 2);
- await workspace.getConfiguration('oxc', fixturesWorkspaceUri()).update('flags', {
- 'fix_kind': 'dangerous_fix',
- }, ConfigurationTarget.WorkspaceFolder);
+ await workspace.getConfiguration('oxc', fixturesWorkspaceUri()).update('fixKind', 'dangerous_fix', ConfigurationTarget.WorkspaceFolder);
await workspace.saveAll();
const codeActionsWithFix: ProviderResult> = await commands.executeCommand(
diff --git a/editors/vscode/tests/e2e_server.spec.ts b/editors/vscode/tests/e2e_server.spec.ts
index 357e8b71a347d..d155c7143e708 100644
--- a/editors/vscode/tests/e2e_server.spec.ts
+++ b/editors/vscode/tests/e2e_server.spec.ts
@@ -32,7 +32,7 @@ suiteSetup(async () => {
});
teardown(async () => {
- await workspace.getConfiguration('oxc').update('flags', undefined);
+ await workspace.getConfiguration('oxc').update('fixKind', undefined);
await workspace.getConfiguration('oxc').update('tsConfigPath', undefined);
await workspace.getConfiguration('oxc').update('typeAware', undefined);
await workspace.getConfiguration('oxc').update('fmt.experimental', undefined);
@@ -137,7 +137,7 @@ suite('E2E Diagnostics', () => {
// We can check the changed with kind with `vscode.executeCodeActionProvider`
// but to be safe that everything works, we will check the applied changes.
// This way we can be sure that everything works as expected.
- test('auto detect changing `fix_kind` flag with fixAll command', async () => {
+ test('auto detect changing `fixKind` with fixAll command', async () => {
const originalContent = 'if (foo == null) { bar();}';
await createOxlintConfiguration({
@@ -163,9 +163,7 @@ suite('E2E Diagnostics', () => {
const content = await workspace.fs.readFile(fileUri);
strictEqual(content.toString(), originalContent);
- await workspace.getConfiguration('oxc').update('flags', {
- fix_kind: 'all',
- });
+ await workspace.getConfiguration('oxc').update('fixKind', 'all');
// wait for server to update the internal linter
await sleep(500);
await workspace.saveAll();