diff --git a/.gitignore b/.gitignore index 2313c7e481136..e8224a0b34300 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ target/ /editors/vscode/out/ /editors/vscode/*.vsix /editors/vscode/test_workspace/ +/editors/vscode/test_workspace_second/ +/editors/vscode/*.test.code-workspace # Cloned conformance repos tasks/coverage/babel/ diff --git a/editors/vscode/.vscode-test.mjs b/editors/vscode/.vscode-test.mjs index 26aea3e2dc527..17945dca64e08 100644 --- a/editors/vscode/.vscode-test.mjs +++ b/editors/vscode/.vscode-test.mjs @@ -1,27 +1,59 @@ import { defineConfig } from '@vscode/test-cli'; -import { existsSync, mkdirSync } from 'node:fs'; +import { mkdirSync, writeFileSync } from 'node:fs'; import path from 'node:path'; -// create dir if not exists -if (!existsSync('./test_workspace')) { - mkdirSync('./test_workspace'); -} +const multiRootWorkspaceFile = './multi-root.test.code-workspace'; + +mkdirSync('./test_workspace', { recursive: true }); +mkdirSync('./test_workspace_second', { recursive: true }); + +const multiRootWorkspaceConfig = { + 'folders': [ + { 'path': 'test_workspace' }, + { 'path': 'test_workspace_second' }, + ], +}; +writeFileSync(multiRootWorkspaceFile, JSON.stringify(multiRootWorkspaceConfig, null, 2)); const ext = process.platform === 'win32' ? '.exe' : ''; export default defineConfig({ - tests: [{ - files: 'out/**/*.spec.js', - workspaceFolder: './test_workspace', - launchArgs: [ - // This disables all extensions except the one being testing - '--disable-extensions', - ], - env: { - SERVER_PATH_DEV: path.resolve(import.meta.dirname, `./target/debug/oxc_language_server${ext}`), + tests: [ + { + files: 'out/**/*.spec.js', + workspaceFolder: './test_workspace', + launchArgs: [ + // This disables all extensions except the one being testing + '--disable-extensions', + ], + env: { + SINGLE_FOLDER_WORKSPACE: 'true', + SERVER_PATH_DEV: path.resolve( + import.meta.dirname, + `./target/debug/oxc_language_server${ext}`, + ), + }, + mocha: { + timeout: 10_000, + }, }, - mocha: { - timeout: 10_000, + { + files: 'out/**/*.spec.js', + workspaceFolder: multiRootWorkspaceFile, + launchArgs: [ + // This disables all extensions except the one being testing + '--disable-extensions', + ], + env: { + MULTI_FOLDER_WORKSPACE: 'true', + SERVER_PATH_DEV: path.resolve( + import.meta.dirname, + `./target/debug/oxc_language_server${ext}`, + ), + }, + mocha: { + timeout: 10_000, + }, }, - }], + ], }); diff --git a/editors/vscode/client/WorkspaceConfig.spec.ts b/editors/vscode/client/WorkspaceConfig.spec.ts deleted file mode 100644 index 95079bc1d37d1..0000000000000 --- a/editors/vscode/client/WorkspaceConfig.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { deepStrictEqual, strictEqual } from 'assert'; -import { Uri, workspace, WorkspaceEdit } from 'vscode'; -import { WORKSPACE_FOLDER } from './test-helpers.js'; -import { WorkspaceConfig } from './WorkspaceConfig.js'; - -const uri = WORKSPACE_FOLDER; -suite('WorkspaceConfig', () => { - setup(async () => { - const wsConfig = workspace.getConfiguration('oxc', uri); - const keys = ['lint.run', 'configPath', 'flags']; - - await Promise.all(keys.map(key => wsConfig.update(key, undefined))); - }); - - suiteTeardown(async () => { - const WORKSPACE_DIR = workspace.workspaceFolders![0].uri.toString(); - const file = Uri.parse(WORKSPACE_DIR + '/.vscode/settings.json'); - const edit = new WorkspaceEdit(); - edit.deleteFile(file); - await workspace.applyEdit(edit); - }); - - test('default values on initialization', () => { - const config = new WorkspaceConfig(uri); - strictEqual(config.runTrigger, 'onType'); - strictEqual(config.configPath, null); - deepStrictEqual(config.flags, {}); - }); - - test('configPath defaults to null when using nested configs and configPath is empty', async () => { - const wsConfig = workspace.getConfiguration('oxc', uri); - await wsConfig.update('configPath', ''); - await wsConfig.update('flags', {}); - - const config = new WorkspaceConfig(uri); - - 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', uri); - await wsConfig.update('configPath', undefined); - await wsConfig.update('flags', { disable_nested_config: '' }); - - const config = new WorkspaceConfig(uri); - - deepStrictEqual(config.flags, { disable_nested_config: '' }); - strictEqual(config.configPath, '.oxlintrc.json'); - }); - - test('updating values updates the workspace configuration', async () => { - const config = new WorkspaceConfig(uri); - - await Promise.all([ - config.updateRunTrigger('onSave'), - config.updateConfigPath('./somewhere'), - config.updateFlags({ test: 'value' }), - ]); - - const wsConfig = workspace.getConfiguration('oxc', uri); - - strictEqual(wsConfig.get('lint.run'), 'onSave'); - strictEqual(wsConfig.get('configPath'), './somewhere'); - deepStrictEqual(wsConfig.get('flags'), { test: 'value' }); - }); -}); diff --git a/editors/vscode/fixtures/debugger_error/.oxlintrc.json b/editors/vscode/fixtures/debugger_error/.oxlintrc.json new file mode 100644 index 0000000000000..a63d73a1442b8 --- /dev/null +++ b/editors/vscode/fixtures/debugger_error/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-debugger": "error" + } +} diff --git a/editors/vscode/fixtures/debugger_error/debugger.js b/editors/vscode/fixtures/debugger_error/debugger.js new file mode 100644 index 0000000000000..d360b2d7a29fa --- /dev/null +++ b/editors/vscode/fixtures/debugger_error/debugger.js @@ -0,0 +1 @@ +/* 😊 */debugger; diff --git a/editors/vscode/package.json b/editors/vscode/package.json index a768dcfe19dd0..ab35f600da479 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -150,7 +150,7 @@ "server:build:debug": "cross-env CARGO_TARGET_DIR=./target cargo build -p oxc_language_server", "server:build:release": "cross-env CARGO_TARGET_DIR=./target cargo build -p oxc_language_server --release", "lint": "npx oxlint --tsconfig=tsconfig.json", - "test": "esbuild client/*.spec.ts --bundle --outdir=out --external:vscode --format=cjs --platform=node --target=node16 --minify --sourcemap && vscode-test", + "test": "esbuild tests/*.spec.ts --bundle --outdir=out --external:vscode --format=cjs --platform=node --target=node16 --sourcemap && vscode-test", "type-check": "tsc --noEmit" }, "devDependencies": { diff --git a/editors/vscode/client/VSCodeConfig.spec.ts b/editors/vscode/tests/VSCodeConfig.spec.ts similarity index 62% rename from editors/vscode/client/VSCodeConfig.spec.ts rename to editors/vscode/tests/VSCodeConfig.spec.ts index 96c814f1da6b7..bf5ec48e04acc 100644 --- a/editors/vscode/client/VSCodeConfig.spec.ts +++ b/editors/vscode/tests/VSCodeConfig.spec.ts @@ -1,6 +1,7 @@ import { strictEqual } from 'assert'; -import { Uri, workspace, WorkspaceEdit } from 'vscode'; -import { VSCodeConfig } from './VSCodeConfig.js'; +import { workspace } from 'vscode'; +import { VSCodeConfig } from '../client/VSCodeConfig.js'; +import { testSingleFolderMode } from './test-helpers.js'; const conf = workspace.getConfiguration('oxc'); @@ -11,15 +12,13 @@ suite('VSCodeConfig', () => { await Promise.all(keys.map(key => conf.update(key, undefined))); }); - suiteTeardown(async () => { - const WORKSPACE_DIR = workspace.workspaceFolders![0].uri.toString(); - const file = Uri.parse(WORKSPACE_DIR + '/.vscode/settings.json'); - const edit = new WorkspaceEdit(); - edit.deleteFile(file); - await workspace.applyEdit(edit); + teardown(async () => { + const keys = ['enable', 'trace.server', 'path.server']; + + await Promise.all(keys.map(key => conf.update(key, undefined))); }); - test('default values on initialization', () => { + testSingleFolderMode('default values on initialization', () => { const config = new VSCodeConfig(); strictEqual(config.enable, true); @@ -27,7 +26,7 @@ suite('VSCodeConfig', () => { strictEqual(config.binPath, ''); }); - test('updating values updates the workspace configuration', async () => { + testSingleFolderMode('updating values updates the workspace configuration', async () => { const config = new VSCodeConfig(); await Promise.all([ diff --git a/editors/vscode/tests/WorkspaceConfig.spec.ts b/editors/vscode/tests/WorkspaceConfig.spec.ts new file mode 100644 index 0000000000000..8d6f259894efb --- /dev/null +++ b/editors/vscode/tests/WorkspaceConfig.spec.ts @@ -0,0 +1,73 @@ +import { deepStrictEqual, strictEqual } from 'assert'; +import { ConfigurationTarget, workspace } from 'vscode'; +import { WorkspaceConfig } from '../client/WorkspaceConfig.js'; +import { WORKSPACE_FOLDER } from './test-helpers.js'; + +suite('WorkspaceConfig', () => { + setup(async () => { + const workspaceConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER); + const globalConfig = workspace.getConfiguration('oxc'); + const keys = ['lint.run', 'configPath', 'flags']; + + 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))); + }); + + teardown(async () => { + const workspaceConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER); + const globalConfig = workspace.getConfiguration('oxc'); + const keys = ['lint.run', 'configPath', 'flags']; + + 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))); + }); + + test('default values on initialization', () => { + const config = new WorkspaceConfig(WORKSPACE_FOLDER); + strictEqual(config.runTrigger, 'onType'); + strictEqual(config.configPath, null); + deepStrictEqual(config.flags, {}); + }); + + 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); + + 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'); + }); + + test('updating values updates the workspace configuration', async () => { + const config = new WorkspaceConfig(WORKSPACE_FOLDER); + + await Promise.all([ + config.updateRunTrigger('onSave'), + config.updateConfigPath('./somewhere'), + config.updateFlags({ test: 'value' }), + ]); + + const wsConfig = workspace.getConfiguration('oxc', WORKSPACE_FOLDER); + + strictEqual(wsConfig.get('lint.run'), 'onSave'); + strictEqual(wsConfig.get('configPath'), './somewhere'); + deepStrictEqual(wsConfig.get('flags'), { test: 'value' }); + }); +}); diff --git a/editors/vscode/tests/code_actions.spec.ts b/editors/vscode/tests/code_actions.spec.ts new file mode 100644 index 0000000000000..1e6d35841f886 --- /dev/null +++ b/editors/vscode/tests/code_actions.spec.ts @@ -0,0 +1,112 @@ + +import { deepStrictEqual, strictEqual } from 'assert'; +import { + commands, + Position, + Range, + Uri, + window, + workspace, + WorkspaceEdit +} from 'vscode'; +import { + activateExtension, + loadFixture, + sleep, + WORKSPACE_DIR +} from './test-helpers'; +import assert = require('assert'); + +const fileUri = Uri.joinPath(WORKSPACE_DIR, 'debugger.js'); + + +suiteSetup(async () => { + await activateExtension(); +}); + +teardown(async () => { + const edit = new WorkspaceEdit(); + edit.deleteFile(fileUri, { + ignoreIfNotExists: true, + }); + await workspace.applyEdit(edit); +}); + +suite('code actions', () => { + test('listed code actions', async () => { + const edit = new WorkspaceEdit(); + edit.createFile(fileUri, { + contents: Buffer.from('debugger;'), + overwrite: true, + }); + + await workspace.applyEdit(edit); + await window.showTextDocument(fileUri); + + const codeActions = await commands.executeCommand( + 'vscode.executeCodeActionProvider', + fileUri, + { + start: { line: 0, character: 8 }, + end: { line: 0, character: 9 }, + }, + ); + + assert(Array.isArray(codeActions)); + const quickFixes = codeActions.filter( + (action) => action.kind?.value === 'quickfix', + ); + strictEqual(quickFixes.length, 3); + deepStrictEqual( + quickFixes.map(({ edit: _edit, kind: _kind, ...fix }) => ({ + ...fix, + })), + [ + { + isPreferred: true, + title: 'Remove the debugger statement', + }, + { + isPreferred: false, + title: 'Disable no-debugger for this line', + }, + { + isPreferred: false, + title: 'Disable no-debugger for this file', + }, + ], + ); + }); + + // https://github.com/oxc-project/oxc/issues/10422 + test('code action `source.fixAll.oxc` on editor.codeActionsOnSave', async () => { + let file = Uri.joinPath(WORKSPACE_DIR, 'fixtures', 'file.js'); + let expectedFile = Uri.joinPath(WORKSPACE_DIR, 'fixtures', 'expected.txt'); + + await workspace.getConfiguration('editor').update('codeActionsOnSave', { + 'source.fixAll.oxc': 'always', + }); + await workspace.saveAll(); + + const range = new Range(new Position(0, 0), new Position(0, 0)); + const edit = new WorkspaceEdit(); + edit.replace(file, range, ' '); + + await sleep(1000); + + await loadFixture('fixall_with_code_actions_on_save'); + await workspace.openTextDocument(file); + await workspace.applyEdit(edit); + await sleep(1000); + await workspace.saveAll(); + await sleep(500); + + const content = await workspace.fs.readFile(file); + const expected = await workspace.fs.readFile(expectedFile); + + strictEqual(content.toString(), expected.toString()); + + await workspace.getConfiguration('editor').update('codeActionsOnSave', undefined); + await workspace.saveAll(); + }); +}); diff --git a/editors/vscode/tests/commands.spec.ts b/editors/vscode/tests/commands.spec.ts new file mode 100644 index 0000000000000..1acb80946e9a8 --- /dev/null +++ b/editors/vscode/tests/commands.spec.ts @@ -0,0 +1,87 @@ +import { deepStrictEqual, notEqual, strictEqual } from 'assert'; +import { + commands, + Uri, + window, + workspace, + WorkspaceEdit +} from 'vscode'; +import { + activateExtension, + sleep, + testSingleFolderMode, + WORKSPACE_DIR +} from './test-helpers'; + +const fileUri = Uri.joinPath(WORKSPACE_DIR, 'debugger.js'); + +suiteSetup(async () => { + await activateExtension(); +}); + +teardown(async () => { + const edit = new WorkspaceEdit(); + edit.deleteFile(fileUri, { + ignoreIfNotExists: true, + }); + await workspace.applyEdit(edit); +}); + +suite('commands', () => { + testSingleFolderMode('listed commands', async () => { + const oxcCommands = (await commands.getCommands(true)).filter(x => x.startsWith('oxc.')); + + deepStrictEqual([ + 'oxc.restartServer', + 'oxc.showOutputChannel', + 'oxc.toggleEnable', + 'oxc.applyAllFixesFile', + 'oxc.fixAll', + ], oxcCommands); + }); + + testSingleFolderMode('oxc.showOutputChannel', async () => { + await commands.executeCommand('oxc.showOutputChannel'); + await sleep(500); + + notEqual(window.activeTextEditor, undefined); + const uri = window.activeTextEditor!.document.uri; + strictEqual(uri.toString(), 'output:oxc.oxc-vscode.Oxc'); + + await commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + testSingleFolderMode('oxc.toggleEnable', async () => { + const isEnabledBefore = workspace.getConfiguration('oxc').get('enable'); + strictEqual(isEnabledBefore, true); + + await commands.executeCommand('oxc.toggleEnable'); + await sleep(500); + + const isEnabledAfter = workspace.getConfiguration('oxc').get('enable'); + strictEqual(isEnabledAfter, false); + + // enable it for other tests + await commands.executeCommand('oxc.toggleEnable'); + await sleep(500); + }); + + test('oxc.fixAll', async () => { + const edit = new WorkspaceEdit(); + edit.createFile(fileUri, { + contents: Buffer.from('/* 😊 */debugger;'), + overwrite: true, + }); + + await workspace.applyEdit(edit); + await window.showTextDocument(fileUri); + await commands.executeCommand('oxc.fixAll', { + uri: fileUri.toString(), + }); + await workspace.saveAll(); + + const content = await workspace.fs.readFile(fileUri); + + strictEqual(content.toString(), '/* 😊 */'); + }); +}); diff --git a/editors/vscode/client/extension.spec.ts b/editors/vscode/tests/e2e_server.spec.ts similarity index 64% rename from editors/vscode/client/extension.spec.ts rename to editors/vscode/tests/e2e_server.spec.ts index 1a3a701a59217..a7dcc9a68a697 100644 --- a/editors/vscode/client/extension.spec.ts +++ b/editors/vscode/tests/e2e_server.spec.ts @@ -1,16 +1,12 @@ -import { deepStrictEqual, notEqual, strictEqual } from 'assert'; +import { strictEqual } from 'assert'; import { - CodeAction, commands, DiagnosticSeverity, languages, - Position, - ProviderResult, - Range, Uri, window, workspace, - WorkspaceEdit, + WorkspaceEdit } from 'vscode'; import { activateExtension, @@ -18,151 +14,18 @@ import { getDiagnostics, loadFixture, sleep, + testMultiFolderMode, WORKSPACE_DIR, + WORKSPACE_SECOND_DIR } from './test-helpers'; import assert = require('assert'); const fileUri = Uri.joinPath(WORKSPACE_DIR, 'debugger.js'); -setup(async () => { +suiteSetup(async () => { await activateExtension(); }); -suite('commands', () => { - test('listed commands', async () => { - const oxcCommands = (await commands.getCommands(true)).filter(x => x.startsWith('oxc.')); - - deepStrictEqual([ - 'oxc.restartServer', - 'oxc.showOutputChannel', - 'oxc.toggleEnable', - 'oxc.applyAllFixesFile', - 'oxc.fixAll', - ], oxcCommands); - }); - - test('oxc.showOutputChannel', async () => { - await commands.executeCommand('oxc.showOutputChannel'); - await sleep(500); - - notEqual(window.activeTextEditor, undefined); - const uri = window.activeTextEditor!.document.uri; - strictEqual(uri.toString(), 'output:oxc.oxc-vscode.Oxc'); - }); - - test('oxc.toggleEnable', async () => { - const isEnabledBefore = workspace.getConfiguration('oxc').get('enable'); - strictEqual(isEnabledBefore, true); - - await commands.executeCommand('oxc.toggleEnable'); - await sleep(500); - - const isEnabledAfter = workspace.getConfiguration('oxc').get('enable'); - strictEqual(isEnabledAfter, false); - - // enable it for other tests - await commands.executeCommand('oxc.toggleEnable'); - await sleep(500); - }); - - test('oxc.fixAll', async () => { - const edit = new WorkspaceEdit(); - edit.createFile(fileUri, { - contents: Buffer.from('/* 😊 */debugger;'), - overwrite: true, - }); - - await workspace.applyEdit(edit); - await window.showTextDocument(fileUri); - await commands.executeCommand('oxc.fixAll', { - uri: fileUri.toString(), - }); - await workspace.saveAll(); - - const content = await workspace.fs.readFile(fileUri); - - strictEqual(content.toString(), '/* 😊 */'); - }); -}); - -suite('code actions', () => { - test('listed code actions', async () => { - const edit = new WorkspaceEdit(); - edit.createFile(fileUri, { - contents: Buffer.from('debugger;'), - overwrite: true, - }); - - await workspace.applyEdit(edit); - await window.showTextDocument(fileUri); - - const codeActions: ProviderResult> = await commands.executeCommand( - 'vscode.executeCodeActionProvider', - fileUri, - { - start: { line: 0, character: 8 }, - end: { line: 0, character: 9 }, - }, - ); - - assert(Array.isArray(codeActions)); - const quickFixes = codeActions.filter( - (action) => action.kind?.value === 'quickfix', - ); - strictEqual(quickFixes.length, 3); - deepStrictEqual( - quickFixes.map(({ edit: _edit, kind: _kind, ...fix }) => ({ - ...fix, - })), - [ - { - isPreferred: true, - title: 'Remove the debugger statement', - }, - { - isPreferred: false, - title: 'Disable no-debugger for this line', - }, - { - isPreferred: false, - title: 'Disable no-debugger for this file', - }, - ], - ); - }); - - // https://github.com/oxc-project/oxc/issues/10422 - test('code action `source.fixAll.oxc` on editor.codeActionsOnSave', async () => { - let file = Uri.joinPath(WORKSPACE_DIR, 'fixtures', 'file.js'); - let expectedFile = Uri.joinPath(WORKSPACE_DIR, 'fixtures', 'expected.txt'); - - await workspace.getConfiguration('editor').update('codeActionsOnSave', { - 'source.fixAll.oxc': 'always', - }); - await workspace.saveAll(); - - const range = new Range(new Position(0, 0), new Position(0, 0)); - const edit = new WorkspaceEdit(); - edit.replace(file, range, ' '); - - await sleep(1000); - - await loadFixture('fixall_with_code_actions_on_save'); - await workspace.openTextDocument(file); - await workspace.applyEdit(edit); - await sleep(1000); - await workspace.saveAll(); - await sleep(500); - - const content = await workspace.fs.readFile(file); - const expected = await workspace.fs.readFile(expectedFile); - - strictEqual(content.toString(), expected.toString()); - - await workspace.getConfiguration('editor').update('codeActionsOnSave', undefined); - await workspace.saveAll(); - }); -}); suite('E2E Diagnostics', () => { test('simple debugger statement', async () => { @@ -195,59 +58,6 @@ suite('E2E Diagnostics', () => { strictEqual(diagnostics[0].range.end.character, 17); }); - test('cross module', async () => { - await loadFixture('cross_module'); - const diagnostics = await getDiagnostics('dep-a.ts'); - - strictEqual(diagnostics.length, 1); - assert(typeof diagnostics[0].code == 'object'); - strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); - strictEqual( - diagnostics[0].message, - 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./dep-b.ts - fixtures/dep-b.ts\n-> ./dep-a.ts - fixtures/dep-a.ts', - ); - strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); - strictEqual(diagnostics[0].range.start.line, 1); - strictEqual(diagnostics[0].range.start.character, 18); - strictEqual(diagnostics[0].range.end.line, 1); - strictEqual(diagnostics[0].range.end.character, 30); - }); - - test('cross module with nested config', async () => { - await loadFixture('cross_module_nested_config'); - const diagnostics = await getDiagnostics('folder/folder-dep-a.ts'); - - strictEqual(diagnostics.length, 1); - assert(typeof diagnostics[0].code == 'object'); - strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); - strictEqual( - diagnostics[0].message, - 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./folder-dep-b.ts - fixtures/folder/folder-dep-b.ts\n-> ./folder-dep-a.ts - fixtures/folder/folder-dep-a.ts', - ); - strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); - strictEqual(diagnostics[0].range.start.line, 1); - strictEqual(diagnostics[0].range.start.character, 18); - strictEqual(diagnostics[0].range.end.line, 1); - strictEqual(diagnostics[0].range.end.character, 37); - }); - - test('cross module with extended config', async () => { - await loadFixture('cross_module_extended_config'); - const diagnostics = await getDiagnostics('dep-a.ts'); - - assert(typeof diagnostics[0].code == 'object'); - strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); - strictEqual( - diagnostics[0].message, - 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./dep-b.ts - fixtures/dep-b.ts\n-> ./dep-a.ts - fixtures/dep-a.ts', - ); - strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); - strictEqual(diagnostics[0].range.start.line, 1); - strictEqual(diagnostics[0].range.start.character, 18); - strictEqual(diagnostics[0].range.end.line, 1); - strictEqual(diagnostics[0].range.end.character, 30); - }); - test('setting rule to error, will report the diagnostic as error', async () => { const edit = new WorkspaceEdit(); edit.createFile(fileUri, { @@ -341,4 +151,73 @@ suite('E2E Diagnostics', () => { strictEqual(nestedDiagnostics[0].code.target.authority, 'oxc.rs'); strictEqual(nestedDiagnostics[0].severity, DiagnosticSeverity.Error); }); + + testMultiFolderMode('different diagnostic severity', async () => { + await loadFixture('debugger'); + await loadFixture('debugger_error', WORKSPACE_SECOND_DIR); + + const firstDiagnostics = await getDiagnostics('debugger.js'); + const secondDiagnostics = await getDiagnostics('debugger.js', WORKSPACE_SECOND_DIR); + + assert(typeof firstDiagnostics[0].code == 'object'); + strictEqual(firstDiagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual(firstDiagnostics[0].severity, DiagnosticSeverity.Warning); + + assert(typeof secondDiagnostics[0].code == 'object'); + strictEqual(secondDiagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual(secondDiagnostics[0].severity, DiagnosticSeverity.Error); + }) + + test('cross module', async () => { + await loadFixture('cross_module'); + const diagnostics = await getDiagnostics('dep-a.ts'); + + strictEqual(diagnostics.length, 1); + assert(typeof diagnostics[0].code == 'object'); + strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual( + diagnostics[0].message, + 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./dep-b.ts - fixtures/dep-b.ts\n-> ./dep-a.ts - fixtures/dep-a.ts', + ); + strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); + strictEqual(diagnostics[0].range.start.line, 1); + strictEqual(diagnostics[0].range.start.character, 18); + strictEqual(diagnostics[0].range.end.line, 1); + strictEqual(diagnostics[0].range.end.character, 30); + }); + + test('cross module with nested config', async () => { + await loadFixture('cross_module_nested_config'); + const diagnostics = await getDiagnostics('folder/folder-dep-a.ts'); + + strictEqual(diagnostics.length, 1); + assert(typeof diagnostics[0].code == 'object'); + strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual( + diagnostics[0].message, + 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./folder-dep-b.ts - fixtures/folder/folder-dep-b.ts\n-> ./folder-dep-a.ts - fixtures/folder/folder-dep-a.ts', + ); + strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); + strictEqual(diagnostics[0].range.start.line, 1); + strictEqual(diagnostics[0].range.start.character, 18); + strictEqual(diagnostics[0].range.end.line, 1); + strictEqual(diagnostics[0].range.end.character, 37); + }); + + test('cross module with extended config', async () => { + await loadFixture('cross_module_extended_config'); + const diagnostics = await getDiagnostics('dep-a.ts'); + + assert(typeof diagnostics[0].code == 'object'); + strictEqual(diagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual( + diagnostics[0].message, + 'Dependency cycle detected\nhelp: These paths form a cycle: \n-> ./dep-b.ts - fixtures/dep-b.ts\n-> ./dep-a.ts - fixtures/dep-a.ts', + ); + strictEqual(diagnostics[0].severity, DiagnosticSeverity.Error); + strictEqual(diagnostics[0].range.start.line, 1); + strictEqual(diagnostics[0].range.start.character, 18); + strictEqual(diagnostics[0].range.end.line, 1); + strictEqual(diagnostics[0].range.end.character, 30); + }); }); diff --git a/editors/vscode/client/test-helpers.ts b/editors/vscode/tests/test-helpers.ts similarity index 69% rename from editors/vscode/client/test-helpers.ts rename to editors/vscode/tests/test-helpers.ts index 6e7b0ccfd9679..b85fa333f0a69 100644 --- a/editors/vscode/client/test-helpers.ts +++ b/editors/vscode/tests/test-helpers.ts @@ -1,4 +1,5 @@ -import { commands, Diagnostic, extensions, languages, Uri, window, workspace, WorkspaceEdit } from 'vscode'; +import { Func } from 'mocha'; +import { commands, Diagnostic, extensions, languages, Uri, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; import path = require('path'); type OxlintConfigPlugins = string[]; @@ -29,20 +30,49 @@ export type OxlintConfig = { ignorePatterns?: OxlintConfigIgnorePatterns; }; -export const WORKSPACE_FOLDER = workspace.workspaceFolders![0]; +export const WORKSPACE_FOLDER: WorkspaceFolder = workspace.workspaceFolders![0]; +export const WORKSPACE_SECOND_FOLDER: WorkspaceFolder | undefined = workspace.workspaceFolders![1]; + export const WORKSPACE_DIR = WORKSPACE_FOLDER.uri; +export const WORKSPACE_SECOND_DIR = WORKSPACE_SECOND_FOLDER?.uri; + const rootOxlintConfigUri = Uri.joinPath(WORKSPACE_DIR, '.oxlintrc.json'); +export function testSingleFolderMode(title: string, fn: Func) { + if (process.env['SINGLE_FOLDER_WORKSPACE'] !== 'true') { + return; + } + + test(`${title} (single folder workspace)`, fn); +} + +export function testMultiFolderMode(title: string, fn: Func) { + if (process.env['MULTI_FOLDER_WORKSPACE'] !== 'true') { + return; + } + + test(`${title} (multi folder workspace)`, fn); +} + export async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -export async function activateExtension(): Promise { +export async function activateExtension(full: boolean = true): Promise { const ext = extensions.getExtension('oxc.oxc-vscode')!; if (!ext.isActive) { await ext.activate(); } + + if (full) { + await loadFixture('debugger'); + const fileUri = Uri.joinPath(WORKSPACE_DIR, 'fixtures', 'debugger.js'); + await window.showTextDocument(fileUri); + // wait for initialized requests + await sleep(500); + await commands.executeCommand('workbench.action.closeActiveEditor'); + } } export async function createOxlintConfiguration(configuration: OxlintConfig): Promise { diff --git a/editors/vscode/tests/workspace_folders.spec.ts b/editors/vscode/tests/workspace_folders.spec.ts new file mode 100644 index 0000000000000..ba7ee9843d091 --- /dev/null +++ b/editors/vscode/tests/workspace_folders.spec.ts @@ -0,0 +1,36 @@ +import { strictEqual } from "assert"; +import { DiagnosticSeverity, Uri, workspace } from "vscode"; +import { activateExtension, getDiagnostics, loadFixture, sleep, testMultiFolderMode, WORKSPACE_DIR } from "./test-helpers"; +import assert = require("assert"); + +suiteSetup(async () => { + await activateExtension(); +}); + +const FIXTURES_URI = Uri.joinPath(WORKSPACE_DIR, '..', 'fixtures'); + +suite('Workspace Folders', () => { + testMultiFolderMode('shows diagnostics to newly adding folder', async () => { + await loadFixture('debugger'); + const folderDiagnostics = await getDiagnostics('debugger.js'); + + assert(typeof folderDiagnostics[0].code == 'object'); + strictEqual(folderDiagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual(folderDiagnostics[0].severity, DiagnosticSeverity.Warning); + + workspace.updateWorkspaceFolders(workspace.workspaceFolders?.length ?? 0, 0, { + name: 'fixtures', + uri: FIXTURES_URI + }); + + await sleep(500); + const thirdWorkspaceDiagnostics = await getDiagnostics('debugger/debugger.js', Uri.joinPath(FIXTURES_URI, '..')); + + assert(typeof thirdWorkspaceDiagnostics[0].code == 'object'); + strictEqual(thirdWorkspaceDiagnostics[0].code.target.authority, 'oxc.rs'); + strictEqual(thirdWorkspaceDiagnostics[0].severity, DiagnosticSeverity.Warning); + + // remove the workspace folder + workspace.updateWorkspaceFolders(workspace.workspaceFolders?.length ?? 0, 1); + }) +}) diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json index 098e7ba6ef597..bfad213f060b0 100644 --- a/editors/vscode/tsconfig.json +++ b/editors/vscode/tsconfig.json @@ -4,10 +4,9 @@ "target": "es2021", "lib": ["ES2021"], "outDir": "dist", - "rootDir": "client", "sourceMap": true, "strict": true }, - "include": ["./client"], + "include": ["./client", "./tests"], "exclude": ["node_modules", ".vscode-test"] }