Repository: yannickcr/eslint-plugin-react (js)

Issue:
False positive react/no-unused-prop-types
Hey guys,

While I was coding I noticed something odd regarding `react/no-unused-prop-types` rules.

For some unknown reason, I have that error on `renderStaticList` but not on `renderStaticItem` nor `renderCarousel`. To my eyes they look similar, but I may be wrong.

<img width="695" alt="Screenshot 2020-08-10 at 23 12 01" src="https://user-images.githubusercontent.com/8364345/89836558-ee524800-db5e-11ea-92f1-8f9b822ac65b.png">

`error  'item' PropType is defined but prop is never used  react/no-unused-prop-types`

Am I missing here something? Thank you 🙏 

Component TypeScript code:
```typescript
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import LightStatusBar from 'components/common/light-status-bar';
import SafeAreaViewWrapper from 'components/common/safe-area-view-wrapper';
import CarouselItem from 'components/common/card';
import { useNavigation } from '@react-navigation/native';
import { INavigation } from 'interfaces/INavigation';
import CarouselSection from 'components/discover/carousel-section';
import { IHome } from 'interfaces/IHome';
import Section from 'components/discover/section';
import { FlashImage } from 'components/discover/header/toggle/styles';
import { theme } from 'styles';
import { ListRenderItem } from 'react-native';
import { IPointOfSale } from 'interfaces/IPointOfSale';
import { IContent } from 'interfaces/IContent';
import { ICarousel } from 'interfaces/ICarousel';
import { HomeFlatList, CarouselFlatList, StaticFlatList } from './styles';

const Home: React.FC = () => {
    const [home, setHome] = useState<IHome>();

    useEffect(() => {
        const fetchData = async (): Promise<void> => {
            const result = await axios('/points-of-sale?type=home');

            setHome(result.data);
        };

        fetchData();
    }, []);

    const navigation = useNavigation();
    const onPress = (navigationProp: INavigation) => () => {
        navigation.navigate(navigationProp.screenName, {
            screen: navigationProp.stackName,
            params: {
                screen: navigationProp.nestedScreenName,
                params: navigationProp.params,
            },
        });
    };

    const renderCarousel: ListRenderItem<ICarousel | any> = ({
        item,
    }: {
        item: ICarousel;
    }) => (
        <Section
            icon={<FlashImage />}
            title={item.title}
            titleFontSize={theme.typography.FONT_SIZE_24}
        >
            <CarouselSection pointsOfSale={item.carousel} />
        </Section>
    );

    const carouselKeyExtractor = (item: ICarousel | any): string => item.title;

    const listHeaderComponent = (): React.ReactElement => (
        <CarouselFlatList
            data={home?.carousels}
            renderItem={renderCarousel}
            keyExtractor={carouselKeyExtractor}
        />
    );

    const renderStaticItem: ListRenderItem<IPointOfSale | any> = ({
        item,
    }: {
        item: IPointOfSale;
    }) => (
        <CarouselItem
            title={item.title}
            subtitle={item.subtitle}
            imagePath={item.imagePath}
            width={96}
            onPress={onPress(item.navigation)}
            key={item.title}
        />
    );

    const staticItemKeyExtractor = (item: IPointOfSale | any): string =>
        item.title;

    const renderStaticList: ListRenderItem<IContent | any> = ({
        item,
    }: {
        item: IContent;
    }) => (
        <Section
            icon={<FlashImage />}
            title={item.title}
            titleFontSize={theme.typography.FONT_SIZE_24}
        >
            <StaticFlatList
                data={item.pointsOfSale}
                renderItem={renderStaticItem}
                keyExtractor={staticItemKeyExtractor}
            />
        </Section>
    );

    const staticListKeyExtractor = (item: IContent | any): string => item.title;

    return (
        <SafeAreaViewWrapper>
            <LightStatusBar />
            <HomeFlatList
                ListHeaderComponent={listHeaderComponent}
                data={home?.static}
                renderItem={renderStaticList}
                keyExtractor={staticListKeyExtractor}
            />
        </SafeAreaViewWrapper>
    );
};

export default Home;
```

Pull request:
[Fix]: improve component declared props detection
Fixes #2752

Patch:
diff --git a/lib/util/Components.js b/lib/util/Components.js
index c68aa06e..71e400be 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -16,6 +16,7 @@ const propTypesUtil = require('./propTypes');
 const jsxUtil = require('./jsx');
 const usedPropTypesUtil = require('./usedPropTypes');
 const defaultPropsUtil = require('./defaultProps');
+const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
 
 function getId(node) {
   return node && node.range.join(':');
@@ -70,14 +71,6 @@ function isReturnsLogicalJSX(node, property, strict) {
     : (returnsLogicalJSXLeft || returnsLogicalJSXRight);
 }
 
-function isFirstLetterCapitalized(word) {
-  if (!word) {
-    return false;
-  }
-  const firstLetter = word.charAt(0);
-  return firstLetter.toUpperCase() === firstLetter;
-}
-
 const Lists = new WeakMap();
 
 /**
diff --git a/lib/util/isFirstLetterCapitalized.js b/lib/util/isFirstLetterCapitalized.js
new file mode 100644
index 00000000..f487489b
--- /dev/null
+++ b/lib/util/isFirstLetterCapitalized.js
@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ * Check if the first letter of a string is capitalized.
+ * @param {String} word String to check
+ * @returns {Boolean} True if first letter is capitalized.
+ */
+function isFirstLetterCapitalized(word) {
+  if (!word) {
+    return false;
+  }
+  const firstLetter = word.charAt(0);
+  return firstLetter.toUpperCase() === firstLetter;
+}
+
+module.exports = isFirstLetterCapitalized;
diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js
index e8168071..50179dd6 100644
--- a/lib/util/propTypes.js
+++ b/lib/util/propTypes.js
@@ -12,6 +12,7 @@ const variableUtil = require('./variable');
 const versionUtil = require('./version');
 const propWrapperUtil = require('./propWrapper');
 const astUtil = require('./ast');
+const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
 
 /**
  * Check if node is function type.
@@ -83,6 +84,13 @@ function isInsideClassBody(node) {
   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.
@@ -878,7 +886,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
     }
 
     // Should ignore function that not return JSXElement
-    if (!utils.isReturningJSXOrNull(node)) {
+    if (!utils.isReturningJSXOrNull(node) || startWithCapitalizedLetter(node)) {
       return;
     }
 


Test patch:
diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js
index e616972a..33f94a75 100644
--- a/tests/lib/rules/no-unused-prop-types.js
+++ b/tests/lib/rules/no-unused-prop-types.js
@@ -3689,6 +3689,80 @@ ruleTester.run('no-unused-prop-types', rule, {
         }
         `,
         parser: parsers.BABEL_ESLINT
+      },
+      {
+        code: `
+        const Home = () => {
+          const renderStaticList = ({
+              item,
+          }: {
+              item: IContent;
+          }) => (
+              <Section
+                  icon={<FlashImage />}
+                  title={item.title}
+                  titleFontSize={theme.typography.FONT_SIZE_24}
+              >
+                  <StaticFlatList
+                      data={item.pointsOfSale}
+                      renderItem={renderStaticItem}
+                      keyExtractor={staticItemKeyExtractor}
+                  />
+              </Section>
+          );
+
+          return (
+              <SafeAreaViewWrapper>
+                  <LightStatusBar />
+                  <HomeFlatList
+                      ListHeaderComponent={listHeaderComponent}
+                      data={home?.static}
+                      renderItem={renderStaticList}
+                      keyExtractor={staticListKeyExtractor}
+                  />
+              </SafeAreaViewWrapper>
+          );
+        };
+        `,
+        parser: parsers['@TYPESCRIPT_ESLINT']
+      },
+      {
+        code: `
+        const Home = () => {
+          const renderStaticList = function({
+              item,
+          }: {
+              item: IContent;
+          }) {
+            return (
+              <Section
+                  icon={<FlashImage />}
+                  title={item.title}
+                  titleFontSize={theme.typography.FONT_SIZE_24}
+              >
+                  <StaticFlatList
+                      data={item.pointsOfSale}
+                      renderItem={renderStaticItem}
+                      keyExtractor={staticItemKeyExtractor}
+                  />
+              </Section>
+            )
+          };
+
+          return (
+              <SafeAreaViewWrapper>
+                  <LightStatusBar />
+                  <HomeFlatList
+                      ListHeaderComponent={listHeaderComponent}
+                      data={home?.static}
+                      renderItem={renderStaticList}
+                      keyExtractor={staticListKeyExtractor}
+                  />
+              </SafeAreaViewWrapper>
+          );
+        };
+        `,
+        parser: parsers['@TYPESCRIPT_ESLINT']
       }
     ])
   ),
diff --git a/tests/util/isFirstLetterCapitalized.js b/tests/util/isFirstLetterCapitalized.js
new file mode 100644
index 00000000..5ccbfea6
--- /dev/null
+++ b/tests/util/isFirstLetterCapitalized.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const assert = require('assert');
+
+const isFirstLetterCapitalized = require('../../lib/util/isFirstLetterCapitalized');
+
+describe('isFirstLetterCapitalized', () => {
+  it('should return false for invalid input', () => {
+    assert.equal(isFirstLetterCapitalized(), false);
+    assert.equal(isFirstLetterCapitalized(null), false);
+    assert.equal(isFirstLetterCapitalized(''), false);
+  });
+
+  it('should return false for uncapitalized string', () => {
+    assert.equal(isFirstLetterCapitalized('isCapitalized'), false);
+    assert.equal(isFirstLetterCapitalized('lowercase'), false);
+  });
+
+  it('should return true for capitalized string', () => {
+    assert.equal(isFirstLetterCapitalized('IsCapitalized'), true);
+    assert.equal(isFirstLetterCapitalized('UPPERCASE'), true);
+  });
+});