Skip to content

Commit 92dca83

Browse files
Merge branch 'main' into fix-empty-test-cases-issue
2 parents c11cc70 + befff3d commit 92dca83

File tree

9 files changed

+553
-765
lines changed

9 files changed

+553
-765
lines changed

package-lock.json

Lines changed: 304 additions & 702 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Mocha for VS Code",
44
"description": "Run and debug Mocha tests right within VS Code.",
55
"publisher": "coderline",
6-
"version": "1.2.0",
6+
"version": "1.2.1",
77
"icon": "icon.png",
88
"engines": {
99
"vscode": "^1.83.0"
@@ -158,14 +158,12 @@
158158
"prettier": "^3.3.3",
159159
"prettier-eslint": "^16.3.0",
160160
"prettier-eslint-cli": "^8.0.1",
161-
"prettier-plugin-organize-imports": "^4.0.0",
161+
"prettier-plugin-organize-imports": "^4.1.0",
162162
"sinon": "^19.0.2",
163163
"ts-node": "^10.9.2",
164-
"tsx": "^4.19.0",
164+
"tsx": "^4.19.1",
165165
"typescript": "^5.6.2",
166-
"typescript-eslint": "^8.6.0"
167-
},
168-
"dependencies": {
166+
"typescript-eslint": "^8.6.0",
169167
"@jridgewell/trace-mapping": "^0.3.25",
170168
"@types/which": "^3.0.4",
171169
"@typescript-eslint/typescript-estree": "^8.7.0",
@@ -174,7 +172,6 @@
174172
"data-uri-to-buffer": "^6.0.2",
175173
"enhanced-resolve": "^5.17.1",
176174
"error-stack-parser": "^2.1.4",
177-
"esbuild": "^0.24.0",
178175
"eslint-visitor-keys": "^4.1.0",
179176
"glob": "^11.0.0",
180177
"minimatch": "^10.0.1",
@@ -183,6 +180,9 @@
183180
"supports-color": "^9.4.0",
184181
"which": "^5.0.0"
185182
},
183+
"dependencies": {
184+
"esbuild": "^0.24.0"
185+
},
186186
"mocha-vscode": {
187187
"version": "1.1.0-preview+FFFFFFF",
188188
"date": "2024-04-02T14:30:00Z"

src/controller.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ export class Controller {
211211
try {
212212
tree = await this.discoverer.discover(uri.fsPath, contents);
213213
} catch (e) {
214-
this.logChannel.error('Error while test extracting ', e);
214+
this.logChannel.error(
215+
'Error while test extracting ',
216+
(e as Error).message,
217+
(e as Error).stack,
218+
);
215219
this.deleteFileTests(uri.toString());
216220

217221
const errorFile = last(this.getContainingItemsForFile(uri, { compiledFile: uri }))!.item!;

src/discoverer/evaluate.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,32 @@ export class EvaluationTestDiscoverer implements ITestDiscoverer {
6262
function placeholder(): unknown {
6363
return new Proxy(placeholder, {
6464
get: (obj, target) => {
65-
const desc = Object.getOwnPropertyDescriptor(obj, target);
66-
if (desc && !desc.writable && !desc.configurable) {
67-
return desc.value; // avoid invariant volation https://stackoverflow.com/q/75148897
65+
try {
66+
const desc = Object.getOwnPropertyDescriptor(obj, target);
67+
if (desc && !desc.writable && !desc.configurable) {
68+
return desc.value; // avoid invariant volation https://stackoverflow.com/q/75148897
69+
}
70+
return placeholder();
71+
} catch (e) {
72+
return placeholder();
6873
}
74+
},
75+
set: () => true,
76+
apply: () => {
6977
return placeholder();
7078
},
79+
});
80+
}
81+
82+
function objectPlaceholder(originalObject: any): unknown {
83+
return new Proxy(objectPlaceholder, {
84+
get: (_, target) => {
85+
if (target === 'create') {
86+
return placeholder();
87+
} else {
88+
return originalObject[target];
89+
}
90+
},
7191
set: () => true,
7292
});
7393
}
@@ -186,6 +206,15 @@ export class EvaluationTestDiscoverer implements ITestDiscoverer {
186206
} else if (prop in target) {
187207
return target[prop]; // top-level `var` defined get set on the contextObj
188208
} else if (prop in globalThis && !replacedGlobals.has(prop as string)) {
209+
// Bug #153: ESBuild will wrap require() calls into __toESM which breaks quite some things
210+
// we want to keep our Proxy placeholder object in all scenarios
211+
// Due to that we provide a special proxy object which will create again placeholder proxies
212+
// on Object.create
213+
// https://github.com/evanw/esbuild/blob/d34e79e2a998c21bb71d57b92b0017ca11756912/internal/runtime/runtime.go#L231-L242
214+
if (prop === 'Object') {
215+
return objectPlaceholder((globalThis as any)[prop]);
216+
}
217+
189218
return (globalThis as any)[prop];
190219
} else {
191220
return placeholder();

src/esbuild.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/**
2+
* Copyright (C) Daniel Kuschny (Danielku15) and contributors.
3+
* Copyright (C) Microsoft Corporation. All rights reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style
6+
* license that can be found in the LICENSE file or at
7+
* https://opensource.org/licenses/MIT.
8+
*/
9+
10+
import { exec } from 'node:child_process';
11+
import fs from 'node:fs';
12+
import os from 'node:os';
13+
import path from 'node:path';
14+
import * as vscode from 'vscode';
15+
16+
// this logic is aligned with the ESBuild install script, unfortunately we cannot use pkgAndSubpathForCurrentPlatform directly
17+
// so we have a copy of the logic, we should reach out to ESBUild to export the API from install.js
18+
19+
type PlatformLookup = { [platform: string]: string };
20+
21+
const knownWindowsPackages: PlatformLookup = {
22+
'win32 arm64 LE': '@esbuild/win32-arm64',
23+
'win32 ia32 LE': '@esbuild/win32-ia32',
24+
'win32 x64 LE': '@esbuild/win32-x64',
25+
};
26+
const knownUnixlikePackages: PlatformLookup = {
27+
'aix ppc64 BE': '@esbuild/aix-ppc64',
28+
'android arm64 LE': '@esbuild/android-arm64',
29+
'darwin arm64 LE': '@esbuild/darwin-arm64',
30+
'darwin x64 LE': '@esbuild/darwin-x64',
31+
'freebsd arm64 LE': '@esbuild/freebsd-arm64',
32+
'freebsd x64 LE': '@esbuild/freebsd-x64',
33+
'linux arm LE': '@esbuild/linux-arm',
34+
'linux arm64 LE': '@esbuild/linux-arm64',
35+
'linux ia32 LE': '@esbuild/linux-ia32',
36+
'linux mips64el LE': '@esbuild/linux-mips64el',
37+
'linux ppc64 LE': '@esbuild/linux-ppc64',
38+
'linux riscv64 LE': '@esbuild/linux-riscv64',
39+
'linux s390x BE': '@esbuild/linux-s390x',
40+
'linux x64 LE': '@esbuild/linux-x64',
41+
'linux loong64 LE': '@esbuild/linux-loong64',
42+
'netbsd x64 LE': '@esbuild/netbsd-x64',
43+
'openbsd arm64 LE': '@esbuild/openbsd-arm64',
44+
'openbsd x64 LE': '@esbuild/openbsd-x64',
45+
'sunos x64 LE': '@esbuild/sunos-x64',
46+
};
47+
const knownWebAssemblyFallbackPackages: PlatformLookup = {
48+
'android arm LE': '@esbuild/android-arm',
49+
'android x64 LE': '@esbuild/android-x64',
50+
};
51+
function getPlatformPackageName() {
52+
let pkg: string;
53+
const platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`;
54+
if (platformKey in knownWindowsPackages) {
55+
pkg = knownWindowsPackages[platformKey];
56+
} else if (platformKey in knownUnixlikePackages) {
57+
pkg = knownUnixlikePackages[platformKey];
58+
} else if (platformKey in knownWebAssemblyFallbackPackages) {
59+
pkg = knownWebAssemblyFallbackPackages[platformKey];
60+
} else {
61+
throw new Error(`Unsupported platform: ${platformKey}`);
62+
}
63+
return pkg;
64+
}
65+
66+
export async function esbuildPackageVersion() {
67+
let pkg: string;
68+
const platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`;
69+
if (platformKey in knownWindowsPackages) {
70+
pkg = knownWindowsPackages[platformKey];
71+
} else if (platformKey in knownUnixlikePackages) {
72+
pkg = knownUnixlikePackages[platformKey];
73+
} else if (platformKey in knownWebAssemblyFallbackPackages) {
74+
pkg = knownWebAssemblyFallbackPackages[platformKey];
75+
} else {
76+
throw new Error(`Unsupported platform: ${platformKey}`);
77+
}
78+
return pkg;
79+
}
80+
81+
// ESBuild needs the platform specific binary for execution
82+
// here we run the init script coming with ESBuild
83+
export async function initESBuild(
84+
context: vscode.ExtensionContext,
85+
logChannel: vscode.LogOutputChannel,
86+
) {
87+
logChannel.debug('Checking ESBuild availability');
88+
89+
const platformPackageName = getPlatformPackageName();
90+
let platformPackageAndVersion: string;
91+
try {
92+
logChannel.debug('Determining ESBuild platform package and version');
93+
const packageJson = JSON.parse(
94+
await fs.promises.readFile(
95+
path.join(context.extensionPath, 'node_modules', 'esbuild', 'package.json'),
96+
'utf-8',
97+
),
98+
);
99+
100+
let packageVersion = packageJson.optionalDependencies?.[platformPackageName];
101+
if (!packageVersion) {
102+
packageVersion = packageJson.version;
103+
}
104+
platformPackageAndVersion = `${platformPackageName}@${packageVersion}`;
105+
logChannel.debug(`Determined ESBuild platform package ${platformPackageAndVersion}`);
106+
107+
// check if already installed
108+
const platformPackageJsonPath = path.join(
109+
context.extensionPath,
110+
'node_modules',
111+
...platformPackageName.split('/'),
112+
'package.json',
113+
);
114+
if (fs.existsSync(platformPackageJsonPath)) {
115+
try {
116+
const platformPackageJson = JSON.parse(
117+
await fs.promises.readFile(platformPackageJsonPath, 'utf-8'),
118+
);
119+
if (platformPackageJson.version === packageVersion) {
120+
logChannel.debug(
121+
`Determining ESBuild platform package ${platformPackageAndVersion} already installed, skipping install`,
122+
);
123+
return;
124+
}
125+
} catch (e) {
126+
// ignore and trigger install
127+
}
128+
}
129+
} catch (e) {
130+
logChannel.error(
131+
`Failed to determine ESBuild platform package`,
132+
(e as Error).message,
133+
(e as Error).stack,
134+
);
135+
return;
136+
}
137+
138+
const args = [
139+
'install',
140+
'--no-save',
141+
'--omit=dev',
142+
'--omit=optional',
143+
'--omit=peer',
144+
'--prefer-offline',
145+
'--no-audit',
146+
'--progress=false',
147+
platformPackageAndVersion,
148+
];
149+
logChannel.debug(`Running npm install ${args.join(' ')}`);
150+
await new Promise<void>((resolve, reject) => {
151+
exec(
152+
`npm ${args.join(' ')}`,
153+
{
154+
cwd: context.extensionPath,
155+
env: {
156+
...process.env,
157+
ELECTRON_RUN_AS_NODE: '1',
158+
},
159+
windowsHide: true,
160+
},
161+
(error, stdout, stderr) => {
162+
if (stdout) {
163+
logChannel.debug('[ESBuild-stdout]', stdout);
164+
}
165+
if (stderr) {
166+
logChannel.debug('[ESBuild-stderr]', stderr);
167+
}
168+
169+
if (error) {
170+
reject(error);
171+
} else {
172+
resolve();
173+
}
174+
},
175+
);
176+
});
177+
}

src/extension.ts

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
* https://opensource.org/licenses/MIT.
88
*/
99

10-
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
11-
import path from 'path';
12-
import split2 from 'split2';
1310
import * as timers from 'timers/promises';
1411
import * as vscode from 'vscode';
1512
import { ConfigValue } from './configValue';
1613
import { ConsoleOuputChannel } from './consoleLogChannel';
1714
import { getControllersForTestCommand } from './constants';
18-
import { getPathToNode } from './node';
15+
import { initESBuild } from './esbuild';
1916
import { TestRunner } from './runner';
2017
import { SourceMapStore } from './source-map-store';
2118
import { WorkspaceFolderWatcher } from './workspaceWatcher';
@@ -136,42 +133,3 @@ export function activate(context: vscode.ExtensionContext) {
136133
}
137134

138135
export function deactivate() {}
139-
140-
// ESBuild needs the platform specific binary for execution
141-
// here we run the init script coming with ESBuild
142-
async function initESBuild(context: vscode.ExtensionContext, logChannel: vscode.LogOutputChannel) {
143-
logChannel.debug('Installing ESBuild binary');
144-
145-
const node = await getPathToNode(logChannel);
146-
const cli = await new Promise<ChildProcessWithoutNullStreams>((resolve, reject) => {
147-
const p = spawn(node, ['install.js'], {
148-
cwd: path.join(context.extensionPath, 'node_modules', 'esbuild'),
149-
env: {
150-
...process.env,
151-
ELECTRON_RUN_AS_NODE: '1',
152-
},
153-
windowsHide: true,
154-
});
155-
p.on('spawn', () => resolve(p));
156-
p.on('error', reject);
157-
});
158-
159-
cli.stderr.pipe(split2()).on('data', (l) => {
160-
logChannel.debug('[ESBuild-stderr]', l);
161-
});
162-
cli.stdout.pipe(split2()).on('data', (l) => {
163-
logChannel.debug('[ESBuild-stdout]', l);
164-
});
165-
166-
await new Promise<void>((resolve, reject) => {
167-
cli.on('error', reject);
168-
cli.on('exit', (code) => {
169-
if (code === 0) {
170-
logChannel.debug(`Installing ESBuild binary exited with code ${code}`);
171-
} else {
172-
logChannel.error(`Installing ESBuild binary exited with code ${code}`);
173-
}
174-
resolve();
175-
});
176-
});
177-
}

src/node.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,35 @@
1010
import * as vscode from 'vscode';
1111
import which from 'which';
1212

13+
export async function getPathTo(logChannel: vscode.LogOutputChannel, bin: string, name: string) {
14+
logChannel.debug(`Resolving ${name} executable`);
15+
let pathToBin = await which(bin, { nothrow: true });
16+
if (pathToBin) {
17+
logChannel.debug(`Found ${name} in PATH at '${pathToBin}'`);
18+
} else {
19+
pathToBin = process.execPath;
20+
logChannel.debug(`${name} not found in PATH using '${pathToBin}' as fallback`);
21+
}
22+
return pathToBin;
23+
}
24+
1325
let pathToNode: string | null = null;
1426

1527
export async function getPathToNode(logChannel: vscode.LogOutputChannel) {
1628
// We cannot use process.execPath as this points to code.exe which is an electron application
1729
// also with ELECTRON_RUN_AS_NODE this can lead to errors (e.g. with the --import option)
1830
// we prefer to use the system level node
1931
if (!pathToNode) {
20-
logChannel.debug('Resolving Node.js executable');
21-
pathToNode = await which('node', { nothrow: true });
22-
if (pathToNode) {
23-
logChannel.debug(`Found Node.js in PATH at '${pathToNode}'`);
24-
} else {
25-
pathToNode = process.execPath;
26-
logChannel.debug(`Node.js not found in PATH using '${pathToNode}' as fallback`);
27-
}
32+
pathToNode = await getPathTo(logChannel, 'node', 'Node.js');
2833
}
2934
return pathToNode;
3035
}
36+
37+
let pathToNpm: string | null = null;
38+
39+
export async function getPathToNpm(logChannel: vscode.LogOutputChannel) {
40+
if (!pathToNpm) {
41+
pathToNpm = await getPathTo(logChannel, 'npm', 'NPM');
42+
}
43+
return pathToNpm;
44+
}

0 commit comments

Comments
 (0)