Skip to content

Commit 48e04c2

Browse files
jomastiljharb
authored andcommitted
[New] add prefer-exact-props rule
1 parent f233eb7 commit 48e04c2

File tree

8 files changed

+896
-69
lines changed

8 files changed

+896
-69
lines changed

CHANGELOG.md

+64-61
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
77

88
### Added
99
* [`jsx-no-useless-fragments`]: add option to allow single expressions in fragments ([#3006][] @mattdarveniza)
10+
* add [`prefer-exact-props`] rule ([#1547][] @jomasti)
1011

1112
### Fixed
1213
* component detection: use `estraverse` to improve component detection ([#2992][] @Wesitos)
@@ -29,6 +30,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
2930
[#2998]: https://github.com/yannickcr/eslint-plugin-react/pull/2998
3031
[#2994]: https://github.com/yannickcr/eslint-plugin-react/pull/2994
3132
[#2992]: https://github.com/yannickcr/eslint-plugin-react/pull/2992
33+
[#1547]: https://github.com/yannickcr/eslint-plugin-react/pull/1547
3234

3335
## [7.24.0] - 2021.05.27
3436

@@ -3304,98 +3306,99 @@ If you're still not using React 15 you can keep the old behavior by setting the
33043306

33053307
[`react/jsx-runtime`]: https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/index.js#L163-L176
33063308

3309+
[`boolean-prop-naming`]: docs/rules/boolean-prop-naming.md
3310+
[`button-has-type`]: docs/rules/button-has-type.md
3311+
[`default-props-match-prop-types`]: docs/rules/default-props-match-prop-types.md
3312+
[`destructuring-assignment`]: docs/rules/destructuring-assignment.md
33073313
[`display-name`]: docs/rules/display-name.md
33083314
[`forbid-component-props`]: docs/rules/forbid-component-props.md
3315+
[`forbid-dom-props`]: docs/rules/forbid-dom-props.md
33093316
[`forbid-elements`]: docs/rules/forbid-elements.md
33103317
[`forbid-foreign-prop-types`]: docs/rules/forbid-foreign-prop-types.md
33113318
[`forbid-prop-types`]: docs/rules/forbid-prop-types.md
3312-
[`no-array-index-key`]: docs/rules/no-array-index-key.md
3313-
[`no-children-prop`]: docs/rules/no-children-prop.md
3314-
[`no-danger`]: docs/rules/no-danger.md
3315-
[`no-danger-with-children`]: docs/rules/no-danger-with-children.md
3316-
[`no-deprecated`]: docs/rules/no-deprecated.md
3317-
[`no-did-mount-set-state`]: docs/rules/no-did-mount-set-state.md
3318-
[`no-did-update-set-state`]: docs/rules/no-did-update-set-state.md
3319-
[`no-direct-mutation-state`]: docs/rules/no-direct-mutation-state.md
3320-
[`no-find-dom-node`]: docs/rules/no-find-dom-node.md
3321-
[`no-is-mounted`]: docs/rules/no-is-mounted.md
3322-
[`no-multi-comp`]: docs/rules/no-multi-comp.md
3323-
[`no-render-return-value`]: docs/rules/no-render-return-value.md
3324-
[`no-set-state`]: docs/rules/no-set-state.md
3325-
[`no-string-refs`]: docs/rules/no-string-refs.md
3326-
[`no-unescaped-entities`]: docs/rules/no-unescaped-entities.md
3327-
[`no-unknown-property`]: docs/rules/no-unknown-property.md
3328-
[`no-unused-prop-types`]: docs/rules/no-unused-prop-types.md
3329-
[`no-will-update-set-state`]: docs/rules/no-will-update-set-state.md
3330-
[`prefer-es6-class`]: docs/rules/prefer-es6-class.md
3331-
[`prefer-stateless-function`]: docs/rules/prefer-stateless-function.md
3332-
[`prop-types`]: docs/rules/prop-types.md
3333-
[`react-in-jsx-scope`]: docs/rules/react-in-jsx-scope.md
3334-
[`require-optimization`]: docs/rules/require-optimization.md
3335-
[`require-render-return`]: docs/rules/require-render-return.md
3336-
[`self-closing-comp`]: docs/rules/self-closing-comp.md
3337-
[`sort-comp`]: docs/rules/sort-comp.md
3338-
[`sort-prop-types`]: docs/rules/sort-prop-types.md
3339-
[`style-prop-object`]: docs/rules/style-prop-object.md
3319+
[`function-component-definition`]: docs/rules/function-component-definition.md
33403320
[`jsx-boolean-value`]: docs/rules/jsx-boolean-value.md
3321+
[`jsx-child-element-spacing`]: docs/rules/jsx-child-element-spacing.md
33413322
[`jsx-closing-bracket-location`]: docs/rules/jsx-closing-bracket-location.md
3323+
[`jsx-closing-tag-location`]: docs/rules/jsx-closing-tag-location.md
3324+
[`jsx-curly-brace-presence`]: docs/rules/jsx-curly-brace-presence.md
3325+
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
33423326
[`jsx-curly-spacing`]: docs/rules/jsx-curly-spacing.md
33433327
[`jsx-equals-spacing`]: docs/rules/jsx-equals-spacing.md
33443328
[`jsx-filename-extension`]: docs/rules/jsx-filename-extension.md
33453329
[`jsx-first-prop-new-line`]: docs/rules/jsx-first-prop-new-line.md
3330+
[`jsx-fragments`]: docs/rules/jsx-fragments.md
33463331
[`jsx-handler-names`]: docs/rules/jsx-handler-names.md
3347-
[`jsx-indent`]: docs/rules/jsx-indent.md
33483332
[`jsx-indent-props`]: docs/rules/jsx-indent-props.md
3333+
[`jsx-indent`]: docs/rules/jsx-indent.md
33493334
[`jsx-key`]: docs/rules/jsx-key.md
3335+
[`jsx-max-depth`]: docs/rules/jsx-max-depth.md
33503336
[`jsx-max-props-per-line`]: docs/rules/jsx-max-props-per-line.md
3337+
[`jsx-newline`]: docs/rules/jsx-newline.md
33513338
[`jsx-no-bind`]: docs/rules/jsx-no-bind.md
33523339
[`jsx-no-comment-textnodes`]: docs/rules/jsx-no-comment-textnodes.md
3340+
[`jsx-no-constructed-context-values`]: docs/rules/jsx-no-constructed-context-values.md
33533341
[`jsx-no-duplicate-props`]: docs/rules/jsx-no-duplicate-props.md
33543342
[`jsx-no-literals`]: docs/rules/jsx-no-literals.md
3343+
[`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md
33553344
[`jsx-no-target-blank`]: docs/rules/jsx-no-target-blank.md
33563345
[`jsx-no-undef`]: docs/rules/jsx-no-undef.md
3346+
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
3347+
[`jsx-one-expression-per-line`]: docs/rules/jsx-one-expression-per-line.md
33573348
[`jsx-pascal-case`]: docs/rules/jsx-pascal-case.md
3358-
[`require-default-props`]: docs/rules/require-default-props.md
3349+
[`jsx-props-no-multi-spaces`]: docs/rules/jsx-props-no-multi-spaces.md
3350+
[`jsx-props-no-spreading`]: docs/rules/jsx-props-no-spreading.md
3351+
[`jsx-props-no-spreading`]: docs/rules/jsx-props-no-spreading.md
3352+
[`jsx-sort-default-props`]: docs/rules/jsx-sort-default-props.md
3353+
[`jsx-sort-prop-types`]: docs/rules/sort-prop-types.md
33593354
[`jsx-sort-props`]: docs/rules/jsx-sort-props.md
33603355
[`jsx-space-before-closing`]: docs/rules/jsx-space-before-closing.md
33613356
[`jsx-tag-spacing`]: docs/rules/jsx-tag-spacing.md
33623357
[`jsx-uses-react`]: docs/rules/jsx-uses-react.md
33633358
[`jsx-uses-vars`]: docs/rules/jsx-uses-vars.md
33643359
[`jsx-wrap-multilines`]: docs/rules/jsx-wrap-multilines.md
3365-
[`void-dom-elements-no-children`]: docs/rules/void-dom-elements-no-children.md
3366-
[`default-props-match-prop-types`]: docs/rules/default-props-match-prop-types.md
3367-
[`no-redundant-should-component-update`]: docs/rules/no-redundant-should-component-update.md
3368-
[`jsx-closing-tag-location`]: docs/rules/jsx-closing-tag-location.md
3369-
[`no-unused-state`]: docs/rules/no-unused-state.md
3370-
[`boolean-prop-naming`]: docs/rules/boolean-prop-naming.md
3371-
[`no-typos`]: docs/rules/no-typos.md
3372-
[`jsx-sort-prop-types`]: docs/rules/sort-prop-types.md
3373-
[`require-extension`]: docs/rules/require-extension.md
3374-
[`no-comment-textnodes`]: docs/rules/jsx-no-comment-textnodes.md
3375-
[`wrap-multilines`]: docs/rules/jsx-wrap-multilines.md
3376-
[`jsx-curly-brace-presence`]: docs/rules/jsx-curly-brace-presence.md
3377-
[`jsx-one-expression-per-line`]: docs/rules/jsx-one-expression-per-line.md
3378-
[`destructuring-assignment`]: docs/rules/destructuring-assignment.md
33793360
[`no-access-state-in-setstate`]: docs/rules/no-access-state-in-setstate.md
3380-
[`button-has-type`]: docs/rules/button-has-type.md
3381-
[`forbid-dom-props`]: docs/rules/forbid-dom-props.md
3382-
[`jsx-child-element-spacing`]: docs/rules/jsx-child-element-spacing.md
3361+
[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md
3362+
[`no-array-index-key`]: docs/rules/no-array-index-key.md
3363+
[`no-children-prop`]: docs/rules/no-children-prop.md
3364+
[`no-comment-textnodes`]: docs/rules/jsx-no-comment-textnodes.md
3365+
[`no-danger-with-children`]: docs/rules/no-danger-with-children.md
3366+
[`no-danger`]: docs/rules/no-danger.md
3367+
[`no-deprecated`]: docs/rules/no-deprecated.md
3368+
[`no-did-mount-set-state`]: docs/rules/no-did-mount-set-state.md
3369+
[`no-did-update-set-state`]: docs/rules/no-did-update-set-state.md
3370+
[`no-direct-mutation-state`]: docs/rules/no-direct-mutation-state.md
3371+
[`no-find-dom-node`]: docs/rules/no-find-dom-node.md
3372+
[`no-is-mounted`]: docs/rules/no-is-mounted.md
3373+
[`no-multi-comp`]: docs/rules/no-multi-comp.md
3374+
[`no-redundant-should-component-update`]: docs/rules/no-redundant-should-component-update.md
3375+
[`no-render-return-value`]: docs/rules/no-render-return-value.md
3376+
[`no-set-state`]: docs/rules/no-set-state.md
3377+
[`no-string-refs`]: docs/rules/no-string-refs.md
33833378
[`no-this-in-sfc`]: docs/rules/no-this-in-sfc.md
3384-
[`jsx-sort-default-props`]: docs/rules/jsx-sort-default-props.md
3385-
[`jsx-max-depth`]: docs/rules/jsx-max-depth.md
3386-
[`jsx-props-no-multi-spaces`]: docs/rules/jsx-props-no-multi-spaces.md
3379+
[`no-typos`]: docs/rules/no-typos.md
3380+
[`no-unescaped-entities`]: docs/rules/no-unescaped-entities.md
3381+
[`no-unknown-property`]: docs/rules/no-unknown-property.md
33873382
[`no-unsafe`]: docs/rules/no-unsafe.md
3388-
[`jsx-fragments`]: docs/rules/jsx-fragments.md
3389-
[`jsx-props-no-spreading`]: docs/rules/jsx-props-no-spreading.md
3383+
[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md
3384+
[`no-unused-prop-types`]: docs/rules/no-unused-prop-types.md
3385+
[`no-unused-state`]: docs/rules/no-unused-state.md
3386+
[`no-will-update-set-state`]: docs/rules/no-will-update-set-state.md
3387+
[`prefer-es6-class`]: docs/rules/prefer-es6-class.md
3388+
[`prefer-exact-props`]: docs/rules/prefer-exact-props.md
33903389
[`prefer-read-only-props`]: docs/rules/prefer-read-only-props.md
3390+
[`prefer-stateless-function`]: docs/rules/prefer-stateless-function.md
3391+
[`prop-types`]: docs/rules/prop-types.md
3392+
[`react-in-jsx-scope`]: docs/rules/react-in-jsx-scope.md
3393+
[`require-default-props`]: docs/rules/require-default-props.md
3394+
[`require-extension`]: docs/rules/require-extension.md
3395+
[`require-optimization`]: docs/rules/require-optimization.md
3396+
[`require-render-return`]: docs/rules/require-render-return.md
3397+
[`self-closing-comp`]: docs/rules/self-closing-comp.md
3398+
[`sort-comp`]: docs/rules/sort-comp.md
3399+
[`sort-prop-types`]: docs/rules/sort-prop-types.md
33913400
[`state-in-constructor`]: docs/rules/state-in-constructor.md
3392-
[`jsx-props-no-spreading`]: docs/rules/jsx-props-no-spreading.md
33933401
[`static-property-placement`]: docs/rules/static-property-placement.md
3394-
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
3395-
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
3396-
[`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md
3397-
[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md
3398-
[`function-component-definition`]: docs/rules/function-component-definition.md
3399-
[`jsx-newline`]: docs/rules/jsx-newline.md
3400-
[`jsx-no-constructed-context-values`]: docs/rules/jsx-no-constructed-context-values.md
3401-
[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md
3402+
[`style-prop-object`]: docs/rules/style-prop-object.md
3403+
[`void-dom-elements-no-children`]: docs/rules/void-dom-elements-no-children.md
3404+
[`wrap-multilines`]: docs/rules/jsx-wrap-multilines.md

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ You should also specify settings that will be shared across all the plugin rules
5353
// The names of any function used to wrap propTypes, e.g. `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped.
5454
"forbidExtraProps",
5555
{"property": "freeze", "object": "Object"},
56-
{"property": "myFavoriteWrapper"}
56+
{"property": "myFavoriteWrapper"},
57+
// for rules that check exact prop wrappers
58+
{"property": "forbidExtraProps", "exact": true}
5759
],
5860
"componentWrapperFunctions": [
5961
// The name of any function used to wrap components, e.g. Mobx `observer` function. If this isn't set, components wrapped by these functions will be skipped.

docs/rules/prefer-exact-props.md

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Prefer exact proptype definitions (react/prefer-exact-props)
2+
3+
Recommends options to ensure only exact prop definitions are used when writing components. This recommends solutions for PropTypes or for Flow types.
4+
5+
In React, you can define prop types for components using propTypes. Such an example is below:
6+
7+
```jsx
8+
class Foo extends React.Component {
9+
render() {
10+
return <p>{this.props.bar}</p>;
11+
}
12+
}
13+
14+
Foo.propTypes = {
15+
bar: PropTypes.string
16+
};
17+
```
18+
19+
The problem with this is that the consumer of the component could still pass in extra props. There could even be a typo for expected props. In order to prevent those situations, one could use the npm package [prop-types-exact](https://www.npmjs.com/package/prop-types-exact) to warn when unexpected props are passed to the component.
20+
21+
One can also define props for a component using Flow types. Such an example is below:
22+
23+
```jsx
24+
class Foo extends React.Component {
25+
props: {
26+
bar: string
27+
}
28+
29+
render() {
30+
return <p>{this.props.bar}</p>;
31+
}
32+
}
33+
```
34+
35+
In this case, one could instead enforce only the exact props being used by using exact type objects, like below:
36+
37+
```jsx
38+
class Foo extends React.Component {
39+
props: {|
40+
bar: string
41+
}|
42+
43+
render() {
44+
return <p>{this.props.bar}</p>;
45+
}
46+
}
47+
```
48+
49+
See the [Flow docs](https://flow.org/en/docs/types/objects/#toc-exact-object-types) on exact object types for more information.
50+
51+
## Rule Details
52+
53+
This rule will only produce errors for prop types when combined with the appropriate entries in `propWrapperFunctions`. For example:
54+
55+
```json
56+
{
57+
"settings": {
58+
"propWrapperFunctions": [
59+
{"property": "exact", "exact": true}
60+
]
61+
}
62+
}
63+
```
64+
65+
The following patterns are considered warnings:
66+
67+
```jsx
68+
class Component extends React.Component {
69+
render() {
70+
return <div />;
71+
}
72+
}
73+
Component.propTypes = {
74+
foo: PropTypes.string
75+
};
76+
```
77+
78+
```jsx
79+
class Component extends React.Component {
80+
static propTypes = {
81+
foo: PropTypes.string
82+
}
83+
render() {
84+
return <div />;
85+
}
86+
}
87+
```
88+
89+
```jsx
90+
class Component extends React.Component {
91+
props: {
92+
foo: string
93+
}
94+
render() {
95+
return <div />;
96+
}
97+
}
98+
```
99+
100+
```jsx
101+
function Component(props: { foo: string }) {
102+
return <div />;
103+
}
104+
```
105+
106+
```jsx
107+
type Props = {
108+
foo: string
109+
}
110+
function Component(props: Props) {
111+
return <div />;
112+
}
113+
```
114+
115+
The following patterns are **not** considered warnings:
116+
117+
```jsx
118+
type Props = {|
119+
foo: string
120+
|}
121+
function Component(props: Props) {
122+
return <div />;
123+
}
124+
```
125+
126+
```jsx
127+
import exact from 'prop-types-exact';
128+
class Component extends React.Component {
129+
render() {
130+
return <div />;
131+
}
132+
}
133+
Component.propTypes = exact({
134+
foo: PropTypes.string
135+
});
136+
```
137+
138+
## When Not To Use It
139+
140+
If you aren't concerned about extra props being passed to a component or potential spelling errors for existing props aren't a common nuisance, then you can leave this rule off.

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const allRules = {
8181
'no-unused-state': require('./lib/rules/no-unused-state'),
8282
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
8383
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
84+
'prefer-exact-props': require('./lib/rules/prefer-exact-props'),
8485
'prefer-read-only-props': require('./lib/rules/prefer-read-only-props'),
8586
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
8687
'prop-types': require('./lib/rules/prop-types'),

0 commit comments

Comments
 (0)