docs: function hoistFunctionEnvironment(
    fnPath,
    specCompliant = false,
    allowInsertArrow = true,
  ) {
    const thisEnvFn = fnPath.findParent(p => {
      return (
        (p.isFunction() && !p.isArrowFunctionExpression()) ||
        p.isProgram() ||
        p.isClassProperty({ static: false })
      );
    });
    const inConstructor = thisEnvFn && thisEnvFn.node.kind === "constructor";
  
    if (thisEnvFn.isClassProperty()) {
      throw fnPath.buildCodeFrameError(
        "Unable to transform arrow inside class property",
      );
    }
  
    const {
      thisPaths,
      argumentsPaths,
      newTargetPaths,
      superProps,
      superCalls,
    } = getScopeInformation(fnPath);
  
    // Convert all super() calls in the constructor, if super is used in an arrow.
    if (inConstructor && superCalls.length > 0) {
      if (!allowInsertArrow) {
        throw superCalls[0].buildCodeFrameError(
          "Unable to handle nested super() usage in arrow",
        );
      }
      const allSuperCalls = [];
      thisEnvFn.traverse({
        Function(child) {
          if (child.isArrowFunctionExpression()) return;
          child.skip();
        },
        ClassProperty(child) {
          child.skip();
        },
        CallExpression(child) {
          if (!child.get("callee").isSuper()) return;
          allSuperCalls.push(child);
        },
      });
      const superBinding = getSuperBinding(thisEnvFn);
      allSuperCalls.forEach(superCall => {
        const callee = t.identifier(superBinding);
        callee.loc = superCall.node.callee.loc;
  
        superCall.get("callee").replaceWith(callee);
      });
    }
  
    // Convert all "this" references in the arrow to point at the alias.
    let thisBinding;
    if (thisPaths.length > 0 || specCompliant) {
      thisBinding = getThisBinding(thisEnvFn, inConstructor);
  
      if (
        !specCompliant ||
        // In subclass constructors, still need to rewrite because "this" can't be bound in spec mode
        // because it might not have been initialized yet.
        (inConstructor && hasSuperClass(thisEnvFn))
      ) {
        thisPaths.forEach(thisChild => {
          const thisRef = thisChild.isJSX()
            ? t.jsxIdentifier(thisBinding)
            : t.identifier(thisBinding);
  
          thisRef.loc = thisChild.node.loc;
          thisChild.replaceWith(thisRef);
        });
  
        if (specCompliant) thisBinding = null;
      }
    }
  
    // Convert all "arguments" references in the arrow to point at the alias.
    if (argumentsPaths.length > 0) {
      const argumentsBinding = getBinding(thisEnvFn, "arguments", () =>
        t.identifier("arguments"),
      );
  
      argumentsPaths.forEach(argumentsChild => {
        const argsRef = t.identifier(argumentsBinding);
        argsRef.loc = argumentsChild.node.loc;
  
        argumentsChild.replaceWith(argsRef);
      });
    }
  
    // Convert all "new.target" references in the arrow to point at the alias.
    if (newTargetPaths.length > 0) {
      const newTargetBinding = getBinding(thisEnvFn, "newtarget", () =>
        t.metaProperty(t.identifier("new"), t.identifier("target")),
      );
  
      newTargetPaths.forEach(targetChild => {
        const targetRef = t.identifier(newTargetBinding);
        targetRef.loc = targetChild.node.loc;
  
        targetChild.replaceWith(targetRef);
      });
    }
  
    // Convert all "super.prop" references to point at aliases.
    if (superProps.length > 0) {
      if (!allowInsertArrow) {
        throw superProps[0].buildCodeFrameError(
          "Unable to handle nested super.prop usage",
        );
      }
  
      const flatSuperProps = superProps.reduce(
        (acc, superProp) => acc.concat(standardizeSuperProperty(superProp)),
        [],
      );
  
      flatSuperProps.forEach(superProp => {
        const key = superProp.node.computed
          ? ""
          : superProp.get("property").node.name;
  
        if (superProp.parentPath.isCallExpression({ callee: superProp.node })) {
          const superBinding = getSuperPropCallBinding(thisEnvFn, key);
  
          if (superProp.node.computed) {
            const prop = superProp.get("property").node;
            superProp.replaceWith(t.identifier(superBinding));
            superProp.parentPath.node.arguments.unshift(prop);
          } else {
            superProp.replaceWith(t.identifier(superBinding));
          }
        } else {
          const isAssignment = superProp.parentPath.isAssignmentExpression({
            left: superProp.node,
          });
          const superBinding = getSuperPropBinding(thisEnvFn, isAssignment, key);
  
          const args = [];
          if (superProp.node.computed) {
            args.push(superProp.get("property").node);
          }
  
          if (isAssignment) {
            const value = superProp.parentPath.node.right;
            args.push(value);
            superProp.parentPath.replaceWith(
              t.callExpression(t.identifier(superBinding), args),
            );
          } else {
            superProp.replaceWith(
              t.callExpression(t.identifier(superBinding), args),
            );
          }
        }
      });
    }
  
    return thisBinding;
  }