Skip to content

Commit

Permalink
[Fix] prop-types: Detect TypeScript types for destructured default …
Browse files Browse the repository at this point in the history
…prop values

Fixes #2762.
  • Loading branch information
sunghyunjo authored and ljharb committed Sep 1, 2020
1 parent 9ce69a2 commit 9a569f7
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions docs/rules/prop-types.md
Expand Up @@ -91,6 +91,20 @@ class HelloEs6WithPublicClassField extends React.Component {
}
```

In TypeScript:

```tsx
// destructured default prop values

function Foo({ bar = "" }): JSX.Element {
return <div>{bar}</div>;
}

function Foo({ bar = "" as string }): JSX.Element {
return <div>{bar}</div>;
}
```

In Flow:

```tsx
Expand Down
33 changes: 33 additions & 0 deletions lib/rules/prop-types.js
Expand Up @@ -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.
Expand All @@ -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;
Expand Down
83 changes: 72 additions & 11 deletions tests/lib/rules/prop-types.js
Expand Up @@ -34,7 +34,6 @@ const settings = {

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('prop-types', rule, {

valid: [].concat(
{
code: [
Expand Down Expand Up @@ -2965,6 +2964,54 @@ ruleTester.run('prop-types', rule, {
};
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
function Foo({ bar = "" }: { bar: string }): JSX.Element {
return <div>{bar}</div>;
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
function Foo({ foo = "" }): JSX.Element {
return <div>{foo}</div>;
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
function Foo({ bar = "" as string }): JSX.Element {
return <div>{bar}</div>;
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
export default function ({ value = 'World' }) {
return <h1>Hello {value}</h1>
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
const Foo: JSX.Element = ({ bar = "" }) => {
return <div>{bar}</div>;
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
},
{
code: `
const Foo: JSX.Element = function foo ({ bar = "" }) {
return <div>{bar}</div>;
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
}
])
),
Expand Down Expand Up @@ -5544,16 +5591,6 @@ ruleTester.run('prop-types', rule, {
message: '\'foo.baz\' is missing in props validation'
}]
},
{
code: `
export default function ({ value = 'World' }) {
return <h1>Hello {value}</h1>
}
`,
errors: [{
message: '\'value\' is missing in props validation'
}]
},
parsers.TS([
{
code: `
Expand Down Expand Up @@ -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 <div>{bar}</div>;
}
`,
settings,
parser: parsers['@TYPESCRIPT_ESLINT'],
errors: [{
message: "'bar' is missing in props validation"
}]
},
{
code: `
const Foo: JSX.Element = function foo ({ bar }) {
return <div>{bar}</div>;
}
`,
settings,
parser: parsers['@TYPESCRIPT_ESLINT'],
errors: [{
message: "'bar' is missing in props validation"
}]
}
])
)
Expand Down

0 comments on commit 9a569f7

Please sign in to comment.