Skip to content
Closed
26 changes: 25 additions & 1 deletion lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -740,11 +742,33 @@ 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,
isRecoverableError,
kStandaloneREPL: Symbol('kStandaloneREPL'),
setupPreview,
setupReverseSearch,
isValidSyntax,
};
5 changes: 4 additions & 1 deletion lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const {
kStandaloneREPL,
setupPreview,
setupReverseSearch,
isValidSyntax,
} = require('internal/repl/utils');
const {
constants: {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1819,6 +1821,7 @@ module.exports = {
REPL_MODE_SLOPPY,
REPL_MODE_STRICT,
Recoverable,
isValidSyntax,
};

ObjectDefineProperty(module.exports, 'builtinModules', {
Expand Down
81 changes: 79 additions & 2 deletions test/parallel/test-repl-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '{},{}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I suggested the wrong test. We would want the following to report a syntax error:

Suggested change
input: '{},{}',
input: '{}),({}',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 078088d

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 &&
Expand All @@ -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);
}
}
}
}
}
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 @@ -310,6 +310,19 @@ const errorTests = [
expect: '[Function (anonymous)]'
},
// Multiline object
{
send: '{}),({}',
expect: '... ',
},
{
send: '}',
expect: [
'{}),({}',
kArrow,
'',
/^Uncaught SyntaxError: /,
]
},
{
send: '{ a: ',
expect: '... '
Expand Down