@babel/types#ArrowFunctionExpression TypeScript Examples

The following examples show how to use @babel/types#ArrowFunctionExpression. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: parseRef.ts    From engine with MIT License 6 votes vote down vote up
parseRef: ParseRef = (babel, state, ref) => {
  const parent = ref.findParent((p) => p.isVariableDeclarator());
  if (!parent) {
    throw new Error(
      "Misuse of the view/producer keyword. It needs to be a variable declaration e.g. let foo: view = ..."
    );
  }

  const declaration = parent.node as VariableDeclarator;
  const fn = declaration.init as ArrowFunctionExpression;
  const params = fn.params[0] as ObjectPattern;
  const result = paramsParser(params);
  return result;
}
Example #2
Source File: babel-polyfill.ts    From nota with MIT License 6 votes vote down vote up
arrowFunctionExpression = (
  params: Array<Identifier | Pattern | RestElement>,
  body: BlockStatement | Expression
): ArrowFunctionExpression => ({
  type: "ArrowFunctionExpression",
  params,
  body,
  expression: false,
  ...baseNode,
})
Example #3
Source File: prepareForEngine.ts    From engine with MIT License 5 votes vote down vote up
prepareForEngine: PrepareForEngine = (babel, state, ref, type) => {
  const validation = validateRef(ref);
  if (validation.error) {
    throw new Error(validation.errorMessage);
  }

  const config = getConfig(state);

  const op = parseRef(babel, state, ref);
  const props = structOperationCompiler(op);
  const parent = ref.findParent((p) => p.isVariableDeclarator());
  if (!parent) {
    throw new Error(
      "Misuse of the view/producer keyword. It needs to be a variable declaration e.g. let foo: view = ..."
    );
  }
  const node = parent.node as VariableDeclarator;
  const fn = node.init as ArrowFunctionExpression;

  fn.params = paramsCompiler(op);
  const result = objectExpression([
    objectProperty(identifier("props"), props),
    objectProperty(identifier("fn"), fn),
  ]);

  if (type === TransformType.PRODUCER) {
    node.init = result;
  } else if (type === TransformType.VIEW) {
    const viewCall = callExpression(identifier("view"), [result]);
    node.init = viewCall;
    const viewImport = config.view.importFrom;
    const program = ref.findParent((p) => p.isProgram());
    if (!program) {
      throw new Error("Internal error. Cannot find program node");
    }
    const macroImport = program.get("body").find((p) => {
      const result =
        p.isImportDeclaration() &&
        p.node.source.value.indexOf("@c11/engine.macro") !== -1;
      return result;
    });

    const engineImport = program.get("body").find((p) => {
      const result =
        p.isImportDeclaration() &&
        p.node.source.value.indexOf(viewImport) !== -1;
      return result;
    });

    if (macroImport) {
      if (!engineImport) {
        const importView = importDeclaration(
          [importSpecifier(identifier("view"), identifier("view"))],
          stringLiteral(viewImport)
        );
        // @ts-ignore
        macroImport.insertAfter(importView);
      } else {
        const node = engineImport.node as ImportDeclaration;
        const viewNode = node.specifiers.find((node) => {
          return (
            isImportSpecifier(node) &&
            isIdentifier(node.imported) &&
            node.imported.name === "view"
          );
        });
        if (!viewNode) {
          node.specifiers.push(
            importSpecifier(identifier("view"), identifier("view"))
          );
        }
      }
    } else {
      throw new Error("Could not find macro import");
    }
  }
}
Example #4
Source File: cook.ts    From next-core with GNU General Public License v3.0 5 votes vote down vote up
/** For next-core internal usage only. */
export function cook(
  rootAst: FunctionDeclaration | Expression,
  codeSource: string,
  { rules, globalVariables = {}, hooks = {} }: CookOptions = {}
): unknown {
  const expressionOnly = rootAst.type !== "FunctionDeclaration";

  const rootEnv = new DeclarativeEnvironment(null);
  const rootContext = new ExecutionContext();
  rootContext.VariableEnvironment = rootEnv;
  rootContext.LexicalEnvironment = rootEnv;
  const executionContextStack = [rootContext];

  for (const [key, value] of Object.entries(globalVariables)) {
    rootEnv.CreateImmutableBinding(key, true);
    rootEnv.InitializeBinding(key, value);
  }

  const TemplateMap = new WeakMap<TemplateLiteral, string[]>();

  // https://tc39.es/ecma262/#sec-gettemplateobject
  function GetTemplateObject(templateLiteral: TemplateLiteral): string[] {
    const memo = TemplateMap.get(templateLiteral);
    if (memo) {
      return memo;
    }
    const rawObj = templateLiteral.quasis.map((quasi) => quasi.value.raw);
    const template = templateLiteral.quasis.map((quasi) => quasi.value.cooked);
    Object.freeze(rawObj);
    Object.defineProperty(template, "raw", {
      value: rawObj,
      writable: false,
      enumerable: false,
      configurable: false,
    });
    Object.freeze(template);
    TemplateMap.set(templateLiteral, template);
    return template;
  }

  function Evaluate(
    node: EstreeNode,
    optionalChainRef?: OptionalChainRef
  ): CompletionRecord {
    hooks.beforeEvaluate?.(node);
    // Expressions:
    switch (node.type) {
      case "ArrayExpression": {
        // https://tc39.es/ecma262/#sec-array-initializer
        const array = [];
        for (const element of node.elements) {
          if (!element) {
            array.length += 1;
          } else if (element.type === "SpreadElement") {
            const spreadValues = GetValue(
              Evaluate(element.argument)
            ) as unknown[];
            array.push(...spreadValues);
          } else {
            array.push(GetValue(Evaluate(element)));
          }
        }
        return NormalCompletion(array);
      }
      case "ArrowFunctionExpression": {
        // https://tc39.es/ecma262/#sec-arrow-function-definitions
        ThrowIfFunctionIsInvalid(node);
        const closure = InstantiateArrowFunctionExpression(node);
        return NormalCompletion(closure);
      }
      case "BinaryExpression": {
        const leftRef = Evaluate(node.left);
        const leftValue = GetValue(leftRef);
        const rightRef = Evaluate(node.right).Value;
        const rightValue = GetValue(rightRef);
        if (expressionOnly && (node.operator as unknown) === "|>") {
          // Minimal pipeline operator is supported only in expression-only mode.
          // See https://tc39.es/proposal-pipeline-operator
          // and https://github.com/tc39/proposal-pipeline-operator
          if (typeof rightValue !== "function") {
            const funcName = codeSource.substring(
              node.right.start,
              node.right.end
            );
            throw new TypeError(`${funcName} is not a function`);
          }
          let thisValue;
          if (rightRef instanceof ReferenceRecord) {
            if (IsPropertyReference(rightRef)) {
              thisValue = rightRef.Base;
            }
          }
          return NormalCompletion(
            (rightValue as unknown as SimpleFunction).call(thisValue, leftValue)
          );
        }
        // https://tc39.es/ecma262/#sec-additive-operators
        const result = ApplyStringOrNumericBinaryOperator(
          leftValue as number,
          node.operator,
          rightValue as number
        );
        return NormalCompletion(result);
      }
      case "CallExpression": {
        // https://tc39.es/ecma262/#sec-function-calls
        const ref = Evaluate(node.callee, optionalChainRef)
          .Value as ReferenceRecord;
        const func = GetValue(ref) as SimpleFunction;
        if (
          (func === undefined || func === null) &&
          (node.optional || optionalChainRef?.skipped)
        ) {
          optionalChainRef.skipped = true;
          return NormalCompletion(undefined);
        }
        sanitize(func);
        return EvaluateCall(func, ref, node.arguments, node.callee);
      }
      case "ChainExpression":
        // https://tc39.es/ecma262/#sec-optional-chains
        return Evaluate(node.expression, {});
      case "ConditionalExpression":
        // https://tc39.es/ecma262/#sec-conditional-operator
        return NormalCompletion(
          GetValue(
            Evaluate(
              GetValue(Evaluate(node.test)) ? node.consequent : node.alternate
            )
          )
        );
      case "Identifier":
        // https://tc39.es/ecma262/#sec-identifiers
        return NormalCompletion(ResolveBinding(node.name));
      case "Literal": {
        // https://tc39.es/ecma262/#sec-primary-expression-literals
        if (node.regex) {
          if (node.value === null) {
            // Invalid regular expression fails silently in @babel/parser.
            throw new SyntaxError(`Invalid regular expression: ${node.raw}`);
          }
          if (node.regex.flags.includes("u")) {
            // Currently unicode flag is not fully supported across major browsers.
            throw new SyntaxError(
              `Unsupported unicode flag in regular expression: ${node.raw}`
            );
          }
        }
        return NormalCompletion(node.value);
      }
      case "LogicalExpression": {
        // https://tc39.es/ecma262/#sec-binary-logical-operators
        const leftValue = GetValue(Evaluate(node.left));
        switch (node.operator) {
          case "&&":
            return NormalCompletion(
              leftValue && GetValue(Evaluate(node.right))
            );
          case "||":
            return NormalCompletion(
              leftValue || GetValue(Evaluate(node.right))
            );
          case "??":
            return NormalCompletion(
              leftValue ?? GetValue(Evaluate(node.right))
            );
          // istanbul ignore next
          default:
            throw new SyntaxError(
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore never reach here.
              `Unsupported logical operator '${node.operator}'`
            );
        }
      }
      case "MemberExpression": {
        // https://tc39.es/ecma262/#sec-property-accessors
        const baseReference = Evaluate(node.object, optionalChainRef)
          .Value as ReferenceRecord;
        const baseValue = GetValue(baseReference) as Record<
          PropertyKey,
          unknown
        >;
        if (
          (baseValue === undefined || baseValue === null) &&
          (node.optional || optionalChainRef?.skipped)
        ) {
          optionalChainRef.skipped = true;
          return NormalCompletion(undefined);
        }
        sanitize(baseValue);
        const result = node.computed
          ? EvaluatePropertyAccessWithExpressionKey(
              baseValue,
              node.property as Expression,
              true
            )
          : EvaluatePropertyAccessWithIdentifierKey(
              baseValue,
              node.property as Identifier,
              true
            );
        sanitize(result);
        return NormalCompletion(result);
      }
      case "NewExpression":
        // https://tc39.es/ecma262/#sec-new-operator
        return EvaluateNew(node.callee, node.arguments);
      case "ObjectExpression": {
        // https://tc39.es/ecma262/#sec-object-initializer
        const object: Record<PropertyKey, unknown> = {};
        for (const prop of (node as EstreeObjectExpression).properties) {
          if (prop.type === "SpreadElement") {
            const fromValue = GetValue(Evaluate(prop.argument));
            CopyDataProperties(object, fromValue, new Set());
          } else {
            if (prop.kind !== "init") {
              throw new SyntaxError("Unsupported object getter/setter");
            }
            const propName =
              !prop.computed && prop.key.type === "Identifier"
                ? prop.key.name
                : EvaluateComputedPropertyName(prop.key);
            if (propName === "__proto__") {
              throw new TypeError(
                "Setting '__proto__' property is not allowed"
              );
            }
            object[propName] = GetValue(Evaluate(prop.value));
          }
        }
        return NormalCompletion(object);
      }
      case "SequenceExpression": {
        // https://tc39.es/ecma262/#sec-comma-operator
        let result: CompletionRecord;
        for (const expr of node.expressions) {
          result = NormalCompletion(GetValue(Evaluate(expr)));
        }
        return result;
      }
      case "TemplateLiteral": {
        // https://tc39.es/ecma262/#sec-template-literals
        const chunks: string[] = [node.quasis[0].value.cooked];
        let index = 0;
        for (const expr of node.expressions) {
          const val = GetValue(Evaluate(expr));
          chunks.push(String(val));
          chunks.push(node.quasis[(index += 1)].value.cooked);
        }
        return NormalCompletion(chunks.join(""));
      }
      case "TaggedTemplateExpression": {
        // https://tc39.es/ecma262/#sec-tagged-templates
        const tagRef = Evaluate(node.tag).Value as ReferenceRecord;
        const tagFunc = GetValue(tagRef) as SimpleFunction;
        sanitize(tagFunc);
        return EvaluateCall(tagFunc, tagRef, node.quasi, node.tag);
      }
      case "UnaryExpression": {
        // https://tc39.es/ecma262/#sec-unary-operators
        const ref = Evaluate(node.argument).Value as ReferenceRecord;
        if (!expressionOnly && node.operator === "delete") {
          // Delete operator is supported only in function mode.
          if (!(ref instanceof ReferenceRecord)) {
            return NormalCompletion(true);
          }
          // istanbul ignore else
          if (IsPropertyReference(ref)) {
            const deleteStatus = delete (
              ref.Base as Record<PropertyKey, unknown>
            )[ref.ReferenceName];
            return NormalCompletion(deleteStatus);
          }
          // Should never reach here in strict mode.
        }
        if (node.operator === "typeof") {
          if (ref instanceof ReferenceRecord && ref.Base === "unresolvable") {
            return NormalCompletion("undefined");
          }
          return NormalCompletion(typeof GetValue(ref));
        }
        return NormalCompletion(
          ApplyUnaryOperator(GetValue(ref), node.operator)
        );
      }
    }
    if (!expressionOnly) {
      // Statements and assignments:
      switch (node.type) {
        case "AssignmentExpression": {
          // https://tc39.es/ecma262/#sec-assignment-operators
          if (node.operator === "=") {
            if (
              !(
                node.left.type === "ArrayPattern" ||
                node.left.type === "ObjectPattern"
              )
            ) {
              const lref = Evaluate(node.left).Value as ReferenceRecord;
              // Todo: IsAnonymousFunctionDefinition(lref)
              const rref = Evaluate(node.right);
              const rval = GetValue(rref);

              PutValue(lref, rval);
              return NormalCompletion(rval);
            }
            const rref = Evaluate(node.right);
            const rval = GetValue(rref) as string | number;
            DestructuringAssignmentEvaluation(node.left, rval);
            return NormalCompletion(rval);
          }
          // Operators other than `=`.
          const lref = Evaluate(node.left).Value as ReferenceRecord;
          const lval = GetValue(lref) as string | number;
          const rref = Evaluate(node.right);
          const rval = GetValue(rref) as string | number;
          const r = ApplyStringOrNumericAssignment(lval, node.operator, rval);
          PutValue(lref, r);
          return NormalCompletion(r);
        }
        case "BlockStatement": {
          // https://tc39.es/ecma262/#sec-block
          if (!node.body.length) {
            return NormalCompletion(Empty);
          }
          const oldEnv = getRunningContext().LexicalEnvironment;
          const blockEnv = new DeclarativeEnvironment(oldEnv);
          BlockDeclarationInstantiation(node.body, blockEnv);
          getRunningContext().LexicalEnvironment = blockEnv;
          const blockValue = EvaluateStatementList(node.body);
          getRunningContext().LexicalEnvironment = oldEnv;
          return blockValue;
        }
        case "BreakStatement":
          // https://tc39.es/ecma262/#sec-break-statement
          return new CompletionRecord("break", Empty);
        case "ContinueStatement":
          // https://tc39.es/ecma262/#sec-continue-statement
          return new CompletionRecord("continue", Empty);
        case "EmptyStatement":
          // https://tc39.es/ecma262/#sec-empty-statement
          return NormalCompletion(Empty);
        case "DoWhileStatement":
          // https://tc39.es/ecma262/#sec-do-while-statement
          return EvaluateBreakableStatement(DoWhileLoopEvaluation(node));
        case "ExpressionStatement":
        case "TSAsExpression":
          // https://tc39.es/ecma262/#sec-expression-statement
          return Evaluate(node.expression);
        case "ForInStatement":
        case "ForOfStatement":
          // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
          return EvaluateBreakableStatement(ForInOfLoopEvaluation(node));
        case "ForStatement":
          // https://tc39.es/ecma262/#sec-for-statement
          return EvaluateBreakableStatement(ForLoopEvaluation(node));
        case "FunctionDeclaration":
          // https://tc39.es/ecma262/#sec-function-definitions
          return NormalCompletion(Empty);
        case "FunctionExpression":
          // https://tc39.es/ecma262/#sec-function-defining-expressions
          ThrowIfFunctionIsInvalid(node);
          return NormalCompletion(InstantiateOrdinaryFunctionExpression(node));
        case "IfStatement":
          // https://tc39.es/ecma262/#sec-if-statement
          return GetValue(Evaluate(node.test))
            ? (hooks.beforeBranch?.(node, "if"),
              UpdateEmpty(Evaluate(node.consequent), undefined))
            : (hooks.beforeBranch?.(node, "else"), node.alternate)
            ? UpdateEmpty(Evaluate(node.alternate), undefined)
            : NormalCompletion(undefined);
        case "ReturnStatement": {
          // https://tc39.es/ecma262/#sec-return-statement
          let v: unknown;
          if (node.argument) {
            const exprRef = Evaluate(node.argument);
            v = GetValue(exprRef);
          }
          return new CompletionRecord("return", v);
        }
        case "ThrowStatement":
          // https://tc39.es/ecma262/#sec-throw-statement
          throw GetValue(Evaluate(node.argument));
        case "UpdateExpression": {
          // https://tc39.es/ecma262/#sec-update-expressions
          const lhs = Evaluate(node.argument).Value as ReferenceRecord;
          const oldValue = Number(GetValue(lhs));
          const newValue = node.operator === "++" ? oldValue + 1 : oldValue - 1;
          PutValue(lhs, newValue);
          return NormalCompletion(node.prefix ? newValue : oldValue);
        }
        case "SwitchCase":
          return EvaluateStatementList(node.consequent);
        case "SwitchStatement": {
          // https://tc39.es/ecma262/#sec-switch-statement
          const exprRef = Evaluate(node.discriminant);
          const switchValue = GetValue(exprRef);
          const oldEnv = getRunningContext().LexicalEnvironment;
          const blockEnv = new DeclarativeEnvironment(oldEnv);
          BlockDeclarationInstantiation(node.cases, blockEnv);
          getRunningContext().LexicalEnvironment = blockEnv;
          const R = CaseBlockEvaluation(node.cases, switchValue);
          getRunningContext().LexicalEnvironment = oldEnv;
          return EvaluateBreakableStatement(R);
        }
        case "TryStatement": {
          // https://tc39.es/ecma262/#sec-try-statement
          let R: CompletionRecord;
          try {
            R = Evaluate(node.block);
          } catch (error) {
            if (node.handler) {
              hooks.beforeEvaluate?.(node.handler);
              R = CatchClauseEvaluation(node.handler, error);
            } else {
              throw error;
            }
          } finally {
            if (node.finalizer) {
              const F = Evaluate(node.finalizer);
              if (F.Type !== "normal") {
                R = F;
              }
            }
          }
          return R;
        }
        case "VariableDeclaration": {
          // https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
          let result: CompletionRecord;
          for (const declarator of node.declarations) {
            if (!declarator.init) {
              // Assert: a declarator without init is always an identifier.
              if (node.kind === "var") {
                result = NormalCompletion(Empty);
              } else {
                const lhs = ResolveBinding((declarator.id as Identifier).name);
                result = InitializeReferencedBinding(lhs, undefined);
              }
            } else if (declarator.id.type === "Identifier") {
              const bindingId = declarator.id.name;
              const lhs = ResolveBinding(bindingId);
              // Todo: IsAnonymousFunctionDefinition(Initializer)
              const rhs = Evaluate(declarator.init);
              const value = GetValue(rhs);
              result =
                node.kind === "var"
                  ? PutValue(lhs, value)
                  : InitializeReferencedBinding(lhs, value);
            } else {
              const rhs = Evaluate(declarator.init);
              const rval = GetValue(rhs);
              result = BindingInitialization(
                declarator.id,
                rval,
                node.kind === "var"
                  ? undefined
                  : getRunningContext().LexicalEnvironment
              );
            }
          }
          return result;
        }
        case "WhileStatement":
          // https://tc39.es/ecma262/#sec-while-statement
          return EvaluateBreakableStatement(WhileLoopEvaluation(node));
      }
    }
    // eslint-disable-next-line no-console
    throw new SyntaxError(`Unsupported node type \`${node.type}\``);
  }

  // https://tc39.es/ecma262/#sec-execution-contexts
  function getRunningContext(): ExecutionContext {
    return executionContextStack[executionContextStack.length - 1];
  }

  // https://tc39.es/ecma262/#sec-resolvebinding
  function ResolveBinding(
    name: string,
    env?: EnvironmentRecord
  ): ReferenceRecord {
    if (!env) {
      env = getRunningContext().LexicalEnvironment;
    }
    return GetIdentifierReference(env, name, true);
  }

  // Try statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation
  function CatchClauseEvaluation(
    node: CatchClause,
    thrownValue: unknown
  ): CompletionRecord {
    const oldEnv = getRunningContext().LexicalEnvironment;
    const catchEnv = new DeclarativeEnvironment(oldEnv);
    for (const argName of collectBoundNames(node.param)) {
      catchEnv.CreateMutableBinding(argName, false);
    }
    getRunningContext().LexicalEnvironment = catchEnv;
    BindingInitialization(node.param, thrownValue, catchEnv);
    const B = Evaluate(node.body);
    getRunningContext().LexicalEnvironment = oldEnv;
    return B;
  }

  // Iteration statements and switch statements.
  // https://tc39.es/ecma262/#prod-BreakableStatement
  function EvaluateBreakableStatement(
    stmtResult: CompletionRecord
  ): CompletionRecord {
    return stmtResult.Type === "break"
      ? stmtResult.Value === Empty
        ? NormalCompletion(undefined)
        : NormalCompletion(stmtResult.Value)
      : stmtResult;
  }

  // Switch statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-caseblockevaluation
  function CaseBlockEvaluation(
    cases: SwitchCase[],
    input: unknown
  ): CompletionRecord {
    let V: unknown;

    const defaultCaseIndex = cases.findIndex((switchCase) => !switchCase.test);
    const hasDefaultCase = defaultCaseIndex >= 0;
    const A = hasDefaultCase ? cases.slice(0, defaultCaseIndex) : cases;
    let found = false;
    for (const C of A) {
      if (!found) {
        found = CaseClauseIsSelected(C, input);
      }
      if (found) {
        const R = Evaluate(C);
        if (R.Value !== Empty) {
          V = R.Value;
        }
        if (R.Type !== "normal") {
          return UpdateEmpty(R, V);
        }
      }
    }

    if (!hasDefaultCase) {
      return NormalCompletion(V);
    }

    let foundInB = false;
    const B = cases.slice(defaultCaseIndex + 1);
    if (!found) {
      for (const C of B) {
        if (!foundInB) {
          foundInB = CaseClauseIsSelected(C, input);
        }
        if (foundInB) {
          const R = Evaluate(C);
          if (R.Value !== Empty) {
            V = R.Value;
          }
          if (R.Type !== "normal") {
            return UpdateEmpty(R, V);
          }
        }
      }
    }

    if (foundInB) {
      return NormalCompletion(V);
    }
    const R = Evaluate(cases[defaultCaseIndex]);
    if (R.Value !== Empty) {
      V = R.Value;
    }
    if (R.Type !== "normal") {
      return UpdateEmpty(R, V);
    }

    // NOTE: The following is another complete iteration of the second CaseClauses.
    for (const C of B) {
      const R = Evaluate(C);
      if (R.Value !== Empty) {
        V = R.Value;
      }
      if (R.Type !== "normal") {
        return UpdateEmpty(R, V);
      }
    }
    return NormalCompletion(V);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-caseclauseisselected
  function CaseClauseIsSelected(C: SwitchCase, input: unknown): boolean {
    const clauseSelector = GetValue(Evaluate(C.test));
    return input === clauseSelector;
  }

  // While statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-whileloopevaluation
  function WhileLoopEvaluation(node: WhileStatement): CompletionRecord {
    let V: unknown;
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const exprValue = GetValue(Evaluate(node.test));
      if (!exprValue) {
        return NormalCompletion(V);
      }
      const stmtResult = Evaluate(node.body);
      if (!LoopContinues(stmtResult)) {
        return UpdateEmpty(stmtResult, V);
      }
      if (stmtResult.Value !== Empty) {
        V = stmtResult.Value;
      }
    }
  }

  // Do-while Statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-dowhileloopevaluation
  function DoWhileLoopEvaluation(node: DoWhileStatement): CompletionRecord {
    let V: unknown;
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const stmtResult = Evaluate(node.body);
      if (!LoopContinues(stmtResult)) {
        return UpdateEmpty(stmtResult, V);
      }
      if (stmtResult.Value !== Empty) {
        V = stmtResult.Value;
      }
      const exprValue = GetValue(Evaluate(node.test));
      if (!exprValue) {
        return NormalCompletion(V);
      }
    }
  }

  // For in/of statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation
  function ForInOfLoopEvaluation(
    node: ForInStatement | ForOfStatement
  ): CompletionRecord {
    const lhs = node.left;
    const isVariableDeclaration = lhs.type === "VariableDeclaration";
    const lhsKind = isVariableDeclaration
      ? lhs.kind === "var"
        ? "varBinding"
        : "lexicalBinding"
      : "assignment";
    const uninitializedBoundNames =
      lhsKind === "lexicalBinding" ? collectBoundNames(lhs) : [];
    const iterationKind =
      node.type === "ForInStatement" ? "enumerate" : "iterate";
    const keyResult = ForInOfHeadEvaluation(
      uninitializedBoundNames,
      node.right,
      iterationKind
    );
    if (keyResult.Type !== "normal") {
      // When enumerate, if the target is nil, a break completion will be returned.
      return keyResult;
    }
    return ForInOfBodyEvaluation(
      lhs,
      node.body,
      keyResult.Value as Iterator<unknown>,
      iterationKind,
      lhsKind
    );
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation
  function ForInOfHeadEvaluation(
    uninitializedBoundNames: string[],
    expr: Expression,
    iterationKind: "enumerate" | "iterate"
  ): CompletionRecord {
    const runningContext = getRunningContext();
    const oldEnv = runningContext.LexicalEnvironment;
    if (uninitializedBoundNames.length > 0) {
      const newEnv = new DeclarativeEnvironment(oldEnv);
      for (const name of uninitializedBoundNames) {
        newEnv.CreateMutableBinding(name, false);
      }
      runningContext.LexicalEnvironment = newEnv;
    }
    const exprRef = Evaluate(expr);
    runningContext.LexicalEnvironment = oldEnv;
    const exprValue = GetValue(exprRef);
    if (iterationKind === "enumerate") {
      if (exprValue === null || exprValue === undefined) {
        return new CompletionRecord("break", Empty);
      }
      const iterator = EnumerateObjectProperties(exprValue);
      return NormalCompletion(iterator);
    }
    const iterator = CreateListIteratorRecord(exprValue as Iterable<unknown>);
    return NormalCompletion(iterator);
  }

  function ForInOfBodyEvaluation(
    node: VariableDeclaration | EstreeLVal,
    stmt: Statement,
    iteratorRecord: Iterator<unknown>,
    iterationKind: "enumerate" | "iterate",
    lhsKind: "varBinding" | "lexicalBinding" | "assignment"
  ): CompletionRecord {
    const lhs =
      lhsKind === "assignment"
        ? (node as EstreeLVal)
        : (node as VariableDeclaration).declarations[0].id;
    const oldEnv = getRunningContext().LexicalEnvironment;
    let V: unknown;
    // When `destructuring` is false,
    // For `node` whose `kind` is assignment:
    //   `lhs` is an `Identifier` or a `MemberExpression`,
    // Otherwise:
    //   `lhs` is an `Identifier`.
    const destructuring =
      lhs.type === "ObjectPattern" || lhs.type === "ArrayPattern";
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const { done, value: nextValue } = iteratorRecord.next();
      if (done) {
        return NormalCompletion(V);
      }
      let lhsRef: ReferenceRecord;
      let iterationEnv: DeclarativeEnvironment;
      if (lhsKind === "lexicalBinding") {
        iterationEnv = new DeclarativeEnvironment(oldEnv);
        ForDeclarationBindingInstantiation(
          node as VariableDeclaration,
          iterationEnv
        );
        getRunningContext().LexicalEnvironment = iterationEnv;
        if (!destructuring) {
          const [lhsName] = collectBoundNames(lhs);
          lhsRef = ResolveBinding(lhsName);
        }
      } else if (!destructuring) {
        lhsRef = Evaluate(lhs).Value as ReferenceRecord;
      }
      destructuring
        ? lhsKind === "assignment"
          ? DestructuringAssignmentEvaluation(lhs, nextValue)
          : lhsKind === "varBinding"
          ? BindingInitialization(lhs, nextValue, undefined)
          : BindingInitialization(lhs, nextValue, iterationEnv)
        : lhsKind === "lexicalBinding"
        ? InitializeReferencedBinding(lhsRef, nextValue)
        : PutValue(lhsRef, nextValue);

      const result = Evaluate(stmt);
      getRunningContext().LexicalEnvironment = oldEnv;
      if (!LoopContinues(result)) {
        const status = UpdateEmpty(result, V);
        if (
          !(
            iterationKind === "enumerate" || iteratorRecord.return === undefined
          )
        ) {
          // Perform *IteratorClose*
          // https://tc39.es/ecma262/#sec-iteratorclose
          const innerResult = iteratorRecord.return();
          if (
            !innerResult ||
            !["object", "function"].includes(typeof innerResult)
          ) {
            throw new TypeError(`Iterator result is not an object`);
          }
        }
        return status;
      }
      if (result.Value !== Empty) {
        V = result.Value;
      }
    }
  }

  // https://tc39.es/ecma262/#sec-enumerate-object-properties
  function* EnumerateObjectProperties(value: any): Iterator<PropertyKey> {
    for (const key in value) {
      yield key;
    }
  }

  // For statements.
  // https://tc39.es/ecma262/#sec-runtime-semantics-forloopevaluation
  function ForLoopEvaluation(node: ForStatement): CompletionRecord {
    if (node.init?.type === "VariableDeclaration") {
      // `for (var … ; … ; … ) …`
      if (node.init.kind === "var") {
        Evaluate(node.init);
        return ForBodyEvaluation(node.test, node.update, node.body, []);
      }
      // `for (let/const … ; … ; … ) …`
      const oldEnv = getRunningContext().LexicalEnvironment;
      const loopEnv = new DeclarativeEnvironment(oldEnv);
      const isConst = node.init.kind === "const";
      const boundNames = collectBoundNames(node.init);
      for (const dn of boundNames) {
        if (isConst) {
          loopEnv.CreateImmutableBinding(dn, true);
        } else {
          loopEnv.CreateMutableBinding(dn, false);
        }
      }
      getRunningContext().LexicalEnvironment = loopEnv;
      Evaluate(node.init);
      const perIterationLets = isConst ? [] : Array.from(boundNames);
      const bodyResult = ForBodyEvaluation(
        node.test,
        node.update,
        node.body,
        perIterationLets
      );
      getRunningContext().LexicalEnvironment = oldEnv;
      return bodyResult;
    }
    // `for ( … ; … ; … ) …`
    if (node.init) {
      const exprRef = Evaluate(node.init);
      GetValue(exprRef);
    }
    return ForBodyEvaluation(node.test, node.update, node.body, []);
  }

  // https://tc39.es/ecma262/#sec-forbodyevaluation
  function ForBodyEvaluation(
    test: Expression,
    increment: Expression,
    stmt: Statement,
    perIterationBindings: string[]
  ): CompletionRecord {
    CreatePerIterationEnvironment(perIterationBindings);
    let V: unknown;
    // eslint-disable-next-line no-constant-condition
    while (true) {
      if (test) {
        const testRef = Evaluate(test);
        const testValue = GetValue(testRef);
        if (!testValue) {
          return NormalCompletion(V);
        }
      }
      const result = Evaluate(stmt) as CompletionRecord;
      if (!LoopContinues(result)) {
        return UpdateEmpty(result, V);
      }
      if (result.Value) {
        V = result.Value;
      }
      CreatePerIterationEnvironment(perIterationBindings);
      if (increment) {
        const incRef = Evaluate(increment);
        GetValue(incRef);
      }
    }
  }

  // https://tc39.es/ecma262/#sec-createperiterationenvironment
  function CreatePerIterationEnvironment(
    perIterationBindings: string[]
  ): unknown {
    if (perIterationBindings.length === 0) {
      return;
    }
    const lastIterationEnv = getRunningContext().LexicalEnvironment;
    const outer = lastIterationEnv.OuterEnv;
    const thisIterationEnv = new DeclarativeEnvironment(outer);
    for (const bn of perIterationBindings) {
      thisIterationEnv.CreateMutableBinding(bn, false);
      const lastValue = lastIterationEnv.GetBindingValue(bn, false);
      thisIterationEnv.InitializeBinding(bn, lastValue);
    }
    getRunningContext().LexicalEnvironment = thisIterationEnv;
  }

  // Destructuring assignments.
  // https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation
  function DestructuringAssignmentEvaluation(
    pattern: ObjectPattern | EstreeObjectPattern | ArrayPattern,
    value: unknown
  ): CompletionRecord {
    if (pattern.type === "ObjectPattern") {
      RequireObjectCoercible(value);
      if (pattern.properties.length > 0) {
        PropertyDestructuringAssignmentEvaluation(
          (pattern as EstreeObjectPattern).properties,
          value
        );
      }
      return NormalCompletion(Empty);
    }
    const iteratorRecord = CreateListIteratorRecord(value as Iterable<unknown>);
    return IteratorDestructuringAssignmentEvaluation(
      pattern.elements,
      iteratorRecord
    );
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation
  function PropertyDestructuringAssignmentEvaluation(
    properties: (EstreeProperty | RestElement)[],
    value: unknown
  ): void {
    const excludedNames = new Set<PropertyKey>();
    for (const prop of properties) {
      if (prop.type === "Property") {
        const propName =
          !prop.computed && prop.key.type === "Identifier"
            ? prop.key.name
            : (EvaluateComputedPropertyName(prop.key) as string);
        const valueTarget =
          prop.value.type === "AssignmentPattern"
            ? prop.value.left
            : prop.value;
        if (valueTarget.type === "Identifier") {
          const lref = ResolveBinding(valueTarget.name);
          let v = GetV(value, propName);
          if (prop.value.type === "AssignmentPattern" && v === undefined) {
            // Todo(steve): check IsAnonymousFunctionDefinition(Initializer)
            const defaultValue = Evaluate(prop.value.right);
            v = GetValue(defaultValue);
          }
          PutValue(lref, v);
          excludedNames.add(propName);
        } else {
          KeyedDestructuringAssignmentEvaluation(prop.value, value, propName);
          excludedNames.add(propName);
        }
      } else {
        RestDestructuringAssignmentEvaluation(prop, value, excludedNames);
      }
    }
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation
  function KeyedDestructuringAssignmentEvaluation(
    node: EstreeNode,
    value: unknown,
    propertyName: PropertyKey
  ): CompletionRecord {
    const assignmentTarget =
      node.type === "AssignmentPattern" ? node.left : node;
    const isObjectOrArray =
      assignmentTarget.type === "ArrayPattern" ||
      assignmentTarget.type === "ObjectPattern";
    let lref;
    if (!isObjectOrArray) {
      lref = Evaluate(assignmentTarget).Value as ReferenceRecord;
    }
    const v = GetV(value, propertyName);
    let rhsValue;
    if (node.type === "AssignmentPattern" && v === undefined) {
      // Todo(steve): check IsAnonymousFunctionDefinition(Initializer)
      const defaultValue = Evaluate(node.right);
      rhsValue = GetValue(defaultValue);
    } else {
      rhsValue = v;
    }
    if (isObjectOrArray) {
      return DestructuringAssignmentEvaluation(assignmentTarget, rhsValue);
    }
    return PutValue(lref, rhsValue);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-restdestructuringassignmentevaluation
  function RestDestructuringAssignmentEvaluation(
    restProperty: RestElement,
    value: unknown,
    excludedNames: Set<PropertyKey>
  ): CompletionRecord {
    const lref = Evaluate(restProperty.argument).Value as ReferenceRecord;
    const restObj = CopyDataProperties({}, value, excludedNames);
    return PutValue(lref, restObj);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-iteratordestructuringassignmentevaluation
  function IteratorDestructuringAssignmentEvaluation(
    elements: PatternLike[],
    iteratorRecord: Iterator<unknown>
  ): CompletionRecord {
    let status = NormalCompletion(Empty);
    for (const element of elements) {
      if (!element) {
        iteratorRecord.next();
        status = NormalCompletion(Empty);
        continue;
      }
      const assignmentTarget =
        element.type === "RestElement"
          ? element.argument
          : element.type === "AssignmentPattern"
          ? element.left
          : element;
      const isObjectOrArray =
        assignmentTarget.type === "ArrayPattern" ||
        assignmentTarget.type === "ObjectPattern";
      let lref: ReferenceRecord;
      if (!isObjectOrArray) {
        lref = Evaluate(assignmentTarget).Value as ReferenceRecord;
      }
      let v: unknown;
      if (element.type !== "RestElement") {
        const { done, value: nextValue } = iteratorRecord.next();
        const value = done ? undefined : nextValue;
        if (element.type === "AssignmentPattern" && value === undefined) {
          // Todo(steve): check IsAnonymousFunctionDefinition(Initializer)
          const defaultValue = Evaluate(element.right);
          v = GetValue(defaultValue);
        } else {
          v = value;
        }
      } else {
        // RestElement
        v = [];
        let n = 0;
        // eslint-disable-next-line no-constant-condition
        while (true) {
          const { done, value: nextValue } = iteratorRecord.next();
          if (done) {
            break;
          }
          (v as unknown[])[n] = nextValue;
          n++;
        }
      }
      if (isObjectOrArray) {
        status = DestructuringAssignmentEvaluation(assignmentTarget, v);
      } else {
        status = PutValue(lref, v);
      }
    }
    return status;
  }

  // Object expressions.
  // https://tc39.es/ecma262/#sec-evaluate-property-access-with-expression-key
  function EvaluatePropertyAccessWithExpressionKey(
    baseValue: Record<PropertyKey, unknown>,
    expression: Expression,
    strict: boolean
  ): ReferenceRecord {
    const propertyNameReference = Evaluate(expression);
    const propertyNameValue = GetValue(propertyNameReference);
    const propertyKey = ToPropertyKey(propertyNameValue);
    return new ReferenceRecord(baseValue, propertyKey, strict);
  }

  // https://tc39.es/ecma262/#sec-evaluate-property-access-with-identifier-key
  function EvaluatePropertyAccessWithIdentifierKey(
    baseValue: Record<PropertyKey, unknown>,
    identifier: Identifier,
    strict: boolean
  ): ReferenceRecord {
    const propertyNameString = identifier.name;
    return new ReferenceRecord(baseValue, propertyNameString, strict);
  }

  // Block statements.
  // https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
  function BlockDeclarationInstantiation(
    code: Statement[] | SwitchCase[],
    env: EnvironmentRecord
  ): void {
    const declarations = collectScopedDeclarations(code, {
      var: false,
      topLevel: false,
    });
    for (const d of declarations) {
      const IsConstantDeclaration =
        d.type === "VariableDeclaration" && d.kind === "const";
      for (const dn of collectBoundNames(d)) {
        if (IsConstantDeclaration) {
          env.CreateImmutableBinding(dn, true);
        } else {
          env.CreateMutableBinding(dn, false);
        }
      }
      if (d.type === "FunctionDeclaration") {
        const [fn] = collectBoundNames(d);
        const fo = InstantiateFunctionObject(d, env);
        env.InitializeBinding(fn, fo);
      }
    }
  }

  // Function declarations and expressions.
  // https://tc39.es/ecma262/#sec-evaluatecall
  function EvaluateCall(
    func: SimpleFunction,
    ref: ReferenceRecord,
    args: CallExpression["arguments"] | TemplateLiteral,
    callee: CallExpression["callee"]
  ): CompletionRecord {
    let thisValue;
    if (ref instanceof ReferenceRecord) {
      if (IsPropertyReference(ref)) {
        thisValue = ref.Base;
      }
    }
    const argList = ArgumentListEvaluation(args);
    if (typeof func !== "function") {
      const funcName = codeSource.substring(callee.start, callee.end);
      throw new TypeError(`${funcName} is not a function`);
    }
    const result = func.apply(thisValue, argList);
    sanitize(result);
    return NormalCompletion(result);
  }

  // https://tc39.es/ecma262/#sec-evaluatenew
  function EvaluateNew(
    constructExpr: CallExpression["callee"],
    args: NewExpression["arguments"]
  ): CompletionRecord {
    const ref = Evaluate(constructExpr);
    const constructor = GetValue(ref) as new (...args: unknown[]) => unknown;
    const argList = ArgumentListEvaluation(args);
    if (
      typeof constructor !== "function" ||
      (constructor as unknown as FunctionObject)[IsConstructor] === false
    ) {
      const constructorName = codeSource.substring(
        constructExpr.start,
        constructExpr.end
      );
      throw new TypeError(`${constructorName} is not a constructor`);
    }
    if (!isAllowedConstructor(constructor)) {
      const constructorName = codeSource.substring(
        constructExpr.start,
        constructExpr.end
      );
      throw new TypeError(`${constructorName} is not an allowed constructor`);
    }
    return NormalCompletion(new constructor(...argList));
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
  function ArgumentListEvaluation(
    args: CallExpression["arguments"] | TemplateLiteral
  ): unknown[] {
    const array: unknown[] = [];
    if (Array.isArray(args)) {
      for (const arg of args) {
        if (arg.type === "SpreadElement") {
          const spreadValues = GetValue(Evaluate(arg.argument)) as unknown[];
          array.push(...spreadValues);
        } else {
          array.push(GetValue(Evaluate(arg)));
        }
      }
    } else {
      array.push(GetTemplateObject(args));
      for (const expr of args.expressions) {
        array.push(GetValue(Evaluate(expr)));
      }
    }
    return array;
  }

  // https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
  function CallFunction(
    closure: FunctionObject,
    args: Iterable<unknown>
  ): unknown {
    hooks.beforeCall?.(closure[SourceNode]);
    PrepareForOrdinaryCall(closure);
    const result = OrdinaryCallEvaluateBody(closure, args);
    executionContextStack.pop();
    if (result.Type === "return") {
      return result.Value;
    }
    return undefined;
  }

  // https://tc39.es/ecma262/#sec-prepareforordinarycall
  function PrepareForOrdinaryCall(F: FunctionObject): ExecutionContext {
    const calleeContext = new ExecutionContext();
    calleeContext.Function = F;
    const localEnv = new FunctionEnvironment(F[Environment]);
    calleeContext.VariableEnvironment = localEnv;
    calleeContext.LexicalEnvironment = localEnv;
    executionContextStack.push(calleeContext);
    return calleeContext;
  }

  // https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
  function OrdinaryCallEvaluateBody(
    F: FunctionObject,
    args: Iterable<unknown>
  ): CompletionRecord {
    return EvaluateFunctionBody(F[ECMAScriptCode], F, args);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
  function EvaluateFunctionBody(
    body: Statement[] | Expression,
    F: FunctionObject,
    args: Iterable<unknown>
  ): CompletionRecord {
    FunctionDeclarationInstantiation(F, args);
    if (Array.isArray(body)) {
      return EvaluateStatementList(body);
    }
    return new CompletionRecord("return", GetValue(Evaluate(body)));
  }

  // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation
  function EvaluateStatementList(statements: Statement[]): CompletionRecord {
    let result = NormalCompletion(Empty);
    for (const stmt of statements) {
      const s = Evaluate(stmt);
      if (s.Type !== "normal") {
        return s;
      }
      result = UpdateEmpty(result, s.Value);
    }
    return result;
  }

  // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
  function FunctionDeclarationInstantiation(
    func: FunctionObject,
    args: Iterable<unknown>
  ): void {
    const calleeContext = getRunningContext();
    const code = func[ECMAScriptCode] as BlockStatement | Expression;
    const formals = func[FormalParameters] as FunctionDeclaration["params"];
    const parameterNames = collectBoundNames(formals);
    const hasParameterExpressions = containsExpression(formals);
    const varDeclarations = collectScopedDeclarations(code, {
      var: true,
      topLevel: true,
    });
    const varNames = collectBoundNames(varDeclarations);

    // `functionNames` ∈ `varNames`
    // `functionsToInitialize` ≈ `functionNames`
    const functionNames: string[] = [];
    const functionsToInitialize: FunctionDeclaration[] = [];
    for (let i = varDeclarations.length - 1; i >= 0; i--) {
      const d = varDeclarations[i];
      if (d.type === "FunctionDeclaration") {
        ThrowIfFunctionIsInvalid(d);
        const [fn] = collectBoundNames(d);
        if (!functionNames.includes(fn)) {
          functionNames.unshift(fn);
          functionsToInitialize.unshift(d);
        }
      } else if (rules?.noVar) {
        throw new SyntaxError(
          "Var declaration is not recommended, use `let` or `const` instead"
        );
      }
    }

    const env = calleeContext.LexicalEnvironment;
    for (const paramName of parameterNames) {
      // In strict mode, it's guaranteed no duplicate params exist.
      env.CreateMutableBinding(paramName, false);
    }

    const iteratorRecord = CreateListIteratorRecord(args);
    IteratorBindingInitialization(formals, iteratorRecord, env);

    let varEnv: EnvironmentRecord;
    if (!hasParameterExpressions) {
      // NOTE: Only a single Environment Record is needed for the parameters
      // and top-level vars.
      // `varNames` are unique.
      for (const n of varNames) {
        if (!parameterNames.includes(n)) {
          env.CreateMutableBinding(n, false);
          env.InitializeBinding(n, undefined);
        }
      }
      varEnv = env;
    } else {
      // NOTE: A separate Environment Record is needed to ensure that closures
      // created by expressions in the formal parameter list do not have
      // visibility of declarations in the function body.
      varEnv = new DeclarativeEnvironment(env);
      calleeContext.VariableEnvironment = varEnv;
      // `varNames` are unique.
      for (const n of varNames) {
        varEnv.CreateMutableBinding(n, false);
        let initialValue: unknown;
        if (parameterNames.includes(n) && !functionNames.includes(n)) {
          initialValue = env.GetBindingValue(n, false);
        }
        varEnv.InitializeBinding(n, initialValue);
        // NOTE: A var with the same name as a formal parameter initially has
        // the same value as the corresponding initialized parameter.
      }
    }
    const lexEnv = varEnv;
    calleeContext.LexicalEnvironment = lexEnv;

    const lexDeclarations = collectScopedDeclarations(code, {
      var: false,
      topLevel: true,
    });
    for (const d of lexDeclarations) {
      for (const dn of collectBoundNames(d)) {
        // Only lexical VariableDeclaration here in top-level.
        if ((d as VariableDeclaration).kind === "const") {
          lexEnv.CreateImmutableBinding(dn, true);
        } else {
          lexEnv.CreateMutableBinding(dn, false);
        }
      }
    }

    for (const f of functionsToInitialize) {
      const [fn] = collectBoundNames(f);
      const fo = InstantiateFunctionObject(f, lexEnv);
      varEnv.SetMutableBinding(fn, fo, false);
    }
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject
  function InstantiateFunctionObject(
    func: FunctionDeclaration,
    scope: EnvironmentRecord
  ): FunctionObject {
    return OrdinaryFunctionCreate(func, scope, true);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression
  function InstantiateOrdinaryFunctionExpression(
    functionExpression: FunctionExpression
  ): FunctionObject {
    const scope = getRunningContext().LexicalEnvironment;
    if (functionExpression.id) {
      const name = functionExpression.id.name;
      const funcEnv = new DeclarativeEnvironment(scope);
      funcEnv.CreateImmutableBinding(name, false);
      const closure = OrdinaryFunctionCreate(
        functionExpression,
        // functionExpression.params,
        // functionExpression.body,
        funcEnv,
        true
      );
      funcEnv.InitializeBinding(name, closure);
      return closure;
    } else {
      const closure = OrdinaryFunctionCreate(functionExpression, scope, true);
      return closure;
    }
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-instantiatearrowfunctionexpression
  function InstantiateArrowFunctionExpression(
    arrowFunction: ArrowFunctionExpression
  ): FunctionObject {
    const scope = getRunningContext().LexicalEnvironment;
    const closure = OrdinaryFunctionCreate(arrowFunction, scope, false);
    return closure;
  }

  // https://tc39.es/ecma262/#sec-ordinaryfunctioncreate
  function OrdinaryFunctionCreate(
    sourceNode:
      | FunctionDeclaration
      | FunctionExpression
      | ArrowFunctionExpression,
    // parameterList: FunctionDeclaration["params"],
    // body: BlockStatement | Expression,
    scope: EnvironmentRecord,
    isConstructor: boolean
  ): FunctionObject {
    const F = function () {
      // eslint-disable-next-line prefer-rest-params
      return CallFunction(F, arguments);
    } as FunctionObject;
    Object.defineProperties(F, {
      [SourceNode]: {
        value: sourceNode,
      },
      [FormalParameters]: {
        value: sourceNode.params,
      },
      [ECMAScriptCode]: {
        value:
          sourceNode.body.type === "BlockStatement"
            ? sourceNode.body.body
            : sourceNode.body,
      },
      [Environment]: {
        value: scope,
      },
      [IsConstructor]: {
        value: isConstructor,
      },
    });
    return F;
  }

  // Patterns initialization.
  // https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
  function BindingInitialization(
    node: EstreeLVal,
    value: unknown,
    environment: EnvironmentRecord
  ): CompletionRecord {
    switch (node.type) {
      case "Identifier":
        return InitializeBoundName(node.name, value, environment);
      case "ObjectPattern":
        RequireObjectCoercible(value);
        return PropertyBindingInitialization(
          (node as EstreeObjectPattern).properties,
          value,
          environment
        );
      case "ArrayPattern": {
        const iteratorRecord = CreateListIteratorRecord(
          value as Iterable<unknown>
        );
        return IteratorBindingInitialization(
          node.elements,
          iteratorRecord,
          environment
        );
      }
    }
  }

  // https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization
  function PropertyBindingInitialization(
    properties: (EstreeProperty | RestElement)[],
    value: unknown,
    environment: EnvironmentRecord
  ): CompletionRecord {
    const excludedNames = new Set<PropertyKey>();
    for (const prop of properties) {
      if (prop.type === "RestElement") {
        return RestBindingInitialization(
          prop,
          value,
          environment,
          excludedNames
        );
      }
      if (!prop.computed && prop.key.type === "Identifier") {
        KeyedBindingInitialization(
          prop.value as EstreeLVal,
          value,
          environment,
          prop.key.name
        );
        excludedNames.add(prop.key.name);
      } else {
        const P = EvaluateComputedPropertyName(prop.key);
        KeyedBindingInitialization(
          prop.value as EstreeLVal,
          value,
          environment,
          P
        );
        excludedNames.add(P);
      }
    }
    return NormalCompletion(Empty);
  }

  // https://tc39.es/ecma262/#prod-ComputedPropertyName
  function EvaluateComputedPropertyName(node: Expression): PropertyKey {
    const propName = GetValue(Evaluate(node));
    return ToPropertyKey(propName);
  }

  // https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-restbindinginitialization
  function RestBindingInitialization(
    restProperty: RestElement,
    value: unknown,
    environment: EnvironmentRecord,
    excludedNames: Set<PropertyKey>
  ): CompletionRecord {
    const lhs = ResolveBinding(
      (restProperty.argument as Identifier).name,
      environment
    );
    const restObj = CopyDataProperties({}, value, excludedNames);
    if (!environment) {
      return PutValue(lhs, restObj);
    }
    return InitializeReferencedBinding(lhs, restObj);
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization
  function IteratorBindingInitialization(
    elements: PatternLike[],
    iteratorRecord: Iterator<unknown>,
    environment: EnvironmentRecord
  ): CompletionRecord {
    if (elements.length === 0) {
      return NormalCompletion(Empty);
    }
    let result;
    for (const node of elements) {
      if (!node) {
        // Elision element.
        iteratorRecord.next();
        result = NormalCompletion(Empty);
      } else if (node.type === "RestElement") {
        // Rest element.
        if (node.argument.type === "Identifier") {
          const lhs = ResolveBinding(node.argument.name, environment);
          const A: unknown[] = [];
          let n = 0;
          // eslint-disable-next-line no-constant-condition
          while (true) {
            const { done, value } = iteratorRecord.next();
            if (done) {
              result = environment
                ? InitializeReferencedBinding(lhs, A)
                : PutValue(lhs, A);
              break;
            }
            A[n] = value;
            n++;
          }
        } else {
          const A: unknown[] = [];
          let n = 0;
          // eslint-disable-next-line no-constant-condition
          while (true) {
            const { done, value } = iteratorRecord.next();
            if (done) {
              result = BindingInitialization(node.argument, A, environment);
              break;
            }
            A[n] = value;
            n++;
          }
        }
      } else {
        // Normal element.
        const bindingElement =
          node.type === "AssignmentPattern" ? node.left : node;
        switch (bindingElement.type) {
          case "ObjectPattern":
          case "ArrayPattern": {
            let v: unknown;
            const { done, value } = iteratorRecord.next();
            if (!done) {
              v = value;
            }
            if (node.type === "AssignmentPattern" && v === undefined) {
              const defaultValue = Evaluate(node.right);
              v = GetValue(defaultValue);
            }
            result = BindingInitialization(bindingElement, v, environment);
            break;
          }
          case "Identifier": {
            const bindingId = bindingElement.name;
            const lhs = ResolveBinding(bindingId, environment);
            let v: unknown;
            const { done, value } = iteratorRecord.next();
            if (!done) {
              v = value;
            }
            if (node.type === "AssignmentPattern" && v === undefined) {
              // IsAnonymousFunctionDefinition(Initializer)
              const defaultValue = Evaluate(node.right);
              v = GetValue(defaultValue);
            }
            result = environment
              ? InitializeReferencedBinding(lhs, v)
              : PutValue(lhs, v);
            break;
          }
        }
      }
    }
    return result;
  }

  // https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization
  function KeyedBindingInitialization(
    node: EstreeLVal,
    value: unknown,
    environment: EnvironmentRecord,
    propertyName: PropertyKey
  ): CompletionRecord {
    const isIdentifier =
      node.type === "Identifier" ||
      (node.type === "AssignmentPattern" && node.left.type === "Identifier");
    if (isIdentifier) {
      const bindingId =
        node.type === "Identifier" ? node.name : (node.left as Identifier).name;
      const lhs = ResolveBinding(bindingId, environment);
      let v = GetV(value, propertyName);
      if (node.type === "AssignmentPattern" && v === undefined) {
        // If IsAnonymousFunctionDefinition(Initializer)
        const defaultValue = Evaluate(node.right);
        v = GetValue(defaultValue);
      }
      if (!environment) {
        return PutValue(lhs, v);
      }
      return InitializeReferencedBinding(lhs, v);
    }

    let v = GetV(value, propertyName);
    if (node.type === "AssignmentPattern" && v === undefined) {
      const defaultValue = Evaluate(node.right);
      v = GetValue(defaultValue);
    }
    return BindingInitialization(
      node.type === "AssignmentPattern" ? node.left : node,
      v,
      environment
    );
  }

  // https://tc39.es/ecma262/#sec-initializeboundname
  function InitializeBoundName(
    name: string,
    value: unknown,
    environment?: EnvironmentRecord
  ): CompletionRecord {
    // Assert: environment is always present.
    environment.InitializeBinding(name, value);
    return NormalCompletion(Empty);
  }

  function ThrowIfFunctionIsInvalid(
    func: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression
  ): void {
    if (func.async || func.generator) {
      throw new SyntaxError(
        `${func.async ? "Async" : "Generator"} function is not allowed`
      );
    }
    if (expressionOnly && !(func as ArrowFunctionExpression).expression) {
      throw new SyntaxError(
        "Only an `Expression` is allowed in `ArrowFunctionExpression`'s body"
      );
    }
  }

  if (expressionOnly) {
    return GetValue(Evaluate(rootAst));
  }

  hooks.beforeEvaluate?.(rootAst);
  ThrowIfFunctionIsInvalid(rootAst);
  const [fn] = collectBoundNames(rootAst);
  // Create an immutable binding for the root function.
  rootEnv.CreateImmutableBinding(fn, true);
  const fo = InstantiateFunctionObject(rootAst, rootEnv);
  rootEnv.InitializeBinding(fn, fo);
  return fo;
}
Example #5
Source File: precook.ts    From next-core with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Analysis an AST of a storyboard function or an evaluation expression.
 *
 * @param rootAst - The root AST.
 * @param options - Analysis options.
 * @returns A set of global variables the root AST attempts to access.
 */
export function precook(
  rootAst: Expression | FunctionDeclaration,
  { expressionOnly, visitors, withParent, hooks = {} }: PrecookOptions = {}
): Set<string> {
  const attemptToVisitGlobals = new Set<string>();
  const analysisContextStack: AnalysisContext[] = [];
  const rootEnv = new AnalysisEnvironment(null);
  const rootContext = new AnalysisContext();
  rootContext.VariableEnvironment = rootEnv;
  rootContext.LexicalEnvironment = rootEnv;
  analysisContextStack.push(rootContext);

  function getRunningContext(): AnalysisContext {
    return analysisContextStack[analysisContextStack.length - 1];
  }

  function visit(node: EstreeNode): void {
    if (hasOwnProperty(visitors, node.type)) {
      visitors[node.type](node);
    }
  }

  function EvaluateChildren<T extends EstreeNode>(
    node: T,
    keys: (keyof T)[],
    parent?: EstreeParent
  ): void {
    for (const key of keys) {
      Evaluate(
        node[key] as unknown as EstreeNode | EstreeNode[],
        parent?.concat({ node, key } as EstreeParentItem)
      );
    }
  }

  function Evaluate(
    node: EstreeNode | EstreeNode[],
    parent?: EstreeParent
  ): void {
    if (Array.isArray(node)) {
      node.forEach((n, index) => {
        Evaluate(
          n,
          parent
            ? parent.slice(0, -1).concat({
                ...parent[parent.length - 1],
                index,
              })
            : parent
        );
      });
    } else if (node) {
      // `node` maybe `null` in some cases.
      hooks.beforeVisit?.(node, parent);
      visitors && visit(node);
      // Expressions:
      switch (node.type) {
        case "Identifier":
          if (!ResolveBinding(node.name)) {
            hooks.beforeVisitGlobal?.(node, parent);
            attemptToVisitGlobals.add(node.name);
          }
          return;
        case "ArrayExpression":
        case "ArrayPattern":
          EvaluateChildren(node, ["elements"], parent);
          return;
        case "ArrowFunctionExpression": {
          const env = getRunningContext().LexicalEnvironment;
          const closure = OrdinaryFunctionCreate(node, env);
          CallFunction(closure, parent);
          return;
        }
        case "AssignmentPattern":
        case "BinaryExpression":
        case "LogicalExpression":
          EvaluateChildren(node, ["left", "right"], parent);
          return;
        case "CallExpression":
        case "NewExpression":
          EvaluateChildren(node, ["callee", "arguments"], parent);
          return;
        case "ChainExpression":
          EvaluateChildren(node, ["expression"], parent);
          return;
        case "ConditionalExpression":
          EvaluateChildren(node, ["test", "consequent", "alternate"], parent);
          return;
        case "MemberExpression":
          EvaluateChildren(node, ["object"], parent);
          if (node.computed) {
            EvaluateChildren(node, ["property"], parent);
          }
          return;
        case "ObjectExpression":
        case "ObjectPattern":
          EvaluateChildren(node, ["properties"], parent);
          return;
        case "Property":
          if (node.computed) {
            EvaluateChildren(node, ["key"], parent);
          }
          EvaluateChildren(node, ["value"], parent);
          return;
        case "RestElement":
        case "SpreadElement":
        case "UnaryExpression":
          EvaluateChildren(node, ["argument"], parent);
          return;
        case "SequenceExpression":
        case "TemplateLiteral":
          EvaluateChildren(node, ["expressions"], parent);
          return;
        case "TaggedTemplateExpression":
          EvaluateChildren(node, ["tag", "quasi"], parent);
          return;
        case "Literal":
          return;
      }
      if (!expressionOnly) {
        // Statements and assignments:
        switch (node.type) {
          case "AssignmentExpression":
            EvaluateChildren(node, ["right", "left"], parent);
            return;
          case "BlockStatement": {
            if (!node.body.length) {
              return;
            }
            const runningContext = getRunningContext();
            const oldEnv = runningContext.LexicalEnvironment;
            const blockEnv = new AnalysisEnvironment(oldEnv);
            BlockDeclarationInstantiation(node.body, blockEnv);
            runningContext.LexicalEnvironment = blockEnv;
            EvaluateChildren(node, ["body"], parent);
            runningContext.LexicalEnvironment = oldEnv;
            return;
          }
          case "BreakStatement":
          case "ContinueStatement":
          case "EmptyStatement":
            return;
          case "CatchClause": {
            const runningContext = getRunningContext();
            const oldEnv = runningContext.LexicalEnvironment;
            const catchEnv = new AnalysisEnvironment(oldEnv);
            BoundNamesInstantiation(node.param, catchEnv);
            runningContext.LexicalEnvironment = catchEnv;
            EvaluateChildren(node, ["param", "body"], parent);
            runningContext.LexicalEnvironment = oldEnv;
            return;
          }
          case "DoWhileStatement":
            EvaluateChildren(node, ["body", "test"], parent);
            return;
          case "ExpressionStatement":
          case "TSAsExpression":
            EvaluateChildren(node, ["expression"], parent);
            return;
          case "ForInStatement":
          case "ForOfStatement": {
            // ForIn/OfHeadEvaluation
            const lexicalBinding =
              node.left.type === "VariableDeclaration" &&
              node.left.kind !== "var";
            const runningContext = getRunningContext();
            const oldEnv = runningContext.LexicalEnvironment;
            if (lexicalBinding) {
              const newEnv = new AnalysisEnvironment(oldEnv);
              BoundNamesInstantiation(node.left, newEnv);
              runningContext.LexicalEnvironment = newEnv;
            }
            EvaluateChildren(node, ["right"], parent);
            runningContext.LexicalEnvironment = oldEnv;

            // ForIn/OfBodyEvaluation
            if (lexicalBinding) {
              const iterationEnv = new AnalysisEnvironment(oldEnv);
              BoundNamesInstantiation(node.left, iterationEnv);
              runningContext.LexicalEnvironment = iterationEnv;
            }
            EvaluateChildren(node, ["left", "body"], parent);
            runningContext.LexicalEnvironment = oldEnv;
            return;
          }
          case "ForStatement": {
            const lexicalBinding =
              node.init?.type === "VariableDeclaration" &&
              node.init.kind !== "var";
            const runningContext = getRunningContext();
            const oldEnv = runningContext.LexicalEnvironment;
            if (lexicalBinding) {
              const loopEnv = new AnalysisEnvironment(oldEnv);
              BoundNamesInstantiation(
                node.init as VariableDeclaration,
                loopEnv
              );
              runningContext.LexicalEnvironment = loopEnv;
            }
            EvaluateChildren(node, ["init", "test", "body", "update"], parent);
            runningContext.LexicalEnvironment = oldEnv;
            return;
          }
          case "FunctionDeclaration": {
            const [fn] = collectBoundNames(node);
            const env = getRunningContext().LexicalEnvironment;
            const fo = OrdinaryFunctionCreate(node, env);
            env.CreateBinding(fn);
            CallFunction(fo, parent);
            return;
          }
          case "FunctionExpression": {
            const closure = InstantiateOrdinaryFunctionExpression(node);
            CallFunction(closure, parent);
            return;
          }
          case "IfStatement":
            EvaluateChildren(node, ["test", "consequent", "alternate"], parent);
            return;
          case "ReturnStatement":
          case "ThrowStatement":
          case "UpdateExpression":
            EvaluateChildren(node, ["argument"], parent);
            return;
          case "SwitchCase":
            EvaluateChildren(node, ["test", "consequent"], parent);
            return;
          case "SwitchStatement": {
            EvaluateChildren(node, ["discriminant"], parent);
            const runningContext = getRunningContext();
            const oldEnv = runningContext.LexicalEnvironment;
            const blockEnv = new AnalysisEnvironment(oldEnv);
            BlockDeclarationInstantiation(node.cases, blockEnv);
            runningContext.LexicalEnvironment = blockEnv;
            EvaluateChildren(node, ["cases"], parent);
            runningContext.LexicalEnvironment = oldEnv;
            return;
          }
          case "TryStatement":
            EvaluateChildren(node, ["block", "handler", "finalizer"], parent);
            return;
          case "VariableDeclaration":
            EvaluateChildren(node, ["declarations"], parent);
            return;
          case "VariableDeclarator":
            EvaluateChildren(node, ["id", "init"], parent);
            return;
          case "WhileStatement":
            EvaluateChildren(node, ["test", "body"], parent);
            return;
        }
      }
      const silent = hooks.beforeVisitUnknown?.(node, parent);
      if (!silent) {
        // eslint-disable-next-line no-console
        console.warn(`Unsupported node type \`${node.type}\``);
      }
    }
  }

  function BoundNamesInstantiation(
    declarations: NodeWithBoundNames | NodeWithBoundNames[],
    env: AnalysisEnvironment
  ): void {
    for (const name of collectBoundNames(declarations)) {
      env.CreateBinding(name);
    }
  }

  function ResolveBinding(name: string): boolean {
    const env = getRunningContext().LexicalEnvironment;
    return GetIdentifierReference(env, name);
  }

  function GetIdentifierReference(
    env: AnalysisEnvironment,
    name: string
  ): boolean {
    return (
      !!env &&
      (env.HasBinding(name) || GetIdentifierReference(env.OuterEnv, name))
    );
  }

  function BlockDeclarationInstantiation(
    code: Statement[] | SwitchCase[],
    env: AnalysisEnvironment
  ): void {
    const declarations = collectScopedDeclarations(code, {
      var: false,
      topLevel: false,
    });
    BoundNamesInstantiation(declarations, env);
  }

  function CallFunction(
    closure: AnalysisFunctionObject,
    parent?: EstreeParent
  ): void {
    PrepareOrdinaryCall(closure);
    FunctionDeclarationInstantiation(closure, parent);
    Evaluate(
      closure.ECMAScriptCode,
      parent
        ?.concat({
          node: closure.Function,
          key: "body",
        })
        .concat(
          closure.Function.body.type === "BlockStatement"
            ? {
                node: closure.Function.body,
                key: "body",
              }
            : []
        )
    );
    analysisContextStack.pop();
  }

  function PrepareOrdinaryCall(F: AnalysisFunctionObject): void {
    const calleeContext = new AnalysisContext();
    const localEnv = new AnalysisEnvironment(F.Environment);
    calleeContext.VariableEnvironment = localEnv;
    calleeContext.LexicalEnvironment = localEnv;
    analysisContextStack.push(calleeContext);
  }

  function FunctionDeclarationInstantiation(
    func: AnalysisFunctionObject,
    parent?: EstreeParent
  ): void {
    const calleeContext = getRunningContext();
    const code = func.ECMAScriptCode;
    const formals = func.FormalParameters;
    const hasParameterExpressions = containsExpression(formals);
    const varDeclarations = collectScopedDeclarations(code, {
      var: true,
      topLevel: true,
    });
    const varNames = collectBoundNames(varDeclarations);

    const env = calleeContext.LexicalEnvironment;
    BoundNamesInstantiation(formals, env);

    Evaluate(formals, parent?.concat({ node: func.Function, key: "params" }));

    let varEnv: AnalysisEnvironment;
    if (!hasParameterExpressions) {
      // NOTE: Only a single Environment Record is needed for the parameters
      // and top-level vars.
      for (const n of varNames) {
        env.CreateBinding(n);
      }
      varEnv = env;
    } else {
      // NOTE: A separate Environment Record is needed to ensure that closures
      // created by expressions in the formal parameter list do not have
      // visibility of declarations in the function body.
      varEnv = new AnalysisEnvironment(env);
      calleeContext.VariableEnvironment = varEnv;
      for (const n of varNames) {
        varEnv.CreateBinding(n);
      }
    }
    const lexEnv = varEnv;
    calleeContext.LexicalEnvironment = lexEnv;

    const lexDeclarations = collectScopedDeclarations(code, {
      var: false,
      topLevel: true,
    });
    BoundNamesInstantiation(lexDeclarations, lexEnv);
  }

  function InstantiateOrdinaryFunctionExpression(
    functionExpression: FunctionExpression
  ): AnalysisFunctionObject {
    const scope = getRunningContext().LexicalEnvironment;
    if (!functionExpression.id) {
      return OrdinaryFunctionCreate(functionExpression, scope);
    }
    const name = functionExpression.id.name;
    const funcEnv = new AnalysisEnvironment(scope);
    funcEnv.CreateBinding(name);
    return OrdinaryFunctionCreate(functionExpression, funcEnv);
  }

  function OrdinaryFunctionCreate(
    func: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
    scope: AnalysisEnvironment
  ): AnalysisFunctionObject {
    return {
      Function: func,
      FormalParameters: func.params,
      ECMAScriptCode:
        func.body.type === "BlockStatement" ? func.body.body : func.body,
      Environment: scope,
    };
  }

  Evaluate(rootAst, withParent ? [] : undefined);

  return attemptToVisitGlobals;
}