diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 82a5a7779b6e9..fe748d767dd5f 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -21,6 +21,25 @@ This is the linter for Oxc. The currently supported features are listed below. to automatically apply fixes when saving the file. - Support for multi root workspaces +## Oxfmt + +This is the formatter for Oxc. The currently supported features are listed below. + +- Experimental formatting with `oxc.fmt.experimental` + +To enable it, use a VSCode `settings.json` like: + +```json +{ + "oxc.fmt.experimental": true, + "editor.defaultFormatter": "oxc.oxc-vscode" + // Or enable it for specific files: + // "[javascript]": { + // "editor.defaultFormatter": "oxc.oxc-vscode" + // }, +} +``` + ## Configuration ### Window Configuration @@ -46,6 +65,7 @@ Following configuration are supported via `settings.json` and can be changed for | `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.fmt.experimental` | `false` | `false` \| `true` | Enable experimental formatting support. This feature is experimental and might not work as expected. | #### Flags diff --git a/editors/vscode/client/WorkspaceConfig.ts b/editors/vscode/client/WorkspaceConfig.ts index ed7d2d2d3f4c0..3c8216f0fc13c 100644 --- a/editors/vscode/client/WorkspaceConfig.ts +++ b/editors/vscode/client/WorkspaceConfig.ts @@ -61,6 +61,14 @@ export interface WorkspaceConfigInterface { * @default {} */ flags: Record; + + /** + * Enable formatting experiment + * `oxc.fmt.experimental` + * + * @default false + */ + ['fmt.experimental']: boolean; } export class WorkspaceConfig { @@ -70,6 +78,7 @@ export class WorkspaceConfig { private _unusedDisableDirectives: UnusedDisableDirectives = 'allow'; private _typeAware: boolean = false; private _flags: Record = {}; + private _formattingExperimental: boolean = false; constructor(private readonly workspace: WorkspaceFolder) { this.refresh(); @@ -91,6 +100,7 @@ export class WorkspaceConfig { 'allow'; this._typeAware = this.configuration.get('typeAware') ?? false; this._flags = flags; + this._formattingExperimental = this.configuration.get('fmt.experimental') ?? false; } public effectsConfigChange(event: ConfigurationChangeEvent): boolean { @@ -112,6 +122,9 @@ export class WorkspaceConfig { if (event.affectsConfiguration(`${ConfigService.namespace}.flags`, this.workspace)) { return true; } + if (event.affectsConfiguration(`${ConfigService.namespace}.fmt.experimental`, this.workspace)) { + return true; + } return false; } @@ -173,6 +186,15 @@ export class WorkspaceConfig { return this.configuration.update('flags', value, ConfigurationTarget.WorkspaceFolder); } + get formattingExperimental(): boolean { + return this._formattingExperimental; + } + + updateFormattingExperimental(value: boolean): PromiseLike { + this._formattingExperimental = value; + return this.configuration.update('fmt.experimental', value, ConfigurationTarget.WorkspaceFolder); + } + public toLanguageServerConfig(): WorkspaceConfigInterface { return { run: this.runTrigger, @@ -181,6 +203,7 @@ export class WorkspaceConfig { unusedDisableDirectives: this.unusedDisableDirectives, typeAware: this.typeAware, flags: this.flags, + ['fmt.experimental']: this.formattingExperimental, }; } } diff --git a/editors/vscode/fixtures/formatting/formatting.ts b/editors/vscode/fixtures/formatting/formatting.ts new file mode 100644 index 0000000000000..112fa1b5e4d9d --- /dev/null +++ b/editors/vscode/fixtures/formatting/formatting.ts @@ -0,0 +1 @@ +class X { foo() { return 42; } } diff --git a/editors/vscode/package.json b/editors/vscode/package.json index bed6c82d7f683..346b06b51d9f8 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -152,6 +152,12 @@ "scope": "resource", "default": {}, "description": "Specific Oxlint flags to pass to the language server." + }, + "oxc.fmt.experimental": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "Enable experimental formatting support. This feature is experimental and might not work as expected." } } }, diff --git a/editors/vscode/tests/WorkspaceConfig.spec.ts b/editors/vscode/tests/WorkspaceConfig.spec.ts index 87909e7fbf274..3ba96f101df73 100644 --- a/editors/vscode/tests/WorkspaceConfig.spec.ts +++ b/editors/vscode/tests/WorkspaceConfig.spec.ts @@ -3,7 +3,7 @@ import { ConfigurationTarget, workspace } from 'vscode'; import { WorkspaceConfig } from '../client/WorkspaceConfig.js'; import { WORKSPACE_FOLDER } from './test-helpers.js'; -const keys = ['lint.run', 'configPath', 'tsConfigPath', 'flags', 'unusedDisableDirectives', 'typeAware']; +const keys = ['lint.run', 'configPath', 'tsConfigPath', 'flags', 'unusedDisableDirectives', 'typeAware', 'fmt.experimental']; suite('WorkspaceConfig', () => { setup(async () => { @@ -35,6 +35,7 @@ suite('WorkspaceConfig', () => { strictEqual(config.unusedDisableDirectives, 'allow'); strictEqual(config.typeAware, false); deepStrictEqual(config.flags, {}); + strictEqual(config.formattingExperimental, false); }); test('configPath defaults to null when using nested configs and configPath is empty', async () => { @@ -69,6 +70,7 @@ suite('WorkspaceConfig', () => { config.updateUnusedDisableDirectives('deny'), config.updateTypeAware(true), config.updateFlags({ test: 'value' }), + config.updateFormattingExperimental(true), ]); const wsConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER); @@ -79,5 +81,6 @@ suite('WorkspaceConfig', () => { strictEqual(wsConfig.get('unusedDisableDirectives'), 'deny'); strictEqual(wsConfig.get('typeAware'), true); deepStrictEqual(wsConfig.get('flags'), { test: 'value' }); + strictEqual(wsConfig.get('fmt.experimental'), true); }); }); diff --git a/editors/vscode/tests/e2e_server.spec.ts b/editors/vscode/tests/e2e_server.spec.ts index 20b91027f9ea0..24fee1eeff5b7 100644 --- a/editors/vscode/tests/e2e_server.spec.ts +++ b/editors/vscode/tests/e2e_server.spec.ts @@ -35,6 +35,8 @@ teardown(async () => { await workspace.getConfiguration('oxc').update('flags', undefined); await workspace.getConfiguration('oxc').update('tsConfigPath', undefined); await workspace.getConfiguration('oxc').update('typeAware', undefined); + await workspace.getConfiguration('oxc').update('fmt.experimental', undefined); + await workspace.getConfiguration('editor').update('defaultFormatter', undefined); await workspace.saveAll(); }); @@ -321,4 +323,19 @@ suite('E2E Diagnostics', () => { strictEqual(diagnostics[0].range.end.line, 1); strictEqual(diagnostics[0].range.end.character, 30); }); + + test('formats code with `oxc.fmt.experimental`', async () => { + await workspace.getConfiguration('oxc').update('fmt.experimental', true); + await workspace.getConfiguration('editor').update('defaultFormatter', 'oxc.oxc-vscode'); + await loadFixture('formatting'); + const fileUri = Uri.joinPath(fixturesWorkspaceUri(), 'fixtures', 'formatting.ts'); + + const document = await workspace.openTextDocument(fileUri); + await window.showTextDocument(document); + await commands.executeCommand('editor.action.formatDocument'); + await workspace.saveAll(); + const content = await workspace.fs.readFile(fileUri); + + strictEqual(content.toString(), "class X {\n foo() {\n return 42;\n }\n}\n"); + }); });