Skip to content
22 changes: 21 additions & 1 deletion lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
Expand All @@ -776,4 +795,5 @@ module.exports = {
setupPreview,
setupReverseSearch,
isObjectLiteral,
isValidSyntax,
};
4 changes: 3 additions & 1 deletion lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ const {
setupPreview,
setupReverseSearch,
isObjectLiteral,
isValidSyntax,
} = require('internal/repl/utils');
const {
constants: {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2156,6 +2157,7 @@ module.exports = {
REPL_MODE_SLOPPY,
REPL_MODE_STRICT,
Recoverable,
isValidSyntax,
};

ObjectDefineProperty(module.exports, 'builtinModules', {
Expand Down
86 changes: 84 additions & 2 deletions test/parallel/test-repl-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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);
}
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions test/parallel/test-repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ const errorTests = [
expect: '[Function (anonymous)]'
},
// Multiline object
{
send: '{}),({}',
expect: '| ',
},
{
send: '}',
expect: [
'{}),({}',
kArrow,
'',
/^Uncaught SyntaxError: /,
]
},
{
send: '{ a: ',
expect: '| '
Expand Down
Loading