Skip to content

Commit cdaf50b

Browse files
committed
feat: add support for typescript compiler
1 parent e93f741 commit cdaf50b

File tree

9 files changed

+109
-42
lines changed

9 files changed

+109
-42
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ jobs:
3838

3939
- name: Run unit tests (node < 16)
4040
if: ${{ matrix.node-version < 16 }}
41-
run: node scripts/legacy-test-runner.js
41+
run: node scripts/legacy-test-runner.js
42+
43+
- name: Run unit tests (compilers)
44+
if: ${{ matrix.node-version >= 16 }}
45+
run: npm install typescript coffee-script --no-save && npm run test:compilers
46+

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ new VM().run('this.constructor.constructor("return process")().exit()');
4343
npm install vm2
4444
```
4545

46-
## Quick Example
46+
## Quick Examples
4747

4848
```js
4949
const { VM } = require('vm2');
@@ -98,7 +98,7 @@ VM is a simple sandbox to synchronously run untrusted code without the `require`
9898

9999
- `timeout` - Script timeout in milliseconds. **WARNING**: You might want to use this option together with `allowAsync=false`. Further, operating on returned objects from the sandbox can run arbitrary code and circumvent the timeout. One should test if the returned object is a primitive with `typeof` and fully discard it (doing logging or creating error messages with such an object might also run arbitrary code again) in the other case.
100100
- `sandbox` - VM's global object.
101-
- `compiler` - `javascript` (default) or `coffeescript` or custom compiler function. The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`.
101+
- `compiler` - `javascript` (default), `typescript`, `coffeescript` or custom compiler function. The library expects you to have compiler pre-installed if the value is set to `typescript` or `coffeescript`.
102102
- `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc.) will throw an `EvalError` (default: `true`).
103103
- `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`).
104104
- `allowAsync` - If set to `false` any attempt to run code using `async` will throw a `VMError` (default: `true`).
@@ -133,7 +133,7 @@ Unlike `VM`, `NodeVM` allows you to require modules in the same way that you wou
133133

134134
- `console` - `inherit` to enable console, `redirect` to redirect to events, `off` to disable console (default: `inherit`).
135135
- `sandbox` - VM's global object.
136-
- `compiler` - `javascript` (default) or `coffeescript` or custom compiler function (which receives the code, and it's file path). The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`.
136+
- `compiler` - `javascript` (default), `typescript`, `coffeescript` or custom compiler function (which receives the code, and it's file path). The library expects you to have compiler pre-installed if the value is set to `typescript` or `coffeescript`.
137137
- `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc.) will throw an `EvalError` (default: `true`).
138138
- `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`).
139139
- `sourceExtensions` - Array of file extensions to treat as source code (default: `['js']`).

index.d.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,14 @@ export function makeResolverFromLegacyOptions(options: VMRequire, override?: {[k
134134
*/
135135
export interface VMOptions {
136136
/**
137-
* `javascript` (default) or `coffeescript` or custom compiler function (which receives the code, and it's file path).
138-
* The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`.
137+
* `javascript` (default), `typescript`, `coffeescript` or custom compiler function (which receives the code, and it's file path).
138+
* The library expects you to have compiler pre-installed if the value is set to `typescript` or `coffeescript`.
139139
*/
140-
compiler?: "javascript" | "coffeescript" | CompilerFunction;
140+
compiler?: "javascript" | "typescript" | "coffeescript" | CompilerFunction;
141+
/**
142+
* Compiler options.
143+
*/
144+
compilerOptions?: Record<string, any>;
141145
/** VM's global object. */
142146
sandbox?: any;
143147
/**
@@ -285,19 +289,22 @@ export class VMScript {
285289
constructor(code: string, path: string, options?: {
286290
lineOffset?: number;
287291
columnOffset?: number;
288-
compiler?: "javascript" | "coffeescript" | CompilerFunction;
292+
compiler?: "javascript" | "typescript" | "coffeescript" | CompilerFunction;
293+
compilerOptions?: Record<string, any>;
289294
});
290295
constructor(code: string, options?: {
291296
filename?: string,
292297
lineOffset?: number;
293298
columnOffset?: number;
294-
compiler?: "javascript" | "coffeescript" | CompilerFunction;
299+
compiler?: "javascript" | "typescript" | "coffeescript" | CompilerFunction;
300+
compilerOptions?: Record<string, any>;
295301
});
296302
readonly code: string;
297303
readonly filename: string;
298304
readonly lineOffset: number;
299305
readonly columnOffset: number;
300-
readonly compiler: "javascript" | "coffeescript" | CompilerFunction;
306+
readonly compiler: "javascript" | "typescript" | "coffeescript" | CompilerFunction;
307+
readonly compilerOptions: Record<string, any> | undefined;
301308
/**
302309
* Wraps the code
303310
* @deprecated

lib/compiler.js

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,55 @@ const {
44
VMError
55
} = require('./bridge');
66

7-
let cacheCoffeeScriptCompiler;
8-
97
/**
108
* Returns the cached coffee script compiler or loads it
119
* if it is not found in the cache.
1210
*
1311
* @private
12+
* @param {Object} [options] - Optional compiler options.
1413
* @return {compileCallback} The coffee script compiler.
1514
* @throws {VMError} If the coffee-script module can't be found.
1615
*/
17-
function getCoffeeScriptCompiler() {
18-
if (!cacheCoffeeScriptCompiler) {
19-
try {
20-
// The warning generated by webpack can be disabled by setting:
21-
// ignoreWarnings[].message = /Can't resolve 'coffee-script'/
22-
/* eslint-disable-next-line global-require */
23-
const coffeeScript = require('coffee-script');
24-
cacheCoffeeScriptCompiler = (code, filename) => {
25-
return coffeeScript.compile(code, {header: false, bare: true});
26-
};
27-
} catch (e) {
28-
throw new VMError('Coffee-Script compiler is not installed.');
29-
}
16+
function getCoffeeScriptCompiler(options) {
17+
try {
18+
// The warning generated by webpack can be disabled by setting:
19+
// ignoreWarnings[].message = /Can't resolve 'coffee-script'/
20+
/* eslint-disable-next-line global-require */
21+
const coffeeScript = require('coffee-script');
22+
return (code, filename) => {
23+
return coffeeScript.compile(code, Object.assign({header: false, bare: true}, options));
24+
};
25+
} catch (e) {
26+
throw new VMError('Coffee-Script compiler is not installed.');
27+
}
28+
}
29+
30+
/**
31+
* Returns the cached TypeScript compiler or loads it
32+
* if it is not found in the cache.
33+
*
34+
* @private
35+
* @param {Object} [options] - Optional compiler options.
36+
* @return {compileCallback} The TypeScript compiler.
37+
* @throws {VMError} If the TypeScript module can't be found.
38+
*/
39+
function getTypeScriptCompiler(options) {
40+
try {
41+
// The warning generated by webpack can be disabled by setting:
42+
// ignoreWarnings[].message = /Can't resolve 'typescript'/
43+
/* eslint-disable-next-line global-require */
44+
const typescript = require('typescript');
45+
return (code, filename) => {
46+
return typescript.transpileModule(code, {
47+
fileName: filename,
48+
compilerOptions: Object.assign({
49+
module: typescript.ModuleKind.CommonJS,
50+
}, options)
51+
}).outputText;
52+
};
53+
} catch (e) {
54+
throw new VMError('TypeScript compiler is not installed.');
3055
}
31-
return cacheCoffeeScriptCompiler;
3256
}
3357

3458
/**
@@ -62,22 +86,28 @@ function jsCompiler(code, filename) {
6286
*
6387
* @private
6488
* @param {(string|compileCallback)} compiler - A compile callback or the name of the compiler.
89+
* @param {Object} [options] - Optional compiler options.
6590
* @return {compileCallback} The resolved compiler.
6691
* @throws {VMError} If the compiler is unknown or the coffee script module was needed and couldn't be found.
6792
*/
68-
function lookupCompiler(compiler) {
93+
function lookupCompiler(compiler, options) {
6994
if ('function' === typeof compiler) return compiler;
7095
switch (compiler) {
7196
case 'coffeescript':
7297
case 'coffee-script':
7398
case 'cs':
7499
case 'text/coffeescript':
75-
return getCoffeeScriptCompiler();
100+
return getCoffeeScriptCompiler(options);
76101
case 'javascript':
77102
case 'java-script':
78103
case 'js':
79104
case 'text/javascript':
80105
return jsCompiler;
106+
case 'typescript':
107+
case 'type-script':
108+
case 'ts':
109+
case 'text/typescript':
110+
return getTypeScriptCompiler(options);
81111
default:
82112
throw new VMError(`Unsupported compiler '${compiler}'.`);
83113
}

lib/script.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,13 @@ class VMScript {
172172

173173
const {
174174
compiler = 'javascript',
175+
compilerOptions,
175176
lineOffset = 0,
176177
columnOffset = 0
177178
} = useOptions;
178179

179180
// Throw if the compiler is unknown.
180-
const resolvedCompiler = lookupCompiler(compiler);
181+
const resolvedCompiler = lookupCompiler(compiler, compilerOptions);
181182

182183
objectDefineProperties(this, {
183184
__proto__: null,
@@ -219,6 +220,11 @@ class VMScript {
219220
value: compiler,
220221
enumerable: true
221222
},
223+
compilerOptions: {
224+
__proto__: null,
225+
value: compilerOptions,
226+
enumerable: true
227+
},
222228
_code: {
223229
__proto__: null,
224230
value: sCode,

lib/vm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class VM extends EventEmitter {
229229
timeout,
230230
sandbox,
231231
compiler = 'javascript',
232+
compilerOptions,
232233
allowAsync: optAllowAsync = true
233234
} = options;
234235
const allowEval = options.eval !== false;
@@ -241,7 +242,7 @@ class VM extends EventEmitter {
241242
}
242243

243244
// Early error if compiler can't be found.
244-
const resolvedCompiler = lookupCompiler(compiler);
245+
const resolvedCompiler = lookupCompiler(compiler, compilerOptions);
245246

246247
// Create a new context for this vm.
247248
const _context = createContext(undefined, {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"node": ">=6.0"
3131
},
3232
"scripts": {
33-
"test": "mocha test",
33+
"test": "mocha test --ignore test/compilers.js",
34+
"test:compilers": "mocha test/compilers.js",
3435
"lint": "eslint ."
3536
},
3637
"bin": {

test/compilers.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const {VM, NodeVM, VMScript} = require('..');
5+
6+
global.isHost = true;
7+
8+
describe('Compilers', () => {
9+
it('run TypeScript', () => {
10+
const vm = new VM();
11+
const script = new VMScript('1 as number', {
12+
compiler: 'typescript'
13+
});
14+
const val = vm.run(script);
15+
assert.strictEqual(val, 1);
16+
});
17+
18+
it('run CoffeeScript', () => {
19+
const vm = new NodeVM({
20+
require: {
21+
external: true
22+
},
23+
compiler: 'coffeescript'
24+
});
25+
26+
assert.equal(vm.run('module.exports = working: true').working, true);
27+
});
28+
});

test/nodevm.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,6 @@ describe('modules', () => {
102102
assert.equal(vm.run(`module.exports = require('./data/json.json')`, `${__dirname}/vm.js`).working, true);
103103
});
104104

105-
it.skip('run coffee-script', () => {
106-
const vm = new NodeVM({
107-
require: {
108-
external: true
109-
},
110-
compiler: 'coffeescript'
111-
});
112-
113-
assert.equal(vm.run('module.exports = working: true').working, true);
114-
});
115-
116105
it('optionally can run a custom compiler function', () => {
117106
let ranCustomCompiler = false;
118107
const scriptCode = 'var a = 1;';

0 commit comments

Comments
 (0)