All files / packages/fds-uif/generator-lwc/src/utils conditionToExpr.ts

97.14% Statements 34/35
96.87% Branches 31/32
100% Functions 3/3
96.87% Lines 31/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                                78x 78x 36x     42x 42x       116x 44x 41x         3x   72x     25x   47x 3x   44x 43x 42x 41x 1x   40x 28x           12x 3x   9x 1x               8x     4x         4x 2x         2x 2x 1x              
import {
  factory,
  SyntaxKind,
  parseExpression,
  getSynthesizedDeepClone,
  isIdentifier,
  isStringLiteral,
  isNumericLiteral,
  isParenthesizedExpression,
  isBinaryExpression,
  isPrefixUnaryExpression,
  isConditionalExpression,
  isPropertyAccessExpression,
  isElementAccessExpression,
  isCallExpression,
  type Expression,
} from './ts7.js';
 
/**
 * Parse a JS-shaped condition string (e.g. `"size === 'medium'"`, `"disabled"`,
 * `"a || b"`) into a TS expression node, prefixing identifier references that
 * match `propNames` with `this.`.
 *
 * The analyzer emits these conditions as plain JS expressions over the
 * component's own props, so a small recursive rewrite is enough.
 *
 * Under the TS 7 native API the parser lives out-of-process, so `parseExpression`
 * returns "remote" nodes that cannot be spliced into a synthetic tree directly
 * (the printer panics). The rewrite below therefore rebuilds every node it
 * touches with the local `factory`, and clones any pass-through subtree with
 * `getSynthesizedDeepClone`.
 */
export function conditionToExpression(condition: string, propNames: ReadonlySet<string>): Expression {
  // Fast path for a bare prop reference (the common shape, e.g. `"disabled"`).
  // This also correctly handles prop names that are JS reserved words (e.g. a
  // state named `new`): the parser tokenizes a bare `new` as an incomplete
  // NewExpression rather than an identifier, so `rewrite` below would miss it
  // and the printer would emit a bare, invalid `new`. Emitting `this.<name>`
  // directly sidesteps that. Byte-identical to the parse path for ordinary
  // identifiers.
  const trimmed = condition.trim();
  if (propNames.has(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
    return factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier(trimmed));
  }
 
  const expr = parseExpression(condition);
  return rewrite(expr, propNames);
}
 
function rewrite(node: Expression, propNames: ReadonlySet<string>): Expression {
  if (isIdentifier(node)) {
    if (propNames.has(node.text)) {
      return factory.createPropertyAccessExpression(
        factory.createThis(),
        factory.createIdentifier(node.text),
      );
    }
    return factory.createIdentifier(node.text);
  }
  if (isStringLiteral(node)) {
    // Rebuild with the local factory so the printer emits the text instead
    // of trying to look it up in the (out-of-process) original source file.
    return factory.createStringLiteral(node.text);
  }
  if (isNumericLiteral(node)) {
    return factory.createNumericLiteral(node.text);
  }
  if (node.kind === SyntaxKind.TrueKeyword) return factory.createTrue();
  if (node.kind === SyntaxKind.FalseKeyword) return factory.createFalse();
  if (node.kind === SyntaxKind.NullKeyword) return factory.createNull();
  if (isParenthesizedExpression(node)) {
    return factory.createParenthesizedExpression(rewrite(node.expression, propNames));
  }
  if (isBinaryExpression(node)) {
    return factory.createBinaryExpression(
      rewrite(node.left, propNames),
      node.operatorToken.kind,
      rewrite(node.right, propNames),
    );
  }
  if (isPrefixUnaryExpression(node)) {
    return factory.createPrefixUnaryExpression(node.operator, rewrite(node.operand, propNames));
  }
  if (isConditionalExpression(node)) {
    return factory.createConditionalExpression(
      rewrite(node.condition, propNames),
      undefined,
      rewrite(node.whenTrue, propNames),
      undefined,
      rewrite(node.whenFalse, propNames),
    );
  }
  if (isPropertyAccessExpression(node)) {
    // Only rewrite the receiver side, not the .name (which is an identifier
    // inside the property-access slot, not a free reference).
    return factory.createPropertyAccessExpression(
      rewrite(node.expression, propNames),
      factory.createIdentifier(node.name.text),
    );
  }
  if (isElementAccessExpression(node)) {
    return factory.createElementAccessExpression(
      rewrite(node.expression, propNames),
      rewrite(node.argumentExpression, propNames),
    );
  }
  Eif (isCallExpression(node)) {
    return factory.createCallExpression(rewrite(node.expression, propNames), undefined, [
      ...node.arguments.map((a) => rewrite(a as Expression, propNames)),
    ]);
  }
  // Any other node passes through untouched, but must be deep-cloned into a
  // synthetic node so the printer can emit it.
  return getSynthesizedDeepClone(node);
}