text: /**
   * @fileoverview Defines where React component static properties should be positioned.
   * @author Daniel Mason
   */
  
  'use strict';
  
  const fromEntries = require('object.fromentries');
  const Components = require('../util/Components');
  const docsUrl = require('../util/docsUrl');
  const astUtil = require('../util/ast');
  const propsUtil = require('../util/props');
  const report = require('../util/report');
  
  // ------------------------------------------------------------------------------
  // Positioning Options
  // ------------------------------------------------------------------------------
  const STATIC_PUBLIC_FIELD = 'static public field';
  const STATIC_GETTER = 'static getter';
  const PROPERTY_ASSIGNMENT = 'property assignment';
  const POSITION_SETTINGS = [STATIC_PUBLIC_FIELD, STATIC_GETTER, PROPERTY_ASSIGNMENT];
  
  // ------------------------------------------------------------------------------
  // Rule messages
  // ------------------------------------------------------------------------------
  const ERROR_MESSAGES = {
    [STATIC_PUBLIC_FIELD]: 'notStaticClassProp',
    [STATIC_GETTER]: 'notGetterClassFunc',
    [PROPERTY_ASSIGNMENT]: 'declareOutsideClass'
  };
  
  // ------------------------------------------------------------------------------
  // Properties to check
  // ------------------------------------------------------------------------------
  const propertiesToCheck = {
    propTypes: propsUtil.isPropTypesDeclaration,
    defaultProps: propsUtil.isDefaultPropsDeclaration,
    childContextTypes: propsUtil.isChildContextTypesDeclaration,
    contextTypes: propsUtil.isContextTypesDeclaration,
    contextType: propsUtil.isContextTypeDeclaration,
    displayName: (node) => propsUtil.isDisplayNameDeclaration(astUtil.getPropertyNameNode(node))
  };
  
  const classProperties = Object.keys(propertiesToCheck);
  const schemaProperties = fromEntries(classProperties.map((property) => [property, {enum: POSITION_SETTINGS}]));
  
  // ------------------------------------------------------------------------------
  // Rule Definition
  // ------------------------------------------------------------------------------
  
  const messages = {
    notStaticClassProp: '\'{{name}}\' should be declared as a static class property.',
    notGetterClassFunc: '\'{{name}}\' should be declared as a static getter class function.',
    declareOutsideClass: '\'{{name}}\' should be declared outside the class body.'
  };
  
  module.exports = {
    meta: {
      docs: {
        description: 'Defines where React component static properties should be positioned.',
        category: 'Stylistic Issues',
        recommended: false,
        url: docsUrl('static-property-placement')
      },
      fixable: null, // or 'code' or 'whitespace'
  
      messages,
  
      schema: [
        {enum: POSITION_SETTINGS},
        {
          type: 'object',
          properties: schemaProperties,
          additionalProperties: false
        }
      ]
    },
  
    create: Components.detect((context, components, utils) => {
      // variables should be defined here
      const options = context.options;
      const defaultCheckType = options[0] || STATIC_PUBLIC_FIELD;
      const hasAdditionalConfig = options.length > 1;
      const additionalConfig = hasAdditionalConfig ? options[1] : {};
  
      // Set config
      const config = fromEntries(classProperties.map((property) => [
        property,
        additionalConfig[property] || defaultCheckType
      ]));
  
      // ----------------------------------------------------------------------
      // Helpers
      // ----------------------------------------------------------------------
  
      /**
        * Checks if we are declaring context in class
        * @returns {Boolean} True if we are declaring context in class, false if not.
       */
      function isContextInClass() {
        let blockNode;
        let scope = context.getScope();
        while (scope) {
          blockNode = scope.block;
          if (blockNode && blockNode.type === 'ClassDeclaration') {
            return true;
          }
          scope = scope.upper;
        }
  
        return false;
      }
  
      /**
       * Check if we should report this property node
       * @param {ASTNode} node
       * @param {string} expectedRule
       */
      function reportNodeIncorrectlyPositioned(node, expectedRule) {
        // Detect if this node is an expected property declaration adn return the property name
        const name = classProperties.find((propertyName) => {
          if (propertiesToCheck[propertyName](node)) {
            return !!propertyName;
          }
  
          return false;
        });
  
        // If name is set but the configured rule does not match expected then report error
        if (name && config[name] !== expectedRule) {
          const messageId = ERROR_MESSAGES[config[name]];
          report(context, messages[messageId], messageId, {
            node,
            data: {name}
          });
        }
      }
  
      // ----------------------------------------------------------------------
      // Public
      // ----------------------------------------------------------------------
      return {
        ClassProperty: (node) => {
          if (!utils.getParentES6Component()) {
            return;
          }
  
          reportNodeIncorrectlyPositioned(node, STATIC_PUBLIC_FIELD);
        },
  
        MemberExpression: (node) => {
          // If definition type is undefined then it must not be a defining expression or if the definition is inside a
          // class body then skip this node.
          const right = node.parent.right;
          if (!right || right.type === 'undefined' || isContextInClass()) {
            return;
          }
  
          // Get the related component
          const relatedComponent = utils.getRelatedComponent(node);
  
          // If the related component is not an ES6 component then skip this node
          if (!relatedComponent || !utils.isES6Component(relatedComponent.node)) {
            return;
          }
  
          // Report if needed
          reportNodeIncorrectlyPositioned(node, PROPERTY_ASSIGNMENT);
        },
  
        MethodDefinition: (node) => {
          // If the function is inside a class and is static getter then check if correctly positioned
          if (utils.getParentES6Component() && node.static && node.kind === 'get') {
            // Report error if needed
            reportNodeIncorrectlyPositioned(node, STATIC_GETTER);
          }
        }
      };
    })
  };
  