<commit_before>/* @flow */

import type { NodePath, Scope } from 'babel-traverse';

class InnerScopeVisitor {
  referencedScopes: Scope[];
  seenThisExpression: boolean;

  ReferencedIdentifier = (path: NodePath) => {
    const binding = path.scope.getBinding(path.node.name);
    if (binding) {
      if (!this.referencedScopes) {
        this.referencedScopes = [];
      }
      this.referencedScopes.push(binding.scope);
    }
  }

  ThisExpression = (path: NodePath) => {
    this.seenThisExpression = true;
  }
}

function uniqueScopes (scopes: Scope[]): Scope[] {
  return scopes
    .sort((a, b) => (a.uid - b.uid))
    .reduce((a, x) => (a.length && a[a.length - 1].uid === x.uid) ? a : a.concat(x), []);
}

function deepestScopeOf (path: NodePath, scopes: Scope[]): ?Scope {
  let scope = path.scope;
  do {
    if (scopes.indexOf(scope) !== -1) {
      return scope;
    }
  } while (scope = scope.parent); // eslint-disable-line no-cond-assign
}

type BabelVisitors = {
  [key: string]: (path: NodePath) => void
};

type BabelPlugin = {
  visitor?: BabelVisitors
};

export default function ({types: t, template}: {types: BabelTypes, template: BabelTemplate}): BabelPlugin {
  const declarationTemplate = template(`
    var NAME = VALUE;
  `);
  return {
    visitor: {
      Function (path: NodePath) {
        if (t.isProgram(path.scope.parent.block)) {
          // Nowhere we can move this function
          return;
        }
        if (path.getData('unhoistable')) return;
        // Determine the outermost scope we can move to,
        // which is the innermost scope we are actually referencing (if any),
        // or the global scope.
        const innerScope = new InnerScopeVisitor();
        path.traverse(innerScope);
        if (innerScope.seenThisExpression && t.isArrowFunctionExpression(path.node)) return;
        const referencedScopes = uniqueScopes(innerScope.referencedScopes || []);
        const targetScope = deepestScopeOf(
          path,
          referencedScopes
            .concat(path.scope.getProgramParent())
            .filter(scope => scope !== path.scope)
         );
        if (!targetScope || targetScope === path.scope.parent) {
          return;
        }
        if (path.node.id) {
          const binding = path.scope.getBinding(path.node.id.name);
          const isUnsafeUse = usePath => {
            if (usePath.parent.type !== 'MemberExpression') return false;
            if (usePath.key !== 'object') return false;
            while (usePath = usePath.parentPath) { // eslint-disable-line no-cond-assign
              if (usePath.parent.type === 'UpdateExpression' && usePath.key === 'argument') {
                return true;
              }
              if (usePath.parent.type === 'AssignmentExpression' && usePath.key === 'left') {
                return true;
              }
              if (usePath.node === path.node) {
                break;
              }
              if (!t.isLVal(usePath.node)) {
                break;
              }
            }
            return false;
          };
          const uses = binding.referencePaths
            .filter(refPath => refPath !== path && t.isIdentifier(refPath));

          if (uses.length) {
            if (uses.some(isUnsafeUse)) {
              return;
            }
          }
        }

        // FunctionDeclaration needs some extra processing to be compatible with Babel's hoister:<commit_msg>fix: remove redundant condition in mutation checking loop<commit_after>/* @flow */

import type { NodePath, Scope } from 'babel-traverse';

class InnerScopeVisitor {
  referencedScopes: Scope[];
  seenThisExpression: boolean;

  ReferencedIdentifier = (path: NodePath) => {
    const binding = path.scope.getBinding(path.node.name);
    if (binding) {
      if (!this.referencedScopes) {
        this.referencedScopes = [];
      }
      this.referencedScopes.push(binding.scope);
    }
  }

  ThisExpression = (path: NodePath) => {
    this.seenThisExpression = true;
  }
}

function uniqueScopes (scopes: Scope[]): Scope[] {
  return scopes
    .sort((a, b) => (a.uid - b.uid))
    .reduce((a, x) => (a.length && a[a.length - 1].uid === x.uid) ? a : a.concat(x), []);
}

function deepestScopeOf (path: NodePath, scopes: Scope[]): ?Scope {
  let scope = path.scope;
  do {
    if (scopes.indexOf(scope) !== -1) {
      return scope;
    }
  } while (scope = scope.parent); // eslint-disable-line no-cond-assign
}

type BabelVisitors = {
  [key: string]: (path: NodePath) => void
};

type BabelPlugin = {
  visitor?: BabelVisitors
};

export default function ({types: t, template}: {types: BabelTypes, template: BabelTemplate}): BabelPlugin {
  const declarationTemplate = template(`
    var NAME = VALUE;
  `);
  return {
    visitor: {
      Function (path: NodePath) {
        if (t.isProgram(path.scope.parent.block)) {
          // Nowhere we can move this function
          return;
        }
        if (path.getData('unhoistable')) return;
        // Determine the outermost scope we can move to,
        // which is the innermost scope we are actually referencing (if any),
        // or the global scope.
        const innerScope = new InnerScopeVisitor();
        path.traverse(innerScope);
        if (innerScope.seenThisExpression && t.isArrowFunctionExpression(path.node)) return;
        const referencedScopes = uniqueScopes(innerScope.referencedScopes || []);
        const targetScope = deepestScopeOf(
          path,
          referencedScopes
            .concat(path.scope.getProgramParent())
            .filter(scope => scope !== path.scope)
         );
        if (!targetScope || targetScope === path.scope.parent) {
          return;
        }
        if (path.node.id) {
          const binding = path.scope.getBinding(path.node.id.name);
          const isUnsafeUse = usePath => {
            if (usePath.parent.type !== 'MemberExpression') return false;
            if (usePath.key !== 'object') return false;
            while (usePath = usePath.parentPath) { // eslint-disable-line no-cond-assign
              if (usePath.parent.type === 'UpdateExpression' && usePath.key === 'argument') {
                return true;
              }
              if (usePath.parent.type === 'AssignmentExpression' && usePath.key === 'left') {
                return true;
              }
              if (!t.isLVal(usePath.node)) {
                break;
              }
            }
            return false;
          };
          const uses = binding.referencePaths
            .filter(refPath => refPath !== path && t.isIdentifier(refPath));

          if (uses.length) {
            if (uses.some(isUnsafeUse)) {
              return;
            }
          }
        }

        // FunctionDeclaration needs some extra processing to be compatible with Babel's hoister:
{'fraction_non_alphanumeric': 0.13068930191716668, 'fraction_numerical': 0.0005853944094833894, 'mean_word_length': 2.1276887871853547, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3771677', 'n_tokens_mistral': 1998, 'n_tokens_neox': 1967, 'n_words': 470}