mirror of
https://github.com/nodejs/node.git
synced 2025-05-05 21:04:16 +00:00
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the choice to wrap command input in parens to coerce to an expression statement. 1. Rather than a growing blacklist of allowed-to-throw syntax errors, just sniff for the one we really care about ("Unexpected end of input") and let all the others pass through. 2. Wrapping {a:1} in parens makes sense, because blocks and line labels are silly and confusing and should not be in JavaScript at all. However, wrapping functions and other types of programs in parens is weird and required yet *more* hacking to work around. By only wrapping statements that start with { and end with }, we can handle the confusing use-case, without having to then do extra work for functions and other cases. This also fixes the repl wart where `console.log)(` works in the repl, but only by virtue of the fact that it's wrapped in parens first, as well as potential side effects of double-running the commands, such as: > x = 1 1 > eval('x++; throw new SyntaxError("e")') ... ^C > x 3
This commit is contained in:
parent
54fbb1da96
commit
9ef9a9dee5
119
lib/repl.js
119
lib/repl.js
@ -50,6 +50,7 @@ var rl = require('readline');
|
||||
var Console = require('console').Console;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var domain = require('domain');
|
||||
var debug = util.debuglog('repl');
|
||||
|
||||
// If obj.hasOwnProperty has been overridden, then calling
|
||||
// obj.hasOwnProperty(prop) will break.
|
||||
@ -119,8 +120,9 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
err._isSyntaxError = isSyntaxError(err);
|
||||
debug('parse error %j', code, e);
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
try {
|
||||
if (self.useGlobal) {
|
||||
@ -130,21 +132,22 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
|
||||
}
|
||||
} catch (e) {
|
||||
err = e;
|
||||
err._isSyntaxError = false;
|
||||
if (err && process.domain) {
|
||||
debug('not recoverable, send to domain');
|
||||
process.domain.emit('error', err);
|
||||
process.domain.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err && process.domain && !err._isSyntaxError) {
|
||||
process.domain.emit('error', err);
|
||||
process.domain.exit();
|
||||
}
|
||||
else {
|
||||
cb(err, result);
|
||||
}
|
||||
|
||||
cb(err, result);
|
||||
}
|
||||
|
||||
self.eval = self._domain.bind(eval_);
|
||||
|
||||
self._domain.on('error', function(e) {
|
||||
debug('domain error');
|
||||
self.outputStream.write((e.stack || e) + '\n');
|
||||
self.bufferedCommand = '';
|
||||
self.lines.level = [];
|
||||
@ -236,6 +239,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
|
||||
});
|
||||
|
||||
rli.on('line', function(cmd) {
|
||||
debug('line %j', cmd);
|
||||
sawSIGINT = false;
|
||||
var skipCatchall = false;
|
||||
cmd = trimWhitespace(cmd);
|
||||
@ -255,60 +259,52 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
|
||||
}
|
||||
|
||||
if (!skipCatchall) {
|
||||
var evalCmd = self.bufferedCommand + cmd + '\n';
|
||||
|
||||
// This try is for determining if the command is complete, or should
|
||||
// continue onto the next line.
|
||||
// We try to evaluate both expressions e.g.
|
||||
// '{ a : 1 }'
|
||||
// and statements e.g.
|
||||
// 'for (var i = 0; i < 10; i++) console.log(i);'
|
||||
|
||||
// First we attempt to eval as expression with parens.
|
||||
// This catches '{a : 1}' properly.
|
||||
self.eval('(' + evalCmd + ')',
|
||||
self.context,
|
||||
'repl',
|
||||
function(e, ret) {
|
||||
if (e && !e._isSyntaxError) return finish(e);
|
||||
|
||||
if (util.isFunction(ret) &&
|
||||
/^[\r\n\s]*function/.test(evalCmd) || e) {
|
||||
// Now as statement without parens.
|
||||
self.eval(evalCmd, self.context, 'repl', finish);
|
||||
} else {
|
||||
finish(null, ret);
|
||||
}
|
||||
});
|
||||
var evalCmd = self.bufferedCommand + cmd;
|
||||
if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
|
||||
// It's confusing for `{ a : 1 }` to be interpreted as a block
|
||||
// statement rather than an object literal. So, we first try
|
||||
// to wrap it in parentheses, so that it will be interpreted as
|
||||
// an expression.
|
||||
evalCmd = '(' + evalCmd + ')\n';
|
||||
} else {
|
||||
// otherwise we just append a \n so that it will be either
|
||||
// terminated, or continued onto the next expression if it's an
|
||||
// unexpected end of input.
|
||||
evalCmd = evalCmd + '\n';
|
||||
}
|
||||
|
||||
debug('eval %j', evalCmd);
|
||||
self.eval(evalCmd, self.context, 'repl', finish);
|
||||
} else {
|
||||
finish(null);
|
||||
}
|
||||
|
||||
function finish(e, ret) {
|
||||
|
||||
debug('finish', e, ret);
|
||||
self.memory(cmd);
|
||||
|
||||
// If error was SyntaxError and not JSON.parse error
|
||||
if (e && e._isSyntaxError) {
|
||||
if (!self.bufferedCommand && cmd.trim().match(/^npm /)) {
|
||||
self.outputStream.write('npm should be run outside of the ' +
|
||||
'node repl, in your normal shell.\n' +
|
||||
'(Press Control-D to exit.)\n');
|
||||
self.bufferedCommand = '';
|
||||
self.displayPrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start buffering data like that:
|
||||
// {
|
||||
// ... x: 1
|
||||
// ... }
|
||||
self.bufferedCommand += cmd + '\n';
|
||||
if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) {
|
||||
self.outputStream.write('npm should be run outside of the ' +
|
||||
'node repl, in your normal shell.\n' +
|
||||
'(Press Control-D to exit.)\n');
|
||||
self.bufferedCommand = '';
|
||||
self.displayPrompt();
|
||||
return;
|
||||
} else if (e) {
|
||||
self._domain.emit('error', e);
|
||||
}
|
||||
|
||||
// If error was SyntaxError and not JSON.parse error
|
||||
if (e) {
|
||||
if (isRecoverableError(e)) {
|
||||
// Start buffering data like that:
|
||||
// {
|
||||
// ... x: 1
|
||||
// ... }
|
||||
self.bufferedCommand += cmd + '\n';
|
||||
self.displayPrompt();
|
||||
return;
|
||||
} else {
|
||||
self._domain.emit('error', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear buffer if no SyntaxErrors
|
||||
@ -940,15 +936,10 @@ REPLServer.prototype.convertToContext = function(cmd) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns `true` if "e" is a SyntaxError, `false` otherwise.
|
||||
* filters out strict-mode errors, which are not recoverable
|
||||
*/
|
||||
function isSyntaxError(e) {
|
||||
// Convert error to string
|
||||
e = e && (e.stack || e.toString());
|
||||
return e && e.match(/^SyntaxError/) &&
|
||||
// "strict mode" syntax errors
|
||||
!e.match(/^SyntaxError: .*strict mode.*/i) &&
|
||||
!e.match(/^SyntaxError: Assignment to constant variable/i);
|
||||
// If the error is that we've unexpectedly ended the input,
|
||||
// then let the user try to recover by adding more input.
|
||||
function isRecoverableError(e) {
|
||||
return e &&
|
||||
e.name === 'SyntaxError' &&
|
||||
/^Unexpected end of input/.test(e.message);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user