Skip to content

Commit 03e1aa9

Browse files
committed
src: add CLI option to syntax check script
1 parent 972a57c commit 03e1aa9

File tree

11 files changed

+136
-13
lines changed

11 files changed

+136
-13
lines changed

doc/node.1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ and servers.
4949

5050
-p, --print print result of --eval
5151

52+
-c, --check syntax check script without executing
53+
5254
-i, --interactive always enter the REPL even if stdin
5355
does not appear to be a terminal
5456

lib/internal/module.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
module.exports.stripBOM = stripBOM;
4+
5+
/**
6+
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
7+
* because the buffer-to-string conversion in `fs.readFileSync()`
8+
* translates it to FEFF, the UTF-16 BOM.
9+
*/
10+
function stripBOM(content) {
11+
if (content.charCodeAt(0) === 0xFEFF) {
12+
content = content.slice(1);
13+
}
14+
return content;
15+
}

lib/module.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const NativeModule = require('native_module');
44
const util = require('util');
5+
const internalModule = require('internal/module');
56
const internalUtil = require('internal/util');
67
const runInThisContext = require('vm').runInThisContext;
78
const assert = require('assert').ok;
@@ -431,29 +432,18 @@ Module.prototype._compile = function(content, filename) {
431432
};
432433

433434

434-
function stripBOM(content) {
435-
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
436-
// because the buffer-to-string conversion in `fs.readFileSync()`
437-
// translates it to FEFF, the UTF-16 BOM.
438-
if (content.charCodeAt(0) === 0xFEFF) {
439-
content = content.slice(1);
440-
}
441-
return content;
442-
}
443-
444-
445435
// Native extension for .js
446436
Module._extensions['.js'] = function(module, filename) {
447437
var content = fs.readFileSync(filename, 'utf8');
448-
module._compile(stripBOM(content), filename);
438+
module._compile(internalModule.stripBOM(content), filename);
449439
};
450440

451441

452442
// Native extension for .json
453443
Module._extensions['.json'] = function(module, filename) {
454444
var content = fs.readFileSync(filename, 'utf8');
455445
try {
456-
module.exports = JSON.parse(stripBOM(content));
446+
module.exports = JSON.parse(internalModule.stripBOM(content));
457447
} catch (err) {
458448
err.message = filename + ': ' + err.message;
459449
throw err;

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
'lib/zlib.js',
7171
'lib/internal/child_process.js',
7272
'lib/internal/freelist.js',
73+
'lib/internal/module.js',
7374
'lib/internal/socket_list.js',
7475
'lib/internal/repl.js',
7576
'lib/internal/util.js',

src/node.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ using v8::Value;
114114

115115
static bool print_eval = false;
116116
static bool force_repl = false;
117+
static bool syntax_check_only = false;
117118
static bool trace_deprecation = false;
118119
static bool throw_deprecation = false;
119120
static bool abort_on_uncaught_exception = false;
@@ -2843,6 +2844,11 @@ void SetupProcessObject(Environment* env,
28432844
READONLY_PROPERTY(process, "_print_eval", True(env->isolate()));
28442845
}
28452846

2847+
// -c, --check
2848+
if (syntax_check_only) {
2849+
READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate()));
2850+
}
2851+
28462852
// -i, --interactive
28472853
if (force_repl) {
28482854
READONLY_PROPERTY(process, "_forceRepl", True(env->isolate()));
@@ -3099,6 +3105,7 @@ static void PrintHelp() {
30993105
" -v, --version print Node.js version\n"
31003106
" -e, --eval script evaluate script\n"
31013107
" -p, --print evaluate script and print result\n"
3108+
" -c, --check syntax check script without executing\n"
31023109
" -i, --interactive always enter the REPL even if stdin\n"
31033110
" does not appear to be a terminal\n"
31043111
" -r, --require module to preload (option can be repeated)\n"
@@ -3227,6 +3234,8 @@ static void ParseArgs(int* argc,
32273234
}
32283235
args_consumed += 1;
32293236
local_preload_modules[preload_module_count++] = module;
3237+
} else if (strcmp(arg, "--check") == 0 || strcmp(arg, "-c") == 0) {
3238+
syntax_check_only = true;
32303239
} else if (strcmp(arg, "--interactive") == 0 || strcmp(arg, "-i") == 0) {
32313240
force_repl = true;
32323241
} else if (strcmp(arg, "--no-deprecation") == 0) {

src/node.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@
9292
process.argv[1] = path.resolve(process.argv[1]);
9393

9494
var Module = NativeModule.require('module');
95+
96+
// check if user passed `-c` or `--check` arguments to Node.
97+
if (process._syntax_check_only != null) {
98+
var vm = NativeModule.require('vm');
99+
var fs = NativeModule.require('fs');
100+
var internalModule = NativeModule.require('internal/module');
101+
// read the source
102+
var filename = Module._resolveFilename(process.argv[1]);
103+
var source = fs.readFileSync(filename, 'utf-8');
104+
// remove shebang and BOM
105+
source = internalModule.stripBOM(source.replace(/^\#\!.*/, ''));
106+
// compile the script, this will throw if it fails
107+
new vm.Script(source, {filename: filename, displayErrors: true});
108+
process.exit(0);
109+
}
110+
95111
startup.preloadModules();
96112
if (global.v8debug &&
97113
process.execArgv.some(function(arg) {

test/fixtures/syntax/bad_syntax.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var foo bar;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
var foo bar;

test/fixtures/syntax/good_syntax.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var foo = 'bar';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
var foo = 'bar';

test/parallel/test-cli-syntax.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const spawnSync = require('child_process').spawnSync;
5+
const path = require('path');
6+
7+
const common = require('../common');
8+
9+
var node = process.execPath;
10+
11+
// test both sets of arguments that check syntax
12+
var syntaxArgs = [
13+
['-c'],
14+
['--check']
15+
];
16+
17+
// test good syntax with and without shebang
18+
[
19+
'syntax/good_syntax.js',
20+
'syntax/good_syntax',
21+
'syntax/good_syntax_shebang.js',
22+
'syntax/good_syntax_shebang',
23+
].forEach(function(file) {
24+
file = path.join(common.fixturesDir, file);
25+
26+
// loop each possible option, `-c` or `--check`
27+
syntaxArgs.forEach(function(args) {
28+
var _args = args.concat(file);
29+
var c = spawnSync(node, _args, {encoding: 'utf8'});
30+
31+
// no output should be produced
32+
assert.equal(c.stdout, '', 'stdout produced');
33+
assert.equal(c.stderr, '', 'stderr produced');
34+
assert.equal(c.status, 0, 'code == ' + c.status);
35+
});
36+
});
37+
38+
// test bad syntax with and without shebang
39+
[
40+
'syntax/bad_syntax.js',
41+
'syntax/bad_syntax',
42+
'syntax/bad_syntax_shebang.js',
43+
'syntax/bad_syntax_shebang'
44+
].forEach(function(file) {
45+
file = path.join(common.fixturesDir, file);
46+
47+
// loop each possible option, `-c` or `--check`
48+
syntaxArgs.forEach(function(args) {
49+
var _args = args.concat(file);
50+
var c = spawnSync(node, _args, {encoding: 'utf8'});
51+
52+
// no stdout should be produced
53+
assert.equal(c.stdout, '', 'stdout produced');
54+
55+
// stderr should have a syntax error message
56+
var match = c.stderr.match(/^SyntaxError: Unexpected identifier$/m);
57+
assert(match, 'stderr incorrect');
58+
59+
assert.equal(c.status, 1, 'code == ' + c.status);
60+
});
61+
});
62+
63+
// test file not found
64+
[
65+
'syntax/file_not_found.js',
66+
'syntax/file_not_found'
67+
].forEach(function(file) {
68+
file = path.join(common.fixturesDir, file);
69+
70+
// loop each possible option, `-c` or `--check`
71+
syntaxArgs.forEach(function(args) {
72+
var _args = args.concat(file);
73+
var c = spawnSync(node, _args, {encoding: 'utf8'});
74+
75+
// no stdout should be produced
76+
assert.equal(c.stdout, '', 'stdout produced');
77+
78+
// stderr should have a module not found error message
79+
var match = c.stderr.match(/^Error: Cannot find module/m);
80+
assert(match, 'stderr incorrect');
81+
82+
assert.equal(c.status, 1, 'code == ' + c.status);
83+
});
84+
});

0 commit comments

Comments
 (0)