All files / src/utils isStatelessComponent.js

96.43% Statements 54/56
90% Branches 45/50
100% Functions 5/5
96.43% Lines 54/56
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 159 160 161                                    7x   7x   7x               247x             175x     175x 84x       58x 58x 59x   59x 1x                 58x 3x     55x     91x   72x 72x       52x 52x     20x 11x   11x 3x 3x         8x   8x 8x 4x     4x 4x 4x     4x       8x 6x   10x       10x 10x 10x 3x     7x     6x       6x 6x         11x       91x                 300x   300x 142x     158x 4x         158x 136x     22x    
/*
 * 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 getPropertyValuePath from './getPropertyValuePath';
import isReactComponentClass from './isReactComponentClass';
import isReactCreateClassCall from './isReactCreateClassCall';
import isReactCreateElementCall from './isReactCreateElementCall';
import recast from 'recast';
import resolveToValue from './resolveToValue';
 
var {types: {namedTypes: types}} = recast;
 
const reNonLexicalBlocks = /^If|^Else|^Switch/;
 
const validPossibleStatelessComponentTypes = [
  'Property',
  'FunctionDeclaration',
  'FunctionExpression',
  'ArrowFunctionExpression',
];
 
function isJSXElementOrReactCreateElement(path) {
  return (
    path.node.type === 'JSXElement' ||
    (path.node.type === 'CallExpression' && isReactCreateElementCall(path))
  );
}
 
function returnsJSXElementOrReactCreateElementCall(path) {
  let visited = false;
 
  // early exit for ArrowFunctionExpressions
  if (isJSXElementOrReactCreateElement(path.get('body'))) {
    return true;
  }
 
  function isSameBlockScope(p) {
    let block = p;
    do {
      block = block.parent;
      // jump over non-lexical blocks
      if (reNonLexicalBlocks.test(block.parent.node.type)) {
        block = block.parent;
      }
    } while (
      !types.BlockStatement.check(block.node) &&
      /Function|Property/.test(block.parent.parent.node.type) &&
      !reNonLexicalBlocks.test(block.parent.node.type)
    );
 
    // special case properties
    if (types.Property.check(path.node)) {
      return block.node === path.get('value', 'body').node;
    }
 
    return block.node === path.get('body').node;
  }
 
  recast.visit(path, {
    visitReturnStatement(returnPath) {
      const resolvedPath = resolveToValue(returnPath.get('argument'));
      if (
        isJSXElementOrReactCreateElement(resolvedPath) &&
        isSameBlockScope(returnPath)
      ) {
        visited = true;
        return false;
      }
 
      if (resolvedPath.node.type === 'CallExpression') {
        let calleeValue = resolveToValue(resolvedPath.get('callee'));
 
        if (returnsJSXElementOrReactCreateElementCall(calleeValue)) {
          visited = true;
          return false;
        }
 
        let resolvedValue;
 
        let namesToResolve = [calleeValue.get('property')];
 
        Eif (calleeValue.node.type === 'MemberExpression') {
          if (calleeValue.get('object').node.type === 'Identifier') {
            resolvedValue = resolveToValue(calleeValue.get('object'));
          }
          else {
            while (calleeValue.get('object').node.type !== 'Identifier') {
              calleeValue = calleeValue.get('object');
              namesToResolve.unshift(calleeValue.get('property'));
            }
 
            resolvedValue = resolveToValue(calleeValue.get('object'));
          }
        }
 
        if (resolvedValue && types.ObjectExpression.check(resolvedValue.node)) {
          var resolvedMemberExpression = namesToResolve
            .reduce((result, path) => { // eslint-disable-line no-shadow
              Iif (!path) {
                return result;
              }
 
              Eif (result) {
                result = getPropertyValuePath(result, path.node.name);
                if (result && types.Identifier.check(result.node)) {
                  return resolveToValue(result);
                }
              }
              return result;
            }, resolvedValue);
 
          Eif (
            !resolvedMemberExpression ||
            returnsJSXElementOrReactCreateElementCall(resolvedMemberExpression)
          ) {
            visited = true;
            return false;
          }
        }
      }
 
      this.traverse(returnPath);
    },
  });
 
  return visited;
}
 
/**
 * Returns `true` if the path represents a function which returns a JSXElement
 */
export default function isStatelessComponent(
  path: NodePath
): bool {
  var node = path.node;
 
  if (validPossibleStatelessComponentTypes.indexOf(node.type) === -1) {
    return false;
  }
 
  if (node.type === 'Property') {
    Iif (isReactCreateClassCall(path.parent) || isReactComponentClass(path.parent)) {
      return false;
    }
  }
 
  if (returnsJSXElementOrReactCreateElementCall(path)) {
    return true;
  }
 
  return false;
}