unexpectedly 0.0.6 → 1.0.0
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 +57 -20
- package/bin/unexpectedly.js +32 -0
- package/index.js +108 -76
- package/modtype.js +60 -0
- package/package.json +29 -21
- package/runner.js +129 -58
- package/testFile.peg.js +507 -572
- package/utils.js +41 -26
- package/bin/unexpectedly +0 -5
package/runner.js
CHANGED
|
@@ -1,81 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
import {ModType} from './modtype.js';
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import {mapObj} from './utils.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import vm from 'node:vm';
|
|
2
6
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
const vmGlobals = new vm.Script('Object.getOwnPropertyNames(globalThis)')
|
|
8
|
+
.runInNewContext()
|
|
9
|
+
.filter(f => !f.startsWith('global'));
|
|
10
|
+
vmGlobals.push('process', 'Buffer');
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this._columnOffset = columnOffset
|
|
12
|
+
async function linker(specifier, referencingModule, cache) {
|
|
13
|
+
// Note: not convinced the cache actually does anything, due to timing.
|
|
14
|
+
if (cache?.has(specifier)) {
|
|
15
|
+
return cache.get(specifier);
|
|
12
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
|
+
);
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return this
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this._compiled = new vm.Script(this.code, {
|
|
20
|
-
filename: this.filename,
|
|
21
|
-
displayErrors: false,
|
|
22
|
-
lineOffset: this._lineOffset,
|
|
23
|
-
columnOffset: this._columnOffset
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
return this
|
|
27
|
-
}
|
|
29
|
+
cache?.set(specifier, imported);
|
|
30
|
+
return imported;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
|
-
* Fancy "eval"
|
|
34
|
+
* Fancy "eval". This is NOT for adding security, even though we're using
|
|
35
|
+
* node:vm. This is about providing:
|
|
36
|
+
*
|
|
37
|
+
* - Good backtraces with file/line/column information correct, even when the
|
|
38
|
+
* code was inlined into a test.
|
|
39
|
+
* - A require() and import() that are relative to the source file.
|
|
32
40
|
*
|
|
33
41
|
* @class Runner
|
|
34
42
|
*/
|
|
35
|
-
class Runner {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
export class Runner {
|
|
44
|
+
#columnOffset;
|
|
45
|
+
#dirname;
|
|
46
|
+
#env;
|
|
47
|
+
#filename;
|
|
48
|
+
#lineOffset;
|
|
49
|
+
#sandbox;
|
|
50
|
+
#type;
|
|
51
|
+
|
|
52
|
+
constructor({
|
|
53
|
+
filename = null, // Resolved
|
|
39
54
|
lineOffset = 0,
|
|
40
55
|
columnOffset = 0,
|
|
41
|
-
sandbox = null
|
|
56
|
+
sandbox = null,
|
|
57
|
+
env = {},
|
|
58
|
+
type = null,
|
|
42
59
|
} = {}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this
|
|
50
|
-
this.sandbox = sandbox
|
|
51
|
-
this.script = new VMScriptLines(text, {
|
|
52
|
-
filename,
|
|
53
|
-
lineOffset,
|
|
54
|
-
columnOffset
|
|
55
|
-
})
|
|
60
|
+
this.#filename = filename;
|
|
61
|
+
this.#dirname = path.dirname(filename);
|
|
62
|
+
this.#env = env;
|
|
63
|
+
this.#sandbox = sandbox;
|
|
64
|
+
this.#columnOffset = columnOffset;
|
|
65
|
+
this.#lineOffset = lineOffset;
|
|
66
|
+
this.#type = type;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
async parse(text, extra = {}) {
|
|
70
|
+
if (!this.#type) {
|
|
71
|
+
this.#type = await ModType.find(this.#filename);
|
|
72
|
+
}
|
|
73
|
+
// Find package.json relative to test, decide whether we're commonjs or
|
|
74
|
+
// module.
|
|
75
|
+
const exports = {};
|
|
76
|
+
const context = {
|
|
77
|
+
...mapObj(vmGlobals, g => (
|
|
78
|
+
(typeof globalThis[g] === 'function') ?
|
|
79
|
+
globalThis[g] :
|
|
80
|
+
{...globalThis[g]}
|
|
81
|
+
)),
|
|
82
|
+
require: createRequire(this.#filename),
|
|
83
|
+
exports,
|
|
84
|
+
module: {
|
|
85
|
+
exports,
|
|
86
|
+
filename: this.#filename,
|
|
87
|
+
id: this.#filename,
|
|
88
|
+
path: this.#dirname,
|
|
64
89
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
__filename: this.#filename,
|
|
91
|
+
__dirname: this.#dirname,
|
|
92
|
+
...this.#sandbox,
|
|
93
|
+
...extra,
|
|
94
|
+
};
|
|
95
|
+
context.process.env = this.#env;
|
|
96
|
+
context.global = context;
|
|
97
|
+
context.globalThis = context;
|
|
98
|
+
vm.createContext(context);
|
|
99
|
+
|
|
100
|
+
let f = null;
|
|
101
|
+
const imports = new Map();
|
|
102
|
+
|
|
103
|
+
if (this.#type === 'module') {
|
|
104
|
+
if (text.indexOf('export') === -1) {
|
|
105
|
+
text = `export default ${text}`; // Most common case
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const mod = new vm.SourceTextModule(text, {
|
|
109
|
+
context,
|
|
110
|
+
id: this.#filename,
|
|
111
|
+
lineOffset: this.#lineOffset,
|
|
112
|
+
columnOffset: this.#columnOffset,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await mod.link((specifier, referencingModule) => {
|
|
116
|
+
if (path.isAbsolute(specifier) || !/^\.\.?\//.test(specifier)) {
|
|
117
|
+
return linker(specifier, referencingModule, imports);
|
|
118
|
+
}
|
|
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
|
+
}
|
|
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);
|
|
70
138
|
}
|
|
139
|
+
return f;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async run(text, extra = {}, ...params) {
|
|
143
|
+
let f = await this.parse(text, extra);
|
|
71
144
|
if (typeof f !== 'function') {
|
|
72
|
-
f = f
|
|
145
|
+
f = f?.test ?? f?.default;
|
|
73
146
|
if (typeof f !== 'function') {
|
|
74
|
-
throw new Error('Must export function or {test}')
|
|
147
|
+
throw new Error('Must export function or {test}');
|
|
75
148
|
}
|
|
76
149
|
}
|
|
77
|
-
return f
|
|
150
|
+
return f(...params);
|
|
78
151
|
}
|
|
79
152
|
}
|
|
80
|
-
|
|
81
|
-
module.exports = Runner
|