Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions editors/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,22 @@ Following configuration are supported via `settings.json` and can be changed for
| `oxc.tsConfigPath` | `null` | `null` \| `<string>` | 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<string, string>` | 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` | `<string>` \| `null` | Path to an oxfmt configuration file. When `null`, the server will use `.oxfmtrc.json` at the workspace root. |
| `oxc.flags` | - | `Record<string, string>` | (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"`
Expand Down
82 changes: 70 additions & 12 deletions editors/vscode/client/WorkspaceConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
*/
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -83,7 +106,8 @@ export class WorkspaceConfig {
private _runTrigger: Trigger = 'onType';
private _unusedDisableDirectives: UnusedDisableDirectives = 'allow';
private _typeAware: boolean = false;
private _flags: Record<string, string> = {};
private _disableNestedConfig: boolean = false;
private _fixKind: FixKind = FixKind.SafeFix;
private _formattingExperimental: boolean = false;
private _formattingConfigPath: string | null = null;

Expand All @@ -97,16 +121,28 @@ export class WorkspaceConfig {

public refresh(): void {
const flags = this.configuration.get<Record<string, string>>('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>('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<boolean>('disableNestedConfig');
if (disableNestedConfig === false && flags.disable_nested_config === 'true') {
disableNestedConfig = true;
}

this._runTrigger = this.configuration.get<Trigger>('lint.run') || 'onType';
this._configPath =
this.configuration.get<string | null>('configPath') || (useNestedConfigs ? null : oxlintConfigFileName);
this._configPath = this.configuration.get<string | null>('configPath') ?? null;
this._tsConfigPath = this.configuration.get<string | null>('tsConfigPath') ?? null;
this._unusedDisableDirectives =
this.configuration.get<UnusedDisableDirectives>('unusedDisableDirectives') ?? 'allow';
this._typeAware = this.configuration.get<boolean>('typeAware') ?? false;
this._flags = flags;
this._disableNestedConfig = disableNestedConfig ?? false;
this._fixKind = fixKind ?? FixKind.SafeFix;
this._formattingExperimental = this.configuration.get<boolean>('fmt.experimental') ?? false;
this._formattingConfigPath = this.configuration.get<string | null>('fmt.configPath') ?? null;
}
Expand All @@ -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)) {
Expand All @@ -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;
}

Expand Down Expand Up @@ -188,13 +231,22 @@ export class WorkspaceConfig {
return this.configuration.update('typeAware', value, ConfigurationTarget.WorkspaceFolder);
}

get flags(): Record<string, string> {
return this._flags;
get disableNestedConfig(): boolean {
return this._disableNestedConfig;
}

updateDisableNestedConfig(value: boolean): PromiseLike<void> {
this._disableNestedConfig = value;
return this.configuration.update('disableNestedConfig', value, ConfigurationTarget.WorkspaceFolder);
}

get fixKind(): FixKind {
return this._fixKind;
}

updateFlags(value: Record<string, string>): PromiseLike<void> {
this._flags = value;
return this.configuration.update('flags', value, ConfigurationTarget.WorkspaceFolder);
updateFixKind(value: FixKind): PromiseLike<void> {
this._fixKind = value;
return this.configuration.update('fixKind', value, ConfigurationTarget.WorkspaceFolder);
}

get formattingExperimental(): boolean {
Expand Down Expand Up @@ -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 } : {}),
},
};
}
}
29 changes: 29 additions & 0 deletions editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
76 changes: 40 additions & 36 deletions editors/vscode/tests/WorkspaceConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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 () => {
Expand All @@ -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'),
]);
Expand All @@ -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');
});
Expand Down
7 changes: 2 additions & 5 deletions editors/vscode/tests/code_actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down Expand Up @@ -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<Array<CodeAction>> = await commands.executeCommand(
Expand Down
8 changes: 3 additions & 5 deletions editors/vscode/tests/e2e_server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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({
Expand All @@ -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();
Expand Down
Loading