dropUncontinuedParens

tcmts.core.migrate.dropUncontinuedParens · #7y6xny3s · term · runs anywhere

Format 1 to 2: parentheses around an optional chain are kept only where the chain goes on after them. Format 1 kept all of them, so a formatter adding redundant ones ((a?.b())) changed a hash. Format 1 dropped every other parenthesis already, so each one left in its IR is around an optional chain.

import type { IR } from "#codebase/tcmts/core/hash";
import { CONTINUES_CHAIN } from "#codebase/tcmts/core/migrate";

export function dropUncontinuedParens(ir: IR): IR {
  const visit = (node: IR, continued: boolean): IR => {
    if (!Array.isArray(node)) return node;
    if (node[0] === "ParenthesizedExpression" && !continued) {
      const inner = (
        node.find((c) => Array.isArray(c) && c[0] === "expression") as IR[] | undefined
      )?.[1];
      if (inner !== undefined) return visit(inner, false);
    }
    const tag = node[0];
    return node.map((c) => {
      // the slot a node goes on from: its `expression` (a tagged template's `tag`)
      const from = tag === "TaggedTemplateExpression" ? "tag" : "expression";
      if (Array.isArray(c) && c[0] === from && typeof tag === "string" && CONTINUES_CHAIN.has(tag))
        return [from, visit(c[1] as IR, true)];
      return visit(c, false);
    });
  };
  return visit(ir, false);
}

uses

used by