content: /**
   * @fileoverview Common propTypes detection functionality.
   */
  
  'use strict';
  
  const flatMap = require('array.prototype.flatmap');
  
  const annotations = require('./annotations');
  const propsUtil = require('./props');
  const variableUtil = require('./variable');
  const testFlowVersion = require('./version').testFlowVersion;
  const propWrapperUtil = require('./propWrapper');
  const astUtil = require('./ast');
  const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
  
  /**
   * Check if node is function type.
   * @param {ASTNode} node
   * @returns {Boolean}
   */
  function isFunctionType(node) {
    if (!node) return false;
    const nodeType = node.type;
    return nodeType === 'FunctionDeclaration'
      || nodeType === 'FunctionExpression'
      || nodeType === 'ArrowFunctionExpression';
  }
  
  /**
   * Checks if we are declaring a props as a generic type in a flow-annotated class.
   *
   * @param {ASTNode} node  the AST node being checked.
   * @returns {Boolean} True if the node is a class with generic prop types, false if not.
   */
  function isSuperTypeParameterPropsDeclaration(node) {
    if (node && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) {
      if (node.superTypeParameters && node.superTypeParameters.params.length > 0) {
        return true;
      }
    }
    return false;
  }
  
  /**
   * Iterates through a properties node, like a customized forEach.
   * @param {Object} context Array of properties to iterate.
   * @param {Object[]} properties Array of properties to iterate.
   * @param {Function} fn Function to call on each property, receives property key
      and property value. (key, value) => void
    * @param {Function} [handleSpreadFn] Function to call on each ObjectTypeSpreadProperty, receives the
      argument
   */
  function iterateProperties(context, properties, fn, handleSpreadFn) {
    if (properties && properties.length && typeof fn === 'function') {
      for (let i = 0, j = properties.length; i < j; i++) {
        const node = properties[i];
        const key = astUtil.getKeyValue(context, node);
  
        if (node.type === 'ObjectTypeSpreadProperty' && typeof handleSpreadFn === 'function') {
          handleSpreadFn(node.argument);
        }
  
        const value = node.value;
        fn(key, value, node);
      }
    }
  }
  
  /**
   * Checks if a node is inside a class body.
   *
   * @param {ASTNode} node  the AST node being checked.
   * @returns {Boolean} True if the node has a ClassBody ancestor, false if not.
   */
  function isInsideClassBody(node) {
    let parent = node.parent;
    while (parent) {
      if (parent.type === 'ClassBody') {
        return true;
      }
      parent = parent.parent;
    }
    return false;
  }
  
  function startWithCapitalizedLetter(node) {
    return (
      node.parent.type === 'VariableDeclarator'
      && !isFirstLetterCapitalized(node.parent.id.name)
    );
  }
  
  module.exports = function propTypesInstructions(context, components, utils) {
    // Used to track the type annotations in scope.
    // Necessary because babel's scopes do not track type annotations.
    let stack = null;
  
    const classExpressions = [];
    const defaults = { customValidators: [] };
    const configuration = Object.assign({}, defaults, context.options[0] || {});
    const customValidators = configuration.customValidators;
    const allowedGenericTypes = new Set(['forwardRef', 'ForwardRefRenderFunction', 'VFC', 'VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']);
    const genericTypeParamIndexWherePropsArePresent = {
      ForwardRefRenderFunction: 1,
      forwardRef: 1,
      VoidFunctionComponent: 0,
      PropsWithChildren: 0,
      SFC: 0,
      StatelessComponent: 0,
      FunctionComponent: 0,
      FC: 0,
    };
    const genericReactTypesImport = new Set();
    // import { FC as X } from 'react' -> localToImportedMap = { x: FC }
    const localToImportedMap = {};
  
    /**
     * Returns the full scope.
     * @returns {Object} The whole scope.
     */
    function typeScope() {
      return stack[stack.length - 1];
    }
  
    /**
     * Gets a node from the scope.
     * @param {string} key The name of the identifier to access.
     * @returns {ASTNode} The ASTNode associated with the given identifier.
     */
    function getInTypeScope(key) {
      return stack[stack.length - 1][key];
    }
  
    /**
     * Sets the new value in the scope.
     * @param {string} key The name of the identifier to access
     * @param {ASTNode} value The new value for the identifier.
     * @returns {ASTNode} The ASTNode associated with the given identifier.
     */
    function setInTypeScope(key, value) {
      stack[stack.length - 1][key] = value;
      return value;
    }
  
    /**
     * Checks if prop should be validated by plugin-react-proptypes
     * @param {String} validator Name of validator to check.
     * @returns {Boolean} True if validator should be checked by custom validator.
     */
    function hasCustomValidator(validator) {
      return customValidators.indexOf(validator) !== -1;
    }
  
    /* eslint-disable no-use-before-define */
    /** @type {TypeDeclarationBuilders} */
    const typeDeclarationBuilders = {
      GenericTypeAnnotation(annotation, parentName, seen) {
        if (getInTypeScope(annotation.id.name)) {
          return buildTypeAnnotationDeclarationTypes(getInTypeScope(annotation.id.name), parentName, seen);
        }
        return {};
      },
  
      ObjectTypeAnnotation(annotation, parentName, seen) {
        let containsUnresolvedObjectTypeSpread = false;
        let containsSpread = false;
        const containsIndexers = Boolean(annotation.indexers && annotation.indexers.length);
        const shapeTypeDefinition = {
          type: 'shape',
          children: {},
        };
        iterateProperties(
          context,
          annotation.properties,
          (childKey, childValue, propNode) => {
            const fullName = [parentName, childKey].join('.');
            if (childKey || childValue) {
              const types = buildTypeAnnotationDeclarationTypes(childValue, fullName, seen);
              types.fullName = fullName;
              types.name = childKey;
              types.node = propNode;
              types.isRequired = !childValue.optional;
              shapeTypeDefinition.children[childKey] = types;
            }
          },
          (spreadNode) => {
            const key = astUtil.getKeyValue(context, spreadNode);
            const types = buildTypeAnnotationDeclarationTypes(spreadNode, key, seen);
            if (!types.children) {
              containsUnresolvedObjectTypeSpread = true;
            } else {
              Object.assign(shapeTypeDefinition, types.children);
            }
            containsSpread = true;
          }
        );
  
        // Mark if this shape has spread or an indexer. We will know to consider all props from this shape as having propTypes,
        // but still have the ability to detect unused children of this shape.
        shapeTypeDefinition.containsUnresolvedSpread = containsUnresolvedObjectTypeSpread;
        shapeTypeDefinition.containsIndexers = containsIndexers;
        // Deprecated: containsSpread is not used anymore in the codebase, ensure to keep API backward compatibility
        shapeTypeDefinition.containsSpread = containsSpread;
  
        return shapeTypeDefinition;
      },
  
      UnionTypeAnnotation(annotation, parentName, seen) {
        /** @type {UnionTypeDefinition} */
        const unionTypeDefinition = {
          type: 'union',
          children: annotation.types.map((type) => buildTypeAnnotationDeclarationTypes(type, parentName, seen)),
        };
        if (unionTypeDefinition.children.length === 0) {
          // no complex type found, simply accept everything
          return {};
        }
        return unionTypeDefinition;
      },
  
      ArrayTypeAnnotation(annotation, parentName, seen) {
        const fullName = [parentName, '*'].join('.');
        const child = buildTypeAnnotationDeclarationTypes(annotation.elementType, fullName, seen);
        child.fullName = fullName;
        child.name = '__ANY_KEY__';
        child.node = annotation;
        return {
          type: 'object',
          children: {
            __ANY_KEY__: child,
          },
        };
      },
    };
    /* eslint-enable no-use-before-define */
  
    /**
     * Resolve the type annotation for a given node.
     * Flow annotations are sometimes wrapped in outer `TypeAnnotation`
     * and `NullableTypeAnnotation` nodes which obscure the annotation we're
     * interested in.
     * This method also resolves type aliases where possible.
     *
     * @param {ASTNode} node The annotation or a node containing the type annotation.
     * @returns {ASTNode} The resolved type annotation for the node.
     */
    function resolveTypeAnnotation(node) {
      let annotation = (node.left && node.left.typeAnnotation) || node.typeAnnotation || node;
      while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
        annotation = annotation.typeAnnotation;
      }
      if (annotation.type === 'GenericTypeAnnotation' && getInTypeScope(annotation.id.name)) {
        return getInTypeScope(annotation.id.name);
      }
      return annotation;
    }
  
    /**
     * Creates the representation of the React props type annotation for the component.
     * The representation is used to verify nested used properties.
     * @param {ASTNode} annotation Type annotation for the props class property.
     * @param {String} parentName
     * @param {Set<ASTNode>} [seen]
     * @return {Object} The representation of the declaration, empty object means
     *    the property is declared without the need for further analysis.
     */
    function buildTypeAnnotationDeclarationTypes(annotation, parentName, seen) {
      if (typeof seen === 'undefined') {
        // Keeps track of annotations we've already seen to
        // prevent problems with recursive types.
        seen = new Set();
      }
      if (seen.has(annotation)) {
        // This must be a recursive type annotation, so just accept anything.
        return {};
      }
      seen.add(annotation);
  
      if (annotation.type in typeDeclarationBuilders) {
        return typeDeclarationBuilders[annotation.type](annotation, parentName, seen);
      }
      return {};
    }
  
    /**
     * Marks all props found inside ObjectTypeAnnotation as declared.
     *
     * Modifies the declaredProperties object
     * @param {ASTNode} propTypes
     * @param {Object} declaredPropTypes
     * @returns {Boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
     */
    function declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes) {
      let ignorePropsValidation = false;
  
      iterateProperties(context, propTypes.properties, (key, value, propNode) => {
        if (!value) {
          ignorePropsValidation = ignorePropsValidation || propNode.type !== 'ObjectTypeSpreadProperty';
          return;
        }
  
        const types = buildTypeAnnotationDeclarationTypes(value, key);
        types.fullName = key;
        types.name = key;
        types.node = propNode;
        types.isRequired = !propNode.optional;
        declaredPropTypes[key] = types;
      }, (spreadNode) => {
        const key = astUtil.getKeyValue(context, spreadNode);
        const spreadAnnotation = getInTypeScope(key);
        if (!spreadAnnotation) {
          ignorePropsValidation = true;
        } else {
          const spreadIgnoreValidation = declarePropTypesForObjectTypeAnnotation(spreadAnnotation, declaredPropTypes);
          ignorePropsValidation = ignorePropsValidation || spreadIgnoreValidation;
        }
      });
  
      return ignorePropsValidation;
    }
  
    /**
     * Marks all props found inside IntersectionTypeAnnotation as declared.
     * Since InterSectionTypeAnnotations can be nested, this handles recursively.
     *
     * Modifies the declaredPropTypes object
     * @param {ASTNode} propTypes
     * @param {Object} declaredPropTypes
     * @returns {Boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
     */
    function declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes) {
      return propTypes.types.some((annotation) => {
        if (annotation.type === 'ObjectTypeAnnotation') {
          return declarePropTypesForObjectTypeAnnotation(annotation, declaredPropTypes);
        }
  
        if (annotation.type === 'UnionTypeAnnotation') {
          return true;
        }
  
        // Type can't be resolved
        if (!annotation.id) {
          return true;
        }
  
        const typeNode = getInTypeScope(annotation.id.name);
  
        if (!typeNode) {
          return true;
        }
        if (typeNode.type === 'IntersectionTypeAnnotation') {
          return declarePropTypesForIntersectionTypeAnnotation(typeNode, declaredPropTypes);
        }
  
        return declarePropTypesForObjectTypeAnnotation(typeNode, declaredPropTypes);
      });
    }
  
    /**
     * Resolve node of type Identifier when building declaration types.
     * @param {ASTNode} node
     * @param {Function} callback called with the resolved value only if resolved.
     */
    function resolveValueForIdentifierNode(node, callback) {
      if (
        node
        && node.type === 'Identifier'
      ) {
        const scope = context.getScope();
        const identVariable = scope.variableScope.variables.find(
          (variable) => variable.name === node.name
        );
        if (identVariable) {
          const definition = identVariable.defs[identVariable.defs.length - 1];
          callback(definition.node.init);
        }
      }
    }
  
    /**
     * Creates the representation of the React propTypes for the component.
     * The representation is used to verify nested used properties.
     * @param {ASTNode} value Node of the PropTypes for the desired property
     * @param {string} parentName
     * @return {Object} The representation of the declaration, empty object means
     *    the property is declared without the need for further analysis.
     */
    function buildReactDeclarationTypes(value, parentName) {
      if (
        value
        && value.callee
        && value.callee.object
        && hasCustomValidator(value.callee.object.name)
      ) {
        return {};
      }
  
      let identNodeResolved = false;
      // Resolve identifier node for cases where isRequired is set in
      // the variable declaration or not at all.
      // const variableType = PropTypes.shape({ foo: ... }).isRequired
      // propTypes = {
      //   example: variableType
      // }
      // --------
      // const variableType = PropTypes.shape({ foo: ... })
      // propTypes = {
      //   example: variableType
      // }
      resolveValueForIdentifierNode(value, (newValue) => {
        identNodeResolved = true;
        value = newValue;
      });
  
      if (
        value
        && value.type === 'MemberExpression'
        && value.property
        && value.property.name
        && value.property.name === 'isRequired'
      ) {
        value = value.object;
      }
  
      // Resolve identifier node for cases where isRequired is set in
      // the prop types.
      // const variableType = PropTypes.shape({ foo: ... })
      // propTypes = {
      //   example: variableType.isRequired
      // }
      if (!identNodeResolved) {
        resolveValueForIdentifierNode(value, (newValue) => {
          value = newValue;
        });
      }
  
      // Verify PropTypes that are functions
      if (
        value
        && value.type === 'CallExpression'
        && value.callee
        && value.callee.property
        && value.callee.property.name
        && value.arguments
        && value.arguments.length > 0
      ) {
        const callName = value.callee.property.name;
        const argument = value.arguments[0];
        switch (callName) {
          case 'shape':
          case 'exact': {
            if (argument.type !== 'ObjectExpression') {
              // Invalid proptype or cannot analyse statically
              return {};
            }
            const shapeTypeDefinition = {
              type: callName,
              children: {},
            };
            iterateProperties(context, argument.properties, (childKey, childValue, propNode) => {
              if (childValue) { // skip spread propTypes
                const fullName = [parentName, childKey].join('.');
                const types = buildReactDeclarationTypes(childValue, fullName);
                types.fullName = fullName;
                types.name = childKey;
                types.node = propNode;
                shapeTypeDefinition.children[childKey] = types;
              }
            });
            return shapeTypeDefinition;
          }
          case 'arrayOf':
          case 'objectOf': {
            const fullName = [parentName, '*'].join('.');
            const child = buildReactDeclarationTypes(argument, fullName);
            child.fullName = fullName;
            child.name = '__ANY_KEY__';
            child.node = argument;
            return {
              type: 'object',
              children: {
                __ANY_KEY__: child,
              },
            };
          }
          case 'oneOfType': {
            if (
              !argument.elements
              || !argument.elements.length
            ) {
              // Invalid proptype or cannot analyse statically
              return {};
            }
  
            /** @type {UnionTypeDefinition} */
            const unionTypeDefinition = {
              type: 'union',
              children: argument.elements.map((element) => buildReactDeclarationTypes(element, parentName)),
            };
            if (unionTypeDefinition.children.length === 0) {
              // no complex type found, simply accept everything
              return {};
            }
            return unionTypeDefinition;
          }
          default:
            return {};
        }
      }
      // Unknown property or accepts everything (any, object, ...)
      return {};
    }
  
    function isValidReactGenericTypeAnnotation(annotation) {
      if (annotation.typeName) {
        if (annotation.typeName.name) { // if FC<Props>
          const typeName = annotation.typeName.name;
          if (!genericReactTypesImport.has(typeName)) {
            return false;
          }
        } else if (annotation.typeName.right.name) { // if React.FC<Props>
          const right = annotation.typeName.right.name;
          const left = annotation.typeName.left.name;
  
          if (!genericReactTypesImport.has(left) || !allowedGenericTypes.has(right)) {
            return false;
          }
        }
      }
      return true;
    }
  
    /**
     * Returns the left most typeName of a node, e.g: FC<Props>, React.FC<Props>
     * The representation is used to verify nested used properties.
     * @param {ASTNode} node
     * @return {string | undefined}
     */
    function getLeftMostTypeName(node) {
      if (node.name) return node.name;
      if (node.left) return getLeftMostTypeName(node.left);
    }
  
    function getRightMostTypeName(node) {
      if (node.name) return node.name;
      if (node.right) return getRightMostTypeName(node.right);
    }
  
    /**
     * Returns true if the node is either a interface or type alias declaration
     * @param {ASTNode} node
     * @return {boolean}
     */
    function filterInterfaceOrTypeAlias(node) {
      return (
        astUtil.isTSInterfaceDeclaration(node) || astUtil.isTSTypeAliasDeclaration(node)
      );
    }
  
    /**
     * Returns true if the interface or type alias declaration node name matches the type-name str
     * @param {ASTNode} node
     * @param {string} typeName
     * @return {boolean}
     */
    function filterInterfaceOrAliasByName(node, typeName) {
      return (
        (node.id && node.id.name === typeName)
        || (node.declaration && node.declaration.id && node.declaration.id.name === typeName)
      );
    }
  
    class DeclarePropTypesForTSTypeAnnotation {
      constructor(propTypes, declaredPropTypes) {
        this.propTypes = propTypes;
        this.declaredPropTypes = declaredPropTypes;
        this.foundDeclaredPropertiesList = [];
        this.referenceNameMap = new Set();
        this.sourceCode = context.getSourceCode();
        this.shouldIgnorePropTypes = false;
        this.visitTSNode(this.propTypes);
        this.endAndStructDeclaredPropTypes();
      }
  
      /**
       * The node will be distribute to different function.
       * @param {ASTNode} node
       */
      visitTSNode(node) {
        if (!node) return;
        if (astUtil.isTSTypeAnnotation(node)) {
          const typeAnnotation = node.typeAnnotation;
          this.visitTSNode(typeAnnotation);
        } else if (astUtil.isTSTypeReference(node)) {
          this.searchDeclarationByName(node);
        } else if (astUtil.isTSInterfaceHeritage(node)) {
          this.searchDeclarationByName(node);
        } else if (astUtil.isTSTypeLiteral(node)) {
          // Check node is an object literal
          if (Array.isArray(node.members)) {
            this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(node.members);
          }
        } else if (astUtil.isTSIntersectionType(node)) {
          this.convertIntersectionTypeToPropTypes(node);
        } else if (astUtil.isTSParenthesizedType(node)) {
          const typeAnnotation = node.typeAnnotation;
          this.visitTSNode(typeAnnotation);
        } else if (astUtil.isTSTypeParameterInstantiation(node)) {
          if (Array.isArray(node.params)) {
            node.params.forEach(this.visitTSNode, this);
          }
        } else {
          this.shouldIgnorePropTypes = true;
        }
      }
  
      /**
       * Search TSInterfaceDeclaration or TSTypeAliasDeclaration,
       * by using TSTypeReference and TSInterfaceHeritage name.
       * @param {ASTNode} node
       */
      searchDeclarationByName(node) {
        let typeName;
        if (astUtil.isTSTypeReference(node)) {
          typeName = node.typeName.name;
          const leftMostName = getLeftMostTypeName(node.typeName);
          const shouldTraverseTypeParams = genericReactTypesImport.has(leftMostName);
          if (shouldTraverseTypeParams && node.typeParameters && node.typeParameters.length !== 0) {
            // All react Generic types are derived from:
            // type PropsWithChildren<P> = P & { children?: ReactNode | undefined }
            // So we should construct an optional children prop
            this.shouldSpecifyOptionalChildrenProps = true;
  
            const rightMostName = getRightMostTypeName(node.typeName);
            const importedName = localToImportedMap[rightMostName];
            const idx = genericTypeParamIndexWherePropsArePresent[
              leftMostName !== rightMostName ? rightMostName : importedName
            ];
            const nextNode = node.typeParameters.params[idx];
            this.visitTSNode(nextNode);
            return;
          }
        } else if (astUtil.isTSInterfaceHeritage(node)) {
          if (!node.expression && node.id) {
            typeName = node.id.name;
          } else {
            typeName = node.expression.name;
          }
        }
        if (!typeName) {
          this.shouldIgnorePropTypes = true;
          return;
        }
        if (typeName === 'ReturnType') {
          this.convertReturnTypeToPropTypes(node);
          return;
        }
        // Prevent recursive inheritance will cause maximum callstack.
        if (this.referenceNameMap.has(typeName)) {
          this.shouldIgnorePropTypes = true;
          return;
        }
        // Add typeName to Set and consider it as traversed.
        this.referenceNameMap.add(typeName);
  
        /**
         * From line 577 to line 581, and line 588 to line 590 are trying to handle typescript-eslint-parser
         * Need to be deprecated after remove typescript-eslint-parser support.
         */
        const candidateTypes = this.sourceCode.ast.body.filter((item) => astUtil.isTSTypeDeclaration(item));
  
        const declarations = flatMap(
          candidateTypes,
          (type) => type.declarations || (type.declaration && type.declaration.declarations) || type.declaration);
  
        // we tried to find either an interface or a type with the TypeReference name
        const typeDeclaration = declarations.filter((dec) => dec.id.name === typeName);
  
        const interfaceDeclarations = this.sourceCode.ast.body
          .filter(filterInterfaceOrTypeAlias)
          .filter((item) => filterInterfaceOrAliasByName(item, typeName))
          .map((item) => (item.declaration || item));
  
        if (typeDeclaration.length !== 0) {
          typeDeclaration.map((t) => t.init || t.typeAnnotation).forEach(this.visitTSNode, this);
        } else if (interfaceDeclarations.length !== 0) {
          interfaceDeclarations.forEach(this.traverseDeclaredInterfaceOrTypeAlias, this);
        } else {
          this.shouldIgnorePropTypes = true;
        }
      }
  
      /**
       * Traverse TSInterfaceDeclaration and TSTypeAliasDeclaration
       * which retrieve from function searchDeclarationByName;
       * @param {ASTNode} node
       */
      traverseDeclaredInterfaceOrTypeAlias(node) {
        if (astUtil.isTSInterfaceDeclaration(node)) {
          // Handle TSInterfaceDeclaration interface Props { name: string, id: number}, should put in properties list directly;
          this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(node.body.body);
        }
        // Handle TSTypeAliasDeclaration type Props = {name:string}
        if (astUtil.isTSTypeAliasDeclaration(node)) {
          const typeAnnotation = node.typeAnnotation;
          this.visitTSNode(typeAnnotation);
        }
        if (Array.isArray(node.extends)) {
          node.extends.forEach(this.visitTSNode, this);
          // This line is trying to handle typescript-eslint-parser
          // typescript-eslint-parser extension is name as heritage
        } else if (Array.isArray(node.heritage)) {
          node.heritage.forEach(this.visitTSNode, this);
        }
      }
  
      convertIntersectionTypeToPropTypes(node) {
        if (!node) return;
        if (Array.isArray(node.types)) {
          node.types.forEach(this.visitTSNode, this);
        } else {
          this.shouldIgnorePropTypes = true;
        }
      }
  
      convertReturnTypeToPropTypes(node) {
        // ReturnType<T> should always have one parameter
        if (node.typeParameters) {
          if (node.typeParameters.params.length === 1) {
            let returnType = node.typeParameters.params[0];
            // This line is trying to handle typescript-eslint-parser
            // typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference
            if (astUtil.isTSTypeReference(returnType)) {
              returnType = returnType.typeName;
            }
            // Handle ReturnType<typeof mapStateToProps>
            if (astUtil.isTSTypeQuery(returnType)) {
              const returnTypeFunction = flatMap(this.sourceCode.ast.body
                .filter((item) => item.type === 'VariableDeclaration'
                  && item.declarations.find((dec) => dec.id.name === returnType.exprName.name)
                ), (type) => type.declarations).map((dec) => dec.init);
  
              if (Array.isArray(returnTypeFunction)) {
                if (returnTypeFunction.length === 0) {
                  // Cannot find identifier in current scope. It might be an exported type.
                  this.shouldIgnorePropTypes = true;
                  return;
                }
                returnTypeFunction.forEach((func) => {
                  if (isFunctionType(func)) {
                    let res = func.body;
                    if (res.type === 'BlockStatement') {
                      res = astUtil.findReturnStatement(func);
                      if (res) {
                        res = res.argument;
                      }
                    }
                    switch (res.type) {
                      case 'ObjectExpression':
                        iterateProperties(context, res.properties, (key, value, propNode) => {
                          if (propNode && propNode.argument && propNode.argument.type === 'CallExpression') {
                            if (propNode.argument.typeParameters) {
                              this.visitTSNode(propNode.argument.typeParameters);
                            } else {
                              // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
                              this.shouldIgnorePropTypes = true;
                              return;
                            }
                          }
                          if (!value) {
                            this.shouldIgnorePropTypes = true;
                            return;
                          }
                          const types = buildReactDeclarationTypes(value, key);
                          types.fullName = key;
                          types.name = key;
                          types.node = propNode;
                          types.isRequired = propsUtil.isRequiredPropType(value);
                          this.declaredPropTypes[key] = types;
                        });
                        break;
                      case 'CallExpression':
                        if (res.typeParameters) {
                          this.visitTSNode(res.typeParameters);
                        } else {
                          // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
                          this.shouldIgnorePropTypes = true;
                        }
                        break;
                      default:
                    }
                  }
                });
                return;
              }
            }
            // Handle ReturnType<()=>returnType>
            if (astUtil.isTSFunctionType(returnType)) {
              if (astUtil.isTSTypeAnnotation(returnType.returnType)) {
                this.visitTSNode(returnType.returnType);
                return;
              }
              // This line is trying to handle typescript-eslint-parser
              // typescript-eslint-parser TSFunction name returnType as typeAnnotation
              if (astUtil.isTSTypeAnnotation(returnType.typeAnnotation)) {
                this.visitTSNode(returnType.typeAnnotation);
                return;
              }
            }
          }
        }
        this.shouldIgnorePropTypes = true;
      }
  
      endAndStructDeclaredPropTypes() {
        if (this.shouldSpecifyOptionalChildrenProps) {
          this.declaredPropTypes.children = {
            fullName: 'children',
            name: 'children',
            isRequired: false,
          };
        }
        this.foundDeclaredPropertiesList.forEach((tsInterfaceBody) => {
          if (tsInterfaceBody && (tsInterfaceBody.type === 'TSPropertySignature' || tsInterfaceBody.type === 'TSMethodSignature')) {
            let accessor = 'name';
            if (tsInterfaceBody.key.type === 'Literal') {
              if (typeof tsInterfaceBody.key.value === 'number') {
                accessor = 'raw';
              } else {
                accessor = 'value';
              }
            }
            this.declaredPropTypes[tsInterfaceBody.key[accessor]] = {
              fullName: tsInterfaceBody.key[accessor],
              name: tsInterfaceBody.key[accessor],
              node: tsInterfaceBody,
              isRequired: !tsInterfaceBody.optional,
            };
          }
        });
      }
    }
  
    /**
     * Mark a prop type as declared
     * @param {ASTNode} node The AST node being checked.
     * @param {ASTNode} propTypes The AST node containing the proptypes
     */
    function markPropTypesAsDeclared(node, propTypes) {
      let componentNode = node;
      while (componentNode && !components.get(componentNode)) {
        componentNode = componentNode.parent;
      }
      const component = components.get(componentNode);
      let declaredPropTypes = (component && component.declaredPropTypes) || {};
      let ignorePropsValidation = (component && component.ignorePropsValidation) || false;
      switch (propTypes && propTypes.type) {
        case 'ObjectTypeAnnotation':
          ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes);
          break;
        case 'ObjectExpression':
          iterateProperties(context, propTypes.properties, (key, value, propNode) => {
            if (!value) {
              ignorePropsValidation = true;
              return;
            }
            const types = buildReactDeclarationTypes(value, key);
            types.fullName = key;
            types.name = key;
            types.node = propNode;
            types.isRequired = propsUtil.isRequiredPropType(value);
            declaredPropTypes[key] = types;
          });
          break;
        case 'MemberExpression': {
          let curDeclaredPropTypes = declaredPropTypes;
          // Walk the list of properties, until we reach the assignment
          // ie: ClassX.propTypes.a.b.c = ...
          while (
            propTypes
            && propTypes.parent
            && propTypes.parent.type !== 'AssignmentExpression'
            && propTypes.property
            && curDeclaredPropTypes
          ) {
            const propName = propTypes.property.name;
            if (propName in curDeclaredPropTypes) {
              curDeclaredPropTypes = curDeclaredPropTypes[propName].children;
              propTypes = propTypes.parent;
            } else {
              // This will crash at runtime because we haven't seen this key before
              // stop this and do not declare it
              propTypes = null;
            }
          }
          if (propTypes && propTypes.parent && propTypes.property) {
            if (!(propTypes === propTypes.parent.left && propTypes.parent.left.object)) {
              ignorePropsValidation = true;
              break;
            }
            const parentProp = context.getSource(propTypes.parent.left.object).replace(/^.*\.propTypes\./, '');
            const types = buildReactDeclarationTypes(
              propTypes.parent.right,
              parentProp
            );
  
            types.name = propTypes.property.name;
            types.fullName = [parentProp, propTypes.property.name].join('.');
            types.node = propTypes.parent;
            types.isRequired = propsUtil.isRequiredPropType(propTypes.parent.right);
            curDeclaredPropTypes[propTypes.property.name] = types;
          } else {
            let isUsedInPropTypes = false;
            let n = propTypes;
            while (n) {
              if (((n.type === 'AssignmentExpression') && propsUtil.isPropTypesDeclaration(n.left))
                || ((n.type === 'ClassProperty' || n.type === 'PropertyDefinition' || n.type === 'Property') && propsUtil.isPropTypesDeclaration(n))) {
                // Found a propType used inside of another propType. This is not considered usage, we'll still validate
                // this component.
                isUsedInPropTypes = true;
                break;
              }
              n = n.parent;
            }
            if (!isUsedInPropTypes) {
              ignorePropsValidation = true;
            }
          }
          break;
        }
        case 'Identifier': {
          const variablesInScope = variableUtil.variablesInScope(context);
          const firstMatchingVariable = variablesInScope
            .find((variableInScope) => variableInScope.name === propTypes.name);
          if (firstMatchingVariable) {
            const defInScope = firstMatchingVariable.defs[firstMatchingVariable.defs.length - 1];
            markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init);
            return;
          }
          ignorePropsValidation = true;
          break;
        }
        case 'CallExpression': {
          if (
            propWrapperUtil.isPropWrapperFunction(
              context,
              context.getSourceCode().getText(propTypes.callee)
            )
            && propTypes.arguments && propTypes.arguments[0]
          ) {
            markPropTypesAsDeclared(node, propTypes.arguments[0]);
            return;
          }
          break;
        }
        case 'IntersectionTypeAnnotation':
          ignorePropsValidation = declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes);
          break;
        case 'GenericTypeAnnotation':
          if (propTypes.id.name === '$ReadOnly') {
            ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(
              propTypes.typeParameters.params[0],
              declaredPropTypes
            );
          } else {
            ignorePropsValidation = true;
          }
          break;
        case 'TSTypeReference':
        case 'TSTypeAnnotation': {
          const tsTypeAnnotation = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes);
          ignorePropsValidation = tsTypeAnnotation.shouldIgnorePropTypes;
          declaredPropTypes = tsTypeAnnotation.declaredPropTypes;
        }
          break;
        case null:
          break;
        default:
          ignorePropsValidation = true;
          break;
      }
  
      components.set(node, {
        declaredPropTypes,
        ignorePropsValidation,
      });
    }
  
    /**
     * @param {ASTNode} node We expect either an ArrowFunctionExpression,
     *   FunctionDeclaration, or FunctionExpression
     */
    function markAnnotatedFunctionArgumentsAsDeclared(node) {
      if (!node.params || !node.params.length) {
        return;
      }
  
      if (
        node.parent
        && node.parent.callee
        && node.parent.typeParameters
        && node.parent.typeParameters.params
        && (
          node.parent.callee.name === 'forwardRef' || (
            node.parent.callee.object
            && node.parent.callee.property
            && node.parent.callee.object.name === 'React'
            && node.parent.callee.property.name === 'forwardRef'
          )
        )
      ) {
        const propTypes = node.parent.typeParameters.params[1];
        const declaredPropTypes = {};
        const obj = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes);
        components.set(node, {
          declaredPropTypes: obj.declaredPropTypes,
          ignorePropsValidation: false,
        });
        return;
      }
  
      const siblingIdentifier = node.parent && node.parent.id;
      const siblingHasTypeAnnotation = siblingIdentifier && siblingIdentifier.typeAnnotation;
      const isNodeAnnotated = annotations.isAnnotatedFunctionPropsDeclaration(node, context);
  
      if (!isNodeAnnotated && !siblingHasTypeAnnotation) {
        return;
      }
  
      // https://github.com/yannickcr/eslint-plugin-react/issues/2784
      if (isInsideClassBody(node) && !astUtil.isFunction(node)) {
        return;
      }
  
      // Should ignore function that not return JSXElement
      if (!utils.isReturningJSXOrNull(node) || startWithCapitalizedLetter(node)) {
        return;
      }
  
      if (isNodeAnnotated) {
        const param = node.params[0];
        if (param.typeAnnotation && param.typeAnnotation.typeAnnotation && param.typeAnnotation.typeAnnotation.type === 'UnionTypeAnnotation') {
          param.typeAnnotation.typeAnnotation.types.forEach((annotation) => {
            if (annotation.type === 'GenericTypeAnnotation') {
              markPropTypesAsDeclared(node, resolveTypeAnnotation(annotation));
            } else {
              markPropTypesAsDeclared(node, annotation);
            }
          });
        } else {
          markPropTypesAsDeclared(node, resolveTypeAnnotation(param));
        }
      } else {
        // implements what's discussed here: https://github.com/yannickcr/eslint-plugin-react/issues/2777#issuecomment-683944481
        const annotation = siblingIdentifier.typeAnnotation.typeAnnotation;
  
        if (
          annotation
          && annotation.type !== 'TSTypeReference'
          && annotation.typeParameters == null
        ) {
          return;
        }
  
        if (!isValidReactGenericTypeAnnotation(annotation)) return;
  
        markPropTypesAsDeclared(node, resolveTypeAnnotation(siblingIdentifier));
      }
    }
  
    /**
     * Resolve the type annotation for a given class declaration node with superTypeParameters.
     *
     * @param {ASTNode} node The annotation or a node containing the type annotation.
     * @returns {ASTNode} The resolved type annotation for the node.
     */
    function resolveSuperParameterPropsType(node) {
      let propsParameterPosition;
      try {
        // Flow <=0.52 had 3 required TypedParameters of which the second one is the Props.
        // Flow >=0.53 has 2 optional TypedParameters of which the first one is the Props.
        propsParameterPosition = testFlowVersion(context, '>= 0.53.0') ? 0 : 1;
      } catch (e) {
        // In case there is no flow version defined, we can safely assume that when there are 3 Props we are dealing with version <= 0.52
        propsParameterPosition = node.superTypeParameters.params.length <= 2 ? 0 : 1;
      }
  
      let annotation = node.superTypeParameters.params[propsParameterPosition];
      while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
        annotation = annotation.typeAnnotation;
      }
  
      if (annotation && annotation.type === 'GenericTypeAnnotation' && getInTypeScope(annotation.id.name)) {
        return getInTypeScope(annotation.id.name);
      }
      return annotation;
    }
  
    /**
     * Checks if we are declaring a `props` class property with a flow type annotation.
     * @param {ASTNode} node The AST node being checked.
     * @returns {Boolean} True if the node is a type annotated props declaration, false if not.
     */
    function isAnnotatedClassPropsDeclaration(node) {
      if (node && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')) {
        const tokens = context.getFirstTokens(node, 2);
        if (
          node.typeAnnotation && (
            tokens[0].value === 'props'
            || (tokens[1] && tokens[1].value === 'props')
          )
        ) {
          return true;
        }
      }
      return false;
    }
  
    return {
      ClassExpression(node) {
        // TypeParameterDeclaration need to be added to typeScope in order to handle ClassExpressions.
        // This visitor is executed before TypeParameterDeclaration are scoped, therefore we postpone
        // processing class expressions until when the program exists.
        classExpressions.push(node);
      },
  
      ClassDeclaration(node) {
        if (isSuperTypeParameterPropsDeclaration(node)) {
          markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
        }
      },
  
      'ClassProperty, PropertyDefinition'(node) {
        if (isAnnotatedClassPropsDeclaration(node)) {
          markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
        } else if (propsUtil.isPropTypesDeclaration(node)) {
          markPropTypesAsDeclared(node, node.value);
        }
      },
  
      ObjectExpression(node) {
        // Search for the proptypes declaration
        node.properties.forEach((property) => {
          if (!propsUtil.isPropTypesDeclaration(property)) {
            return;
          }
          markPropTypesAsDeclared(node, property.value);
        });
      },
  
      FunctionExpression(node) {
        if (node.parent.type !== 'MethodDefinition') {
          markAnnotatedFunctionArgumentsAsDeclared(node);
        }
      },
  
      ImportDeclaration(node) {
        // parse `import ... from 'react`
        if (node.source.value === 'react') {
          node.specifiers.forEach((specifier) => {
            if (
              // handles import * as X from 'react'
              specifier.type === 'ImportNamespaceSpecifier'
              // handles import React from 'react'
              || specifier.type === 'ImportDefaultSpecifier'
            ) {
              genericReactTypesImport.add(specifier.local.name);
            }
  
            // handles import { FC } from 'react' or import { FC as X } from 'react'
            if (specifier.type === 'ImportSpecifier' && allowedGenericTypes.has(specifier.imported.name)) {
              genericReactTypesImport.add(specifier.local.name);
              localToImportedMap[specifier.local.name] = specifier.imported.name;
            }
          });
        }
      },
  
      FunctionDeclaration: markAnnotatedFunctionArgumentsAsDeclared,
  
      ArrowFunctionExpression: markAnnotatedFunctionArgumentsAsDeclared,
  
      MemberExpression(node) {
        if (propsUtil.isPropTypesDeclaration(node)) {
          const component = utils.getRelatedComponent(node);
          if (!component) {
            return;
          }
          try {
            markPropTypesAsDeclared(component.node, node.parent.right || node.parent);
          } catch (e) {
            if (e.constructor !== RangeError) { throw e; }
          }
        }
      },
  
      MethodDefinition(node) {
        if (!node.static || node.kind !== 'get' || !propsUtil.isPropTypesDeclaration(node)) {
          return;
        }
  
        let i = node.value.body.body.length - 1;
        for (; i >= 0; i--) {
          if (node.value.body.body[i].type === 'ReturnStatement') {
            break;
          }
        }
  
        if (i >= 0) {
          markPropTypesAsDeclared(node, node.value.body.body[i].argument);
        }
      },
  
      TypeAlias(node) {
        setInTypeScope(node.id.name, node.right);
      },
  
      TypeParameterDeclaration(node) {
        const identifier = node.params[0];
  
        if (identifier.typeAnnotation) {
          setInTypeScope(identifier.name, identifier.typeAnnotation.typeAnnotation);
        }
      },
  
      Program() {
        stack = [{}];
      },
  
      BlockStatement() {
        stack.push(Object.create(typeScope()));
      },
  
      'BlockStatement:exit'() {
        stack.pop();
      },
  
      'Program:exit'() {
        classExpressions.forEach((node) => {
          if (isSuperTypeParameterPropsDeclaration(node)) {
            markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
          }
        });
      },
    };
  };
  