Skip to content

Commit a4b0bbc

Browse files
akulsr0ljharb
authored andcommitted
[Fix] require-default-props: report when required props have default value
1 parent a08cb93 commit a4b0bbc

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1818
### Fixed
1919
* [`no-invalid-html-attribute`]: substitute placeholders in suggestion messages ([#3759][] @mdjermanovic)
2020
* [`sort-prop-types`]: single line type ending without semicolon ([#3784][] @akulsr0)
21+
* [`require-default-props`]: report when required props have default value ([#3785][] @akulsr0)
2122

2223
### Changed
2324
* [Refactor] `variableUtil`: Avoid creating a single flat variable scope for each lookup ([#3782][] @DanielRosenwasser)
2425

26+
[#3785]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3785
2527
[#3784]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3784
2628
[#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782
2729
[#3777]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3777

lib/rules/require-default-props.js

+23-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ const messages = {
2424
destructureInSignature: 'Must destructure props in the function signature to initialize an optional prop.',
2525
};
2626

27+
function isPropWithNoDefaulVal(prop) {
28+
if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
29+
return false;
30+
}
31+
return prop.value.type !== 'AssignmentPattern';
32+
}
33+
2734
/** @type {import('eslint').Rule.RuleModule} */
2835
module.exports = {
2936
meta: {
@@ -134,15 +141,23 @@ module.exports = {
134141
});
135142
}
136143
} else if (props.type === 'ObjectPattern') {
144+
// Filter required props with default value and report error
137145
props.properties.filter((prop) => {
138-
if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
139-
return false;
140-
}
141-
const propType = propTypes[prop.key.name];
142-
if (!propType || propType.isRequired) {
143-
return false;
144-
}
145-
return prop.value.type !== 'AssignmentPattern';
146+
const propName = prop && prop.key && prop.key.name;
147+
const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
148+
return propTypes[propName] && isPropRequired && !isPropWithNoDefaulVal(prop);
149+
}).forEach((prop) => {
150+
report(context, messages.noDefaultWithRequired, 'noDefaultWithRequired', {
151+
node: prop,
152+
data: { name: prop.key.name },
153+
});
154+
});
155+
156+
// Filter non required props with no default value and report error
157+
props.properties.filter((prop) => {
158+
const propName = prop && prop.key && prop.key.name;
159+
const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
160+
return propTypes[propName] && !isPropRequired && isPropWithNoDefaulVal(prop);
146161
}).forEach((prop) => {
147162
report(context, messages.shouldAssignObjectDefault, 'shouldAssignObjectDefault', {
148163
node: prop,

tests/lib/rules/require-default-props.js

+42
Original file line numberDiff line numberDiff line change
@@ -3042,5 +3042,47 @@ ruleTester.run('require-default-props', rule, {
30423042
propWrapperFunctions: ['forbidExtraProps'],
30433043
},
30443044
},
3045+
{
3046+
code: `
3047+
function MyStatelessComponent({ foo = 'foo' }) {
3048+
return <div>{foo}{bar}</div>;
3049+
}
3050+
MyStatelessComponent.propTypes = {
3051+
foo: PropTypes.string.isRequired,
3052+
};
3053+
`,
3054+
options: [{ functions: 'defaultArguments' }],
3055+
errors: [
3056+
{
3057+
messageId: 'noDefaultWithRequired',
3058+
data: { name: 'foo' },
3059+
line: 2,
3060+
},
3061+
],
3062+
},
3063+
{
3064+
code: `
3065+
function MyStatelessComponent({ foo = 'foo', bar }) {
3066+
return <div>{foo}{bar}</div>;
3067+
}
3068+
MyStatelessComponent.propTypes = {
3069+
foo: PropTypes.string.isRequired,
3070+
bar: PropTypes.string
3071+
};
3072+
`,
3073+
options: [{ functions: 'defaultArguments' }],
3074+
errors: [
3075+
{
3076+
messageId: 'noDefaultWithRequired',
3077+
data: { name: 'foo' },
3078+
line: 2,
3079+
},
3080+
{
3081+
messageId: 'shouldAssignObjectDefault',
3082+
data: { name: 'bar' },
3083+
line: 2,
3084+
},
3085+
],
3086+
},
30453087
]),
30463088
});

0 commit comments

Comments
 (0)