All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.core.parse.interpreter.js Maven / Gradle / Ivy

import { isDefined, isFunction, isObject } from "../../shared/utils";
import { ASTType } from "./ast-type";

export const PURITY_ABSOLUTE = 1;
export const PURITY_RELATIVE = 2;

export class ASTInterpreter {
  /**
   * @param {function(any):any} $filter
   */
  constructor($filter) {
    this.$filter = $filter;
  }

  /**
   * Compiles the AST into a function.
   * @param {import("./ast/ast").ASTNode} ast - The AST to compile.
   * @returns {import("./parse").CompiledExpression}
   */
  compile(ast) {
    let decoratedNode = findConstantAndWatchExpressions(ast, this.$filter);
    /** @type {import("./ast/ast").ASTNode} */
    let assignable;
    /** @type {import("./parse").CompiledExpression} */
    let assign;
    if ((assignable = assignableAST(decoratedNode))) {
      assign = /** @type {import("./parse").CompiledExpression} */ (
        this.recurse(assignable)
      );
    }
    const toWatch = getInputs(decoratedNode.body);
    let inputs;
    if (toWatch) {
      inputs = [];
      for (const [key, watch] of Object.entries(toWatch)) {
        const input = /** @type {import("./parse").CompiledExpression} */ (
          this.recurse(watch)
        );
        input.isPure = watch.isPure;
        watch.input = input;
        inputs.push(input);
        watch.watchId = key;
      }
    }
    const expressions = [];
    decoratedNode.body.forEach((expression) => {
      expressions.push(this.recurse(expression.expression));
    });

    /** @type {import("./parse").CompiledExpression} */
    const fn =
      decoratedNode.body.length === 0
        ? () => {}
        : decoratedNode.body.length === 1
          ? expressions[0]
          : function (scope, locals) {
              let lastValue;
              expressions.forEach((exp) => {
                lastValue = exp(scope, locals);
              });
              return lastValue;
            };
    if (assign) {
      fn.assign = (scope, value, locals) => assign(scope, locals, value);
    }
    if (inputs) {
      fn.inputs = inputs;
    }
    fn.decoratedNode = decoratedNode;
    return fn;
  }

  /**
   * Recurses the AST nodes.
   * @param {import("./ast/ast").ASTNode} ast - The AST node.
   * @param {Object} [context] - The context.
   * @param {boolean|1} [create] - The create flag.
   * @returns {import("./parse").CompiledExpressionFunction} The recursive function.
   */
  recurse(ast, context, create) {
    let left;
    let right;
    const self = this;
    let args;
    switch (ast.type) {
      case ASTType.Literal:
        return this.value(ast.value, context);
      case ASTType.UnaryExpression:
        right = this.recurse(ast.argument);
        return this[`unary${ast.operator}`](right, context);
      case ASTType.BinaryExpression:
        left = this.recurse(ast.left);
        right = this.recurse(ast.right);
        return this[`binary${ast.operator}`](left, right, context);
      case ASTType.LogicalExpression:
        left = this.recurse(ast.left);
        right = this.recurse(ast.right);
        return this[`binary${ast.operator}`](left, right, context);
      case ASTType.ConditionalExpression:
        return this["ternary?:"](
          this.recurse(ast.test),
          this.recurse(ast.alternate),
          this.recurse(ast.consequent),
          context,
        );
      case ASTType.Identifier:
        return self.identifier(ast.name, context, create);
      case ASTType.MemberExpression:
        left = this.recurse(ast.object, false, !!create);
        if (!ast.computed) {
          right = ast.property.name;
        }
        if (ast.computed) right = this.recurse(ast.property);
        return ast.computed
          ? this.computedMember(
              left,
              /** @type {function } */ (right),
              context,
              create,
            )
          : this.nonComputedMember(
              left,
              /** @type {string } */ (right),
              context,
              create,
            );
      case ASTType.CallExpression:
        args = [];
        ast.arguments.forEach((expr) => {
          args.push(self.recurse(expr));
        });
        if (ast.filter) right = this.$filter(ast.callee.name);
        if (!ast.filter) right = this.recurse(ast.callee, true);
        return ast.filter
          ? function (scope, locals, assign) {
              const values = [];
              for (let i = 0; i < args.length; ++i) {
                const res = args[i](scope, locals, assign);
                values.push(isFunction(res) ? res() : res);
              }
              const value = () => right.apply(undefined, values);
              return context
                ? { context: undefined, name: undefined, value }
                : value;
            }
          : function (scope, locals, assign) {
              const rhs = right(scope, locals, assign);
              let value;
              if (rhs.value != null && isFunction(rhs.value)) {
                const values = [];
                for (let i = 0; i < args.length; ++i) {
                  const res = args[i](scope, locals, assign);
                  values.push(isFunction(res) ? res() : res);
                }
                value = rhs.value.apply(rhs.context, values);
              }
              return context ? { value } : value;
            };
      case ASTType.AssignmentExpression:
        left = this.recurse(ast.left, true, 1);
        right = this.recurse(ast.right);
        return function (scope, locals, assign) {
          const lhs = left(scope, locals, assign);
          const rhs = right(scope, locals, assign);
          lhs.context[lhs.name] = rhs;
          return context ? { value: rhs } : rhs;
        };
      case ASTType.ArrayExpression:
        args = [];
        ast.elements.forEach((expr) => {
          args.push(self.recurse(expr));
        });
        return function (scope, locals, assign) {
          const value = [];
          for (let i = 0; i < args.length; ++i) {
            value.push(args[i](scope, locals, assign));
          }
          return context ? { value } : value;
        };
      case ASTType.ObjectExpression:
        args = [];
        ast.properties.forEach((property) => {
          if (property.computed) {
            args.push({
              key: self.recurse(property.key),
              computed: true,
              value: self.recurse(property.value),
            });
          } else {
            args.push({
              key:
                property.key.type === ASTType.Identifier
                  ? property.key.name
                  : `${property.key.value}`,
              computed: false,
              value: self.recurse(property.value),
            });
          }
        });
        return function (scope, locals, assign) {
          const value = {};
          for (let i = 0; i < args.length; ++i) {
            if (args[i].computed) {
              value[args[i].key(scope, locals, assign)] = args[i].value(
                scope,
                locals,
                assign,
              );
            } else {
              value[args[i].key] = args[i].value(scope, locals, assign);
            }
          }
          return context ? { value } : value;
        };
      case ASTType.ThisExpression:
        return function (scope) {
          return context ? { value: scope } : scope;
        };
      case ASTType.LocalsExpression:
        return function (scope, locals) {
          return context ? { value: locals } : locals;
        };
      case ASTType.NGValueParameter:
        return function (scope, locals, assign) {
          return context ? { value: assign } : assign;
        };
    }
  }

  /**
   * Unary plus operation.
   * @param {function} argument - The argument function.
   * @param {Object} [context] - The context.
   * @returns {function} The unary plus function.
   */
  "unary+"(argument, context) {
    return function (scope, locals, assign) {
      let arg = argument(scope, locals, assign);
      if (isDefined(arg)) {
        arg = +arg;
      } else {
        arg = 0;
      }
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Unary minus operation.
   * @param {function} argument - The argument function.
   * @param {Object} [context] - The context.
   * @returns {function} The unary minus function.
   */
  "unary-"(argument, context) {
    return function (scope, locals, assign) {
      let arg = argument(scope, locals, assign);
      if (isDefined(arg)) {
        arg = -arg;
      } else {
        arg = -0;
      }
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Unary negation operation.
   * @param {function} argument - The argument function.
   * @param {Object} [context] - The context.
   * @returns {function} The unary negation function.
   */
  "unary!"(argument, context) {
    return function (scope, locals, assign) {
      const arg = !argument(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary plus operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary plus function.
   */
  "binary+"(left, right, context) {
    return function (scope, locals, assign) {
      const lhs = left(scope, locals, assign);
      const rhs = right(scope, locals, assign);
      const arg = plusFn(lhs, rhs);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary minus operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary minus function.
   */
  "binary-"(left, right, context) {
    return function (scope, locals, assign) {
      const lhs = left(scope, locals, assign);
      const rhs = right(scope, locals, assign);
      const arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary multiplication operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary multiplication function.
   */
  "binary*"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) * right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  "binary/"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) / right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary division operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary division function.
   */
  "binary%"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) % right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary strict equality operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary strict equality function.
   */
  "binary==="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) === right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary strict inequality operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary strict inequality function.
   */
  "binary!=="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) !== right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary equality operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary equality function.
   */
  "binary=="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) == right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary inequality operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary inequality function.
   */
  "binary!="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) != right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary less-than operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary less-than function.
   */
  "binary<"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) < right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary greater-than operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary greater-than function.
   */
  "binary>"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) > right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary less-than-or-equal-to operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary less-than-or-equal-to function.
   */
  "binary<="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) <= right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary greater-than-or-equal-to operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary greater-than-or-equal-to function.
   */
  "binary>="(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) >= right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }
  /**
   * Binary logical AND operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary logical AND function.
   */
  "binary&&"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) && right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Binary logical OR operation.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @returns {function} The binary logical OR function.
   */
  "binary||"(left, right, context) {
    return function (scope, locals, assign) {
      const arg = left(scope, locals, assign) || right(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Ternary conditional operation.
   * @param {function} test - The test function.
   * @param {function} alternate - The alternate function.
   * @param {function} consequent - The consequent function.
   * @param {Object} [context] - The context.
   * @returns {function} The ternary conditional function.
   */
  "ternary?:"(test, alternate, consequent, context) {
    return function (scope, locals, assign) {
      const arg = test(scope, locals, assign)
        ? alternate(scope, locals, assign)
        : consequent(scope, locals, assign);
      return context ? { value: arg } : arg;
    };
  }

  /**
   * Returns the value of a literal.
   * @param {*} value - The literal value.
   * @param {Object} [context] - The context.
   * @returns {function} The function returning the literal value.
   */
  value(value, context) {
    return function () {
      return context ? { context: undefined, name: undefined, value } : value;
    };
  }

  /**
   * Returns the value of an identifier.
   * @param {string} name - The identifier name.
   * @param {Object} [context] - The context.
   * @param {boolean|1} [create] - Whether to create the identifier if it does not exist.
   * @returns {function} The function returning the identifier value.
   */
  identifier(name, context, create) {
    return function (scope, locals) {
      const base = locals && name in locals ? locals : scope;
      if (create && create !== 1 && base && base[name] == null) {
        base[name] = {};
      }
      const value = base ? base[name] : undefined;
      if (context) {
        return { context: base, name, value };
      }
      return value;
    };
  }

  /**
   * Returns the value of a computed member expression.
   * @param {function} left - The left operand function.
   * @param {function} right - The right operand function.
   * @param {Object} [context] - The context.
   * @param {boolean|1} [create] - Whether to create the member if it does not exist.
   * @returns {function} The function returning the computed member value.
   */
  computedMember(left, right, context, create) {
    return function (scope, locals, assign) {
      const lhs = left(scope, locals, assign);
      let rhs;
      let value;
      if (lhs != null) {
        rhs = right(scope, locals, assign);
        rhs = getStringValue(rhs);
        if (create && create !== 1) {
          if (lhs && !lhs[rhs]) {
            lhs[rhs] = {};
          }
        }
        value = lhs[rhs];
      }
      if (context) {
        return { context: lhs, name: rhs, value };
      }
      return value;
    };
  }

  /**
   * Returns the value of a non-computed member expression.
   * @param {function} left - The left operand function.
   * @param {string} right - The right operand function.
   * @param {Object} [context] - The context.
   * @param {boolean|1} [create] - Whether to create the member if it does not exist.
   * @returns {function} The function returning the non-computed member value.
   */
  nonComputedMember(left, right, context, create) {
    return function (scope, locals, assign) {
      const lhs = left(scope, locals, assign);
      if (create && create !== 1) {
        if (lhs && lhs[right] == null) {
          lhs[right] = {};
        }
      }
      const value = lhs != null ? lhs[right] : undefined;
      if (context) {
        return { context: lhs, name: right, value };
      }
      return value;
    };
  }
}

/**
 * @typedef {import("./ast/ast").ASTNode & {
 *  isPure: boolean|number,
 *  constant: boolean,
 *  toWatch: Array,
 * }} DecoratedASTNode
 */

/**
 * Decorates AST with constant, toWatch, and isPure properties
 * @param {import("./ast/ast").ASTNode} ast
 * @param {function(any):any} $filter
 * @param {boolean|1|2} [parentIsPure]
 * @returns {DecoratedASTNode}
 */
function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
  let allConstants;
  let argsToWatch;
  let isStatelessFilter;
  let decoratedNode = /** @type  {DecoratedASTNode} */ (ast);
  let decoratedLeft,
    decoratedRight,
    decoratedTest,
    decoratedAlternate,
    decoratedConsequent,
    decoratedObject,
    decoratedProperty,
    decoratedKey;
  const astIsPure = (decoratedNode.isPure = isPure(ast, parentIsPure));

  switch (ast.type) {
    case ASTType.Program:
      allConstants = true;
      decoratedNode.body.forEach((expr) => {
        let decorated = findConstantAndWatchExpressions(
          expr.expression,
          $filter,
          astIsPure,
        );
        allConstants = allConstants && decorated.constant;
      });
      decoratedNode.constant = allConstants;
      return decoratedNode;
    case ASTType.Literal:
      decoratedNode.constant = true;
      decoratedNode.toWatch = [];
      return decoratedNode;
    case ASTType.UnaryExpression:
      var decorated = findConstantAndWatchExpressions(
        decoratedNode.argument,
        $filter,
        astIsPure,
      );
      decoratedNode.constant = decorated.constant;
      decoratedNode.toWatch = decorated.toWatch;
      return decoratedNode;
    case ASTType.BinaryExpression:
      decoratedLeft = findConstantAndWatchExpressions(
        decoratedNode.left,
        $filter,
        astIsPure,
      );
      decoratedRight = findConstantAndWatchExpressions(
        decoratedNode.right,
        $filter,
        astIsPure,
      );
      decoratedNode.constant =
        decoratedLeft.constant && decoratedRight.constant;
      decoratedNode.toWatch = decoratedLeft.toWatch.concat(
        decoratedRight.toWatch,
      );
      return decoratedNode;
    case ASTType.LogicalExpression:
      decoratedLeft = findConstantAndWatchExpressions(
        decoratedNode.left,
        $filter,
        astIsPure,
      );
      decoratedRight = findConstantAndWatchExpressions(
        decoratedNode.right,
        $filter,
        astIsPure,
      );
      decoratedNode.constant =
        decoratedLeft.constant && decoratedRight.constant;
      decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
      return decoratedNode;
    case ASTType.ConditionalExpression:
      decoratedTest = findConstantAndWatchExpressions(
        ast.test,
        $filter,
        astIsPure,
      );
      decoratedAlternate = findConstantAndWatchExpressions(
        ast.alternate,
        $filter,
        astIsPure,
      );
      decoratedConsequent = findConstantAndWatchExpressions(
        ast.consequent,
        $filter,
        astIsPure,
      );
      decoratedNode.constant =
        decoratedTest.constant &&
        decoratedAlternate.constant &&
        decoratedConsequent.constant;
      decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
      return decoratedNode;
    case ASTType.Identifier:
      decoratedNode.constant = false;
      decoratedNode.toWatch = [ast];
      return decoratedNode;
    case ASTType.MemberExpression:
      decoratedObject = findConstantAndWatchExpressions(
        ast.object,
        $filter,
        astIsPure,
      );
      if (ast.computed) {
        decoratedProperty = findConstantAndWatchExpressions(
          ast.property,
          $filter,
          astIsPure,
        );
      }
      decoratedNode.constant =
        decoratedObject.constant &&
        (!decoratedNode.computed || decoratedProperty.constant);
      decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
      return decoratedNode;
    case ASTType.CallExpression:
      isStatelessFilter = ast.filter
        ? isStateless($filter, ast.callee.name)
        : false;
      allConstants = isStatelessFilter;
      argsToWatch = [];
      ast.arguments.forEach((expr) => {
        decorated = findConstantAndWatchExpressions(expr, $filter, astIsPure);
        allConstants = allConstants && decorated.constant;
        argsToWatch.push.apply(argsToWatch, decorated.toWatch);
      });
      decoratedNode.constant = allConstants;
      decoratedNode.toWatch = isStatelessFilter ? argsToWatch : [decoratedNode];
      return decoratedNode;
    case ASTType.AssignmentExpression:
      decoratedLeft = findConstantAndWatchExpressions(
        ast.left,
        $filter,
        astIsPure,
      );
      decoratedRight = findConstantAndWatchExpressions(
        ast.right,
        $filter,
        astIsPure,
      );
      decoratedNode.constant =
        decoratedLeft.constant && decoratedRight.constant;
      decoratedNode.toWatch = [decoratedNode];
      return decoratedNode;
    case ASTType.ArrayExpression:
      allConstants = true;
      argsToWatch = [];
      ast.elements.forEach((expr) => {
        decorated = findConstantAndWatchExpressions(expr, $filter, astIsPure);
        allConstants = allConstants && decorated.constant;
        argsToWatch.push.apply(argsToWatch, decorated.toWatch);
      });
      decoratedNode.constant = allConstants;
      decoratedNode.toWatch = argsToWatch;
      return decoratedNode;
    case ASTType.ObjectExpression:
      allConstants = true;
      argsToWatch = [];
      ast.properties.forEach((property) => {
        decorated = findConstantAndWatchExpressions(
          property.value,
          $filter,
          astIsPure,
        );
        allConstants = allConstants && decorated.constant;
        argsToWatch.push.apply(argsToWatch, decorated.toWatch);
        if (property.computed) {
          // `{[key]: value}` implicitly does `key.toString()` which may be non-pure
          decoratedKey = findConstantAndWatchExpressions(
            property.key,
            $filter,
            false,
          );
          allConstants = allConstants && decoratedKey.constant;
          argsToWatch.push.apply(argsToWatch, decoratedKey.toWatch);
        }
      });
      decoratedNode.constant = allConstants;
      decoratedNode.toWatch = argsToWatch;
      return decoratedNode;
    case ASTType.ThisExpression:
      decoratedNode.constant = false;
      decoratedNode.toWatch = [];
      return decoratedNode;
    case ASTType.LocalsExpression:
      decoratedNode.constant = false;
      decoratedNode.toWatch = [];
      return decoratedNode;
  }
}

/**
 * Converts a single expression AST node into an assignment expression if the expression is assignable.
 *
 * @param {import("./ast/ast").ASTNode} ast
 * @returns {import("./ast/ast").ASTNode}
 */
function assignableAST(ast) {
  if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
    return {
      type: ASTType.AssignmentExpression,
      left: ast.body[0].expression,
      right: { type: ASTType.NGValueParameter },
      operator: "=",
    };
  }
}

function plusFn(l, r) {
  if (typeof l === "undefined" || isObject(l)) return r;
  if (typeof r === "undefined" || isObject(r)) return l;
  return l + r;
}

/**
 *
 * @param {import("./ast/ast").ASTNode[]} body
 * @returns {any}
 */
function getInputs(body) {
  if (body.length !== 1) return;
  const lastExpression = /** @type {DecoratedASTNode} */ (body[0].expression);
  const candidate = lastExpression.toWatch;
  if (candidate.length !== 1) return candidate;
  return candidate[0] !== lastExpression ? candidate : undefined;
}

/**
 * Detect nodes which could depend on non-shallow state of objects
 * @param {import("./ast/ast").ASTNode} node
 * @param {boolean|PURITY_ABSOLUTE|PURITY_RELATIVE} parentIsPure
 * @returns {boolean|PURITY_ABSOLUTE|PURITY_RELATIVE}
 */
function isPure(node, parentIsPure) {
  switch (node.type) {
    // Computed members might invoke a stateful toString()
    case ASTType.MemberExpression:
      if (node.computed) {
        return false;
      }
      break;

    // Unary always convert to primitive
    case ASTType.UnaryExpression:
      return PURITY_ABSOLUTE;

    // The binary + operator can invoke a stateful toString().
    case ASTType.BinaryExpression:
      return node.operator !== "+" ? PURITY_ABSOLUTE : false;

    // Functions / filters probably read state from within objects
    case ASTType.CallExpression:
      return false;
  }

  return undefined === parentIsPure ? PURITY_RELATIVE : parentIsPure;
}

function isStateless($filter, filterName) {
  const fn = $filter(filterName);
  return !fn.$stateful;
}

/**
 * Converts parameter to  strings property name for use  as keys in an object.
 * Any non-string object, including a number, is typecasted into a string via the toString method.
 * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names}
 *
 * @param {!any} name
 * @returns {string}
 */
function getStringValue(name) {
  return `${name}`;
}

/**
 * @param {import("./ast/ast").ASTNode} ast
 * @returns {boolean}
 */
export function isAssignable(ast) {
  return (
    ast.type === ASTType.Identifier || ast.type === ASTType.MemberExpression
  );
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy