Repository: nodejs/node
Pull request: debugger: fix behavior of plain object exec in debugger repl

Fixes: #46808

(PS: this is a continuation of #46924 which has been stale for almost a year at this point)

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/debugger/inspect_repl.js
patch: @@ -56,6 +56,7 @@ const { fileURLToPath } = require('internal/url');
 
 const { customInspectSymbol, SideEffectFreeRegExpPrototypeSymbolReplace } = require('internal/util');
 const { inspect: utilInspect } = require('internal/util/inspect');
+const { isObjectLiteral } = require('internal/repl/utils');
 const debuglog = require('internal/util/debuglog').debuglog('inspect');
 
 const SHORTCUTS = {
@@ -573,8 +574,15 @@ function createRepl(inspector) {
     if (input === '\n') return lastCommand;
     // Add parentheses: exec process.title => exec("process.title");
     const match = RegExpPrototypeExec(/^\s*(?:exec|p)\s+([^\n]*)/, input);
+    input = match ? match[1] : input;
+
+    if (isObjectLiteral(input)) {
+      // Add parentheses to make sure `input` is parsed as an expression
+      input = `(${StringPrototypeTrim(input)})\n`;
+    }
+
     if (match) {
-      lastCommand = `exec(${JSONStringify(match[1])})`;
+      lastCommand = `exec(${JSONStringify(input)})`;
     } else {
       lastCommand = input;
     }

filename: lib/internal/repl/utils.js
patch: @@ -739,11 +739,28 @@ function setupReverseSearch(repl) {
   return { reverseSearch };
 }
 
+const startsWithBraceRegExp = /^\s*{/;
+const endsWithSemicolonRegExp = /;\s*$/;
+
+/**
+ * Checks if some provided code represents an object literal.
+ * This is helpful to prevent confusing repl code evaluations where
+ * strings such as `{ a : 1 }` would get interpreted as block statements
+ * rather than object literals.
+ * @param {string} code the code to check
+ * @returns {boolean} true if the code represents an object literal, false otherwise
+ */
+function isObjectLiteral(code) {
+  return RegExpPrototypeExec(startsWithBraceRegExp, code) !== null &&
+    RegExpPrototypeExec(endsWithSemicolonRegExp, code) === null;
+}
+
 module.exports = {
   REPL_MODE_SLOPPY: Symbol('repl-sloppy'),
   REPL_MODE_STRICT,
   isRecoverableError,
   kStandaloneREPL: Symbol('kStandaloneREPL'),
   setupPreview,
   setupReverseSearch,
+  isObjectLiteral,
 };

filename: lib/repl.js
patch: @@ -172,6 +172,7 @@ const {
   kStandaloneREPL,
   setupPreview,
   setupReverseSearch,
+  isObjectLiteral,
 } = require('internal/repl/utils');
 const {
   constants: {
@@ -436,13 +437,8 @@ function REPLServer(prompt,
     let awaitPromise = false;
     const input = code;
 
-    // It's confusing for `{ a : 1 }` to be interpreted as a block
-    // statement rather than an object literal.  So, we first try
-    // to wrap it in parentheses, so that it will be interpreted as
-    // 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) {
+    if (isObjectLiteral(code)) {
+      // Add parentheses to make sure `code` is parsed as an expression
       code = `(${StringPrototypeTrim(code)})\n`;
       wrappedCmd = true;
     }

filename: test/parallel/test-debugger-exec.js
patch: @@ -60,6 +60,11 @@ async function waitInitialBreak() {
       /\[ 'undefined', 'function' \]/,
       'non-paused exec can see global but not module-scope values'
     );
+
+    // Ref: https://github.com/nodejs/node/issues/46808
+    await cli.waitForPrompt();
+    await cli.command('exec { a: 1 }');
+    assert.match(cli.output, /\{ a: 1 \}/);
   } finally {
     await cli.quit();
   }