Skip to content

Incorrect Parentheses Emission With LogicalExpression/Nullish Coalescing With TSESTree AST #1422

@kelvinwop

Description

@kelvinwop

Summary
When printing a TSESTree AST that contains both a LogicalExpression (||) and a Nullish Coalescing (??) operator, recast omits required parentheses, resulting in invalid JavaScript output.

Environment

  • Node.js: 21.7.3
  • recast: 0.23.11
  • @typescript-eslint/typescript-estree: 8.36.0
  • @typescript-eslint/parser: 8.36.0
  • astring: 1.9.0 (for reference)

Minimal Repro Example

import recast from "recast";

const ast = {
  type: "Program",
  body: [
    {
      type: "VariableDeclaration",
      declarations: [
        {
          type: "VariableDeclarator",
          id: { type: "Identifier", name: "_0x176f73" },
          init: {
            type: "LogicalExpression",
            operator: "??",
            left: {
              type: "LogicalExpression",
              operator: "||",
              left: {
                type: "MemberExpression",
                computed: true,
                object: { type: "Identifier", name: "_0x2778c4" },
                property: { type: "Literal", value: "loading", raw: '"loading"' }
              },
              right: {
                type: "MemberExpression",
                computed: true,
                object: { type: "Identifier", name: "_0x2778c4" },
                property: { type: "Literal", value: "disabled", raw: '"disabled"' }
              }
            },
            right: {
              type: "LogicalExpression",
              operator: "||",
              left: {
                type: "LogicalExpression",
                operator: "||",
                left: {
                  type: "MemberExpression",
                  computed: true,
                  object: { type: "Identifier", name: "_0x23798f" },
                  property: { type: "Literal", value: "disabled", raw: '"disabled"' }
                },
                right: { type: "Identifier", name: "_0x3df9d4" }
              },
              right: { type: "Identifier", name: "_0x7c2427" }
            }
          }
        }
      ],
      kind: "const"
    }
  ],
  sourceType: "script"
};

console.log(recast.print(ast).code);

Expected Output:

const _0x176f73 = (_0x2778c4["loading"] || _0x2778c4["disabled"]) ??
  (_0x23798f["disabled"] || _0x3df9d4 || _0x7c2427);

Actual Output (bug):

const _0x176f73 = _0x2778c4["loading"] || _0x2778c4["disabled"] ?? _0x23798f["disabled"] || _0x3df9d4 || _0x7c2427;

Notes

  • The output is not valid JavaScript or changes semantics.
  • TSESTree does not emit ParenthesizedExpression nodes here.
  • Other code generators such as astring produce the correct output with the same AST.

Expected behavior

Recast should emit the necessary parentheses in these situations, preserving correct operator precedence even in the absence of explicit parenthesis nodes.

Thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions