text: /**
   * @fileoverview Enforce ES5 or ES6 class for returning value in render function.
   * @author Mark Orel
   */
  'use strict';
  
  const has = require('has');
  const Components = require('../util/Components');
  
  // ------------------------------------------------------------------------------
  // Rule Definition
  // ------------------------------------------------------------------------------
  
  module.exports = {
    meta: {
      docs: {
        description: 'Enforce ES5 or ES6 class for returning value in render function',
        category: 'Possible Errors',
        recommended: true
      },
      schema: [{}]
    },
  
    create: Components.detect((context, components, utils) => {
      /**
       * Mark a return statement as present
       * @param {ASTNode} node The AST node being checked.
       */
      function markReturnStatementPresent(node) {
        components.set(node, {
          hasReturnStatement: true
        });
      }
  
      /**
       * Get properties for a given AST node
       * @param {ASTNode} node The AST node being checked.
       * @returns {Array} Properties array.
       */
      function getComponentProperties(node) {
        switch (node.type) {
          case 'ClassDeclaration':
            return node.body.body;
          case 'ObjectExpression':
            return node.properties;
          default:
            return [];
        }
      }
  
      /**
       * Get properties name
       * @param {Object} node - Property.
       * @returns {String} Property name.
       */
      function getPropertyName(node) {
        // Special case for class properties
        // (babel-eslint does not expose property name so we have to rely on tokens)
        if (node.type === 'ClassProperty') {
          const tokens = context.getFirstTokens(node, 2);
          return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
        } else if (['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
          return node.key.name;
        }
        return '';
      }
  
      /**
       * Check if a given AST node has a render method
       * @param {ASTNode} node The AST node being checked.
       * @returns {Boolean} True if there is a render method, false if not
       */
      function hasRenderMethod(node) {
        const properties = getComponentProperties(node);
        for (let i = 0, j = properties.length; i < j; i++) {
          if (getPropertyName(properties[i]) !== 'render' || !properties[i].value) {
            continue;
          }
          return /FunctionExpression$/.test(properties[i].value.type);
        }
        return false;
      }
  
      return {
        ReturnStatement: function(node) {
          const ancestors = context.getAncestors(node).reverse();
          let depth = 0;
          for (let i = 0, j = ancestors.length; i < j; i++) {
            if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
              depth++;
            }
            if (
              !/(MethodDefinition|(Class)?Property)$/.test(ancestors[i].type) ||
              getPropertyName(ancestors[i]) !== 'render' ||
              depth > 1
            ) {
              continue;
            }
            markReturnStatementPresent(node);
          }
        },
  
        ArrowFunctionExpression: function(node) {
          if (node.expression === false || getPropertyName(node.parent) !== 'render') {
            return;
          }
          markReturnStatementPresent(node);
        },
  
        'Program:exit': function() {
          const list = components.list();
          for (const component in list) {
            if (
              !has(list, component) ||
              !hasRenderMethod(list[component].node) ||
              list[component].hasReturnStatement ||
              (!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
            ) {
              continue;
            }
            context.report({
              node: list[component].node,
              message: 'Your render method should have return statement'
            });
          }
        }
      };
    })
  };
  