unexpectedly 0.0.7 → 2.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/runner.js CHANGED
@@ -1,81 +1,152 @@
1
- 'use strict'
1
+ import GrammarLocation from './vendor/peggy/grammar-location.cjs';
2
+ import fromMem from '@peggyjs/from-mem';
3
+ import path from 'node:path';
4
+ import semver from 'semver';
2
5
 
3
- const fs = require('fs')
4
- const vm = require('vm')
5
- const vm2 = require('vm2')
6
+ const PREFIX_MJS = 'export default ';
7
+ const PREFIX_CJS = 'module.exports = ';
8
+ const PREFIX_MJS_LEN = PREFIX_MJS.length;
9
+ const PREFIX_CJS_LEN = PREFIX_CJS.length;
6
10
 
7
- class VMScriptLines extends vm2.VMScript {
8
- constructor (code, { filename, lineOffset = 0, columnOffset = 0 } = {}) {
9
- super(code, filename)
10
- this._lineOffset = lineOffset
11
- this._columnOffset = columnOffset
12
- }
13
-
14
- compile () {
15
- if (this._compiled) {
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
- })
11
+ const is20 = semver.satisfies(process.version, '>=20.8');
25
12
 
26
- return this
27
- }
28
- }
13
+ export const SKIPPED = Symbol('SKIPPED');
29
14
 
30
15
  /**
31
- * Fancy "eval"
16
+ * Fancy "eval". This is NOT for adding security. This is about providing:
17
+ *
18
+ * - Good backtraces with file/line/column information correct, even when the
19
+ * code was inlined into a test.
20
+ * - A require() and import() that are relative to the source file.
32
21
  *
33
22
  * @class Runner
34
23
  */
35
- class Runner {
36
- constructor ({
37
- filename = null,
38
- text = null,
24
+ export class Runner {
25
+ #columnOffset;
26
+ #context;
27
+ #dirname;
28
+ #env;
29
+ #filename;
30
+ #lineOffset;
31
+ #silent18;
32
+ #testFunction;
33
+ #type;
34
+ #skipped;
35
+
36
+ constructor({
37
+ filename = null, // Fully-qualified
39
38
  lineOffset = 0,
40
39
  columnOffset = 0,
41
- sandbox = null
40
+ context = {},
41
+ env = {},
42
+ silent18 = false,
43
+ testFunction = 'test',
44
+ type = 'guess',
42
45
  } = {}) {
43
- if (!text) {
44
- if (!filename) {
45
- throw new Error('Either file or text is required')
46
- }
47
- text = fs.readFileSync(filename, 'utf8')
46
+ if (typeof filename !== 'string') {
47
+ throw new TypeError('filename not specified');
48
48
  }
49
- this.filename = filename
50
- this.sandbox = sandbox
51
- this.script = new VMScriptLines(text, {
52
- filename,
53
- lineOffset,
54
- columnOffset
55
- })
49
+ this.#filename = filename;
50
+ this.#dirname = path.dirname(filename);
51
+ this.#env = env;
52
+ this.#context = context;
53
+ this.#columnOffset = columnOffset;
54
+ this.#lineOffset = lineOffset;
55
+ this.#silent18 = silent18;
56
+ this.#skipped = null;
57
+ this.#type = type;
58
+ this.#testFunction = testFunction;
59
+ }
60
+
61
+ get skipped() {
62
+ return this.#skipped;
56
63
  }
57
64
 
58
- run (extra = {}, ...params) {
59
- const nvm = new vm2.NodeVM({
60
- console: 'inherit',
61
- require: { // TODO: Think about how much of this is for security?
62
- external: true,
63
- builtin: ['*']
65
+ async parse(text, extra = {}) {
66
+ const format = this.#type === 'guess' ?
67
+ await fromMem.guessModuleType(this.#filename) :
68
+ this.#type;
69
+
70
+ let columnOffset = this.#columnOffset;
71
+ if (format === 'es') {
72
+ if (this.#silent18 && !is20) {
73
+ this.#skipped = true;
74
+ return SKIPPED;
75
+ }
76
+
77
+ if (text.indexOf('export') === -1) {
78
+ text = PREFIX_MJS + text; // Most common case
79
+ columnOffset -= PREFIX_MJS_LEN;
80
+ }
81
+ } else if (text.indexOf('exports') === -1) {
82
+ text = PREFIX_CJS + text; // Most common case
83
+ columnOffset -= PREFIX_CJS_LEN;
84
+ }
85
+
86
+ return await fromMem(text, {
87
+ filename: this.#filename,
88
+ format,
89
+ env: this.#env,
90
+ lineOffset: this.#lineOffset,
91
+ columnOffset,
92
+ context: {
93
+ filename: this.#filename,
94
+ id: this.#filename,
95
+ path: this.#dirname,
96
+ ...this.#context,
97
+ ...extra,
64
98
  },
65
- sandbox: Object.assign({}, this.sandbox, extra)
66
- })
67
- let f = nvm.run(this.script, this.filename)
68
- if (!f) {
69
- throw new Error('Nothing exported')
99
+ });
100
+ }
101
+
102
+ async run(text, extra = {}, ...params) {
103
+ let f = await this.parse(text, extra);
104
+ if (f === SKIPPED) {
105
+ return f;
106
+ }
107
+
108
+ const possibleFunctions = [
109
+ this.#context.function,
110
+ this.#testFunction,
111
+ 'default',
112
+ ];
113
+
114
+ let source = null;
115
+ if ('peggy' in this.#context) {
116
+ source = new GrammarLocation(this.#filename, {
117
+ line: extra.__line,
118
+ column: extra.__column,
119
+ offset: extra.__offset,
120
+ });
121
+ const popts = {
122
+ grammarSource: source,
123
+ };
124
+ if (this.#context.peggy?.length > 0) {
125
+ popts.startRule = this.#context.peggy;
126
+ }
127
+ params[1] = popts;
128
+ possibleFunctions.splice(1, 0, 'parse');
129
+ }
130
+
131
+ if (f && (typeof f !== 'function')) {
132
+ for (const possible of possibleFunctions) {
133
+ if (possible && (typeof f[possible] === 'function')) {
134
+ f = f[possible];
135
+ break;
136
+ }
137
+ }
70
138
  }
71
139
  if (typeof f !== 'function') {
72
- f = f.test
73
- if (typeof f !== 'function') {
74
- throw new Error('Must export function or {test}')
140
+ throw new Error('Must export function or {test}');
141
+ }
142
+
143
+ try {
144
+ return f(...params);
145
+ } catch (er) {
146
+ if (source && (typeof er.format === 'function')) {
147
+ er.message = er.format([{source, text: params[0]}]);
75
148
  }
149
+ throw er;
76
150
  }
77
- return f.apply(null, params)
78
151
  }
79
152
  }
80
-
81
- module.exports = Runner