diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4e364fc20..3d8f285d46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,8 +14,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`forbid-component-props`]: Implemented support for "namespaced" components ([#2767][] @mnn)
* [`prefer-read-only-props`]: support Flow `$ReadOnly` ([#2772][], [#2779][], [#2770][] @karolina-benitez)
* [`jsx-handler-names`]: handle whitespace ([#2789][] @AriPerkkio)
+* [`prop-types`]: Detect TypeScript types for destructured default prop values ([#2780][] @sunghyunjo)
[#2789]: https://github.com/yannickcr/eslint-plugin-react/pull/2789
+[#2780]: https://github.com/yannickcr/eslint-plugin-react/pull/2780
[#2779]: https://github.com/yannickcr/eslint-plugin-react/pull/2779
[#2772]: https://github.com/yannickcr/eslint-plugin-react/pull/2772
[#2771]: https://github.com/yannickcr/eslint-plugin-react/pull/2771
diff --git a/docs/rules/prop-types.md b/docs/rules/prop-types.md
index b0e65baddf..0a70baf9ba 100644
--- a/docs/rules/prop-types.md
+++ b/docs/rules/prop-types.md
@@ -91,6 +91,20 @@ class HelloEs6WithPublicClassField extends React.Component {
}
```
+In TypeScript:
+
+```tsx
+// destructured default prop values
+
+function Foo({ bar = "" }): JSX.Element {
+ return
{bar}
;
+}
+
+function Foo({ bar = "" as string }): JSX.Element {
+ return {bar}
;
+}
+```
+
In Flow:
```tsx
diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js
index b1538193fb..ee939df065 100644
--- a/lib/rules/prop-types.js
+++ b/lib/rules/prop-types.js
@@ -137,6 +137,34 @@ module.exports = {
return true;
}
+ /**
+ * Checks if the prop is declared in destructured params
+ * @param {Object[]} params List of destructured param among props without declaredPropTypes
+ * @returns {Boolean} True if the prop is declared, false if not.
+ */
+ function isDeclaredInDestructuredParam(params) {
+ let result = true;
+ params.forEach((param) => {
+ if (!param.properties) {
+ result = false;
+ return;
+ }
+ param.properties.forEach((property) => {
+ const type = property.value.type;
+ const right = property.value.right;
+ if (type !== 'AssignmentPattern') {
+ result = false;
+ return;
+ }
+ if (type === 'AssignmentPattern' && right && right.expression && right.expression.type && right.expression.type !== 'Literal') {
+ result = false;
+ }
+ });
+ });
+
+ return result;
+ }
+
/**
* Checks if the prop is declared
* @param {ASTNode} node The AST node being checked.
@@ -149,9 +177,14 @@ module.exports = {
const isDeclared = component && component.confidence === 2
&& internalIsDeclaredInComponent(component.declaredPropTypes || {}, names);
+
if (isDeclared) {
return true;
}
+
+ if (component && !isDeclared && !component.declaredPropTypes && component.node.params && (component.node.type === 'FunctionDeclaration' || component.node.type === 'FunctionExpression' || component.node.type === 'ArrowFunctionExpression')) {
+ return isDeclaredInDestructuredParam(component.node.params);
+ }
node = node.parent;
}
return false;
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 82a6a60079..29b04794cd 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -34,7 +34,6 @@ const settings = {
const ruleTester = new RuleTester({parserOptions});
ruleTester.run('prop-types', rule, {
-
valid: [].concat(
{
code: [
@@ -2965,6 +2964,54 @@ ruleTester.run('prop-types', rule, {
};
`,
parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ function Foo({ bar = "" }: { bar: string }): JSX.Element {
+ return {bar}
;
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ function Foo({ foo = "" }): JSX.Element {
+ return {foo}
;
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ function Foo({ bar = "" as string }): JSX.Element {
+ return {bar}
;
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ export default function ({ value = 'World' }) {
+ return Hello {value}
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ const Foo: JSX.Element = ({ bar = "" }) => {
+ return {bar}
;
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ {
+ code: `
+ const Foo: JSX.Element = function foo ({ bar = "" }) {
+ return {bar}
;
+ }
+ `,
+ parser: parsers['@TYPESCRIPT_ESLINT']
}
])
),
@@ -5544,16 +5591,6 @@ ruleTester.run('prop-types', rule, {
message: '\'foo.baz\' is missing in props validation'
}]
},
- {
- code: `
- export default function ({ value = 'World' }) {
- return Hello {value}
- }
- `,
- errors: [{
- message: '\'value\' is missing in props validation'
- }]
- },
parsers.TS([
{
code: `
@@ -5958,6 +5995,30 @@ ruleTester.run('prop-types', rule, {
errors: [{
message: "'foo.c' is missing in props validation"
}]
+ },
+ {
+ code: `
+ const Foo: JSX.Element = ({ bar }) => {
+ return {bar}
;
+ }
+ `,
+ settings,
+ parser: parsers['@TYPESCRIPT_ESLINT'],
+ errors: [{
+ message: "'bar' is missing in props validation"
+ }]
+ },
+ {
+ code: `
+ const Foo: JSX.Element = function foo ({ bar }) {
+ return {bar}
;
+ }
+ `,
+ settings,
+ parser: parsers['@TYPESCRIPT_ESLINT'],
+ errors: [{
+ message: "'bar' is missing in props validation"
+ }]
}
])
)