diff --git a/doc/api/repl.md b/doc/api/repl.md index 99b8c0dbb5d958..411f7da0669c79 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -27,11 +27,7 @@ customizable evaluation functions. The following special commands are supported by all REPL instances: -* `.break` - When in the process of inputting a multi-line expression, entering - the `.break` command (or pressing the `-C` key combination) will abort - further input or processing of that expression. -* `.clear` - Resets the REPL `context` to an empty object and clears any - multi-line expression currently being input. +* `.clear` - Resets the local REPL `context` to an empty object. * `.exit` - Close the I/O stream, causing the REPL to exit. * `.help` - Show this list of special commands. * `.save` - Save the current REPL session to a file: @@ -56,7 +52,7 @@ welcome('Node.js User'); The following key combinations in the REPL have these special effects: -* `-C` - When pressed once, has the same effect as the `.break` command. +* `-C` - When pressed once, it will break the current command. When pressed twice on a blank line, has the same effect as the `.exit` command. * `-D` - Has the same effect as the `.exit` command. @@ -174,34 +170,6 @@ function myEval(cmd, context, filename, callback) { repl.start({prompt: '> ', eval: myEval}); ``` -#### Recoverable Errors - -As a user is typing input into the REPL prompt, pressing the `` key will -send the current line of input to the `eval` function. In order to support -multi-line input, the eval function can return an instance of `repl.Recoverable` -to the provided callback function: - -```js -function eval(cmd, context, filename, callback) { - var result; - try { - result = vm.runInThisContext(cmd); - } catch (e) { - if (isRecoverableError(e)) { - return callback(new repl.Recoverable(e)); - } - } - callback(null, result); -} - -function isRecoverableError(error) { - if (error.name === 'SyntaxError') { - return /^(Unexpected end of input|Unexpected token)/.test(error.message); - } - return false; -} -``` - ### Customizing REPL Output By default, `repl.REPLServer` instances format output using the @@ -286,15 +254,15 @@ reset to its initial value using the `.clear` command: ```js $ ./node example.js ->m +> m 'test' ->m = 1 +> m = 1 1 ->m +> m 1 ->.clear +> .clear Clearing context... ->m +> m 'test' > ``` @@ -408,6 +376,18 @@ added: v0.1.91 * `breakEvalOnSigint` - Stop evaluating the current piece of code when `SIGINT` is received, i.e. `Ctrl+C` is pressed. This cannot be used together with a custom `eval` function. Defaults to `false`. + * `executeOnTimeout` {number} If `terminal` is false,`executeOnTimeout` + delay is used to determine the end of expression. Defaults to 50ms. + `executeOnTimeout` will be coerced to `[100, 2000]` range. + * `displayWelcomeMessage` {boolean} If `true`, welcome message will be + displayed. Defaults to `false`. +```js +> node +Welcome to Node.js <> (<> VM, <>) +Type ^M or enter to execute, ^J to continue, ^C to exit +Or try .help for help, more at https://nodejs.org/dist/<>/docs/api/repl.html +> +``` The `repl.start()` method creates and starts a `repl.REPLServer` instance. @@ -419,11 +399,15 @@ without passing any arguments (or by passing the `-i` argument): ```js $ node -> a = [1, 2, 3]; +Welcome to Node.js v6.5.0 (v8 VM, 5.1.281.81) +Type ^M or enter to execute, ^J to continue, ^C to exit +Or try .help for help, more at https://nodejs.org/dist/v6.5.0/docs/api/repl.html + +> a = [1, 2, 3]; // ^M or ⏎ [ 1, 2, 3 ] -> a.forEach((v) => { -... console.log(v); -... }); +> a.forEach((v) => { // ^J to continue + console.log(v); + }); 1 2 3 diff --git a/lib/_debugger.js b/lib/_debugger.js index 738eb9871bb2b6..2cda2673b3a637 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -740,7 +740,8 @@ function Interface(stdin, stdout, args) { output: this.stdout, eval: (code, ctx, file, cb) => this.controlEval(code, ctx, file, cb), useGlobal: false, - ignoreUndefined: true + ignoreUndefined: true, + displayWelcomeMessage: false, }; if (parseInt(process.env['NODE_NO_READLINE'], 10)) { opts.terminal = false; diff --git a/lib/internal/repl.js b/lib/internal/repl.js index be0201e3045c3d..677ae0679e642a 100644 --- a/lib/internal/repl.js +++ b/lib/internal/repl.js @@ -24,7 +24,8 @@ function createRepl(env, opts, cb) { ignoreUndefined: false, terminal: process.stdout.isTTY, useGlobal: true, - breakEvalOnSigint: true + breakEvalOnSigint: true, + displayWelcomeMessage: process.execArgv.indexOf('-i') === -1, }, opts); if (parseInt(env.NODE_NO_READLINE)) { diff --git a/lib/readline.js b/lib/readline.js index 9b925a6d990d8b..c388ea570ef140 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -969,7 +969,7 @@ function emitKeypressEvents(stream, iface) { clearTimeout(timeoutId); if (iface) { - iface._sawKeyPress = r.length === 1; + iface._sawKeyPress = r.length === 1 || (r[0] === '\x1b'); } for (var i = 0; i < r.length; i++) { diff --git a/lib/repl.js b/lib/repl.js index 021078eb336d55..7de98d93e66937 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -21,6 +21,10 @@ 'use strict'; +const kMinExecuteOnTimeout = 50; +const kMaxExecuteOnTimeout = 2000; + + const internalModule = require('internal/module'); const internalUtil = require('internal/util'); const util = require('util'); @@ -37,7 +41,6 @@ const domain = require('domain'); const debug = util.debuglog('repl'); const parentModule = module; -const replMap = new WeakMap(); const GLOBAL_OBJECT_PROPERTIES = ['NaN', 'Infinity', 'undefined', 'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', @@ -77,99 +80,6 @@ exports.writer = util.inspect; exports._builtinLibs = internalModule.builtinLibs; - -class LineParser { - - constructor() { - this.reset(); - } - - reset() { - this._literal = null; - this.shouldFail = false; - this.blockComment = false; - this.regExpLiteral = false; - } - - parseLine(line) { - var previous = null; - this.shouldFail = false; - const wasWithinStrLiteral = this._literal !== null; - - for (const current of line) { - if (previous === '\\') { - // valid escaping, skip processing. previous doesn't matter anymore - previous = null; - continue; - } - - if (!this._literal) { - if (this.regExpLiteral && current === '/') { - this.regExpLiteral = false; - previous = null; - continue; - } - if (previous === '*' && current === '/') { - if (this.blockComment) { - this.blockComment = false; - previous = null; - continue; - } else { - this.shouldFail = true; - break; - } - } - - // ignore rest of the line if `current` and `previous` are `/`s - if (previous === current && previous === '/' && !this.blockComment) { - break; - } - - if (previous === '/') { - if (current === '*') { - this.blockComment = true; - } else { - this.regExpLiteral = true; - } - previous = null; - } - } - - if (this.blockComment || this.regExpLiteral) continue; - - if (current === this._literal) { - this._literal = null; - } else if (current === '\'' || current === '"') { - this._literal = this._literal || current; - } - - previous = current; - } - - const isWithinStrLiteral = this._literal !== null; - - if (!wasWithinStrLiteral && !isWithinStrLiteral) { - // Current line has nothing to do with String literals, trim both ends - line = line.trim(); - } else if (wasWithinStrLiteral && !isWithinStrLiteral) { - // was part of a string literal, but it is over now, trim only the end - line = line.trimRight(); - } else if (isWithinStrLiteral && !wasWithinStrLiteral) { - // was not part of a string literal, but it is now, trim only the start - line = line.trimLeft(); - } - - const lastChar = line.charAt(line.length - 1); - - this.shouldFail = this.shouldFail || - ((!this._literal && lastChar === '\\') || - (this._literal && lastChar !== '\\')); - - return line; - } -} - - function REPLServer(prompt, stream, eval_, @@ -212,6 +122,7 @@ function REPLServer(prompt, var self = this; self._domain = dom || domain.create(); + self._cmdContinue = false; self.useGlobal = !!useGlobal; self.ignoreUndefined = !!ignoreUndefined; @@ -221,7 +132,15 @@ function REPLServer(prompt, self.breakEvalOnSigint = !!breakEvalOnSigint; self.editorMode = false; - self._inTemplateLiteral = false; + // For non-terminal case, used to demarcate the executable + // unit of code by delay + self.executeOnTimeout = Math.max( + kMinExecuteOnTimeout, + Math.min(kMaxExecuteOnTimeout, + options.executeOnTimeout >>> 0)); + // For non-terminal case, this flag will be set to true + // on execeeding `executeOnTimeout`. + self._readyToExecute = false; // just for backwards compat, see github.com/joyent/node/pull/7127 self.rli = this; @@ -263,11 +182,7 @@ function REPLServer(prompt, continue; } // preserve original error for wrapped command - const error = wrappedErr || e; - if (isRecoverableError(error, self)) - err = new Recoverable(error); - else - err = error; + err = wrappedErr || e; } break; } @@ -318,11 +233,7 @@ function REPLServer(prompt, } } } catch (e) { - err = e; - if (err.message === 'Script execution interrupted.') { - // The stack trace for this case is not very useful anyway. - Object.defineProperty(err, 'stack', { value: '' }); - } + err = prettifyStackTrace(e); if (err && process.domain) { debug('not recoverable, send to domain'); @@ -345,23 +256,16 @@ function REPLServer(prompt, self.eval = self._domain.bind(eval_); self._domain.on('error', function debugDomainError(e) { - debug('domain error'); - const top = replMap.get(self); + debug('domain error', e); internalUtil.decorateErrorStack(e); - if (e instanceof SyntaxError && e.stack) { - // remove repl:line-number and stack trace - e.stack = e.stack - .replace(/^repl:\d+\r?\n/, '') - .replace(/^\s+at\s.*\n?/gm, ''); - } else if (e.stack && self.replMode === exports.REPL_MODE_STRICT) { + if (e.stack && self.replMode === exports.REPL_MODE_STRICT) { e.stack = e.stack.replace(/(\s+at\s+repl:)(\d+)/, (_, pre, line) => pre + (line - 1)); } - top.outputStream.write((e.stack || e) + '\n'); - top.lineParser.reset(); - top.bufferedCommand = ''; - top.lines.level = []; - top.displayPrompt(); + self.outputStream.write((e.stack || e) + '\n'); + self.bufferedCommand = ''; + self.lines.level = []; + self.displayPrompt(); }); if (!input && !output) { @@ -385,7 +289,6 @@ function REPLServer(prompt, self.outputStream = output; self.resetContext(); - self.lineParser = new LineParser(); self.bufferedCommand = ''; self.lines.level = []; @@ -395,7 +298,7 @@ function REPLServer(prompt, : completer; function completer(text, cb) { - complete.call(self, text, self.editorMode + self.defaultComplete(text, self.editorMode || self._cmdContinue ? self.completeOnEditorMode(cb) : cb); } @@ -431,13 +334,12 @@ function REPLServer(prompt, }); var sawSIGINT = false; - var sawCtrlD = false; self.on('SIGINT', function onSigInt() { var empty = self.line.length === 0; self.clearLine(); + const editorMode = self.editorMode; self.turnOffEditorMode(); - - if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) { + if (!self.bufferedCommand && !editorMode && empty) { if (sawSIGINT) { self.close(); sawSIGINT = false; @@ -449,19 +351,55 @@ function REPLServer(prompt, sawSIGINT = false; } - self.lineParser.reset(); + self._cmdContinue = false; self.bufferedCommand = ''; self.lines.level = []; self.displayPrompt(); }); + let executeOnTimeoutID; self.on('line', function onLine(cmd) { debug('line %j', cmd); sawSIGINT = false; + cmd = cmd || ''; - if (self.editorMode) { + // Check to see if a REPL keyword was used. If it returns true, + // display next prompt and return. + const command = `${self.bufferedCommand}${cmd}`.trim(); + if (command && command.charAt(0) === '.' && isNaN(parseFloat(command))) { + var matches = command.match(/^\.([^\s]+)\s*(.*)$/); + var keyword = matches && matches[1]; + var rest = matches && matches[2]; + if (self.parseREPLKeyword(keyword, rest) === true) { + return; + } else { + self.outputStream.write('Invalid REPL keyword\n'); + self.displayPrompt(); + return; + } + } + + if (!self.terminal) { + clearTimeout(executeOnTimeoutID); + if (self._readyToExecute) { + self._readyToExecute = false; + } else { + self.bufferedCommand += cmd + '\n'; + self.memory(cmd); + executeOnTimeoutID = setTimeout(() => { + self._readyToExecute = true; + self.emit('line', self._line_buffer); + self._line_buffer = ''; + }, self.executeOnTimeout); + return; + } + } else if (self.editorMode || self._cmdContinue) { self.bufferedCommand += cmd + '\n'; + if (self._cmdContinue && !self.editorMode) { + self.prompt(false); + } + // code alignment const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null; if (matches) { @@ -474,76 +412,37 @@ function REPLServer(prompt, return; } - // leading whitespaces in template literals should not be trimmed. - if (self._inTemplateLiteral) { - self._inTemplateLiteral = false; - } else { - cmd = self.lineParser.parseLine(cmd); - } + // Clear buffer + self.bufferedCommand = ''; - // Check to see if a REPL keyword was used. If it returns true, - // display next prompt and return. - if (cmd && cmd.charAt(0) === '.' && isNaN(parseFloat(cmd))) { - var matches = cmd.match(/^\.([^\s]+)\s*(.*)$/); - var keyword = matches && matches[1]; - var rest = matches && matches[2]; - if (self.parseREPLKeyword(keyword, rest) === true) { - return; - } else if (!self.bufferedCommand) { - self.outputStream.write('Invalid REPL keyword\n'); - finish(null); - return; - } + const evalCmd = preprocess(command); + // Ignore empty command + if (!evalCmd.trim()) { + self.displayPrompt(); + return; } - var evalCmd = self.bufferedCommand + cmd; - evalCmd = preprocess(evalCmd); - - debug('eval %j', evalCmd); self.eval(evalCmd, self.context, 'repl', finish); + self.wrappedCmd = false; function finish(e, ret) { - debug('finish', e, ret); + debug('finish', e || ret); self.memory(cmd); - self.wrappedCmd = false; - if (e && !self.bufferedCommand && cmd.trim().startsWith('npm ')) { + if (e && evalCmd.match(/\s*npm(?:\s+[\w\-]+)?/)) { self.outputStream.write('npm should be run outside of the ' + 'node repl, in your normal shell.\n' + '(Press Control-D to exit.)\n'); - self.lineParser.reset(); - self.bufferedCommand = ''; self.displayPrompt(); return; } - // If error was SyntaxError and not JSON.parse error if (e) { - if (e instanceof Recoverable && !self.lineParser.shouldFail && - !sawCtrlD) { - // Start buffering data like that: - // { - // ... x: 1 - // ... } - self.bufferedCommand += cmd + '\n'; - self.displayPrompt(); - return; - } else { - self._domain.emit('error', e.err || e); - } + self._domain.emit('error', prettifyStackTrace(e.err || e)); } - // Clear buffer if no SyntaxErrors - self.lineParser.reset(); - self.bufferedCommand = ''; - sawCtrlD = false; - // If we got any output - print it (if no error) if (!e && - // When an invalid REPL command is used, error message is printed - // immediately. We don't have to print anything else. So, only when - // the second argument to this function is there, print it. - arguments.length === 2 && (!self.ignoreUndefined || ret !== undefined)) { if (!self.underscoreAssigned) { self.last = ret; @@ -572,7 +471,8 @@ function REPLServer(prompt, // Append a \n so that it will be either // terminated, or continued onto the next expression if it's an // unexpected end of input. - return `${cmd}\n`; + // return `${cmd}\n`; + return cmd.endsWith('\n') ? cmd : `${cmd}\n`; } }); @@ -590,9 +490,29 @@ function REPLServer(prompt, // Wrap readline tty to enable editor mode const ttyWrite = self._ttyWrite.bind(self); - self._ttyWrite = (d, key) => { - key = key || {}; + + self._ttyWrite = (d, key = { name: '' }) => { + const {name} = key; + if (!self.editorMode && (name === 'return' || name === 'enter')) { + const lastMode = self._cmdContinue; + // While pasting code, repl works like expecting more input + // User has to hit 'return' to execute or ^C / ^D to cancel it + self._cmdContinue = !self._sawKeyPress || + self._sawKeyPress && name === 'enter'; + if (self._cmdContinue !== lastMode) { + REPLServer.super_.prototype.setPrompt.call( + this, + self._cmdContinue + ? ' '.repeat(self._initialPrompt.length) + : self._initialPrompt + ); + } + } + if (!self.editorMode || !self.terminal) { + if (self._cmdContinue) { + self._previousKey = null; + } ttyWrite(d, key); return; } @@ -602,8 +522,7 @@ function REPLServer(prompt, switch (key.name) { case 'd': // End editor mode self.turnOffEditorMode(); - sawCtrlD = true; - ttyWrite(d, { name: 'return' }); + ttyWrite(null, { name: 'return' }); break; case 'n': // Override next history item case 'p': // Override previous history item @@ -627,6 +546,22 @@ function REPLServer(prompt, } }; + self.displayWelcomeMessage = !!options.displayWelcomeMessage && + self.terminal; + if (self.displayWelcomeMessage) { + const version = process.version; + const jsEngine = process.jsEngine || 'v8'; + const jsEngineVersion = process.versions[jsEngine]; + const docURL = `https://nodejs.org/dist/${version}/docs/api/repl.html`; + + const welcome = `Welcome to Node.js ${version} (${jsEngine} VM,` + + ` ${jsEngineVersion})\nType ^M or enter to execute,` + + ` ^J to continue, ^C to exit\n` + + `Or try .help for help, more at ${docURL}\n\n`; + + self.outputStream.write(welcome); + } + if (self.replMode === exports.REPL_MODE_MAGIC) { self.outputStream.write( 'magic mode is deprecated. Switched to sloppy mode\n'); @@ -657,11 +592,14 @@ exports.start = function(prompt, ignoreUndefined, replMode); if (!exports.repl) exports.repl = repl; - replMap.set(repl, repl); return repl; }; REPLServer.prototype.close = function close() { + if (!this.terminal) { + this.forceExecute(); + } + if (this.terminal && this._flushing && !this._closingOnFlush) { this._closingOnFlush = true; this.once('flushHistory', () => @@ -675,6 +613,17 @@ REPLServer.prototype.close = function close() { ); }; +REPLServer.prototype.forceExecute = function() { + // execute buffered command forcefully + // helpful for clean up & simulation cases + if (this.bufferedCommand) { + this._readyToExecute = true; + this._cmdContinue = false; + this.turnOffEditorMode(); + this.emit('line', ''); + } +}; + REPLServer.prototype.createContext = function() { var context; if (this.useGlobal) { @@ -737,13 +686,6 @@ REPLServer.prototype.resetContext = function() { REPLServer.prototype.displayPrompt = function(preserveCursor) { var prompt = this._initialPrompt; - if (this.bufferedCommand.length) { - prompt = '...'; - const len = this.lines.level.length ? this.lines.level.length - 1 : 0; - const levelInd = '..'.repeat(len); - prompt += levelInd + ' '; - } - // Do not overwrite `_initialPrompt` here REPLServer.super_.prototype.setPrompt.call(this, prompt); this.prompt(preserveCursor); @@ -757,20 +699,19 @@ REPLServer.prototype.setPrompt = function setPrompt(prompt) { REPLServer.prototype.turnOffEditorMode = function() { this.editorMode = false; + this._cmdContinue = false; this.setPrompt(this._initialPrompt); }; - // A stream to push an array into a REPL // used in REPLServer.complete function ArrayStream() { Stream.call(this); - this.run = function(data) { - var self = this; - data.forEach(function(line) { - self.emit('data', line + '\n'); - }); + this.run = (data, repl) => { + repl._readyToExecute = true; + repl._cmdContinue = false; + repl.emit('line', `${data.join('\n')}\n`); }; } util.inherits(ArrayStream, Stream); @@ -807,7 +748,7 @@ REPLServer.prototype.complete = function() { // // Warning: This eval's code like "foo.bar.baz", so it will run property // getter code. -function complete(line, callback) { +REPLServer.prototype.defaultComplete = function(line, callback) { // There may be local variables to evaluate, try a nested REPL if (this.bufferedCommand !== undefined && this.bufferedCommand.length) { // Get a new array of inputed lines @@ -821,9 +762,8 @@ function complete(line, callback) { }); var flat = new ArrayStream(); // make a new "input" stream var magic = new REPLServer('', flat); // make a nested REPL - replMap.set(magic, replMap.get(this)); magic.context = magic.createContext(); - flat.run(tmp); // eval the flattened code + flat.run(tmp, magic); // eval the flattened code // all this is only profitable if the nested REPL // does not have a bufferedCommand if (!magic.bufferedCommand) { @@ -1063,10 +1003,9 @@ function complete(line, callback) { completions.pop(); } } - callback(null, [completions || [], completeOn]); } -} +}; function longestCommonPrefix(arr = []) { const cnt = arr.length; @@ -1218,33 +1157,17 @@ function addStandardGlobals(completionGroups, filter) { } function defineDefaultCommands(repl) { - repl.defineCommand('break', { - help: 'Sometimes you get stuck, this gets you out', - action: function() { - this.lineParser.reset(); - this.bufferedCommand = ''; - this.displayPrompt(); - } - }); - - var clearMessage; - if (repl.useGlobal) { - clearMessage = 'Alias for .break'; - } else { - clearMessage = 'Break, and also clear the local context'; - } - repl.defineCommand('clear', { - help: clearMessage, - action: function() { - this.lineParser.reset(); - this.bufferedCommand = ''; - if (!this.useGlobal) { + if (!repl.useGlobal) { + repl.defineCommand('clear', { + help: 'Clear the local context', + action: function() { + this.bufferedCommand = ''; this.outputStream.write('Clearing context...\n'); this.resetContext(); + this.displayPrompt(); } - this.displayPrompt(); - } - }); + }); + } repl.defineCommand('exit', { help: 'Exit the repl', @@ -1314,6 +1237,7 @@ function defineDefaultCommands(repl) { action() { if (!this.terminal) return; this.editorMode = true; + this._cmdContinue = false; REPLServer.super_.prototype.setPrompt.call(this, ''); this.outputStream.write( '// Entering editor mode (^D to finish, ^C to cancel)\n'); @@ -1326,6 +1250,32 @@ function regexpEscape(s) { } +function indexOfInternalFrame(frames) { + return frames.indexOf('vm.js') !== -1 + || frames.indexOf('REPLServer.') !== -1; +} + +function prettifyStackTrace(e) { + // node emitted error message + if (e.message === 'Script execution interrupted.') { + // The stack trace for this case is not very useful anyway. + Object.defineProperty(e, 'stack', { value: '' }); + } else if (e.stack) { + const frames = e.stack.split('\n'); + if (e instanceof SyntaxError && /^repl:/.test(e.stack)) { + // remove repl:line-number + frames.splice(0, 1); + } + + const pos = frames.findIndex(indexOfInternalFrame); + if (pos !== -1) frames.splice(pos); + + e.stack = frames.join('\n'); + } + + return e; +} + /** * Converts commands that use var and function () to use the * local exports.context when evaled. This provides a local context @@ -1355,37 +1305,3 @@ REPLServer.prototype.convertToContext = util.deprecate(function(cmd) { return cmd; }, 'replServer.convertToContext() is deprecated'); - -function bailOnIllegalToken(parser) { - return parser._literal === null && - !parser.blockComment && - !parser.regExpLiteral; -} - -// 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, self) { - if (e && e.name === 'SyntaxError') { - var message = e.message; - if (message === 'Unterminated template literal' || - message === 'Missing } in template expression') { - self._inTemplateLiteral = true; - return true; - } - - if (message.startsWith('Unexpected end of input') || - message.startsWith('missing ) after argument list') || - message.startsWith('Unexpected token')) - return true; - - if (message === 'Invalid or unexpected token') - return !bailOnIllegalToken(self.lineParser); - } - return false; -} - -function Recoverable(err) { - this.err = err; -} -inherits(Recoverable, SyntaxError); -exports.Recoverable = Recoverable; diff --git a/test/known_issues/test-repl-function-redefinition-edge-case.js b/test/known_issues/test-repl-function-redefinition-edge-case.js index 03b721fba7e7d5..c72a292f2369ec 100644 --- a/test/known_issues/test-repl-function-redefinition-edge-case.js +++ b/test/known_issues/test-repl-function-redefinition-edge-case.js @@ -10,8 +10,11 @@ common.globalCheck = false; const r = initRepl(); r.input.emit('data', 'function a() { return 42; } (1)\n'); +r.forceExecute(); r.input.emit('data', 'a\n'); +r.forceExecute(); r.input.emit('data', '.exit'); +r.forceExecute(); const expected = '1\n[Function a]\n'; const got = r.output.accumulator.join(''); @@ -32,7 +35,7 @@ function initRepl() { input, output, useColors: false, - terminal: false, + terminal: true, prompt: '' }); } diff --git a/test/known_issues/test-repl-require-context.js b/test/known_issues/test-repl-require-context.js index 2c9195d940e2e4..445aa3705068b0 100644 --- a/test/known_issues/test-repl-require-context.js +++ b/test/known_issues/test-repl-require-context.js @@ -26,7 +26,16 @@ r.on('exit', common.mustCall(() => { assert.deepStrictEqual(results, ['undefined', 'true', 'true', '']); })); -inputStream.write('const isObject = (obj) => obj.constructor === Object;\n'); -inputStream.write('isObject({});\n'); -inputStream.write(`require('${fixture}').isObject({});\n`); -r.close(); +const cmds = [ + 'const isObject = (obj) => obj.constructor === Object;', + 'isObject({});', + `require('${fixture}').isObject({});` +]; + +const run = ([cmd, ...rest], cb = () => {}) => { + if (!cmd) return cb(); + inputStream.write(`${cmd}\n`); + setTimeout(() => run(rest, cb), 200); +}; + +run(cmds, () => r.close()); diff --git a/test/parallel/test-debug-no-context.js b/test/parallel/test-debug-no-context.js index fd78612ef174d6..dbd9acfb02fecd 100644 --- a/test/parallel/test-debug-no-context.js +++ b/test/parallel/test-debug-no-context.js @@ -6,11 +6,19 @@ const spawn = require('child_process').spawn; const args = ['--debug', `--debug-port=${common.PORT}`, '--interactive']; const proc = spawn(process.execPath, args); -proc.stdin.write(` - util.inspect(Promise.resolve(42)); - util.inspect(Promise.resolve(1337)); - .exit -`); +let commands = [ + 'util.inspect(Promise.resolve(42));', + 'util.inspect(Promise.resolve(1337));', + '.exit' +]; +const run = ([cmd, ...rest]) => { + if (!cmd) { + return; + } + commands = rest; + proc.stdin.write(`${cmd}\n`); +}; + proc.on('exit', common.mustCall((exitCode, signalCode) => { // This next line should be included but unfortunately Win10 fails from time // to time in CI. See https://github.com/nodejs/node/issues/5268 @@ -19,8 +27,15 @@ proc.on('exit', common.mustCall((exitCode, signalCode) => { })); let stdout = ''; proc.stdout.setEncoding('utf8'); -proc.stdout.on('data', (data) => stdout += data); +proc.stdout.on('data', (data) => { + if (data !== '> ') { + setTimeout(() => run(commands), 100); + } + stdout += data; +}); process.on('exit', () => { assert(stdout.includes('Promise { 42 }')); assert(stdout.includes('Promise { 1337 }')); }); + +run(commands); diff --git a/test/parallel/test-preload.js b/test/parallel/test-preload.js index e60b29dfbe7e47..a4af541b5b2326 100644 --- a/test/parallel/test-preload.js +++ b/test/parallel/test-preload.js @@ -126,8 +126,19 @@ const interactive = childProcess.exec(nodeBinary + ' ' assert.strictEqual(stdout, `> 'test'\n> `); })); -interactive.stdin.write('a\n'); -interactive.stdin.write('process.exit()\n'); +const commands = [ + 'a' +]; + +const run = ([cmd, ...rest]) => { + if (!cmd) { + interactive.stdin.end(); + return; + } + interactive.stdin.write(`${cmd}\n`); + setTimeout(() => run(rest), 200); +}; +run(commands); childProcess.exec(nodeBinary + ' ' + '--require ' + fixture('cluster-preload.js') + ' ' diff --git a/test/parallel/test-repl-.editor.js b/test/parallel/test-repl-.editor.js index 678d6d5c6d94f8..18b21d3e1a43bb 100644 --- a/test/parallel/test-repl-.editor.js +++ b/test/parallel/test-repl-.editor.js @@ -29,6 +29,7 @@ function run({input, output, event, checkTerminalCodes = true}) { }); stream.emit('data', '.editor\n'); + replServer.forceExecute(); stream.emit('data', input); replServer.write('', event); replServer.close(); @@ -44,7 +45,7 @@ function run({input, output, event, checkTerminalCodes = true}) { const tests = [ { input: '', - output: '\n(To exit, press ^C again or type .exit)', + output: '', event: {ctrl: true, name: 'c'} }, { @@ -65,8 +66,7 @@ const tests = [ { input: '', output: '', - checkTerminalCodes: false, - event: null, + checkTerminalCodes: false } ]; @@ -87,7 +87,9 @@ function testCodeAligment({input, cursor = 0, line = ''}) { useColors: false }); - stream.emit('data', '.editor\n'); + stream.emit('data', '.editor'); + replServer._sawKeyPress = true; + replServer.write('', {name: 'return'}); input.split('').forEach((ch) => stream.emit('data', ch)); // Test the content of current line and the cursor position assert.strictEqual(line, replServer.line); diff --git a/test/parallel/test-repl-.save.load.js b/test/parallel/test-repl-.save.load.js index 247f359e829d25..90c7d58a226ba4 100644 --- a/test/parallel/test-repl-.save.load.js +++ b/test/parallel/test-repl-.save.load.js @@ -11,52 +11,41 @@ var repl = require('repl'); var works = [['inner.one'], 'inner.o']; const putIn = new common.ArrayStream(); -var testMe = repl.start('', putIn); +const testMe = repl.start('', putIn); -var testFile = [ +const testFile = [ 'var top = function() {', 'var inner = {one:1};' ]; var saveFileName = join(common.tmpDir, 'test.save.js'); -// input some data +// // input some data putIn.run(testFile); +testMe.forceExecute(); // save it to a file putIn.run(['.save ' + saveFileName]); +testMe.forceExecute(); // the file should have what I wrote -assert.equal(fs.readFileSync(saveFileName, 'utf8'), testFile.join('\n') + '\n'); - -{ - // save .editor mode code - const cmds = [ - 'function testSave() {', - 'return "saved";', - '}' - ]; - const putIn = new common.ArrayStream(); - const replServer = repl.start('', putIn); - - putIn.run(['.editor']); - putIn.run(cmds); - replServer.write('', {ctrl: true, name: 'd'}); - - putIn.run([`.save ${saveFileName}`]); - replServer.close(); - assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'), - `${cmds.join('\n')}\n`); -} +assert.equal(fs.readFileSync(saveFileName, 'utf8'), + testFile.join('\n') + '\n\n'); // make sure that the REPL data is "correct" // so when I load it back I know I'm good +putIn.run(['.clear']); +testMe.forceExecute(); +putIn.run(testFile); + testMe.complete('inner.o', function(error, data) { assert.deepStrictEqual(data, works); }); // clear the REPL +testMe.forceExecute(); putIn.run(['.clear']); +testMe.forceExecute(); // Load the file back in putIn.run(['.load ' + saveFileName]); @@ -68,6 +57,7 @@ testMe.complete('inner.o', function(error, data) { // clear the REPL putIn.run(['.clear']); +testMe.forceExecute(); var loadFile = join(common.tmpDir, 'file.does.not.exist'); @@ -79,6 +69,7 @@ putIn.write = function(data) { putIn.write = function() {}; }; putIn.run(['.load ' + loadFile]); +testMe.forceExecute(); // throw error on loading directory loadFile = common.tmpDir; @@ -87,9 +78,11 @@ putIn.write = function(data) { putIn.write = function() {}; }; putIn.run(['.load ' + loadFile]); +testMe.forceExecute(); // clear the REPL putIn.run(['.clear']); +testMe.forceExecute(); // NUL (\0) is disallowed in filenames in UNIX-like operating systems and // Windows so we can use that to test failed saves @@ -105,3 +98,31 @@ putIn.write = function(data) { // save it to a file putIn.run(['.save ' + invalidFileName]); +testMe.forceExecute(); + +{ + // save .editor mode code + const cmds = [ + 'function testSave() {', + 'return "saved";', + '}' + ]; + const putIn = new common.ArrayStream(); + const replServer = repl.start({ + prompt: '', + input: putIn, + output: putIn, + terminal: true, + }); + + putIn.run(['.editor']); + replServer.forceExecute(); + putIn.run(cmds); + replServer.write('', {ctrl: true, name: 'd'}); + + putIn.run([`.save ${saveFileName}`]); + replServer.forceExecute(); + replServer.close(); + assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'), + `${cmds.join('\n')}\n\n`); +} diff --git a/test/parallel/test-repl-autolibs.js b/test/parallel/test-repl-autolibs.js index 15f779d3b12269..e05fe1c56147f8 100644 --- a/test/parallel/test-repl-autolibs.js +++ b/test/parallel/test-repl-autolibs.js @@ -8,7 +8,7 @@ var repl = require('repl'); common.globalCheck = false; const putIn = new common.ArrayStream(); -repl.start('', putIn, null, true); +const cli = repl.start('', putIn, null, true); test1(); @@ -27,6 +27,7 @@ function test1() { }; assert(!gotWrite); putIn.run(['fs']); + cli.forceExecute(); assert(gotWrite); } @@ -45,5 +46,6 @@ function test2() { global.url = val; assert(!gotWrite); putIn.run(['url']); + cli.forceExecute(); assert(gotWrite); } diff --git a/test/parallel/test-repl-definecommand.js b/test/parallel/test-repl-definecommand.js index 6c0c75bca8db79..0c3cbcd792033e 100644 --- a/test/parallel/test-repl-definecommand.js +++ b/test/parallel/test-repl-definecommand.js @@ -35,9 +35,12 @@ r.defineCommand('say2', function() { }); inputStream.write('.help\n'); +r.forceExecute(); assert(/\n.say1 help for say1\n/.test(output), 'help for say1 not present'); assert(/\n.say2\n/.test(output), 'help for say2 not present'); inputStream.write('.say1 node developer\n'); +r.forceExecute(); assert(/> hello node developer/.test(output), 'say1 outputted incorrectly'); inputStream.write('.say2 node developer\n'); +r.forceExecute(); assert(/> hello from say2/.test(output), 'say2 outputted incorrectly'); diff --git a/test/parallel/test-repl-eval.js b/test/parallel/test-repl-eval.js index 7e5c7d39946c67..c99db675bbb8bf 100644 --- a/test/parallel/test-repl-eval.js +++ b/test/parallel/test-repl-eval.js @@ -19,8 +19,10 @@ const repl = require('repl'); try { r.write('foo\n'); + r.forceExecute(); } finally { r.write('.exit\n'); + r.forceExecute(); } process.on('exit', () => { diff --git a/test/parallel/test-repl-mode.js b/test/parallel/test-repl-mode.js index 2732b9be39f115..cd79e9973bfc76 100644 --- a/test/parallel/test-repl-mode.js +++ b/test/parallel/test-repl-mode.js @@ -22,12 +22,14 @@ function testSloppyMode() { cli.input.emit('data', ` x = 3 `.trim() + '\n'); + cli.forceExecute(); assert.equal(cli.output.accumulator.join(''), '> 3\n> '); cli.output.accumulator.length = 0; cli.input.emit('data', ` let y = 3 `.trim() + '\n'); + cli.forceExecute(); assert.equal(cli.output.accumulator.join(''), 'undefined\n> '); } @@ -37,6 +39,7 @@ function testStrictMode() { cli.input.emit('data', ` x = 3 `.trim() + '\n'); + cli.forceExecute(); assert.ok(/ReferenceError: x is not defined/.test( cli.output.accumulator.join(''))); cli.output.accumulator.length = 0; @@ -44,6 +47,7 @@ function testStrictMode() { cli.input.emit('data', ` let y = 3 `.trim() + '\n'); + cli.forceExecute(); assert.equal(cli.output.accumulator.join(''), 'undefined\n> '); } @@ -51,18 +55,20 @@ function testAutoMode() { var cli = initRepl(repl.REPL_MODE_MAGIC); assert.equal(cli.output.accumulator.join(''), - 'magic mode is deprecated. Switched to sloppy mode\n> '); + 'magic mode is deprecated. Switched to sloppy mode\n> '); cli.output.accumulator.length = 0; cli.input.emit('data', ` x = 3 `.trim() + '\n'); + cli.forceExecute(); assert.equal(cli.output.accumulator.join(''), '3\n> '); cli.output.accumulator.length = 0; cli.input.emit('data', ` let y = 3 `.trim() + '\n'); + cli.forceExecute(); assert.equal(cli.output.accumulator.join(''), 'undefined\n> '); } diff --git a/test/parallel/test-repl-null.js b/test/parallel/test-repl-null.js index 337e194447d1f8..39a6550283eea2 100644 --- a/test/parallel/test-repl-null.js +++ b/test/parallel/test-repl-null.js @@ -5,8 +5,6 @@ const assert = require('assert'); var replserver = new repl.REPLServer(); -replserver._inTemplateLiteral = true; - // `null` gets treated like an empty string. (Should it? You have to do some // strange business to get it into the REPL. Maybe it should really throw?) @@ -15,3 +13,4 @@ assert.doesNotThrow(() => { }); replserver.emit('line', '.exit'); +replserver.close(); diff --git a/test/parallel/test-repl-options.js b/test/parallel/test-repl-options.js index a4360f1b77eec1..5b2ca38f078fb8 100644 --- a/test/parallel/test-repl-options.js +++ b/test/parallel/test-repl-options.js @@ -25,6 +25,8 @@ assert.equal(r1.useGlobal, false); assert.equal(r1.ignoreUndefined, false); assert.equal(r1.replMode, repl.REPL_MODE_SLOPPY); assert.equal(r1.historySize, 30); +assert.strictEqual(r1.displayWelcomeMessage, false); +assert.strictEqual(r1.executeOnTimeout, 50); // test r1 for backwards compact assert.equal(r1.rli.input, stream); @@ -47,7 +49,9 @@ var r2 = repl.start({ eval: evaler, writer: writer, replMode: repl.REPL_MODE_STRICT, - historySize: 50 + historySize: 50, + displayWelcomeMessage: true, + executeOnTimeout: 500 }); assert.equal(r2.input, stream); assert.equal(r2.output, stream); @@ -59,6 +63,8 @@ assert.equal(r2.useGlobal, true); assert.equal(r2.ignoreUndefined, true); assert.equal(r2.writer, writer); assert.equal(r2.replMode, repl.REPL_MODE_STRICT); +assert.strictEqual(r2.displayWelcomeMessage, false); +assert.strictEqual(r2.executeOnTimeout, 500); // test r2 for backwards compact assert.equal(r2.rli.input, stream); @@ -80,4 +86,6 @@ assert.strictEqual(r4.useGlobal, false); assert.strictEqual(r4.ignoreUndefined, false); assert.strictEqual(r4.replMode, repl.REPL_MODE_SLOPPY); assert.strictEqual(r4.historySize, 30); +assert.strictEqual(r4.displayWelcomeMessage, false); +assert.strictEqual(r4.executeOnTimeout, 50); r4.close(); diff --git a/test/parallel/test-repl-persistent-history.js b/test/parallel/test-repl-persistent-history.js index 2cdc1ab19c32c1..b4de87da4b2d79 100644 --- a/test/parallel/test-repl-persistent-history.js +++ b/test/parallel/test-repl-persistent-history.js @@ -19,13 +19,14 @@ os.homedir = function() { // Create an input stream specialized for testing an array of actions class ActionStream extends stream.Stream { - run(data) { + run(data, repl) { const _iter = data[Symbol.iterator](); const self = this; function doAction() { const next = _iter.next(); if (next.done) { + repl.forceExecute(); // Close the repl. Note that it must have a clean prompt to do so. setImmediate(function() { self.emit('keypress', '', { ctrl: true, name: 'd' }); @@ -33,7 +34,6 @@ class ActionStream extends stream.Stream { return; } const action = next.value; - if (typeof action === 'object') { self.emit('keypress', '', action); } else { @@ -51,7 +51,7 @@ ActionStream.prototype.readable = true; // Mock keys const UP = { name: 'up' }; -const ENTER = { name: 'enter' }; +const RETURN = { name: 'return' }; const CLEAR = { ctrl: true, name: 'u' }; // Common message bits const prompt = '> '; @@ -136,14 +136,14 @@ const tests = [ }, { env: { NODE_REPL_HISTORY_FILE: oldHistoryPath }, - test: [UP, CLEAR, '\'42\'', ENTER], + test: [UP, CLEAR, '\'42\'', RETURN], expected: [prompt, convertMsg, prompt, prompt + '\'=^.^=\'', prompt, '\'', - '4', '2', '\'', '\'42\'\n', prompt, prompt], + '4', '2', '\'', '\'42\'\n', prompt], clean: false }, { // Requires the above testcase env: {}, - test: [UP, UP, ENTER], + test: [UP, UP, RETURN], expected: [prompt, prompt + '\'42\'', prompt + '\'=^.^=\'', '\'=^.^=\'\n', prompt] }, @@ -199,7 +199,7 @@ function cleanupTmpFile() { // Copy our fixture to the tmp directory fs.createReadStream(historyFixturePath) - .pipe(fs.createWriteStream(historyPath)).on('unpipe', () => runTest()); + .pipe(fs.createWriteStream(historyPath)).on('unpipe', runTest); function runTest(assertCleaned) { const opts = tests.shift(); @@ -229,9 +229,8 @@ function runTest(assertCleaned) { output: new stream.Writable({ write(chunk, _, next) { const output = chunk.toString(); - // Ignore escapes and blank lines - if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) + if (output.charCodeAt(0) === 27 || /^\s+$/.test(output)) return next(); try { @@ -245,7 +244,8 @@ function runTest(assertCleaned) { }), prompt: prompt, useColors: false, - terminal: true + terminal: true, + displayWelcomeMessage: false }, function(err, repl) { if (err) { console.error(`Failed test # ${numtests - tests.length}`); @@ -277,7 +277,6 @@ function runTest(assertCleaned) { throw err; } } - - repl.inputStream.run(test); + repl.inputStream.run(test, repl); }); } diff --git a/test/parallel/test-repl-pretty-stack-trace.js b/test/parallel/test-repl-pretty-stack-trace.js new file mode 100644 index 00000000000000..c90fa7850a4dc4 --- /dev/null +++ b/test/parallel/test-repl-pretty-stack-trace.js @@ -0,0 +1,49 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const repl = require('repl'); + +function run({code, validate}) { + const putIn = new common.ArrayStream(); + let error; + putIn.write = (output) => (error += output); + const replServer = repl.start({ + prompt: '', + input: putIn, + output: putIn, + terminal: true + }); + putIn.run(code); + replServer.forceExecute(); + validate(error); +} + +const tests = [{ + code: [ + 'var z y;' + ], + validate: (data) => { + assert.strictEqual(/^\s*\^\s*$/m.test(data), true); + assert.strictEqual(/^SyntaxError/m.test(data), true); + } +}, { + code: [ + 'throw new Error("tiny stack");' + ], + validate: (data) => { + assert.strictEqual(/Error/.test(data), true); + assert.strictEqual(/REPLServer/.test(data), false); + assert.strictEqual(/vm\.js/.test(data), false); + + run({ + code: ['new Error("tiny stack");'], + validate: common.mustCall((fullStack) => { + const lines = fullStack.split('\n').length; + assert.strictEqual(lines > data.split('\n').length, true); + }) + }); + } +}]; + +tests.forEach(run); diff --git a/test/parallel/test-repl-recoverable.js b/test/parallel/test-repl-recoverable.js deleted file mode 100644 index 6788d84595066c..00000000000000 --- a/test/parallel/test-repl-recoverable.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const common = require('../common'); -const assert = require('assert'); -const repl = require('repl'); - -let evalCount = 0; -let recovered = false; -let rendered = false; - -function customEval(code, context, file, cb) { - evalCount++; - - return cb(evalCount === 1 ? new repl.Recoverable() : null, true); -} - -const putIn = new common.ArrayStream(); - -putIn.write = function(msg) { - if (msg === '... ') { - recovered = true; - } - - if (msg === 'true\n') { - rendered = true; - } -}; - -repl.start('', putIn, customEval); - -// https://github.com/nodejs/node/issues/2939 -// Expose recoverable errors to the consumer. -putIn.emit('data', '1\n'); -putIn.emit('data', '2\n'); - -process.on('exit', function() { - assert(recovered, 'REPL never recovered'); - assert(rendered, 'REPL never rendered the result'); - assert.strictEqual(evalCount, 2); -}); diff --git a/test/parallel/test-repl-require-context.js b/test/parallel/test-repl-require-context.js index 798d6f2db30f49..7b2e266ee1612d 100644 --- a/test/parallel/test-repl-require-context.js +++ b/test/parallel/test-repl-require-context.js @@ -7,9 +7,26 @@ const child = cp.spawn(process.execPath, ['--interactive']); const fixture = path.join(common.fixturesDir, 'is-object.js').replace(/\\/g, '/'); let output = ''; +let cmds = [ + 'const isObject = (obj) => obj.constructor === Object;', + 'isObject({});', + `require('${fixture}').isObject({});` +]; + +const run = ([cmd, ...rest]) => { + if (!cmd) { + setTimeout(() => child.stdin.end(), 100); + return; + } + cmds = rest; + child.stdin.write(`${cmd}\n`); +}; child.stdout.setEncoding('utf8'); child.stdout.on('data', (data) => { + if (data !== '> ') { + setTimeout(() => run(cmds), 100); + } output += data; }); @@ -21,8 +38,4 @@ child.on('exit', common.mustCall(() => { assert.deepStrictEqual(results, ['undefined', 'true', 'true', '']); })); -child.stdin.write('const isObject = (obj) => obj.constructor === Object;\n'); -child.stdin.write('isObject({});\n'); -child.stdin.write(`require('${fixture}').isObject({});\n`); -child.stdin.write('.exit'); -child.stdin.end(); +run(cmds); diff --git a/test/parallel/test-repl-require.js b/test/parallel/test-repl-require.js index 9dc3b51de7a88a..629d52a78dbd62 100644 --- a/test/parallel/test-repl-require.js +++ b/test/parallel/test-repl-require.js @@ -22,9 +22,26 @@ var answer = ''; server.listen(options, function() { options.port = this.address().port; const conn = net.connect(options); + let cmds = [ + 'require("baz")', + 'require("./baz")', + '.exit' + ]; + + const run = ([cmd, ...rest]) => { + if (!cmd) return; + cmds = rest; + conn.write(`${cmd}\n`); + }; + conn.setEncoding('utf8'); - conn.on('data', (data) => answer += data); - conn.write('require("baz")\nrequire("./baz")\n.exit\n'); + conn.on('data', (data) => { + if (data !== '> ') { + setTimeout(() => run(cmds), 100); + } + answer += data; + }); + run(cmds); }); process.on('exit', function() { diff --git a/test/parallel/test-repl-syntax-error-stack.js b/test/parallel/test-repl-syntax-error-stack.js index af7e8bed6ea89c..12b1e7bfc2bc1d 100644 --- a/test/parallel/test-repl-syntax-error-stack.js +++ b/test/parallel/test-repl-syntax-error-stack.js @@ -4,23 +4,38 @@ const common = require('../common'); const assert = require('assert'); const path = require('path'); const repl = require('repl'); -let found = false; process.on('exit', () => { - assert.strictEqual(found, true); + assert.strictEqual(/var foo bar;/.test(error), true); }); -common.ArrayStream.prototype.write = function(output) { - if (/var foo bar;/.test(output)) - found = true; +let error = ''; +common.ArrayStream.prototype.write = (output) => { + error += output; +}; + +const runAsHuman = (cmd, repl) => { + const cmds = Array.isArray(cmd) ? cmd : [cmd]; + repl.input.run(cmds); + repl._sawKeyPress = true; + repl.input.emit('keypress', null, { name: 'return' }); +}; + +const clear = (repl) => { + repl.input.emit('keypress', null, { name: 'c', ctrl: true}); + runAsHuman('.clear', repl); }; const putIn = new common.ArrayStream(); -repl.start('', putIn); +const replServer = repl.start({ + input: putIn, + output: putIn, + terminal: true +}); let file = path.join(common.fixturesDir, 'syntax', 'bad_syntax'); if (common.isWindows) file = file.replace(/\\/g, '\\\\'); -putIn.run(['.clear']); -putIn.run([`require('${file}');`]); +clear(replServer); +runAsHuman(`require('${file}');`, replServer); diff --git a/test/parallel/test-repl-tab-complete-crash.js b/test/parallel/test-repl-tab-complete-crash.js index 0874ba41f074fa..e520f19b49cd09 100644 --- a/test/parallel/test-repl-tab-complete-crash.js +++ b/test/parallel/test-repl-tab-complete-crash.js @@ -6,21 +6,38 @@ const repl = require('repl'); common.ArrayStream.prototype.write = function(msg) {}; +const runAsHuman = (cmd, repl) => { + const cmds = Array.isArray(cmd) ? cmd : [cmd]; + repl.input.run(cmds); + repl._sawKeyPress = true; + repl.input.emit('keypress', null, { name: 'return' }); +}; + +const clear = (repl) => { + repl.input.emit('keypress', null, { name: 'c', ctrl: true}); + runAsHuman('.clear', repl); +}; + const putIn = new common.ArrayStream(); -const testMe = repl.start('', putIn); +const testMe = repl.start({ + input: putIn, + output: putIn, + terminal: true +}); +// Test cases for default tab completion +testMe.complete = testMe.defaultComplete; // https://github.com/nodejs/node/issues/3346 // Tab-completion should be empty -putIn.run(['.clear']); +clear(testMe); putIn.run(['function () {']); testMe.complete('arguments.', common.mustCall((err, completions) => { assert.strictEqual(err, null); assert.deepStrictEqual(completions, [[], 'arguments.']); })); -putIn.run(['.clear']); -putIn.run(['function () {']); -putIn.run(['undef;']); +clear(testMe); +putIn.run(['function () {', 'undef;']); testMe.complete('undef.', common.mustCall((err, completions) => { assert.strictEqual(err, null); assert.deepStrictEqual(completions, [[], 'undef.']); diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 77c14deaf3b023..f060567b7d79c0 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -13,7 +13,25 @@ function getNoResultsFunction() { var works = [['inner.one'], 'inner.o']; const putIn = new common.ArrayStream(); -var testMe = repl.start('', putIn); +const testMe = repl.start({ + input: putIn, + output: putIn, + terminal: true +}); +// Test cases for default tab completion +testMe.complete = testMe.defaultComplete; + +const runAsHuman = (cmd, repl) => { + const cmds = Array.isArray(cmd) ? cmd : [cmd]; + repl.input.run(cmds); + repl._sawKeyPress = true; + repl.input.emit('keypress', null, { name: 'return' }); +}; + +const clear = (repl) => { + repl.input.emit('keypress', null, { name: 'c', ctrl: true}); + runAsHuman('.clear', repl); +}; // Some errors are passed to the domain, but do not callback testMe._domain.on('error', function(err) { @@ -21,7 +39,7 @@ testMe._domain.on('error', function(err) { }); // Tab Complete will not break in an object literal -putIn.run(['.clear']); +clear(testMe); putIn.run([ 'var inner = {', 'one:1' @@ -33,12 +51,12 @@ testMe.complete('console.lo', common.mustCall(function(error, data) { })); // Tab Complete will return globally scoped variables -putIn.run(['};']); +runAsHuman(['};'], testMe); testMe.complete('inner.o', common.mustCall(function(error, data) { assert.deepStrictEqual(data, works); })); -putIn.run(['.clear']); +clear(testMe); // Tab Complete will not break in an ternary operator with () putIn.run([ @@ -48,7 +66,7 @@ putIn.run([ ]); testMe.complete('inner.o', getNoResultsFunction()); -putIn.run(['.clear']); +clear(testMe); // Tab Complete will return a simple local variable putIn.run([ @@ -61,10 +79,10 @@ testMe.complete('inner.o', common.mustCall(function(error, data) { // When you close the function scope tab complete will not return the // locally scoped variable -putIn.run(['};']); +runAsHuman(['};'], testMe); testMe.complete('inner.o', getNoResultsFunction()); -putIn.run(['.clear']); +clear(testMe); // Tab Complete will return a complex local variable putIn.run([ @@ -77,7 +95,7 @@ testMe.complete('inner.o', common.mustCall(function(error, data) { assert.deepStrictEqual(data, works); })); -putIn.run(['.clear']); +clear(testMe); // Tab Complete will return a complex local variable even if the function // has parameters @@ -91,7 +109,7 @@ testMe.complete('inner.o', common.mustCall(function(error, data) { assert.deepStrictEqual(data, works); })); -putIn.run(['.clear']); +clear(testMe); // Tab Complete will return a complex local variable even if the // scope is nested inside an immediately executed function @@ -106,7 +124,7 @@ testMe.complete('inner.o', common.mustCall(function(error, data) { assert.deepStrictEqual(data, works); })); -putIn.run(['.clear']); +clear(testMe); // def has the params and { on a separate line putIn.run([ @@ -119,8 +137,7 @@ putIn.run([ ]); testMe.complete('inner.o', getNoResultsFunction()); -putIn.run(['.clear']); - +clear(testMe); // currently does not work, but should not break, not the { putIn.run([ 'var top = function() {', @@ -132,7 +149,7 @@ putIn.run([ ]); testMe.complete('inner.o', getNoResultsFunction()); -putIn.run(['.clear']); +clear(testMe); // currently does not work, but should not break putIn.run([ @@ -146,7 +163,7 @@ putIn.run([ ]); testMe.complete('inner.o', getNoResultsFunction()); -putIn.run(['.clear']); +clear(testMe); // make sure tab completion works on non-Objects putIn.run([ @@ -156,7 +173,7 @@ testMe.complete('str.len', common.mustCall(function(error, data) { assert.deepStrictEqual(data, [['str.length'], 'str.len']); })); -putIn.run(['.clear']); +clear(testMe); // tab completion should not break on spaces var spaceTimeout = setTimeout(function() { @@ -175,7 +192,7 @@ testMe.complete('toSt', common.mustCall(function(error, data) { })); // Tab complete provides built in libs for require() -putIn.run(['.clear']); +clear(testMe); testMe.complete('require(\'', common.mustCall(function(error, data) { assert.strictEqual(error, null); @@ -197,7 +214,7 @@ testMe.complete('require(\'n', common.mustCall(function(error, data) { })); // Make sure tab completion works on context properties -putIn.run(['.clear']); +clear(testMe); putIn.run([ 'var custom = "test";' @@ -208,7 +225,7 @@ testMe.complete('cus', common.mustCall(function(error, data) { // Make sure tab completion doesn't crash REPL with half-baked proxy objects. // See: https://github.com/nodejs/node/issues/2119 -putIn.run(['.clear']); +clear(testMe); putIn.run([ 'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});' @@ -219,7 +236,7 @@ testMe.complete('proxy.', common.mustCall(function(error, data) { })); // Make sure tab completion does not include integer members of an Array -putIn.run(['.clear']); +clear(testMe); putIn.run(['var ary = [1,2,3];']); testMe.complete('ary.', common.mustCall(function(error, data) { @@ -229,7 +246,7 @@ testMe.complete('ary.', common.mustCall(function(error, data) { })); // Make sure tab completion does not include integer keys in an object -putIn.run(['.clear']); +clear(testMe); putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); testMe.complete('obj.', common.mustCall(function(error, data) { @@ -239,13 +256,13 @@ testMe.complete('obj.', common.mustCall(function(error, data) { })); // Don't try to complete results of non-simple expressions -putIn.run(['.clear']); +clear(testMe); putIn.run(['function a() {}']); testMe.complete('a().b.', getNoResultsFunction()); // Works when prefixed with spaces -putIn.run(['.clear']); +clear(testMe); putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); testMe.complete(' obj.', common.mustCall((error, data) => { @@ -255,17 +272,17 @@ testMe.complete(' obj.', common.mustCall((error, data) => { })); // Works inside assignments -putIn.run(['.clear']); +clear(testMe); testMe.complete('var log = console.lo', common.mustCall((error, data) => { assert.deepStrictEqual(data, [['console.log'], 'console.lo']); })); // tab completion for defined commands -putIn.run(['.clear']); +clear(testMe); -testMe.complete('.b', common.mustCall((error, data) => { - assert.deepStrictEqual(data, [['break'], 'b']); +testMe.complete('.h', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [['help'], 'h']); })); const testNonGlobal = repl.start({ @@ -357,16 +374,55 @@ const editor = repl.start({ useColors: false }); -editorStream.run(['.clear']); -editorStream.run(['.editor']); +clear(editor); +runAsHuman('.editor', editor); editor.completer('co', common.mustCall((error, data) => { assert.deepStrictEqual(data, [['con'], 'co']); })); -editorStream.run(['.clear']); -editorStream.run(['.editor']); +clear(editor); +runAsHuman('.editor', editor); editor.completer('var log = console.l', common.mustCall((error, data) => { assert.deepStrictEqual(data, [['console.log'], 'console.l']); })); + +// tab completion in continuous mode +const contStream = new common.ArrayStream(); +const contEditor = repl.start({ + stream: contStream, + terminal: true, + useColors: false +}); + +clear(contEditor); +contStream.run(['{']); + +contEditor.completer('co', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [['con'], 'co']); +})); + +contEditor.completer('var log = console.l', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [['console.log'], 'console.l']); +})); + +clear(contEditor); +contStream.run([ + 'var top = function() {', + 'var inner = {one:1, only: true};' +]); + +contEditor.completer('inner.o', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [[ 'inner.on' ], 'inner.o']); +})); + +clear(contEditor); +contStream.run([ + 'var top = function() {', + 'var inner = {one:1, ok: true};' +]); + +contEditor.completer('inner.o', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [[ 'inner.o' ], 'inner.o']); +})); diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index dd6713bf572d95..111eb372f9c012 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -13,123 +13,162 @@ testMagicMode(); function testSloppyMode() { const r = initRepl(repl.REPL_MODE_SLOPPY); - // cannot use `let` in sloppy mode - r.write(`_; // initial value undefined - var x = 10; // evaluates to undefined - _; // still undefined - y = 10; // evaluates to 10 - _; // 10 from last eval - _ = 20; // explicitly set to 20 - _; // 20 from user input - _ = 30; // make sure we can set it twice and no prompt - _; // 30 from user input - y = 40; // make sure eval doesn't change _ - _; // remains 30 from user input - `); - - assertOutput(r.output, [ - 'undefined', - 'undefined', - 'undefined', - '10', - '10', - 'Expression assignment to _ now disabled.', - '20', - '20', - '30', - '30', - '40', - '30' - ]); + const commands = + `_; // initial value undefined + var x = 10; // evaluates to undefined + _; // still undefined + y = 10; // evaluates to 10 + _; // 10 from last eval + _ = 20; // explicitly set to 20 + _; // 20 from user input + _ = 30; // make sure we can set it twice and no prompt + _; // 30 from user input + y = 40; // make sure eval doesn't change _ + _; // remains 30 from user input + `.split('\n'); + + const runCommands = ([cmd, ...rest]) => { + if (cmd) { + r.write(`${cmd.trim()}\n`); + r.forceExecute(); + runCommands(rest); + } else { + assertOutput(r.output, [ + 'undefined', + 'undefined', + 'undefined', + '10', + '10', + 'Expression assignment to _ now disabled.', + '20', + '20', + '30', + '30', + '40', + '30' + ]); + } + }; + runCommands(commands); } function testStrictMode() { const r = initRepl(repl.REPL_MODE_STRICT); - r.write(`_; // initial value undefined - var x = 10; // evaluates to undefined - _; // still undefined - let _ = 20; // use 'let' only in strict mode - evals to undefined - _; // 20 from user input - _ = 30; // make sure we can set it twice and no prompt - _; // 30 from user input - var y = 40; // make sure eval doesn't change _ - _; // remains 30 from user input - function f() { let _ = 50; } // undefined - f(); // undefined - _; // remains 30 from user input - `); - - assertOutput(r.output, [ - 'undefined', - 'undefined', - 'undefined', - 'undefined', - '20', - '30', - '30', - 'undefined', - '30', - 'undefined', - 'undefined', - '30' - ]); + const commands = + `_; // initial value undefined + var x = 10; // evaluates to undefined + _; // still undefined + let _ = 20; // use 'let' only in strict mode - evals to undefined + _; // 20 from user input + _ = 30; // make sure we can set it twice and no prompt + _; // 30 from user input + var y = 40; // make sure eval doesn't change _ + _; // remains 30 from user input + function f() { let _ = 50; } // undefined + f(); // undefined + _; // remains 30 from user input + `.split('\n'); + + const runCommands = ([cmd, ...rest]) => { + if (cmd) { + r.write(`${cmd.trim()}\n`); + r.forceExecute(); + runCommands(rest); + } else { + assertOutput(r.output, [ + 'undefined', + 'undefined', + 'undefined', + 'undefined', + '20', + '30', + '30', + 'undefined', + '30', + 'undefined', + 'undefined', + '30' + ]); + } + }; + runCommands(commands); } function testMagicMode() { const r = initRepl(repl.REPL_MODE_MAGIC); - r.write(`_; // initial value undefined - x = 10; // - _; // last eval - 10 - let _ = 20; // undefined - _; // 20 from user input - _ = 30; // make sure we can set it twice and no prompt - _; // 30 from user input - var y = 40; // make sure eval doesn't change _ - _; // remains 30 from user input - function f() { let _ = 50; return _; } // undefined - f(); // 50 - _; // remains 30 from user input - `); - - assertOutput(r.output, [ - 'magic mode is deprecated. Switched to sloppy mode', - 'undefined', - '10', - '10', - 'undefined', - '20', - '30', - '30', - 'undefined', - '30', - 'undefined', - '50', - '30' - ]); + const commands = + `_; // initial value undefined + x = 10; // + _; // last eval - 10 + let _ = 20; // undefined + _; // 20 from user input + _ = 30; // make sure we can set it twice and no prompt + _; // 30 from user input + var y = 40; // make sure eval doesn't change _ + _; // remains 30 from user input + function f() { let _ = 50; return _; } // undefined + f(); // 50 + _; // remains 30 from user input + `.split('\n'); + + const runCommands = ([cmd, ...rest]) => { + if (cmd) { + r.write(`${cmd.trim()}\n`); + r.forceExecute(); + runCommands(rest); + } else { + assertOutput(r.output, [ + 'magic mode is deprecated. Switched to sloppy mode', + 'undefined', + '10', + '10', + 'undefined', + '20', + '30', + '30', + 'undefined', + '30', + 'undefined', + '50', + '30' + ]); + } + }; + runCommands(commands); } function testResetContext() { const r = initRepl(repl.REPL_MODE_SLOPPY); - r.write(`_ = 10; // explicitly set to 10 - _; // 10 from user input - .clear // Clearing context... - _; // remains 10 - x = 20; // but behavior reverts to last eval - _; // expect 20 - `); - - assertOutput(r.output, [ - 'Expression assignment to _ now disabled.', - '10', - '10', - 'Clearing context...', - '10', - '20', - '20' - ]); + const commands = + `_ = 10; // explicitly set to 10 + _; // 10 from user input + .clear // Clearing context... + _; // remains 10 + x = 20; // but behavior reverts to last eval + _; // expect 20 + `.split('\n'); + + const runCommands = ([cmd, ...rest]) => { + if (cmd) { + r.write(`${cmd.trim()}\n`); + r.forceExecute(); + runCommands(rest); + } else { + assertOutput(r.output, [ + 'Expression assignment to _ now disabled.', + '10', + '10', + 'Clearing context...', + '10', + '20', + '20' + ]); + } + }; + runCommands(commands); } function initRepl(mode) { diff --git a/test/parallel/test-repl-unexpected-token-recoverable.js b/test/parallel/test-repl-unexpected-token-recoverable.js index 84668c8657c453..e74954e4a95328 100644 --- a/test/parallel/test-repl-unexpected-token-recoverable.js +++ b/test/parallel/test-repl-unexpected-token-recoverable.js @@ -11,8 +11,7 @@ var args = [ '-i' ]; var child = spawn(process.execPath, args); var input = 'var foo = "bar\\\nbaz"'; -// Match '...' as well since it marks a multi-line statement -var expectOut = /^> ... undefined\n/; +var expectOut = /^> undefined\n/; child.stderr.setEncoding('utf8'); child.stderr.on('data', function(c) { diff --git a/test/parallel/test-repl-use-global.js b/test/parallel/test-repl-use-global.js index 79e13cd819aeb6..704def101c4d28 100644 --- a/test/parallel/test-repl-use-global.js +++ b/test/parallel/test-repl-use-global.js @@ -25,18 +25,25 @@ const globalTest = (useGlobal, cb, output) => (err, repl) => { output.on('data', (data) => (str += data)); global.lunch = 'tacos'; repl.write('global.lunch;\n'); - repl.close(); - delete global.lunch; - cb(null, str.trim()); + setTimeout(() => { + delete global.lunch; + repl.close(); + cb(null, str.trim()); + }, 100); }; // Test how the global object behaves in each state for useGlobal -for (const [option, expected] of globalTestCases) { +const runGlobalTestCases = ([testCase, ...rest]) => { + if (!testCase) return; + const [option, expected] = testCase; runRepl(option, globalTest, common.mustCall((err, output) => { assert.ifError(err); assert.strictEqual(output, expected); + runGlobalTestCases(rest); })); -} +}; + +runGlobalTestCases(globalTestCases); // Test how shadowing the process object via `let` // behaves in each useGlobal state. Note: we can't @@ -53,19 +60,29 @@ const processTest = (useGlobal, cb, output) => (err, repl) => { let str = ''; output.on('data', (data) => (str += data)); - // if useGlobal is false, then `let process` should work - repl.write('let process;\n'); + // if useGlobal is false, then `var process` should work + repl.write('var process;\n'); repl.write('21 * 2;\n'); - repl.close(); - cb(null, str.trim()); + setTimeout(() => { + repl.close(); + cb(null, str.trim()); + }, 100); }; -for (const option of processTestCases) { - runRepl(option, processTest, common.mustCall((err, output) => { - assert.ifError(err); - assert.strictEqual(output, 'undefined\n42'); - })); -} + +const runProcessTestCases = () => { + const testCase = processTestCases.splice(0, 1); + if (testCase.length) { + const option = testCase[0]; + runRepl(option, processTest, common.mustCall((err, output) => { + assert.ifError(err); + assert.strictEqual(output, '42'); + runProcessTestCases(); + })); + } +}; + +runProcessTestCases(); function runRepl(useGlobal, testFunc, cb) { const inputStream = new stream.PassThrough(); @@ -76,6 +93,7 @@ function runRepl(useGlobal, testFunc, cb) { useGlobal: useGlobal, useColors: false, terminal: false, + displayWelcomeMessage: false, prompt: '' }; diff --git a/test/parallel/test-repl-welcome-message.js b/test/parallel/test-repl-welcome-message.js new file mode 100644 index 00000000000000..5428471bd15abf --- /dev/null +++ b/test/parallel/test-repl-welcome-message.js @@ -0,0 +1,44 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const repl = require('repl'); + +// \u001b[1G - Moves the cursor to 1st column +// \u001b[0J - Clear screen +// \u001b[3G - Moves the cursor to 3rd column +const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G'; +const version = process.version; +const jsEngine = process.jsEngine || 'v8'; +const jsEngineVersion = process.versions[jsEngine]; +const docURL = `https://nodejs.org/dist/${version}/docs/api/repl.html`; +const welcomeMessage = + `Welcome to Node.js ${version} (${jsEngine} VM,` + + ` ${jsEngineVersion})\nType ^M or enter to execute,` + + ` ^J to continue, ^C to exit\n` + + `Or try .help for help, more at ${docURL}\n\n`; + +function run(displayWelcomeMessage, callback) { + const putIn = new common.ArrayStream(); + let output = ''; + putIn.write = (data) => (output += data); + const replServer = repl.start({ + prompt: '> ', + input: putIn, + output: putIn, + terminal: true, + displayWelcomeMessage + }); + replServer.close(); + callback(output); +} + +// when no welcome message +run(false, common.mustCall((output) => { + assert.strictEqual(output, terminalCode); +})); + +// show welcome message +run(true, common.mustCall((output) => { + assert.strictEqual(output, `${welcomeMessage}${terminalCode}`); +})); diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index 29c85e36076dbd..f7acdc8b4e4d49 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -10,7 +10,6 @@ const repl = require('repl'); const message = 'Read, Eval, Print Loop'; const prompt_unix = 'node via Unix socket> '; const prompt_tcp = 'node via TCP socket> '; -const prompt_multiline = '... '; const prompt_npm = 'npm should be run outside of the ' + 'node repl, in your normal shell.\n' + '(Press Control-D to exit.)\n'; @@ -71,8 +70,6 @@ function error_test() { // if it's an exact match, then don't do the regexp if (read_buffer !== client_unix.expect) { var expect = client_unix.expect; - if (expect === prompt_multiline) - expect = /[.]{3} /; assert.ok(read_buffer.match(expect)); console.error('match'); } @@ -88,21 +85,6 @@ function error_test() { tcp_test(); } - } else if (read_buffer.indexOf(prompt_multiline) !== -1) { - // Check that you meant to send a multiline test - assert.strictEqual(prompt_multiline, client_unix.expect); - read_buffer = ''; - if (client_unix.list && client_unix.list.length > 0) { - send_expect(client_unix.list); - } else if (run_strict_test) { - replServer.replMode = repl.REPL_MODE_STRICT; - run_strict_test = false; - strict_mode_error_test(); - } else { - console.error('End of Error test, running TCP test.\n'); - tcp_test(); - } - } else { console.error('didn\'t see prompt yet, buffering.'); } @@ -112,9 +94,9 @@ function error_test() { // Uncaught error throws and prints out { client: client_unix, send: 'throw new Error(\'test error\');', expect: /^Error: test error/ }, - // Common syntax error is treated as multiline command + // Common syntax error for partial command { client: client_unix, send: 'function test_func() {', - expect: prompt_multiline }, + expect: /\bSyntaxError: Unexpected end of input/ }, // You can recover with the .break command { client: client_unix, send: '.break', expect: prompt_unix }, @@ -122,20 +104,17 @@ function error_test() { { client: client_unix, send: 'eval("function test_func() {")', expect: /\bSyntaxError: Unexpected end of input/ }, // Can handle multiline template literals - { client: client_unix, send: '`io.js', - expect: prompt_multiline }, + { client: client_unix, send: '`io.js\n`', + expect: `'io.js\\n'\n${prompt_unix}` }, // Special REPL commands still available { client: client_unix, send: '.break', expect: prompt_unix }, // Template expressions can cross lines - { client: client_unix, send: '`io.js ${"1.0"', - expect: prompt_multiline }, - { client: client_unix, send: '+ ".2"}`', + { client: client_unix, send: '`io.js ${"1.0"\n' + ' + ".2"}`', expect: `'io.js 1.0.2'\n${prompt_unix}` }, // Dot prefix in multiline commands aren't treated as commands - { client: client_unix, send: '("a"', - expect: prompt_multiline }, - { client: client_unix, send: '.charAt(0))', + { client: client_unix, send: '("a"\n.charAt(0))', + // { client: client_unix, send: '.charAt(0))', expect: `'a'\n${prompt_unix}` }, // Floating point numbers are not interpreted as REPL commands. { client: client_unix, send: '.1234', @@ -187,26 +166,14 @@ function error_test() { { client: client_unix, send: 'var I = [1,2,3,function() {}]; I.pop()', expect: '[Function]' }, // Multiline object - { client: client_unix, send: '{ a: ', - expect: prompt_multiline }, - { client: client_unix, send: '1 }', + { client: client_unix, send: '{ a: \n1 }', expect: '{ a: 1 }' }, // Multiline anonymous function with comment - { client: client_unix, send: '(function() {', - expect: prompt_multiline }, - { client: client_unix, send: '// blah', - expect: prompt_multiline }, - { client: client_unix, send: 'return 1;', - expect: prompt_multiline }, - { client: client_unix, send: '})()', + { client: client_unix, send: ['(function() {', '// blah', 'return 1;', '})()'].join('\n'), expect: '1' }, // Multiline function call - { client: client_unix, send: 'function f(){}; f(f(1,', - expect: prompt_multiline }, - { client: client_unix, send: '2)', - expect: prompt_multiline }, - { client: client_unix, send: ')', - expect: 'undefined\n' + prompt_unix }, + { client: client_unix, send: ['function f(){}; f(f(1,', '2)', ')'].join('\n'), + expect: `undefined\n${prompt_unix}` }, // npm prompt error message { client: client_unix, send: 'npm install foobar', expect: expect_npm }, @@ -231,8 +198,7 @@ function error_test() { expect: /\bSyntaxError: Invalid or unexpected token/ }, // do not fail when a String is created with line continuation { client: client_unix, send: '\'the\\\nfourth\\\neye\'', - expect: prompt_multiline + prompt_multiline + - '\'thefourtheye\'\n' + prompt_unix }, + expect: '\'thefourtheye\'\n' + prompt_unix }, // Don't fail when a partial String is created and line continuation is used // with whitespace characters at the end of the string. We are to ignore it. // This test is to make sure that we properly remove the whitespace @@ -241,89 +207,76 @@ function error_test() { expect: prompt_unix }, // multiline strings preserve whitespace characters in them { client: client_unix, send: '\'the \\\n fourth\t\t\\\n eye \'', - expect: prompt_multiline + prompt_multiline + - '\'the fourth\\t\\t eye \'\n' + prompt_unix }, + expect: '\'the fourth\\t\\t eye \'\n' + prompt_unix }, // more than one multiline strings also should preserve whitespace chars { client: client_unix, send: '\'the \\\n fourth\' + \'\t\t\\\n eye \'', - expect: prompt_multiline + prompt_multiline + - '\'the fourth\\t\\t eye \'\n' + prompt_unix }, + expect: '\'the fourth\\t\\t eye \'\n' + prompt_unix }, // using REPL commands within a string literal should still work { client: client_unix, send: '\'\\\n.break', expect: prompt_unix }, - // using REPL command "help" within a string literal should still work + // using REPL command "help" in multiline mode should not work { client: client_unix, send: '\'thefourth\\\n.help\neye\'', - expect: /'thefourtheye'/ }, + expect: /\bSyntaxError: Invalid or unexpected token/ }, // empty lines in the REPL should be allowed { client: client_unix, send: '\n\r\n\r\n', - expect: prompt_unix + prompt_unix + prompt_unix }, + expect: prompt_unix }, // empty lines in the string literals should not affect the string { client: client_unix, send: '\'the\\\n\\\nfourtheye\'\n', - expect: prompt_multiline + prompt_multiline + - '\'thefourtheye\'\n' + prompt_unix }, + expect: '\'thefourtheye\'\n' + prompt_unix }, // Regression test for https://github.com/nodejs/node/issues/597 { client: client_unix, send: '/(.)(.)(.)(.)(.)(.)(.)(.)(.)/.test(\'123456789\')\n', expect: `true\n${prompt_unix}` }, // the following test's result depends on the RegEx's match from the above { client: client_unix, - send: 'RegExp.$1\nRegExp.$2\nRegExp.$3\nRegExp.$4\nRegExp.$5\n' + - 'RegExp.$6\nRegExp.$7\nRegExp.$8\nRegExp.$9\n', - expect: ['\'1\'\n', '\'2\'\n', '\'3\'\n', '\'4\'\n', '\'5\'\n', '\'6\'\n', - '\'7\'\n', '\'8\'\n', '\'9\'\n'].join(`${prompt_unix}`) }, + send: '[RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$4, RegExp.$5,' + + ' RegExp.$6, RegExp.$7, RegExp.$8, RegExp.$9]\n', + expect: `[ '1', '2', '3', '4', '5', '6', '7', '8', '9' ]\n${prompt_unix}` }, // regression tests for https://github.com/nodejs/node/issues/2749 { client: client_unix, send: 'function x() {\nreturn \'\\n\';\n }', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x() {\nreturn \'\\\\\';\n }', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, // regression tests for https://github.com/nodejs/node/issues/3421 { client: client_unix, send: 'function x() {\n//\'\n }', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x() {\n//"\n }', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x() {//\'\n }', - expect: prompt_multiline + 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x() {//"\n }', - expect: prompt_multiline + 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x() {\nvar i = "\'";\n }', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x(/*optional*/) {}', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x(/* // 5 */) {}', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: '// /* 5 */', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: '"//"', expect: '\'//\'\n' + prompt_unix }, { client: client_unix, send: '"data /*with*/ comment"', expect: '\'data /*with*/ comment\'\n' + prompt_unix }, { client: client_unix, send: 'function x(/*fn\'s optional params*/) {}', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: '/* \'\n"\n\'"\'\n*/', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, // REPL should get a normal require() function, not one that allows // access to internal modules without the --expose_internals flag. { client: client_unix, send: 'require("internal/repl")', expect: /^Error: Cannot find module 'internal\/repl'/ }, // REPL should handle quotes within regexp literal in multiline mode { client: client_unix, send: "function x(s) {\nreturn s.replace(/'/,'');\n}", - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: "function x(s) {\nreturn s.replace(/'/,'');\n}", - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x(s) {\nreturn s.replace(/"/,"");\n}', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: 'function x(s) {\nreturn s.replace(/.*/,"");\n}', - expect: prompt_multiline + prompt_multiline + - 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, { client: client_unix, send: '{ var x = 4; }', - expect: 'undefined\n' + prompt_unix }, + expect: `undefined\n${prompt_unix}` }, // Illegal token is not recoverable outside string literal, RegExp literal, // or block comment. https://github.com/nodejs/node/issues/3611 { client: client_unix, send: 'a = 3.5e', diff --git a/test/sequential/test-repl-timeout-throw.js b/test/sequential/test-repl-timeout-throw.js index 0188b3b8c502d8..ef67dded6d00ca 100644 --- a/test/sequential/test-repl-timeout-throw.js +++ b/test/sequential/test-repl-timeout-throw.js @@ -28,11 +28,11 @@ child.stdout.once('data', function() { child.stdin.write('function thrower(){console.log("THROW",throws++);XXX};'); child.stdin.write('setTimeout(thrower);""\n'); - setTimeout(fsTest, 50); + setTimeout(fsTest, 150); function fsTest() { var f = JSON.stringify(__filename); child.stdin.write('fs.readFile(' + f + ', thrower);\n'); - setTimeout(eeTest, 50); + setTimeout(eeTest, 150); } function eeTest() {