Skip to content

Commit d1c9d80

Browse files
meixgmarco-ippolito
authored andcommitted
repl: add isValidParentheses check before wrap input
PR-URL: #59607 Backport-PR-URL: #60066 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 1788fb5 commit d1c9d80

File tree

4 files changed

+121
-4
lines changed

4 files changed

+121
-4
lines changed

lib/internal/repl/utils.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
296296
function getInputPreview(input, callback) {
297297
// For similar reasons as `defaultEval`, wrap expressions starting with a
298298
// curly brace with parenthesis.
299-
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';') {
299+
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';' && isValidSyntax(input)) {
300300
input = `(${input})`;
301301
wrapped = true;
302302
}
@@ -741,6 +741,25 @@ function setupReverseSearch(repl) {
741741

742742
const startsWithBraceRegExp = /^\s*{/;
743743
const endsWithSemicolonRegExp = /;\s*$/;
744+
function isValidSyntax(input) {
745+
try {
746+
AcornParser.parse(input, {
747+
ecmaVersion: 'latest',
748+
allowAwaitOutsideFunction: true,
749+
});
750+
return true;
751+
} catch {
752+
try {
753+
AcornParser.parse(`_=${input}`, {
754+
ecmaVersion: 'latest',
755+
allowAwaitOutsideFunction: true,
756+
});
757+
return true;
758+
} catch {
759+
return false;
760+
}
761+
}
762+
}
744763

745764
/**
746765
* Checks if some provided code represents an object literal.
@@ -763,4 +782,5 @@ module.exports = {
763782
setupPreview,
764783
setupReverseSearch,
765784
isObjectLiteral,
785+
isValidSyntax,
766786
};

lib/repl.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ const {
172172
setupPreview,
173173
setupReverseSearch,
174174
isObjectLiteral,
175+
isValidSyntax,
175176
} = require('internal/repl/utils');
176177
const {
177178
constants: {
@@ -442,7 +443,7 @@ function REPLServer(prompt,
442443
let awaitPromise = false;
443444
const input = code;
444445

445-
if (isObjectLiteral(code)) {
446+
if (isObjectLiteral(code) && isValidSyntax(code)) {
446447
// Add parentheses to make sure `code` is parsed as an expression
447448
code = `(${StringPrototypeTrim(code)})\n`;
448449
wrappedCmd = true;
@@ -1859,6 +1860,7 @@ module.exports = {
18591860
REPL_MODE_SLOPPY,
18601861
REPL_MODE_STRICT,
18611862
Recoverable,
1863+
isValidSyntax,
18621864
};
18631865

18641866
ObjectDefineProperty(module.exports, 'builtinModules', {

test/parallel/test-repl-preview.js

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,83 @@ async function tests(options) {
157157
'\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
158158
'\x1B[33m1\x1B[39m',
159159
]
160+
}, {
161+
input: 'aaaa',
162+
noPreview: 'Uncaught ReferenceError: aaaa is not defined',
163+
preview: [
164+
'aaaa\r',
165+
'Uncaught ReferenceError: aaaa is not defined',
166+
]
167+
}, {
168+
input: '/0',
169+
noPreview: '/0',
170+
preview: [
171+
'/0\r',
172+
'/0',
173+
'^',
174+
'',
175+
'Uncaught SyntaxError: Invalid regular expression: missing /',
176+
]
177+
}, {
178+
input: '{})',
179+
noPreview: '{})',
180+
preview: [
181+
'{})\r',
182+
'{})',
183+
' ^',
184+
'',
185+
"Uncaught SyntaxError: Unexpected token ')'",
186+
],
187+
}, {
188+
input: "{ a: '{' }",
189+
noPreview: "{ a: \x1B[32m'{'\x1B[39m }",
190+
preview: [
191+
"{ a: '{' }\r",
192+
"{ a: \x1B[32m'{'\x1B[39m }",
193+
],
194+
}, {
195+
input: "{'{':0}",
196+
noPreview: "{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }",
197+
preview: [
198+
"{'{':0}",
199+
"\x1B[90m{ '{': 0 }\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r",
200+
"{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }",
201+
],
202+
}, {
203+
input: '{[Symbol.for("{")]: 0 }',
204+
noPreview: '{ [\x1B[32mSymbol({)\x1B[39m]: \x1B[33m0\x1B[39m }',
205+
preview: [
206+
'{[Symbol.for("{")]: 0 }\r',
207+
'{ [\x1B[32mSymbol({)\x1B[39m]: \x1B[33m0\x1B[39m }',
208+
],
209+
}, {
210+
input: '{},{}',
211+
noPreview: '{}',
212+
preview: [
213+
'{},{}',
214+
'\x1B[90m{}\x1B[39m\x1B[13G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
215+
'{}',
216+
],
217+
}, {
218+
input: '{} //',
219+
noPreview: 'repl > ',
220+
preview: [
221+
'{} //\r',
222+
],
223+
}, {
224+
input: '{} //;',
225+
noPreview: 'repl > ',
226+
preview: [
227+
'{} //;\r',
228+
],
229+
}, {
230+
input: '{throw 0}',
231+
noPreview: 'Uncaught \x1B[33m0\x1B[39m',
232+
preview: [
233+
'{throw 0}',
234+
'\x1B[90m0\x1B[39m\x1B[17G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
235+
'Uncaught \x1B[33m0\x1B[39m',
236+
],
160237
}];
161238

162239
const hasPreview = repl.terminal &&
@@ -177,8 +254,13 @@ async function tests(options) {
177254
assert.deepStrictEqual(lines, preview);
178255
} else {
179256
assert.ok(lines[0].includes(noPreview), lines.map(inspect));
180-
if (preview.length !== 1 || preview[0] !== `${input}\r`)
181-
assert.strictEqual(lines.length, 2);
257+
if (preview.length !== 1 || preview[0] !== `${input}\r`) {
258+
if (preview[preview.length - 1].includes('Uncaught SyntaxError')) {
259+
assert.strictEqual(lines.length, 5);
260+
} else {
261+
assert.strictEqual(lines.length, 2);
262+
}
263+
}
182264
}
183265
}
184266
}

test/parallel/test-repl.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,19 @@ const errorTests = [
321321
expect: '[Function (anonymous)]'
322322
},
323323
// Multiline object
324+
{
325+
send: '{}),({}',
326+
expect: '... ',
327+
},
328+
{
329+
send: '}',
330+
expect: [
331+
'{}),({}',
332+
kArrow,
333+
'',
334+
/^Uncaught SyntaxError: /,
335+
]
336+
},
324337
{
325338
send: '{ a: ',
326339
expect: '... '

0 commit comments

Comments
 (0)