diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index b17635e3d42877..88919653d26508 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -301,7 +301,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { function getInputPreview(input, callback) { // For similar reasons as `defaultEval`, wrap expressions starting with a // curly brace with parenthesis. - if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';') { + if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';' && isValidSyntax(input)) { input = `(${input})`; wrapped = true; } @@ -754,6 +754,25 @@ function setupReverseSearch(repl) { const startsWithBraceRegExp = /^\s*{/; const endsWithSemicolonRegExp = /;\s*$/; +function isValidSyntax(input) { + try { + AcornParser.parse(input, { + ecmaVersion: 'latest', + allowAwaitOutsideFunction: true, + }); + return true; + } catch { + try { + AcornParser.parse(`_=${input}`, { + ecmaVersion: 'latest', + allowAwaitOutsideFunction: true, + }); + return true; + } catch { + return false; + } + } +} /** * Checks if some provided code represents an object literal. @@ -776,4 +795,5 @@ module.exports = { setupPreview, setupReverseSearch, isObjectLiteral, + isValidSyntax, }; diff --git a/lib/repl.js b/lib/repl.js index 443971df63b0e8..3d66e928601f07 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -177,6 +177,7 @@ const { setupPreview, setupReverseSearch, isObjectLiteral, + isValidSyntax, } = require('internal/repl/utils'); const { constants: { @@ -445,7 +446,7 @@ function REPLServer(prompt, let awaitPromise = false; const input = code; - if (isObjectLiteral(code)) { + if (isObjectLiteral(code) && isValidSyntax(code)) { // Add parentheses to make sure `code` is parsed as an expression code = `(${StringPrototypeTrim(code)})\n`; wrappedCmd = true; @@ -2156,6 +2157,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 06a044f5a11a13..9ab84b5c9f3ae4 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -157,6 +157,83 @@ 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: [ + '{[Symbol.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: '{} //;', + noPreview: 'repl > ', + preview: [ + '{} //;\r', + ], + }, { + input: '{throw 0}', + noPreview: 'Uncaught \x1B[33m0\x1B[39m', + preview: [ + '{throw 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 +254,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 9f14ebb39eeac7..a8f483e90135cb 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -328,6 +328,19 @@ const errorTests = [ expect: '[Function (anonymous)]' }, // Multiline object + { + send: '{}),({}', + expect: '| ', + }, + { + send: '}', + expect: [ + '{}),({}', + kArrow, + '', + /^Uncaught SyntaxError: /, + ] + }, { send: '{ a: ', expect: '| '