Skip to content

Commit 9d65528

Browse files
bahamas10jasnell
authored andcommitted
node: add -c|--check CLI arg to syntax check script
PR-URL: #2411 Reviewed-By: Rod Vagg <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
1 parent 4a35ba4 commit 9d65528

11 files changed

+136
-13
lines changed

doc/node.1

+2
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

+15
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

+3-13
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;
@@ -435,29 +436,18 @@ Module.prototype._compile = function(content, filename) {
435436
};
436437

437438

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

455445

456446
// Native extension for .json
457447
Module._extensions['.json'] = function(module, filename) {
458448
var content = fs.readFileSync(filename, 'utf8');
459449
try {
460-
module.exports = JSON.parse(stripBOM(content));
450+
module.exports = JSON.parse(internalModule.stripBOM(content));
461451
} catch (err) {
462452
err.message = filename + ': ' + err.message;
463453
throw err;

node.gyp

+1
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

+9
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ using v8::Value;
121121

122122
static bool print_eval = false;
123123
static bool force_repl = false;
124+
static bool syntax_check_only = false;
124125
static bool trace_deprecation = false;
125126
static bool throw_deprecation = false;
126127
static bool abort_on_uncaught_exception = false;
@@ -2823,6 +2824,11 @@ void SetupProcessObject(Environment* env,
28232824
READONLY_PROPERTY(process, "_print_eval", True(env->isolate()));
28242825
}
28252826

2827+
// -c, --check
2828+
if (syntax_check_only) {
2829+
READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate()));
2830+
}
2831+
28262832
// -i, --interactive
28272833
if (force_repl) {
28282834
READONLY_PROPERTY(process, "_forceRepl", True(env->isolate()));
@@ -3079,6 +3085,7 @@ static void PrintHelp() {
30793085
" -v, --version print Node.js version\n"
30803086
" -e, --eval script evaluate script\n"
30813087
" -p, --print evaluate script and print result\n"
3088+
" -c, --check syntax check script without executing\n"
30823089
" -i, --interactive always enter the REPL even if stdin\n"
30833090
" does not appear to be a terminal\n"
30843091
" -r, --require module to preload (option can be repeated)\n"
@@ -3208,6 +3215,8 @@ static void ParseArgs(int* argc,
32083215
}
32093216
args_consumed += 1;
32103217
local_preload_modules[preload_module_count++] = module;
3218+
} else if (strcmp(arg, "--check") == 0 || strcmp(arg, "-c") == 0) {
3219+
syntax_check_only = true;
32113220
} else if (strcmp(arg, "--interactive") == 0 || strcmp(arg, "-i") == 0) {
32123221
force_repl = true;
32133222
} else if (strcmp(arg, "--no-deprecation") == 0) {

src/node.js

+16
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@
9393
process.argv[1] = path.resolve(process.argv[1]);
9494

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

test/fixtures/syntax/bad_syntax.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var foo bar;
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

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var foo = 'bar';
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

+84
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)