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

97.05% Statements 33/34
96.42% Branches 27/28
100% Functions 3/3
96.77% Lines 30/31

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                      49x             49x 49x 49x       101x 50x 48x         2x   51x     15x   36x 3x   33x 32x 31x 30x 1x   29x 17x           12x 3x         9x 1x               8x     4x   4x 2x           2x 2x       1x              
import ts from 'typescript';
 
/**
 * 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.
 */
export function conditionToExpression(condition: string, propNames: ReadonlySet<string>): ts.Expression {
  const sourceFile = ts.createSourceFile(
    '__cond.ts',
    `(${condition});`,
    ts.ScriptTarget.ESNext,
    true,
    ts.ScriptKind.TS,
  );
  const stmt = sourceFile.statements[0] as ts.ExpressionStatement;
  const expr = (stmt.expression as ts.ParenthesizedExpression).expression;
  return rewrite(expr, propNames);
}
 
function rewrite(node: ts.Expression, propNames: ReadonlySet<string>): ts.Expression {
  if (ts.isIdentifier(node)) {
    if (propNames.has(node.text)) {
      return ts.factory.createPropertyAccessExpression(
        ts.factory.createThis(),
        ts.factory.createIdentifier(node.text),
      );
    }
    return ts.factory.createIdentifier(node.text);
  }
  if (ts.isStringLiteral(node)) {
    // Clone with fresh position info so the printer emits the text instead
    // of trying to look it up in the (different) original source file.
    return ts.factory.createStringLiteral(node.text);
  }
  if (ts.isNumericLiteral(node)) {
    return ts.factory.createNumericLiteral(node.text);
  }
  if (node.kind === ts.SyntaxKind.TrueKeyword) return ts.factory.createTrue();
  if (node.kind === ts.SyntaxKind.FalseKeyword) return ts.factory.createFalse();
  if (node.kind === ts.SyntaxKind.NullKeyword) return ts.factory.createNull();
  if (ts.isParenthesizedExpression(node)) {
    return ts.factory.createParenthesizedExpression(rewrite(node.expression, propNames));
  }
  if (ts.isBinaryExpression(node)) {
    return ts.factory.createBinaryExpression(
      rewrite(node.left, propNames),
      node.operatorToken,
      rewrite(node.right, propNames),
    );
  }
  if (ts.isPrefixUnaryExpression(node)) {
    return ts.factory.createPrefixUnaryExpression(
      node.operator,
      rewrite(node.operand, propNames) as ts.UnaryExpression,
    );
  }
  if (ts.isConditionalExpression(node)) {
    return ts.factory.createConditionalExpression(
      rewrite(node.condition, propNames),
      node.questionToken,
      rewrite(node.whenTrue, propNames),
      node.colonToken,
      rewrite(node.whenFalse, propNames),
    );
  }
  if (ts.isPropertyAccessExpression(node)) {
    // Only rewrite the receiver side, not the .name (which is an identifier
    // inside the property-access slot, not a free reference).
    return ts.factory.updatePropertyAccessExpression(node, rewrite(node.expression, propNames), node.name);
  }
  if (ts.isElementAccessExpression(node)) {
    return ts.factory.updateElementAccessExpression(
      node,
      rewrite(node.expression, propNames),
      rewrite(node.argumentExpression, propNames),
    );
  }
  Eif (ts.isCallExpression(node)) {
    return ts.factory.updateCallExpression(
      node,
      rewrite(node.expression, propNames),
      node.typeArguments,
      node.arguments.map((a) => rewrite(a as ts.Expression, propNames)),
    );
  }
  // Literals (string, numeric, boolean, null/undefined, etc.) pass through
  // untouched.
  return node;
}