unexpectedly 1.0.0 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/bin/unexpectedly.js +19 -14
- package/eslint.config.js +26 -0
- package/index.js +69 -34
- package/package.json +5 -27
- package/runner.js +89 -100
- package/testFile.peg.js +413 -366
- package/testFile.peggy +111 -0
- package/vendor/peggy/LICENSE +21 -0
- package/vendor/peggy/grammar-location.cjs +81 -0
- package/vendor/peggy/rationale.md +4 -0
- package/version.js +4 -0
- package/modtype.js +0 -60
package/README.md
CHANGED
|
@@ -11,13 +11,20 @@ npm install -g unexpectedly
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
unexpectedly [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
Usage: unexpectedly [options] [...file]
|
|
15
|
+
|
|
16
|
+
Arguments:
|
|
17
|
+
...file Files or directories to test (default:
|
|
18
|
+
"./test/")
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-V, --version output the version number
|
|
22
|
+
-d,--defaultScript <replacement> Find the script from the file name.
|
|
23
|
+
Replace `$<base>` with the basename of the
|
|
24
|
+
file. (default: "../$<base>.js")
|
|
25
|
+
-f,--function <functionName> Use this function for testing in the
|
|
26
|
+
associated script (default: "test")
|
|
27
|
+
-h, --help display help for command
|
|
21
28
|
```
|
|
22
29
|
|
|
23
30
|
## Example
|
|
@@ -63,8 +70,10 @@ unexpectedly tests/
|
|
|
63
70
|
```js
|
|
64
71
|
import {suite} from 'unexpectedly';
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
await suite(directoryOrFile, {
|
|
74
|
+
defaultScript: '../$<base>.js',
|
|
75
|
+
function: 'test',
|
|
76
|
+
}).catch(console.error)
|
|
68
77
|
```
|
|
69
78
|
|
|
70
79
|
## Test language
|
|
@@ -101,6 +110,9 @@ Special globals include:
|
|
|
101
110
|
instead of the default
|
|
102
111
|
- "timeout": If any test in the file takes longer than this (in ms),
|
|
103
112
|
it fails. Default: 2000
|
|
113
|
+
- "peggy": Treat the input as a compiled peggy grammar; look for a "parse"
|
|
114
|
+
function in the input script, provide better errors, and use the value
|
|
115
|
+
of the peggy global (if any) as the startRule.
|
|
104
116
|
|
|
105
117
|
Environment variables can be set for the test code with `#!! name: value`.
|
|
106
118
|
|
package/bin/unexpectedly.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --experimental-vm-modules --no-warnings
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import {Command} from 'commander';
|
|
4
3
|
import {suite} from '../index.js';
|
|
4
|
+
import {version} from '../version.js';
|
|
5
5
|
|
|
6
6
|
let failures = 0;
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
const program = new Command();
|
|
10
|
+
const opts = program
|
|
11
|
+
.version(version)
|
|
12
|
+
.argument('[file...]', 'Files or directories to test', './test/')
|
|
13
|
+
.option('-d,--defaultScript <replacement>', 'Find the script from the file name. Replace `$<base>` with the basename of the file.', '../$<base>.js')
|
|
14
|
+
.option('-f,--function <functionName>', 'Use this function for testing in the associated script', 'test')
|
|
15
|
+
.parse()
|
|
16
|
+
.opts();
|
|
17
|
+
|
|
18
|
+
const {args} = program;
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
args.push('./test/');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const arg of args) {
|
|
24
|
+
// eslint-disable-next-line require-atomic-updates
|
|
25
|
+
failures += await suite(arg, opts);
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import base from '@cto.af/eslint-config';
|
|
2
|
+
import mocha from '@cto.af/eslint-config/mocha.js';
|
|
3
|
+
import mod from '@cto.af/eslint-config/module.js';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: [
|
|
8
|
+
'docs/**',
|
|
9
|
+
'coverage/**',
|
|
10
|
+
'**/*.peg.js',
|
|
11
|
+
'**/*.d.ts',
|
|
12
|
+
'test/pkg_bad/package.json',
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
...base,
|
|
16
|
+
...mod,
|
|
17
|
+
...mocha,
|
|
18
|
+
{
|
|
19
|
+
files: [
|
|
20
|
+
'test/**/*.js',
|
|
21
|
+
],
|
|
22
|
+
rules: {
|
|
23
|
+
'n/no-top-level-await': 'off',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
];
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import {Runner, SKIPPED} from './runner.js';
|
|
1
2
|
import {hexlify, mapObj} from './utils.js';
|
|
2
3
|
import Mocha from 'mocha';
|
|
3
|
-
import {Runner} from './runner.js';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
import {parse} from './testFile.peg.js';
|
|
@@ -13,6 +13,9 @@ async function coerce(actual, pt, runner) {
|
|
|
13
13
|
if (typeof actual === 'string') {
|
|
14
14
|
return [actual, expected];
|
|
15
15
|
}
|
|
16
|
+
if (actual === SKIPPED) {
|
|
17
|
+
return [SKIPPED, SKIPPED];
|
|
18
|
+
}
|
|
16
19
|
if (actual && (typeof actual === 'object') && actual.constructor) {
|
|
17
20
|
if (/^0x/.test(expected)) {
|
|
18
21
|
const {name} = actual.constructor;
|
|
@@ -42,18 +45,30 @@ async function coerce(actual, pt, runner) {
|
|
|
42
45
|
return [actual, def];
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} SuiteOptions
|
|
50
|
+
* @property {string} [defaultScript='../$<base>.js'] Find the script from the
|
|
51
|
+
* file name. Replace `$<base>` with the basename of the file. For
|
|
52
|
+
* example, "foo.test" will use "../foo.js" by default.
|
|
53
|
+
* @property {string} [function='test'] Use this function for testing in the
|
|
54
|
+
* associated script.
|
|
55
|
+
*/
|
|
56
|
+
|
|
45
57
|
/**
|
|
46
58
|
* Run a test suite from a file or directory.
|
|
47
59
|
*
|
|
48
60
|
* @param {string} [target='.'] The file or directory to read. If a
|
|
49
61
|
* directory, reads all files ending in `.tests`.
|
|
50
|
-
* @param {
|
|
51
|
-
* file name. Replace $1 with the basename of the file. For example,
|
|
52
|
-
* "foo.test" will use "../foo.js" by default.
|
|
62
|
+
* @param {SuiteOptions} [options={}] Options for processing the suite.
|
|
53
63
|
* @returns {Promise<number>} Promise fulfills on completion, with number of
|
|
54
64
|
* test failures.
|
|
55
65
|
*/
|
|
56
|
-
export async function suite(target = '.',
|
|
66
|
+
export async function suite(target = '.', options = {}) {
|
|
67
|
+
options = {
|
|
68
|
+
defaultScript: '../$<base>.js',
|
|
69
|
+
function: 'test',
|
|
70
|
+
...options,
|
|
71
|
+
};
|
|
57
72
|
let dir = path.resolve(target);
|
|
58
73
|
let files = [dir];
|
|
59
74
|
const mocha = new Mocha();
|
|
@@ -90,42 +105,61 @@ export async function suite(target = '.', defaultScript = '../$<base>.js') {
|
|
|
90
105
|
throw er;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
text =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
for (const pt of parsed) {
|
|
109
|
+
const opts = {
|
|
110
|
+
testFunction: options.function,
|
|
111
|
+
};
|
|
112
|
+
let text = '';
|
|
113
|
+
if (pt.vars.inline) {
|
|
114
|
+
opts.filename = f;
|
|
115
|
+
text = pt.vars.inline.value;
|
|
116
|
+
opts.lineOffset = pt.vars.inline.line - 1;
|
|
117
|
+
opts.columnOffset = pt.vars.inline.column;
|
|
118
|
+
} else {
|
|
119
|
+
if (pt.vars.script) {
|
|
120
|
+
opts.filename = path.resolve(dir, pt.vars.script.value);
|
|
121
|
+
} else {
|
|
122
|
+
// Avoid a previous ReDOS regexp.
|
|
123
|
+
const p = path.parse(f);
|
|
124
|
+
if (/^\.tests?$/.test(p.ext)) {
|
|
125
|
+
delete p.base;
|
|
126
|
+
delete p.ext;
|
|
127
|
+
p.name = p.name.replace(/^(?<base>.*)$/, options.defaultScript);
|
|
128
|
+
opts.filename = path.resolve(dir, path.format(p));
|
|
129
|
+
} else {
|
|
130
|
+
opts.filename = path.resolve(dir, f);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
text = await fs.readFile(opts.filename, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
if (pt.vars.timeout) {
|
|
136
|
+
msuite.timeout(parseInt(pt.vars.timeout.value, 10));
|
|
137
|
+
}
|
|
138
|
+
opts.context = mapObj(
|
|
139
|
+
Object.entries(pt.vars).filter(([_k, v]) => !v.env),
|
|
140
|
+
([key, value]) => [key, value.value]
|
|
141
|
+
);
|
|
142
|
+
opts.env = mapObj(
|
|
143
|
+
Object.entries(pt.vars).filter(([_k, v]) => v.env),
|
|
144
|
+
([key, value]) => [key, value.value]
|
|
145
|
+
);
|
|
118
146
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
147
|
+
const runner = new Runner(opts);
|
|
148
|
+
|
|
149
|
+
const firstLine = (pt.expected === EXCEPTION) ? `! ${pt.inputs[0]}` : pt.expected.split(/\n/)[0];
|
|
150
|
+
const t = new Mocha.Test(`line ${pt.line}: ${firstLine || '""'}`, async () => {
|
|
122
151
|
let actual = null;
|
|
123
152
|
try {
|
|
124
153
|
actual = await runner.run(text, {
|
|
125
154
|
__expected: pt.expected,
|
|
126
155
|
__line: pt.line,
|
|
127
156
|
__column: pt.column,
|
|
157
|
+
__offset: pt.offset,
|
|
128
158
|
}, ...pt.inputs);
|
|
159
|
+
if (actual === SKIPPED) {
|
|
160
|
+
t.skip('Node 20.8 required');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
129
163
|
} catch (e) {
|
|
130
164
|
if (pt.expected !== EXCEPTION) {
|
|
131
165
|
throw e;
|
|
@@ -135,11 +169,12 @@ export async function suite(target = '.', defaultScript = '../$<base>.js') {
|
|
|
135
169
|
assert.notStrictEqual(pt.expected, EXCEPTION);
|
|
136
170
|
assert.deepEqual.apply(null, await coerce(actual, pt, runner));
|
|
137
171
|
});
|
|
172
|
+
|
|
138
173
|
msuite.addTest(t);
|
|
139
174
|
}
|
|
140
175
|
}
|
|
141
176
|
|
|
142
|
-
return new Promise((resolve,
|
|
177
|
+
return new Promise((resolve, _reject) => {
|
|
143
178
|
mocha.run(resolve);
|
|
144
179
|
});
|
|
145
180
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unexpectedly",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Keep a bunch of test cases in a file, with their expected results",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "peggy -o testFile.peg.js --format es testFile.pegjs",
|
|
9
|
-
"pretest": "npm run build",
|
|
10
|
-
"test": "c8 npm run runtests",
|
|
11
|
-
"runtests": "mocha && bin/unexpectedly.js test test/pkg_mjs/test test/pkg_cjs/test",
|
|
12
|
-
"lint": "eslint --ext js,cjs,mjs . bin/*",
|
|
13
|
-
"ci": "npm run lint && npm run test",
|
|
14
|
-
"clean": "rimraf coverage testFile.peg.js"
|
|
15
|
-
},
|
|
16
7
|
"bin": {
|
|
17
8
|
"unexpectedly": "bin/unexpectedly.js"
|
|
18
9
|
},
|
|
@@ -32,24 +23,11 @@
|
|
|
32
23
|
},
|
|
33
24
|
"license": "MPL-2.0",
|
|
34
25
|
"dependencies": {
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"@cto.af/eslint-config": "3.0.2",
|
|
39
|
-
"c8": "8.0.1",
|
|
40
|
-
"eslint": "^8.54.0",
|
|
41
|
-
"eslint-config-standard": "^17.1.0",
|
|
42
|
-
"eslint-plugin-import": "^2.29.0",
|
|
43
|
-
"eslint-plugin-jsdoc": "46.9.0",
|
|
44
|
-
"eslint-plugin-n": "16.3.1",
|
|
45
|
-
"eslint-plugin-node": "^11.1.0",
|
|
46
|
-
"eslint-plugin-promise": "^6.1.1",
|
|
47
|
-
"eslint-plugin-standard": "^4.1.0",
|
|
48
|
-
"peggy": "^3.0.2",
|
|
49
|
-
"rimraf": "5.0.5",
|
|
50
|
-
"zx": "7.2.3"
|
|
26
|
+
"@peggyjs/from-mem": "3.1.1",
|
|
27
|
+
"commander": "14.0.0",
|
|
28
|
+
"mocha": "^11.7.1"
|
|
51
29
|
},
|
|
52
30
|
"engines": {
|
|
53
|
-
"node": ">=
|
|
31
|
+
"node": ">=20"
|
|
54
32
|
}
|
|
55
33
|
}
|
package/runner.js
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {mapObj} from './utils.js';
|
|
1
|
+
import GrammarLocation from './vendor/peggy/grammar-location.cjs';
|
|
2
|
+
import fromMem from '@peggyjs/from-mem';
|
|
4
3
|
import path from 'node:path';
|
|
5
|
-
import vm from 'node:vm';
|
|
6
4
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
const PREFIX_MJS = 'export default ';
|
|
6
|
+
const PREFIX_CJS = 'module.exports = ';
|
|
7
|
+
const PREFIX_MJS_LEN = PREFIX_MJS.length;
|
|
8
|
+
const PREFIX_CJS_LEN = PREFIX_CJS.length;
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
// Note: not convinced the cache actually does anything, due to timing.
|
|
14
|
-
if (cache?.has(specifier)) {
|
|
15
|
-
return cache.get(specifier);
|
|
16
|
-
}
|
|
17
|
-
const m = await import(specifier);
|
|
18
|
-
const exportNames = Object.keys(m);
|
|
19
|
-
const imported = new vm.SyntheticModule(
|
|
20
|
-
exportNames,
|
|
21
|
-
// eslint-disable-next-line func-names
|
|
22
|
-
function() {
|
|
23
|
-
// eslint-disable-next-line no-invalid-this
|
|
24
|
-
exportNames.forEach(key => this.setExport(key, m[key]));
|
|
25
|
-
},
|
|
26
|
-
{identifier: specifier, context: referencingModule.context}
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
cache?.set(specifier, imported);
|
|
30
|
-
return imported;
|
|
31
|
-
}
|
|
10
|
+
export const SKIPPED = Symbol('SKIPPED');
|
|
32
11
|
|
|
33
12
|
/**
|
|
34
|
-
* Fancy "eval". This is NOT for adding security
|
|
35
|
-
* node:vm. This is about providing:
|
|
13
|
+
* Fancy "eval". This is NOT for adding security. This is about providing:
|
|
36
14
|
*
|
|
37
15
|
* - Good backtraces with file/line/column information correct, even when the
|
|
38
16
|
* code was inlined into a test.
|
|
@@ -42,111 +20,122 @@ async function linker(specifier, referencingModule, cache) {
|
|
|
42
20
|
*/
|
|
43
21
|
export class Runner {
|
|
44
22
|
#columnOffset;
|
|
23
|
+
#context;
|
|
45
24
|
#dirname;
|
|
46
25
|
#env;
|
|
47
26
|
#filename;
|
|
48
27
|
#lineOffset;
|
|
49
|
-
#
|
|
28
|
+
#testFunction;
|
|
50
29
|
#type;
|
|
30
|
+
#skipped;
|
|
51
31
|
|
|
52
32
|
constructor({
|
|
53
|
-
filename = null, //
|
|
33
|
+
filename = null, // Fully-qualified
|
|
54
34
|
lineOffset = 0,
|
|
55
35
|
columnOffset = 0,
|
|
56
|
-
|
|
36
|
+
context = {},
|
|
57
37
|
env = {},
|
|
58
|
-
|
|
38
|
+
testFunction = 'test',
|
|
39
|
+
type = 'guess',
|
|
59
40
|
} = {}) {
|
|
41
|
+
if (typeof filename !== 'string') {
|
|
42
|
+
throw new TypeError('filename not specified');
|
|
43
|
+
}
|
|
60
44
|
this.#filename = filename;
|
|
61
45
|
this.#dirname = path.dirname(filename);
|
|
62
46
|
this.#env = env;
|
|
63
|
-
this.#
|
|
47
|
+
this.#context = context;
|
|
64
48
|
this.#columnOffset = columnOffset;
|
|
65
49
|
this.#lineOffset = lineOffset;
|
|
50
|
+
this.#skipped = null;
|
|
66
51
|
this.#type = type;
|
|
52
|
+
this.#testFunction = testFunction;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get skipped() {
|
|
56
|
+
return this.#skipped;
|
|
67
57
|
}
|
|
68
58
|
|
|
69
59
|
async parse(text, extra = {}) {
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
const format = this.#type === 'guess' ?
|
|
61
|
+
await fromMem.guessModuleType(this.#filename) :
|
|
62
|
+
this.#type;
|
|
63
|
+
|
|
64
|
+
let columnOffset = this.#columnOffset;
|
|
65
|
+
if (format === 'es') {
|
|
66
|
+
if (text.indexOf('export') === -1) {
|
|
67
|
+
text = PREFIX_MJS + text; // Most common case
|
|
68
|
+
columnOffset -= PREFIX_MJS_LEN;
|
|
69
|
+
}
|
|
70
|
+
} else if (text.indexOf('exports') === -1) {
|
|
71
|
+
text = PREFIX_CJS + text; // Most common case
|
|
72
|
+
columnOffset -= PREFIX_CJS_LEN;
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)),
|
|
82
|
-
require: createRequire(this.#filename),
|
|
83
|
-
exports,
|
|
84
|
-
module: {
|
|
85
|
-
exports,
|
|
74
|
+
|
|
75
|
+
return await fromMem(text, {
|
|
76
|
+
filename: this.#filename,
|
|
77
|
+
format,
|
|
78
|
+
env: this.#env,
|
|
79
|
+
lineOffset: this.#lineOffset,
|
|
80
|
+
columnOffset,
|
|
81
|
+
context: {
|
|
86
82
|
filename: this.#filename,
|
|
87
83
|
id: this.#filename,
|
|
88
84
|
path: this.#dirname,
|
|
85
|
+
...this.#context,
|
|
86
|
+
...extra,
|
|
89
87
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
...this.#sandbox,
|
|
93
|
-
...extra,
|
|
94
|
-
};
|
|
95
|
-
context.process.env = this.#env;
|
|
96
|
-
context.global = context;
|
|
97
|
-
context.globalThis = context;
|
|
98
|
-
vm.createContext(context);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
async run(text, extra = {}, ...params) {
|
|
92
|
+
let f = await this.parse(text, extra);
|
|
93
|
+
if (f === SKIPPED) {
|
|
94
|
+
return f;
|
|
95
|
+
}
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
const possibleFunctions = [
|
|
98
|
+
this.#context.function,
|
|
99
|
+
this.#testFunction,
|
|
100
|
+
'default',
|
|
101
|
+
];
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
let source = null;
|
|
104
|
+
if ('peggy' in this.#context) {
|
|
105
|
+
source = new GrammarLocation(this.#filename, {
|
|
106
|
+
line: extra.__line,
|
|
107
|
+
column: extra.__column,
|
|
108
|
+
offset: extra.__offset,
|
|
113
109
|
});
|
|
110
|
+
const popts = {
|
|
111
|
+
grammarSource: source,
|
|
112
|
+
};
|
|
113
|
+
if (this.#context.peggy?.length > 0) {
|
|
114
|
+
popts.startRule = this.#context.peggy;
|
|
115
|
+
}
|
|
116
|
+
params[1] = popts;
|
|
117
|
+
possibleFunctions.splice(1, 0, 'parse');
|
|
118
|
+
}
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
if (f && (typeof f !== 'function')) {
|
|
121
|
+
for (const possible of possibleFunctions) {
|
|
122
|
+
if (possible && (typeof f[possible] === 'function')) {
|
|
123
|
+
f = f[possible];
|
|
124
|
+
break;
|
|
118
125
|
}
|
|
119
|
-
return linker(
|
|
120
|
-
path.resolve(path.dirname(this.#filename), specifier),
|
|
121
|
-
referencingModule,
|
|
122
|
-
imports
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
await mod.evaluate();
|
|
126
|
-
f = mod.namespace;
|
|
127
|
-
} else {
|
|
128
|
-
if (text.indexOf('exports') === -1) {
|
|
129
|
-
text = `module.exports= ${text}`; // Most common case
|
|
130
126
|
}
|
|
131
|
-
|
|
132
|
-
const script = new vm.Script(text, {
|
|
133
|
-
filename: this.#filename,
|
|
134
|
-
lineOffset: this.#lineOffset,
|
|
135
|
-
columnOffset: this.#columnOffset,
|
|
136
|
-
});
|
|
137
|
-
f = script.runInContext(context);
|
|
138
127
|
}
|
|
139
|
-
return f;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async run(text, extra = {}, ...params) {
|
|
143
|
-
let f = await this.parse(text, extra);
|
|
144
128
|
if (typeof f !== 'function') {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
throw new Error('Must export function or {test}');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
return f(...params);
|
|
134
|
+
} catch (er) {
|
|
135
|
+
if (source && (typeof er.format === 'function')) {
|
|
136
|
+
er.message = er.format([{source, text: params[0]}]);
|
|
148
137
|
}
|
|
138
|
+
throw er;
|
|
149
139
|
}
|
|
150
|
-
return f(...params);
|
|
151
140
|
}
|
|
152
141
|
}
|