diff --git a/doc/node.1 b/doc/node.1 index af494dade9..854cc68ef2 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -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 index 0000000000..7f3a39e539 --- /dev/null +++ b/lib/internal/module.js @@ -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; +} diff --git a/lib/module.js b/lib/module.js index da8a906f95..b1091ca66d 100644 --- a/lib/module.js +++ b/lib/module.js @@ -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; diff --git a/node.gyp b/node.gyp index 22079785d9..9575f4cbcd 100644 --- 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', diff --git a/src/node.cc b/src/node.cc index dd09c4d10a..ca9d4e8a7f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -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; @@ -2811,6 +2812,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())); @@ -3067,6 +3073,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" @@ -3196,6 +3203,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) { diff --git a/src/node.js b/src/node.js index a2b21fb434..da0fc6725e 100644 --- a/src/node.js +++ b/src/node.js @@ -93,6 +93,22 @@ 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 index 0000000000..c2cd118b23 --- /dev/null +++ b/test/fixtures/syntax/bad_syntax.js @@ -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 index 0000000000..1de5d2adc4 --- /dev/null +++ b/test/fixtures/syntax/bad_syntax_shebang.js @@ -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 index 0000000000..d8427075a4 --- /dev/null +++ b/test/fixtures/syntax/good_syntax.js @@ -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 index 0000000000..f9ff7d5694 --- /dev/null +++ b/test/fixtures/syntax/good_syntax_shebang.js @@ -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 index 0000000000..20fdfdc995 --- /dev/null +++ b/test/parallel/test-cli-syntax.js @@ -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); + }); +});