All files / src/utils resolveToValue.js

92.98% Statements 53/57
86.44% Branches 51/59
100% Functions 5/5
92.98% Lines 53/57
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158                                          24x     23x 23x 8x 8x 8x                 15x 8x 7x 7x           656x     656x 656x   656x           614x 42x   7x     7x 7x       649x 648x                 639x   639x   357x     357x         350x   7x 7x       639x 632x   7x                   2410x 2410x 506x 506x   1904x         169x 1735x       1735x 721x       33x     688x   688x       639x 639x 632x 632x     49x 49x 24x 24x     688x     1014x    
/*
 * Copyright (c) 2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 *
 */
 
import recast from 'recast';
import {traverseShallow} from './traverse';
 
var {
  types: {
    NodePath,
    builders,
    namedTypes: types,
  },
} = recast;
 
function buildMemberExpressionFromPattern(path: NodePath): ?NodePath {
  var node = path.node;
  if (types.Property.check(node)) {
    var objPath = buildMemberExpressionFromPattern(path.parent);
    Eif (objPath) {
      return new NodePath(
        builders.memberExpression(
          objPath.node,
          node.key,
          types.Literal.check(node.key)
        ),
        objPath
      );
    }
  } else if (types.ObjectPattern.check(node)) {
    return buildMemberExpressionFromPattern(path.parent);
  } else Eif (types.VariableDeclarator.check(node)) {
    return path.get('init');
  }
  return null;
}
 
function findScopePath(paths: Array<NodePath>, path: NodePath): ?NodePath {
  Iif (paths.length < 1) {
    return;
  }
  let resultPath = paths[0];
  const parentPath = resultPath.parent;
 
  if (types.ImportDefaultSpecifier.check(parentPath.node) ||
    types.ImportSpecifier.check(parentPath.node) ||
    types.ImportNamespaceSpecifier.check(parentPath.node) ||
    types.VariableDeclarator.check(parentPath.node) ||
    types.TypeAlias.check(parentPath.node)
  ) {
    resultPath = parentPath;
  } else if (types.Property.check(parentPath.node)) {
    // must be inside a pattern
    const memberExpressionPath = buildMemberExpressionFromPattern(
      parentPath
    );
    Eif (memberExpressionPath) {
      return memberExpressionPath;
    }
  }
 
  if (resultPath.node !== path.node) {
    return resolveToValue(resultPath);
  }
}
 
/**
 * Tries to find the last value assigned to `name` in the scope created by
 * `scope`. We are not descending into any statements (blocks).
 */
function findLastAssignedValue(scope, name) {
  let results = [];
 
  traverseShallow(scope.path.node, {
    visitAssignmentExpression: function(path) {
      const node = path.node;
      // Skip anything that is not an assignment to a variable with the
      // passed name.
      if (
        !types.Identifier.check(node.left) ||
        node.left.name !== name ||
        node.operator !== '='
      ) {
        return this.traverse(path);
      }
      results.push(path.get('right'));
      return false;
    },
  });
 
  if (results.length === 0) {
    return null;
  }
  return resolveToValue(results.pop());
}
 
/**
 * If the path is an identifier, it is resolved in the scope chain.
 * If it is an assignment expression, it resolves to the right hand side.
 *
 * Else the path itself is returned.
 */
export default function resolveToValue(path: NodePath): NodePath {
  var node = path.node;
  if (types.VariableDeclarator.check(node)) {
     Eif (node.init) {
       return resolveToValue(path.get('init'));
     }
  } else if (
    types.ImportDefaultSpecifier.check(node) ||
    types.ImportNamespaceSpecifier.check(node) ||
    types.ImportSpecifier.check(node)
  ) {
    return path.parentPath;
  } else Iif (types.AssignmentExpression.check(node)) {
    if (node.operator === '=') {
      return resolveToValue(path.get('right'));
    }
  } else if (types.Identifier.check(node)) {
    if ((types.ClassDeclaration.check(path.parentPath.node) ||
        types.ClassExpression.check(path.parentPath.node) ||
        types.Function.check(path.parentPath.node)) &&
        path.parentPath.get('id') === path) {
      return path.parentPath;
    }
 
    let scope = path.scope.lookup(node.name);
    let resolvedPath: ?NodePath;
    if (scope) {
      // The variable may be assigned a different value after initialization.
      // We are first trying to find all assignments to the variable in the
      // block where it is defined (i.e. we are not traversing into statements)
      resolvedPath = findLastAssignedValue(scope, node.name);
      if (!resolvedPath) {
        const bindings = scope.getBindings()[node.name];
        resolvedPath = findScopePath(bindings, path);
      }
    } else {
      scope = path.scope.lookupType(node.name);
      if (scope) {
        const types = scope.getTypes()[node.name];
        resolvedPath = findScopePath(types, path);
      }
    }
    return resolvedPath || path;
  }
 
  return path;
}