Skip to content

Commit 99ea08d

Browse files
authored
repl: add isValidParentheses check before wrap input
PR-URL: #59607 Backport-PR-URL: #60066 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 7e0e86c commit 99ea08d

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
@@ -292,7 +292,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
292292
function getInputPreview(input, callback) {
293293
// For similar reasons as `defaultEval`, wrap expressions starting with a
294294
// curly brace with parenthesis.
295-
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';') {
295+
if (!wrapped && input[0] === '{' && input[input.length - 1] !== ';' && isValidSyntax(input)) {
296296
input = `(${input})`;
297297
wrapped = true;
298298
}
@@ -737,6 +737,25 @@ function setupReverseSearch(repl) {
737737

738738
const startsWithBraceRegExp = /^\s*{/;
739739
const endsWithSemicolonRegExp = /;\s*$/;
740+
function isValidSyntax(input) {
741+
try {
742+
AcornParser.parse(input, {
743+
ecmaVersion: 'latest',
744+
allowAwaitOutsideFunction: true,
745+
});
746+
return true;
747+
} catch {
748+
try {
749+
AcornParser.parse(`_=${input}`, {
750+
ecmaVersion: 'latest',
751+
allowAwaitOutsideFunction: true,
752+
});
753+
return true;
754+
} catch {
755+
return false;
756+
}
757+
}
758+
}
740759

741760
/**
742761
* Checks if some provided code represents an object literal.
@@ -759,4 +778,5 @@ module.exports = {
759778
setupPreview,
760779
setupReverseSearch,
761780
isObjectLiteral,
781+
isValidSyntax,
762782
};

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: {
@@ -440,7 +441,7 @@ function REPLServer(prompt,
440441
let awaitPromise = false;
441442
const input = code;
442443

443-
if (isObjectLiteral(code)) {
444+
if (isObjectLiteral(code) && isValidSyntax(code)) {
444445
// Add parentheses to make sure `code` is parsed as an expression
445446
code = `(${StringPrototypeTrim(code)})\n`;
446447
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
@@ -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)