diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index 84eddf6e86322d..e37d1841a8897b 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -299,7 +299,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { // For similar reasons as `defaultEval`, wrap expressions starting with a // curly brace with parenthesis. if (StringPrototypeStartsWith(input, '{') && - !StringPrototypeEndsWith(input, ';') && !wrapped) { + !StringPrototypeEndsWith(input, ';') && + isValidSyntax(input) && + !wrapped) { input = `(${input})`; wrapped = true; } @@ -740,6 +742,27 @@ function setupReverseSearch(repl) { return { reverseSearch }; } +function isValidSyntax(input) { + const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser; + try { + Parser.parse(input, { + ecmaVersion: 'latest', + allowAwaitOutsideFunction: true, + }); + return true; + } catch { + try { + Parser.parse(`_=${input}`, { + ecmaVersion: 'latest', + allowAwaitOutsideFunction: true, + }); + return true; + } catch { + return false; + } + } +} + module.exports = { REPL_MODE_SLOPPY: Symbol('repl-sloppy'), REPL_MODE_STRICT, @@ -747,4 +770,5 @@ module.exports = { kStandaloneREPL: Symbol('kStandaloneREPL'), setupPreview, setupReverseSearch, + isValidSyntax, }; diff --git a/lib/repl.js b/lib/repl.js index 241e25f0f2095a..18d4bacfbe0d4e 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -163,6 +163,7 @@ const { kStandaloneREPL, setupPreview, setupReverseSearch, + isValidSyntax, } = require('internal/repl/utils'); const { constants: { @@ -420,7 +421,8 @@ function REPLServer(prompt, // an expression. Note that if the above condition changes, // lib/internal/repl/utils.js needs to be changed to match. if (RegExpPrototypeExec(/^\s*{/, code) !== null && - RegExpPrototypeExec(/;\s*$/, code) === null) { + RegExpPrototypeExec(/;\s*$/, code) === null && + isValidSyntax(code)) { code = `(${StringPrototypeTrim(code)})\n`; wrappedCmd = true; } @@ -1819,6 +1821,7 @@ module.exports = { REPL_MODE_SLOPPY, REPL_MODE_STRICT, Recoverable, + isValidSyntax, }; ObjectDefineProperty(module.exports, 'builtinModules', { diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 6eb2a169918a51..eb1724796d488e 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -157,6 +157,78 @@ async function tests(options) { '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', '\x1B[33m1\x1B[39m', ] + }, { + input: 'aaaa', + noPreview: 'Uncaught ReferenceError: aaaa is not defined', + preview: [ + 'aaaa\r', + 'Uncaught ReferenceError: aaaa is not defined', + ] + }, { + input: '/0', + noPreview: '/0', + preview: [ + '/0\r', + '/0', + '^', + '', + 'Uncaught SyntaxError: Invalid regular expression: missing /', + ] + }, { + input: '{})', + noPreview: '{})', + preview: [ + '{})\r', + '{})', + ' ^', + '', + "Uncaught SyntaxError: Unexpected token ')'", + ], + }, { + input: "{ a: '{' }", + noPreview: "{ a: \x1B[32m'{'\x1B[39m }", + preview: [ + "{ a: '{' }\r", + "{ a: \x1B[32m'{'\x1B[39m }", + ], + }, { + input: "{'{':0}", + noPreview: "{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }", + preview: [ + "{'{':0}", + "\x1B[90m{ '{': 0 }\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r", + "{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }", + ], + }, { + input: '{[Symbol.for("{")]: 0 }', + noPreview: '{ [\x1B[32mSymbol({)\x1B[39m]: \x1B[33m0\x1B[39m }', + preview: [ + // eslint-disable-next-line max-len + '{[Sym\x1B[90mbol\x1B[39m\x1B[13G\x1B[0Kb\x1B[90mol\x1B[39m\x1B[14G\x1B[0Ko\x1B[90ml\x1B[39m\x1B[15G\x1B[0Kl.for("{")]: 0 }\r', + '{ [\x1B[32mSymbol({)\x1B[39m]: \x1B[33m0\x1B[39m }', + ], + }, { + input: '{},{}', + noPreview: '{}', + preview: [ + '{},{}', + '\x1B[90m{}\x1B[39m\x1B[13G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '{}', + ], + }, { + input: '{} //', + noPreview: 'repl > ', + preview: [ + '{} //\r', + ], + }, { + input: '{throw 0}', + noPreview: 'Uncaught \x1B[33m0\x1B[39m', + preview: [ + '{thr\x1B[90mow\x1B[39m\x1B[12G\x1B[0Ko\x1B[90mw\x1B[39m\x1B[13G\x1B[0Kw 0}', + '\x1B[90m0\x1B[39m\x1B[17G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + 'Uncaught \x1B[33m0\x1B[39m', + ], }]; const hasPreview = repl.terminal && @@ -177,8 +249,13 @@ async function tests(options) { assert.deepStrictEqual(lines, preview); } else { assert.ok(lines[0].includes(noPreview), lines.map(inspect)); - if (preview.length !== 1 || preview[0] !== `${input}\r`) - assert.strictEqual(lines.length, 2); + if (preview.length !== 1 || preview[0] !== `${input}\r`) { + if (preview[preview.length - 1].includes('Uncaught SyntaxError')) { + assert.strictEqual(lines.length, 5); + } else { + assert.strictEqual(lines.length, 2); + } + } } } } diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index 4981816151f55b..02709b29adddc1 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -310,6 +310,19 @@ const errorTests = [ expect: '[Function (anonymous)]' }, // Multiline object + { + send: '{}),({}', + expect: '... ', + }, + { + send: '}', + expect: [ + '{}),({}', + kArrow, + '', + /^Uncaught SyntaxError: /, + ] + }, { send: '{ a: ', expect: '... '