Skip to content

Commit 9a569f7

Browse files
sunghyunjoljharb
authored andcommitted
[Fix] prop-types: Detect TypeScript types for destructured default prop values
Fixes #2762.
1 parent 9ce69a2 commit 9a569f7

File tree

4 files changed

+121
-11
lines changed

4 files changed

+121
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1414
* [`forbid-component-props`]: Implemented support for "namespaced" components ([#2767][] @mnn)
1515
* [`prefer-read-only-props`]: support Flow `$ReadOnly` ([#2772][], [#2779][], [#2770][] @karolina-benitez)
1616
* [`jsx-handler-names`]: handle whitespace ([#2789][] @AriPerkkio)
17+
* [`prop-types`]: Detect TypeScript types for destructured default prop values ([#2780][] @sunghyunjo)
1718

1819
[#2789]: https://github.com/yannickcr/eslint-plugin-react/pull/2789
20+
[#2780]: https://github.com/yannickcr/eslint-plugin-react/pull/2780
1921
[#2779]: https://github.com/yannickcr/eslint-plugin-react/pull/2779
2022
[#2772]: https://github.com/yannickcr/eslint-plugin-react/pull/2772
2123
[#2771]: https://github.com/yannickcr/eslint-plugin-react/pull/2771

docs/rules/prop-types.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ class HelloEs6WithPublicClassField extends React.Component {
9191
}
9292
```
9393

94+
In TypeScript:
95+
96+
```tsx
97+
// destructured default prop values
98+
99+
function Foo({ bar = "" }): JSX.Element {
100+
return <div>{bar}</div>;
101+
}
102+
103+
function Foo({ bar = "" as string }): JSX.Element {
104+
return <div>{bar}</div>;
105+
}
106+
```
107+
94108
In Flow:
95109

96110
```tsx

lib/rules/prop-types.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,34 @@ module.exports = {
137137
return true;
138138
}
139139

140+
/**
141+
* Checks if the prop is declared in destructured params
142+
* @param {Object[]} params List of destructured param among props without declaredPropTypes
143+
* @returns {Boolean} True if the prop is declared, false if not.
144+
*/
145+
function isDeclaredInDestructuredParam(params) {
146+
let result = true;
147+
params.forEach((param) => {
148+
if (!param.properties) {
149+
result = false;
150+
return;
151+
}
152+
param.properties.forEach((property) => {
153+
const type = property.value.type;
154+
const right = property.value.right;
155+
if (type !== 'AssignmentPattern') {
156+
result = false;
157+
return;
158+
}
159+
if (type === 'AssignmentPattern' && right && right.expression && right.expression.type && right.expression.type !== 'Literal') {
160+
result = false;
161+
}
162+
});
163+
});
164+
165+
return result;
166+
}
167+
140168
/**
141169
* Checks if the prop is declared
142170
* @param {ASTNode} node The AST node being checked.
@@ -149,9 +177,14 @@ module.exports = {
149177

150178
const isDeclared = component && component.confidence === 2
151179
&& internalIsDeclaredInComponent(component.declaredPropTypes || {}, names);
180+
152181
if (isDeclared) {
153182
return true;
154183
}
184+
185+
if (component && !isDeclared && !component.declaredPropTypes && component.node.params && (component.node.type === 'FunctionDeclaration' || component.node.type === 'FunctionExpression' || component.node.type === 'ArrowFunctionExpression')) {
186+
return isDeclaredInDestructuredParam(component.node.params);
187+
}
155188
node = node.parent;
156189
}
157190
return false;

tests/lib/rules/prop-types.js

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const settings = {
3434

3535
const ruleTester = new RuleTester({parserOptions});
3636
ruleTester.run('prop-types', rule, {
37-
3837
valid: [].concat(
3938
{
4039
code: [
@@ -2965,6 +2964,54 @@ ruleTester.run('prop-types', rule, {
29652964
};
29662965
`,
29672966
parser: parsers['@TYPESCRIPT_ESLINT']
2967+
},
2968+
{
2969+
code: `
2970+
function Foo({ bar = "" }: { bar: string }): JSX.Element {
2971+
return <div>{bar}</div>;
2972+
}
2973+
`,
2974+
parser: parsers['@TYPESCRIPT_ESLINT']
2975+
},
2976+
{
2977+
code: `
2978+
function Foo({ foo = "" }): JSX.Element {
2979+
return <div>{foo}</div>;
2980+
}
2981+
`,
2982+
parser: parsers['@TYPESCRIPT_ESLINT']
2983+
},
2984+
{
2985+
code: `
2986+
function Foo({ bar = "" as string }): JSX.Element {
2987+
return <div>{bar}</div>;
2988+
}
2989+
`,
2990+
parser: parsers['@TYPESCRIPT_ESLINT']
2991+
},
2992+
{
2993+
code: `
2994+
export default function ({ value = 'World' }) {
2995+
return <h1>Hello {value}</h1>
2996+
}
2997+
`,
2998+
parser: parsers['@TYPESCRIPT_ESLINT']
2999+
},
3000+
{
3001+
code: `
3002+
const Foo: JSX.Element = ({ bar = "" }) => {
3003+
return <div>{bar}</div>;
3004+
}
3005+
`,
3006+
parser: parsers['@TYPESCRIPT_ESLINT']
3007+
},
3008+
{
3009+
code: `
3010+
const Foo: JSX.Element = function foo ({ bar = "" }) {
3011+
return <div>{bar}</div>;
3012+
}
3013+
`,
3014+
parser: parsers['@TYPESCRIPT_ESLINT']
29683015
}
29693016
])
29703017
),
@@ -5544,16 +5591,6 @@ ruleTester.run('prop-types', rule, {
55445591
message: '\'foo.baz\' is missing in props validation'
55455592
}]
55465593
},
5547-
{
5548-
code: `
5549-
export default function ({ value = 'World' }) {
5550-
return <h1>Hello {value}</h1>
5551-
}
5552-
`,
5553-
errors: [{
5554-
message: '\'value\' is missing in props validation'
5555-
}]
5556-
},
55575594
parsers.TS([
55585595
{
55595596
code: `
@@ -5958,6 +5995,30 @@ ruleTester.run('prop-types', rule, {
59585995
errors: [{
59595996
message: "'foo.c' is missing in props validation"
59605997
}]
5998+
},
5999+
{
6000+
code: `
6001+
const Foo: JSX.Element = ({ bar }) => {
6002+
return <div>{bar}</div>;
6003+
}
6004+
`,
6005+
settings,
6006+
parser: parsers['@TYPESCRIPT_ESLINT'],
6007+
errors: [{
6008+
message: "'bar' is missing in props validation"
6009+
}]
6010+
},
6011+
{
6012+
code: `
6013+
const Foo: JSX.Element = function foo ({ bar }) {
6014+
return <div>{bar}</div>;
6015+
}
6016+
`,
6017+
settings,
6018+
parser: parsers['@TYPESCRIPT_ESLINT'],
6019+
errors: [{
6020+
message: "'bar' is missing in props validation"
6021+
}]
59616022
}
59626023
])
59636024
)

0 commit comments

Comments
 (0)