text: /**
   * @fileoverview Enforce stateless components to be written as a pure function
   * @author Yannick Croissant
   * @author Alberto Rodríguez
   * @copyright 2015 Alberto Rodríguez. All rights reserved.
   */
  
  'use strict';
  
  const Components = require('../util/Components');
  const astUtil = require('../util/ast');
  const docsUrl = require('../util/docsUrl');
  
  // ------------------------------------------------------------------------------
  // Rule Definition
  // ------------------------------------------------------------------------------
  
  module.exports = {
    meta: {
      docs: {
        description: 'Enforce stateless components to be written as a pure function',
        category: 'Stylistic Issues',
        recommended: false,
        url: docsUrl('prefer-stateless-function')
      },
  
      messages: {
        componentShouldBePure: 'Component should be written as a pure function'
      },
  
      schema: [{
        type: 'object',
        properties: {
          ignorePureComponents: {
            default: false,
            type: 'boolean'
          }
        },
        additionalProperties: false
      }]
    },
  
    create: Components.detect((context, components, utils) => {
      const configuration = context.options[0] || {};
      const ignorePureComponents = configuration.ignorePureComponents || false;
  
      // --------------------------------------------------------------------------
      // Public
      // --------------------------------------------------------------------------
  
      /**
       * Checks whether a given array of statements is a single call of `super`.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode[]} body - An array of statements to check.
       * @returns {boolean} `true` if the body is a single call of `super`.
       */
      function isSingleSuperCall(body) {
        return (
          body.length === 1
          && body[0].type === 'ExpressionStatement'
          && body[0].expression.type === 'CallExpression'
          && body[0].expression.callee.type === 'Super'
        );
      }
  
      /**
       * Checks whether a given node is a pattern which doesn't have any side effects.
       * Default parameters and Destructuring parameters can have side effects.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode} node - A pattern node.
       * @returns {boolean} `true` if the node doesn't have any side effects.
       */
      function isSimple(node) {
        return node.type === 'Identifier' || node.type === 'RestElement';
      }
  
      /**
       * Checks whether a given array of expressions is `...arguments` or not.
       * `super(...arguments)` passes all arguments through.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode[]} superArgs - An array of expressions to check.
       * @returns {boolean} `true` if the superArgs is `...arguments`.
       */
      function isSpreadArguments(superArgs) {
        return (
          superArgs.length === 1
          && superArgs[0].type === 'SpreadElement'
          && superArgs[0].argument.type === 'Identifier'
          && superArgs[0].argument.name === 'arguments'
        );
      }
  
      /**
       * Checks whether given 2 nodes are identifiers which have the same name or not.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode} ctorParam - A node to check.
       * @param {ASTNode} superArg - A node to check.
       * @returns {boolean} `true` if the nodes are identifiers which have the same
       *      name.
       */
      function isValidIdentifierPair(ctorParam, superArg) {
        return (
          ctorParam.type === 'Identifier'
          && superArg.type === 'Identifier'
          && ctorParam.name === superArg.name
        );
      }
  
      /**
       * Checks whether given 2 nodes are a rest/spread pair which has the same values.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode} ctorParam - A node to check.
       * @param {ASTNode} superArg - A node to check.
       * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
       *      same values.
       */
      function isValidRestSpreadPair(ctorParam, superArg) {
        return (
          ctorParam.type === 'RestElement'
          && superArg.type === 'SpreadElement'
          && isValidIdentifierPair(ctorParam.argument, superArg.argument)
        );
      }
  
      /**
       * Checks whether given 2 nodes have the same value or not.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode} ctorParam - A node to check.
       * @param {ASTNode} superArg - A node to check.
       * @returns {boolean} `true` if the nodes have the same value or not.
       */
      function isValidPair(ctorParam, superArg) {
        return (
          isValidIdentifierPair(ctorParam, superArg)
          || isValidRestSpreadPair(ctorParam, superArg)
        );
      }
  
      /**
       * Checks whether the parameters of a constructor and the arguments of `super()`
       * have the same values or not.
       * @see ESLint no-useless-constructor rule
       * @param {ASTNode[]} ctorParams - The parameters of a constructor to check.
       * @param {ASTNode} superArgs - The arguments of `super()` to check.
       * @returns {boolean} `true` if those have the same values.
       */
      function isPassingThrough(ctorParams, superArgs) {
        if (ctorParams.length !== superArgs.length) {
          return false;
        }
  
        for (let i = 0; i < ctorParams.length; ++i) {
          if (!isValidPair(ctorParams[i], superArgs[i])) {
            return false;
          }
        }
  
        return true;
      }
  
      /**
       * Checks whether the constructor body is a redundant super call.
       * @see ESLint no-useless-constructor rule
       * @param {Array} body - constructor body content.
       * @param {Array} ctorParams - The params to check against super call.
       * @returns {boolean} true if the construtor body is redundant
       */
      function isRedundantSuperCall(body, ctorParams) {
        return (
          isSingleSuperCall(body)
          && ctorParams.every(isSimple)
          && (
            isSpreadArguments(body[0].expression.arguments)
            || isPassingThrough(ctorParams, body[0].expression.arguments)
          )
        );
      }
  
      /**
       * Check if a given AST node have any other properties the ones available in stateless components
       * @param {ASTNode} node The AST node being checked.
       * @returns {Boolean} True if the node has at least one other property, false if not.
       */
      function hasOtherProperties(node) {
        const properties = astUtil.getComponentProperties(node);
        return properties.some((property) => {
          const name = astUtil.getPropertyName(property);
          const isDisplayName = name === 'displayName';
          const isPropTypes = name === 'propTypes' || ((name === 'props') && property.typeAnnotation);
          const contextTypes = name === 'contextTypes';
          const defaultProps = name === 'defaultProps';
          const isUselessConstructor = property.kind === 'constructor'
            && !!property.value.body
            && isRedundantSuperCall(property.value.body.body, property.value.params);
          const isRender = name === 'render';
          return !isDisplayName && !isPropTypes && !contextTypes && !defaultProps && !isUselessConstructor && !isRender;
        });
      }
  
      /**
       * Mark component as pure as declared
       * @param {ASTNode} node The AST node being checked.
       */
      function markSCUAsDeclared(node) {
        components.set(node, {
          hasSCU: true
        });
      }
  
      /**
       * Mark childContextTypes as declared
       * @param {ASTNode} node The AST node being checked.
       */
      function markChildContextTypesAsDeclared(node) {
        components.set(node, {
          hasChildContextTypes: true
        });
      }
  
      /**
       * Mark a setState as used
       * @param {ASTNode} node The AST node being checked.
       */
      function markThisAsUsed(node) {
        components.set(node, {
          useThis: true
        });
      }
  
      /**
       * Mark a props or context as used
       * @param {ASTNode} node The AST node being checked.
       */
      function markPropsOrContextAsUsed(node) {
        components.set(node, {
          usePropsOrContext: true
        });
      }
  
      /**
       * Mark a ref as used
       * @param {ASTNode} node The AST node being checked.
       */
      function markRefAsUsed(node) {
        components.set(node, {
          useRef: true
        });
      }
  
      /**
       * Mark return as invalid
       * @param {ASTNode} node The AST node being checked.
       */
      function markReturnAsInvalid(node) {
        components.set(node, {
          invalidReturn: true
        });
      }
  
      /**
       * Mark a ClassDeclaration as having used decorators
       * @param {ASTNode} node The AST node being checked.
       */
      function markDecoratorsAsUsed(node) {
        components.set(node, {
          useDecorators: true
        });
      }
  
      function visitClass(node) {
        if (ignorePureComponents && utils.isPureComponent(node)) {
          markSCUAsDeclared(node);
        }
  
        if (node.decorators && node.decorators.length) {
          markDecoratorsAsUsed(node);
        }
      }
  
      return {
        ClassDeclaration: visitClass,
        ClassExpression: visitClass,
  
        // Mark `this` destructuring as a usage of `this`
        VariableDeclarator(node) {
          // Ignore destructuring on other than `this`
          if (!node.id || node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'ThisExpression') {
            return;
          }
          // Ignore `props` and `context`
          const useThis = node.id.properties.some((property) => {
            const name = astUtil.getPropertyName(property);
            return name !== 'props' && name !== 'context';
          });
          if (!useThis) {
            markPropsOrContextAsUsed(node);
            return;
          }
          markThisAsUsed(node);
        },
  
        // Mark `this` usage
        MemberExpression(node) {
          if (node.object.type !== 'ThisExpression') {
            if (node.property && node.property.name === 'childContextTypes') {
              const component = utils.getRelatedComponent(node);
              if (!component) {
                return;
              }
              markChildContextTypesAsDeclared(component.node);
            }
            return;
          // Ignore calls to `this.props` and `this.context`
          }
          if (
            (node.property.name || node.property.value) === 'props'
            || (node.property.name || node.property.value) === 'context'
          ) {
            markPropsOrContextAsUsed(node);
            return;
          }
          markThisAsUsed(node);
        },
  
        // Mark `ref` usage
        JSXAttribute(node) {
          const name = context.getSourceCode().getText(node.name);
          if (name !== 'ref') {
            return;
          }
          markRefAsUsed(node);
        },
  
        // Mark `render` that do not return some JSX
        ReturnStatement(node) {
          let blockNode;
          let scope = context.getScope();
          while (scope) {
            blockNode = scope.block && scope.block.parent;
            if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) {
              break;
            }
            scope = scope.upper;
          }
          const isRender = blockNode && blockNode.key && blockNode.key.name === 'render';
          const isReturningJSX = utils.isReturningJSX(node, false);
          const isReturningNull = node.argument && (node.argument.value === null || node.argument.value === false);
          if (
            !isRender
           || (isReturningJSX || isReturningNull)
          ) {
            return;
          }
          markReturnAsInvalid(node);
        },
  
        'Program:exit'() {
          const list = components.list();
  
          for (let i = 0; i < list.length; i++) {
            const component = list[i];
  
            if (
              hasOtherProperties(component.node)
              || component.useThis
              || component.useRef
              || component.invalidReturn
              || component.hasChildContextTypes
              || component.useDecorators
              || (!utils.isES5Component(component.node) && !utils.isES6Component(component.node))
            ) {
              return;
            }
  
            if (component.hasSCU) {
              return;
            }
            context.report({
              node: component.node,
              messageId: 'componentShouldBePure'
            });
          }
        }
      };
    })
  };
  