Skip to content

Commit 5390451

Browse files
committed
chore: Cleanup and todo resolving
1 parent a36d8e2 commit 5390451

20 files changed

+226
-212
lines changed

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ This is the Mocha extension for VS Code enabling developers to run and debug tes
55
## Credits
66

77
This project started as a fork of the `Extension Test Runner` and `Command-line runner for VS Code tests` developed by Microsoft and then was adapted to work with Mocha directly.
8-
The main credits of this extension go over to the folks at Microsoft (and their contributors) and without them it would have been
9-
a lot more effort to ship a Mocha test runner for VS Code.
10-
11-
* https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner
12-
* https://github.com/microsoft/vscode-extension-test-runner
13-
* https://github.com/microsoft/vscode-test-cl
8+
The main credits of this extension go over to the folks at Microsoft (and their contributors) and without them it would have been
9+
a lot more effort to ship a Mocha test runner for VS Code.
1410

11+
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner
12+
- https://github.com/microsoft/vscode-extension-test-runner
13+
- https://github.com/microsoft/vscode-test-cl
1514

1615
## Getting Started
1716

@@ -26,6 +25,7 @@ This extension automatically discovers and works with the `.mocharc.js/cjs/yaml/
2625
- `mocha-vscode.extractSettings`: configures how tests get extracted. You can configure:
2726

2827
- The `extractWith` mode, that specifies if tests are extracted via evaluation or syntax-tree parsing. Evaluation is likely to lead to better results, but may have side-effects. Defaults to `evaluation`.
28+
- The `extractTimeout` limiting how long the extraction of tests for a single file is allowed to take.
2929
- The `test` and `suite` identifiers the process extracts. Defaults to `["it", "test"]` and `["describe", "suite"]` respectively, covering Mocha's common interfaces.
3030

3131
- `mocha-vscode.debugOptions`: options, normally found in the launch.json, to pass when debugging the extension. See [the docs](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes) for a complete list of options.

package.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"title": "Extension Test Runner",
3939
"properties": {
4040
"mocha-vscode.extractSettings": {
41-
"markdownDescription": "Configures how tests get extracted. You can configure:\n\n- The `extractWith` mode, that specifies if tests are extracted via evaluation or syntax-tree parsing.\n- The `test` and `suite` identifiers the process extracts.",
41+
"markdownDescription": "Configures how tests get extracted. You can configure:\n\n- The `extractWith` mode, that specifies if tests are extracted via evaluation or syntax-tree parsing.\n- The `extractTimeout` limiting how long the extraction of tests for a single file is allowed to take.\n- The `test` and `suite` identifiers the process extracts.",
4242
"type": "object",
4343
"properties": {
4444
"suite": {
@@ -59,6 +59,9 @@
5959
"evaluation",
6060
"syntax"
6161
]
62+
},
63+
"extractTimeout": {
64+
"type": "number"
6265
}
6366
},
6467
"default": {
@@ -70,12 +73,14 @@
7073
"it",
7174
"test"
7275
],
73-
"extractWith": "evaluation"
76+
"extractWith": "evaluation",
77+
"extractTimeout": 10000
7478
},
7579
"required": [
7680
"suite",
7781
"test",
78-
"extractWith"
82+
"extractWith",
83+
"extractTimeout"
7984
]
8085
},
8186
"mocha-vscode.debugOptions": {

src/configurationFile.ts

+33-31
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import { DisposableStore } from './disposable';
1414
import { HumanError } from './errors';
1515

1616
type OptionsModule = {
17-
loadOptions(): IResolvedConfiguration
17+
loadOptions(): IResolvedConfiguration;
1818
};
1919

2020
type ConfigModule = {
21-
findConfig(): string
21+
findConfig(): string;
2222
};
2323

24-
export type IResolvedConfiguration = Mocha.MochaOptions & { "_": string[] | undefined, "node-option": string[] | undefined }
24+
export type IResolvedConfiguration = Mocha.MochaOptions & {
25+
_: string[] | undefined;
26+
'node-option': string[] | undefined;
27+
};
2528

2629
export class ConfigurationFile implements vscode.Disposable {
2730
private readonly ds = new DisposableStore();
@@ -89,29 +92,20 @@ export class ConfigurationFile implements vscode.Disposable {
8992
// We cannot use process.execPath as this points to code.exe which is an electron application
9093
// also with ELECTRON_RUN_AS_NODE this can lead to errors (e.g. with the --import option)
9194
// we prefer to use the system level node
92-
this._pathToNode ??= (await which("node", { nothrow: true })) ?? process.execPath;
95+
this._pathToNode ??= (await which('node', { nothrow: true })) ?? process.execPath;
9396
return this._pathToNode;
9497
}
9598

9699
async getMochaSpawnArgs(customArgs: readonly string[]): Promise<string[]> {
97-
// TODO: resolve from package.json?
98100
this._pathToMocha ??= await this._resolveLocalMochaPath('/bin/mocha.js');
99101

100-
return [await this.getPathToNode(), this._pathToMocha, '--config', this.uri.fsPath, ...customArgs];
101-
}
102-
103-
public async spawnMocha(args: readonly string[]) {
104-
105-
const spawnArgs = await this.getMochaSpawnArgs(args);
106-
107-
return await new Promise<ChildProcessWithoutNullStreams>((resolve, reject) => {
108-
const p = spawn(spawnArgs[0], spawnArgs.slice(1), {
109-
cwd: path.dirname(this.uri.fsPath),
110-
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
111-
});
112-
p.on('spawn', () => resolve(p));
113-
p.on('error', reject);
114-
});
102+
return [
103+
await this.getPathToNode(),
104+
this._pathToMocha,
105+
'--config',
106+
this.uri.fsPath,
107+
...customArgs,
108+
];
115109
}
116110

117111
private async _resolveLocalMochaPath(suffix?: string): Promise<string> {
@@ -124,11 +118,17 @@ export class ConfigurationFile implements vscode.Disposable {
124118
this._resolver!.resolve(
125119
{},
126120
path.dirname(this.uri.fsPath),
127-
'mocha' + (suffix ?? ""),
121+
'mocha' + (suffix ?? ''),
128122
{},
129123
(err, res) => {
130124
if (err) {
131-
reject(new HumanError(`Could not find mocha in working directory '${path.dirname(this.uri.fsPath)}', please install mocha to run tests.`));
125+
reject(
126+
new HumanError(
127+
`Could not find mocha in working directory '${path.dirname(
128+
this.uri.fsPath,
129+
)}', please install mocha to run tests.`,
130+
),
131+
);
132132
} else {
133133
resolve(res as string);
134134
}
@@ -138,13 +138,17 @@ export class ConfigurationFile implements vscode.Disposable {
138138
}
139139

140140
private async _read() {
141-
this._optionsModule ??= require(await this._resolveLocalMochaPath('/lib/cli/options')) as OptionsModule;
142-
this._configModule ??= require(await this._resolveLocalMochaPath('/lib/cli/config')) as ConfigModule;
141+
this._optionsModule ??= require(
142+
await this._resolveLocalMochaPath('/lib/cli/options'),
143+
) as OptionsModule;
144+
this._configModule ??= require(
145+
await this._resolveLocalMochaPath('/lib/cli/config'),
146+
) as ConfigModule;
143147
let config: IResolvedConfiguration;
144148

145-
// need to change to the working dir for loading the config,
149+
// need to change to the working dir for loading the config,
146150
// TODO[mocha]: allow specifying the cwd in loadOptions()
147-
const currentCwd = process.cwd();;
151+
const currentCwd = process.cwd();
148152
try {
149153
process.chdir(path.dirname(this.uri.fsPath));
150154

@@ -154,14 +158,12 @@ export class ConfigurationFile implements vscode.Disposable {
154158
try {
155159
const resolved = require.resolve(configFile);
156160
delete require.cache[resolved];
157-
}
158-
catch (e) {
161+
} catch (e) {
159162
// ignore
160163
}
161164

162165
config = this._optionsModule.loadOptions();
163-
}
164-
finally {
166+
} finally {
165167
process.chdir(currentCwd);
166168
}
167169

@@ -199,7 +201,7 @@ export class ConfigurationList {
199201
positional = ['./test/*.{js,cjs,mjs}'];
200202
}
201203

202-
this.patterns = positional.map(f => {
204+
this.patterns = positional.map((f) => {
203205
if (path.isAbsolute(f)) {
204206
return { glob: false, value: path.normalize(f) };
205207
} else {

src/constants.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const defaultTestSymbols: ITestSymbols = {
1313
suite: ['describe', 'suite'],
1414
test: ['it', 'test'],
1515
extractWith: 'evaluation',
16+
extractTimeout: 10_000,
1617
};
1718

1819
export const showConfigErrorCommand = 'mocha-vscode.showConfigError';
@@ -25,12 +26,17 @@ function equalsIgnoreCase(a: string, b: string) {
2526
export function isTypeScript(filePath: string) {
2627
const ext = path.extname(filePath);
2728
// TODO: configuration for this extension list?
28-
return equalsIgnoreCase(ext, '.ts') || equalsIgnoreCase(ext, '.mts') || equalsIgnoreCase(ext, '.tsx') || equalsIgnoreCase(ext, '.cts') || equalsIgnoreCase(ext, '.jsx');
29+
return (
30+
equalsIgnoreCase(ext, '.ts') ||
31+
equalsIgnoreCase(ext, '.mts') ||
32+
equalsIgnoreCase(ext, '.tsx') ||
33+
equalsIgnoreCase(ext, '.cts') ||
34+
equalsIgnoreCase(ext, '.jsx')
35+
);
2936
}
3037

31-
3238
export function isEsm(filePath: string, code: string): boolean {
3339
const ext = path.extname(filePath);
3440
// very basic detection
35-
return equalsIgnoreCase(ext, '.mjs') || code.includes("import ") || code.includes("export ");
36-
}
41+
return equalsIgnoreCase(ext, '.mjs') || code.includes('import ') || code.includes('export ');
42+
}

src/controller.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class Controller {
6060
}
6161

6262
constructor(
63+
private readonly logChannel: vscode.LogOutputChannel,
6364
public readonly ctrl: vscode.TestController,
6465
private readonly wf: vscode.WorkspaceFolder,
6566
private readonly smStore: SourceMapStore,
@@ -110,7 +111,7 @@ export class Controller {
110111

111112
let tree: IParsedNode[];
112113
try {
113-
tree = await extract(uri.fsPath, contents, this.extractMode.value);
114+
tree = await extract(this.logChannel, uri.fsPath, contents, this.extractMode.value);
114115
} catch (e) {
115116
this.deleteFileTests(uri.toString());
116117
return;
@@ -175,8 +176,7 @@ export class Controller {
175176
node.endLine !== undefined && node.endColumn !== undefined
176177
? sourceMap.originalPositionFor(node.endLine, node.endColumn)
177178
: start;
178-
const file = last(this.getContainingItemsForFile(start.uri, { compiledFile: uri }))!
179-
.item!;
179+
const file = last(this.getContainingItemsForFile(start.uri, { compiledFile: uri }))!.item!;
180180
diagnosticCollection.delete(start.uri);
181181
newTestsInFile.set(node.name, add(file, node, start, end));
182182
}
@@ -280,14 +280,14 @@ export class Controller {
280280
if (prev) {
281281
this.runProfiles.set(name, prev);
282282
oldRunHandlers.delete(name);
283-
return
283+
return;
284284
}
285285

286286
const run = this.runner.makeHandler(this.ctrl, this.configFile, false);
287287
const debug = this.runner.makeHandler(this.ctrl, this.configFile, true);
288288
const profiles = [
289289
this.ctrl.createRunProfile(name, vscode.TestRunProfileKind.Run, run, true),
290-
this.ctrl.createRunProfile(name, vscode.TestRunProfileKind.Debug, debug, true)
290+
this.ctrl.createRunProfile(name, vscode.TestRunProfileKind.Debug, debug, true),
291291
];
292292

293293
this.runProfiles.set(name, profiles);

src/errors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------*/
55

66
/** Errors with a human-readable message. */
7-
export class HumanError extends Error { }
7+
export class HumanError extends Error {}
88

99
export class ConfigProcessReadError extends HumanError {
1010
constructor(output: string) {

src/extension.ts

+13-23
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ const enum FolderSyncState {
2323
}
2424

2525
export function activate(context: vscode.ExtensionContext) {
26+
const logChannel = vscode.window.createOutputChannel('Mocha VS Code Extension', { log: true });
27+
2628
const smStore = new SourceMapStore();
27-
const runner = new TestRunner(smStore, new ConfigValue('debugOptions', {}));
29+
const runner = new TestRunner(logChannel, smStore, new ConfigValue('debugOptions', {}));
2830

2931
let ctrls: Controller[] = [];
3032
let resyncState: FolderSyncState = FolderSyncState.Idle;
3133

3234
const syncWorkspaceFolders = async () => {
35+
logChannel.debug('Syncing workspace folders', resyncState);
36+
3337
if (resyncState === FolderSyncState.Syncing) {
3438
resyncState = FolderSyncState.ReSyncNeeded;
3539
}
@@ -45,8 +49,12 @@ export function activate(context: vscode.ExtensionContext) {
4549
await Promise.all(
4650
folders.map(async (folder) => {
4751
const files = await vscode.workspace.findFiles(
48-
new vscode.RelativePattern(folder, configFilePattern), '**/node_modules/**'
52+
new vscode.RelativePattern(folder, configFilePattern),
53+
'**/node_modules/**',
4954
);
55+
56+
logChannel.debug('Checking workspace folder for config files', folder);
57+
5058
for (const file of files) {
5159
const rel = path.relative(folder.uri.fsPath, path.dirname(file.fsPath));
5260
const ctrl = vscode.tests.createTestController(
@@ -58,7 +66,7 @@ export function activate(context: vscode.ExtensionContext) {
5866
: folder.name,
5967
);
6068

61-
ctrls.push(new Controller(ctrl, folder, smStore, file, runner));
69+
ctrls.push(new Controller(logChannel, ctrl, folder, smStore, file, runner));
6270
}
6371
}),
6472
);
@@ -71,24 +79,6 @@ export function activate(context: vscode.ExtensionContext) {
7179
}
7280
};
7381

74-
const openUntitledEditor = async (contents: string) => {
75-
const untitledDoc = await vscode.workspace.openTextDocument({ content: contents });
76-
await vscode.window.showTextDocument(untitledDoc);
77-
};
78-
79-
const showConfigError = async (configUriStr: string) => {
80-
const configUri = vscode.Uri.parse(configUriStr);
81-
const ctrl = ctrls.find((c) => c.configFile.uri.toString() === configUri.toString());
82-
try {
83-
await ctrl?.configFile.read();
84-
} catch (e) {
85-
await openUntitledEditor(String(e));
86-
return;
87-
}
88-
89-
vscode.window.showInformationMessage('No configuration error detected');
90-
};
91-
9282
const initialSync = (async () => {
9383
// Workaround for vscode#179203 where findFiles doesn't work on startup.
9484
// This extension is only activated on workspaceContains, so we have pretty
@@ -105,12 +95,12 @@ export function activate(context: vscode.ExtensionContext) {
10595

10696
context.subscriptions.push(
10797
vscode.workspace.onDidChangeWorkspaceFolders(syncWorkspaceFolders),
108-
vscode.commands.registerCommand(showConfigErrorCommand, showConfigError),
10998
vscode.commands.registerCommand(getControllersForTestCommand, () =>
11099
initialSync.then(() => ctrls),
111100
),
112101
new vscode.Disposable(() => ctrls.forEach((c) => c.dispose())),
102+
logChannel,
113103
);
114104
}
115105

116-
export function deactivate() { }
106+
export function deactivate() {}

0 commit comments

Comments
 (0)