node: add -c|--check CLI arg to syntax check script
authorDave Eddy <dave@daveeddy.com>
Mon, 17 Aug 2015 21:33:13 +0000 (17:33 -0400)
committerJames M Snell <jasnell@gmail.com>
Thu, 8 Oct 2015 03:39:15 +0000 (20:39 -0700)
PR-URL: https://github.com/nodejs/node/pull/2411
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Evan Lucas <evanlucas@me.com>
doc/node.1
lib/internal/module.js [new file with mode: 0644]
lib/module.js
node.gyp
src/node.cc
src/node.js
test/fixtures/syntax/bad_syntax.js [new file with mode: 0644]
test/fixtures/syntax/bad_syntax_shebang.js [new file with mode: 0644]
test/fixtures/syntax/good_syntax.js [new file with mode: 0644]
test/fixtures/syntax/good_syntax_shebang.js [new file with mode: 0644]
test/parallel/test-cli-syntax.js [new file with mode: 0644]

index af494da..854cc68 100644 (file)
@@ -49,6 +49,8 @@ and servers.
 
   -p, --print            print result of --eval
 
+  -c, --check            syntax check script without executing
+
   -i, --interactive      always enter the REPL even if stdin
                          does not appear to be a terminal
 
diff --git a/lib/internal/module.js b/lib/internal/module.js
new file mode 100644 (file)
index 0000000..7f3a39e
--- /dev/null
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports.stripBOM = stripBOM;
+
+/**
+ * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
+ * because the buffer-to-string conversion in `fs.readFileSync()`
+ * translates it to FEFF, the UTF-16 BOM.
+ */
+function stripBOM(content) {
+  if (content.charCodeAt(0) === 0xFEFF) {
+    content = content.slice(1);
+  }
+  return content;
+}
index da8a906..b1091ca 100644 (file)
@@ -2,6 +2,7 @@
 
 const NativeModule = require('native_module');
 const util = require('util');
+const internalModule = require('internal/module');
 const internalUtil = require('internal/util');
 const runInThisContext = require('vm').runInThisContext;
 const assert = require('assert').ok;
@@ -435,21 +436,10 @@ Module.prototype._compile = function(content, filename) {
 };
 
 
-function stripBOM(content) {
-  // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
-  // because the buffer-to-string conversion in `fs.readFileSync()`
-  // translates it to FEFF, the UTF-16 BOM.
-  if (content.charCodeAt(0) === 0xFEFF) {
-    content = content.slice(1);
-  }
-  return content;
-}
-
-
 // Native extension for .js
 Module._extensions['.js'] = function(module, filename) {
   var content = fs.readFileSync(filename, 'utf8');
-  module._compile(stripBOM(content), filename);
+  module._compile(internalModule.stripBOM(content), filename);
 };
 
 
@@ -457,7 +447,7 @@ Module._extensions['.js'] = function(module, filename) {
 Module._extensions['.json'] = function(module, filename) {
   var content = fs.readFileSync(filename, 'utf8');
   try {
-    module.exports = JSON.parse(stripBOM(content));
+    module.exports = JSON.parse(internalModule.stripBOM(content));
   } catch (err) {
     err.message = filename + ': ' + err.message;
     throw err;
index 2207978..9575f4c 100644 (file)
--- a/node.gyp
+++ b/node.gyp
@@ -70,6 +70,7 @@
       'lib/zlib.js',
       'lib/internal/child_process.js',
       'lib/internal/freelist.js',
+      'lib/internal/module.js',
       'lib/internal/socket_list.js',
       'lib/internal/repl.js',
       'lib/internal/util.js',
index 501caf2..faac8b4 100644 (file)
@@ -121,6 +121,7 @@ using v8::Value;
 
 static bool print_eval = false;
 static bool force_repl = false;
+static bool syntax_check_only = false;
 static bool trace_deprecation = false;
 static bool throw_deprecation = false;
 static bool abort_on_uncaught_exception = false;
@@ -2823,6 +2824,11 @@ void SetupProcessObject(Environment* env,
     READONLY_PROPERTY(process, "_print_eval", True(env->isolate()));
   }
 
+  // -c, --check
+  if (syntax_check_only) {
+    READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate()));
+  }
+
   // -i, --interactive
   if (force_repl) {
     READONLY_PROPERTY(process, "_forceRepl", True(env->isolate()));
@@ -3079,6 +3085,7 @@ static void PrintHelp() {
          "  -v, --version         print Node.js version\n"
          "  -e, --eval script     evaluate script\n"
          "  -p, --print           evaluate script and print result\n"
+         "  -c, --check           syntax check script without executing\n"
          "  -i, --interactive     always enter the REPL even if stdin\n"
          "                        does not appear to be a terminal\n"
          "  -r, --require         module to preload (option can be repeated)\n"
@@ -3208,6 +3215,8 @@ static void ParseArgs(int* argc,
       }
       args_consumed += 1;
       local_preload_modules[preload_module_count++] = module;
+    } else if (strcmp(arg, "--check") == 0 || strcmp(arg, "-c") == 0) {
+      syntax_check_only = true;
     } else if (strcmp(arg, "--interactive") == 0 || strcmp(arg, "-i") == 0) {
       force_repl = true;
     } else if (strcmp(arg, "--no-deprecation") == 0) {
index a2b21fb..da0fc67 100644 (file)
         process.argv[1] = path.resolve(process.argv[1]);
 
         var Module = NativeModule.require('module');
+
+        // check if user passed `-c` or `--check` arguments to Node.
+        if (process._syntax_check_only != null) {
+          var vm = NativeModule.require('vm');
+          var fs = NativeModule.require('fs');
+          var internalModule = NativeModule.require('internal/module');
+          // read the source
+          var filename = Module._resolveFilename(process.argv[1]);
+          var source = fs.readFileSync(filename, 'utf-8');
+          // remove shebang and BOM
+          source = internalModule.stripBOM(source.replace(/^\#\!.*/, ''));
+          // compile the script, this will throw if it fails
+          new vm.Script(source, {filename: filename, displayErrors: true});
+          process.exit(0);
+        }
+
         startup.preloadModules();
         if (global.v8debug &&
             process.execArgv.some(function(arg) {
diff --git a/test/fixtures/syntax/bad_syntax.js b/test/fixtures/syntax/bad_syntax.js
new file mode 100644 (file)
index 0000000..c2cd118
--- /dev/null
@@ -0,0 +1 @@
+var foo bar;
diff --git a/test/fixtures/syntax/bad_syntax_shebang.js b/test/fixtures/syntax/bad_syntax_shebang.js
new file mode 100644 (file)
index 0000000..1de5d2a
--- /dev/null
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+var foo bar;
diff --git a/test/fixtures/syntax/good_syntax.js b/test/fixtures/syntax/good_syntax.js
new file mode 100644 (file)
index 0000000..d842707
--- /dev/null
@@ -0,0 +1 @@
+var foo = 'bar';
diff --git a/test/fixtures/syntax/good_syntax_shebang.js b/test/fixtures/syntax/good_syntax_shebang.js
new file mode 100644 (file)
index 0000000..f9ff7d5
--- /dev/null
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+var foo = 'bar';
diff --git a/test/parallel/test-cli-syntax.js b/test/parallel/test-cli-syntax.js
new file mode 100644 (file)
index 0000000..20fdfdc
--- /dev/null
@@ -0,0 +1,84 @@
+'use strict';
+
+const assert = require('assert');
+const spawnSync = require('child_process').spawnSync;
+const path = require('path');
+
+const common = require('../common');
+
+var node = process.execPath;
+
+// test both sets of arguments that check syntax
+var syntaxArgs = [
+  ['-c'],
+  ['--check']
+];
+
+// test good syntax with and without shebang
+[
+  'syntax/good_syntax.js',
+  'syntax/good_syntax',
+  'syntax/good_syntax_shebang.js',
+  'syntax/good_syntax_shebang',
+].forEach(function(file) {
+  file = path.join(common.fixturesDir, file);
+
+  // loop each possible option, `-c` or `--check`
+  syntaxArgs.forEach(function(args) {
+    var _args = args.concat(file);
+    var c = spawnSync(node, _args, {encoding: 'utf8'});
+
+    // no output should be produced
+    assert.equal(c.stdout, '', 'stdout produced');
+    assert.equal(c.stderr, '', 'stderr produced');
+    assert.equal(c.status, 0, 'code == ' + c.status);
+  });
+});
+
+// test bad syntax with and without shebang
+[
+  'syntax/bad_syntax.js',
+  'syntax/bad_syntax',
+  'syntax/bad_syntax_shebang.js',
+  'syntax/bad_syntax_shebang'
+].forEach(function(file) {
+  file = path.join(common.fixturesDir, file);
+
+  // loop each possible option, `-c` or `--check`
+  syntaxArgs.forEach(function(args) {
+    var _args = args.concat(file);
+    var c = spawnSync(node, _args, {encoding: 'utf8'});
+
+    // no stdout should be produced
+    assert.equal(c.stdout, '', 'stdout produced');
+
+    // stderr should have a syntax error message
+    var match = c.stderr.match(/^SyntaxError: Unexpected identifier$/m);
+    assert(match, 'stderr incorrect');
+
+    assert.equal(c.status, 1, 'code == ' + c.status);
+  });
+});
+
+// test file not found
+[
+  'syntax/file_not_found.js',
+  'syntax/file_not_found'
+].forEach(function(file) {
+  file = path.join(common.fixturesDir, file);
+
+  // loop each possible option, `-c` or `--check`
+  syntaxArgs.forEach(function(args) {
+    var _args = args.concat(file);
+    var c = spawnSync(node, _args, {encoding: 'utf8'});
+
+    // no stdout should be produced
+    assert.equal(c.stdout, '', 'stdout produced');
+
+    // stderr should have a module not found error message
+    var match = c.stderr.match(/^Error: Cannot find module/m);
+    assert(match, 'stderr incorrect');
+
+    assert.equal(c.status, 1, 'code == ' + c.status);
+  });
+});