Skip to content

Commit c206f8d

Browse files
meixgtargos
authored andcommitted
repl: add isValidParentheses check before wrap input
PR-URL: #59607 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 0718a70 commit c206f8d

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
@@ -301,7 +301,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
301301
function getInputPreview(input, callback) {
302302
// For similar reasons as `defaultEval`, wrap expressions starting with a
303303
// curly brace with parenthesis.
304-
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';') {
304+
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';' && isValidSyntax(input)) {
305305
input = `(${input})`;
306306
wrapped = true;
307307
}
@@ -754,6 +754,25 @@ function setupReverseSearch(repl) {
754754

755755
const startsWithBraceRegExp = /^\s*{/;
756756
const endsWithSemicolonRegExp = /;\s*$/;
757+
function isValidSyntax(input) {
758+
try {
759+
AcornParser.parse(input, {
760+
ecmaVersion: 'latest',
761+
allowAwaitOutsideFunction: true,
762+
});
763+
return true;
764+
} catch {
765+
try {
766+
AcornParser.parse(`_=${input}`, {
767+
ecmaVersion: 'latest',
768+
allowAwaitOutsideFunction: true,
769+
});
770+
return true;
771+
} catch {
772+
return false;
773+
}
774+
}
775+
}
757776

758777
/**
759778
* Checks if some provided code represents an object literal.
@@ -776,4 +795,5 @@ module.exports = {
776795
setupPreview,
777796
setupReverseSearch,
778797
isObjectLiteral,
798+
isValidSyntax,
779799
};

lib/repl.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ const {
177177
setupPreview,
178178
setupReverseSearch,
179179
isObjectLiteral,
180+
isValidSyntax,
180181
} = require('internal/repl/utils');
181182
const {
182183
constants: {
@@ -445,7 +446,7 @@ function REPLServer(prompt,
445446
let awaitPromise = false;
446447
const input = code;
447448

448-
if (isObjectLiteral(code)) {
449+
if (isObjectLiteral(code) && isValidSyntax(code)) {
449450
// Add parentheses to make sure `code` is parsed as an expression
450451
code = `(${StringPrototypeTrim(code)})\n`;
451452
wrappedCmd = true;
@@ -2156,6 +2157,7 @@ module.exports = {
21562157
REPL_MODE_SLOPPY,
21572158
REPL_MODE_STRICT,
21582159
Recoverable,
2160+
isValidSyntax,
21592161
};
21602162

21612163
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
@@ -328,6 +328,19 @@ const errorTests = [
328328
expect: '[Function (anonymous)]'
329329
},
330330
// Multiline object
331+
{
332+
send: '{}),({}',
333+
expect: '| ',
334+
},
335+
{
336+
send: '}',
337+
expect: [
338+
'{}),({}',
339+
kArrow,
340+
'',
341+
/^Uncaught SyntaxError: /,
342+
]
343+
},
331344
{
332345
send: '{ a: ',
333346
expect: '| '

0 commit comments

Comments
 (0)