Skip to content

Commit 072ed2c

Browse files
committed
Add forbidDefaultForRequired option
New option for require-default-props rule that forbids defining default props for props that are required.
1 parent 4f3fc51 commit 072ed2c

File tree

3 files changed

+226
-14
lines changed

3 files changed

+226
-14
lines changed

docs/rules/require-default-props.md

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ Greeting.defaultProps = {
102102
};
103103
```
104104

105+
```jsx
106+
type Props = {
107+
foo: string,
108+
bar?: string
109+
};
110+
111+
function MyStatelessComponent(props: Props) {
112+
return <div>Hello {props.foo} {props.bar}</div>;
113+
}
114+
```
115+
116+
The following patterns are **not** considered warnings:
117+
105118
```jsx
106119
class Greeting extends React.Component {
107120
render() {
@@ -121,19 +134,6 @@ class Greeting extends React.Component {
121134
}
122135
```
123136

124-
```jsx
125-
type Props = {
126-
foo: string,
127-
bar?: string
128-
};
129-
130-
function MyStatelessComponent(props: Props) {
131-
return <div>Hello {props.foo} {props.bar}</div>;
132-
}
133-
```
134-
135-
The following patterns are **not** considered warnings:
136-
137137
```jsx
138138
function MyStatelessComponent({ foo, bar }) {
139139
return <div>{foo}{bar}</div>;
@@ -184,6 +184,91 @@ NotAComponent.propTypes = {
184184
};
185185
```
186186

187+
## Rule Options
188+
189+
```js
190+
...
191+
"react/require-default-props": [<enabled>, { forbidDefaultForRequired: <boolean> }]
192+
...
193+
```
194+
195+
* `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
196+
* `forbidDefaultForRequired`: optional boolean to forbid prop default for a required prop. Defaults to false.
197+
198+
### `forbidDefaultForRequired`
199+
200+
Forbids setting a default for props that are marked as `isRequired`.
201+
202+
The following patterns are warnings:
203+
204+
```jsx
205+
class Greeting extends React.Component {
206+
render() {
207+
return (
208+
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
209+
);
210+
}
211+
212+
static propTypes = {
213+
foo: PropTypes.string,
214+
bar: PropTypes.string.isRequired
215+
};
216+
217+
static defaultProps = {
218+
foo: "foo",
219+
bar: "bar"
220+
};
221+
}
222+
```
223+
224+
```jsx
225+
function MyStatelessComponent({ foo, bar }) {
226+
return <div>{foo}{bar}</div>;
227+
}
228+
229+
MyStatelessComponent.propTypes = {
230+
foo: PropTypes.string.isRequired,
231+
bar: PropTypes.string
232+
};
233+
234+
MyStatelessComponent.defaultProps = {
235+
foo: 'foo',
236+
bar: 'bar'
237+
};
238+
```
239+
240+
The following patterns are **not** warnings:
241+
242+
```jsx
243+
class Greeting extends React.Component {
244+
render() {
245+
return (
246+
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
247+
);
248+
}
249+
250+
static propTypes = {
251+
foo: PropTypes.string,
252+
bar: PropTypes.string.isRequired
253+
};
254+
255+
static defaultProps = {
256+
foo: "foo"
257+
};
258+
}
259+
```
260+
261+
```jsx
262+
function MyStatelessComponent({ foo, bar }) {
263+
return <div>{foo}{bar}</div>;
264+
}
265+
266+
MyStatelessComponent.propTypes = {
267+
foo: PropTypes.string.isRequired,
268+
bar: PropTypes.string.isRequired
269+
};
270+
```
271+
187272
## When Not To Use It
188273

189274
If you don't care about using `defaultsProps` for your component's props that are not required, you can disable this rule.

lib/rules/require-default-props.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,22 @@ module.exports = {
2424
category: 'Best Practices'
2525
},
2626

27-
schema: []
27+
schema: [{
28+
type: 'object',
29+
properties: {
30+
forbidDefaultForRequired: {
31+
type: 'boolean'
32+
}
33+
},
34+
additionalProperties: false
35+
}]
2836
},
2937

3038
create: Components.detect((context, components, utils) => {
3139
const sourceCode = context.getSourceCode();
3240
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
41+
const configuration = context.options[0] || {};
42+
const forbidDefaultForRequired = configuration.forbidDefaultForRequired || false;
3343

3444
/**
3545
* Find a variable by name in the current scope.
@@ -285,6 +295,13 @@ module.exports = {
285295

286296
propTypes.forEach(prop => {
287297
if (prop.isRequired) {
298+
if (forbidDefaultForRequired && defaultProps[prop.name]) {
299+
context.report(
300+
prop.node,
301+
'propType "{{name}}" is required and should not have a defaultProp declaration.',
302+
{name: prop.name}
303+
);
304+
}
288305
return;
289306
}
290307

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,20 @@ ruleTester.run('require-default-props', rule, {
735735
'};'
736736
].join('\n'),
737737
parser: 'babel-eslint'
738+
},
739+
{
740+
code: [
741+
'class Hello extends React.Component {',
742+
' static propTypes = {',
743+
' foo: PropTypes.string.isRequired',
744+
' }',
745+
' render() {',
746+
' return <div>Hello {this.props.foo}</div>;',
747+
' }',
748+
'}'
749+
].join('\n'),
750+
parser: 'babel-eslint',
751+
options: [{forbidDefaultForRequired: true}]
738752
}
739753
],
740754

@@ -1896,6 +1910,102 @@ ruleTester.run('require-default-props', rule, {
18961910
errors: [{
18971911
message: 'propType "first-name" is not required, but has no corresponding defaultProp declaration.'
18981912
}]
1913+
},
1914+
{
1915+
code: [
1916+
'class Hello extends React.Component {',
1917+
' render() {',
1918+
' return <div>Hello {this.props.foo}</div>;',
1919+
' }',
1920+
'}',
1921+
'Hello.propTypes = {',
1922+
' foo: PropTypes.string.isRequired',
1923+
'};',
1924+
'Hello.defaultProps = {',
1925+
' foo: \'bar\'',
1926+
'};'
1927+
].join('\n'),
1928+
options: [{forbidDefaultForRequired: true}],
1929+
errors: [{
1930+
message: 'propType "foo" is required and should not have a defaultProp declaration.'
1931+
}]
1932+
},
1933+
{
1934+
code: [
1935+
'function Hello(props) {',
1936+
' return <div>Hello {props.foo}</div>;',
1937+
'}',
1938+
'Hello.propTypes = {',
1939+
' foo: PropTypes.string.isRequired',
1940+
'};',
1941+
'Hello.defaultProps = {',
1942+
' foo: \'bar\'',
1943+
'};'
1944+
].join('\n'),
1945+
options: [{forbidDefaultForRequired: true}],
1946+
errors: [{
1947+
message: 'propType "foo" is required and should not have a defaultProp declaration.'
1948+
}]
1949+
},
1950+
{
1951+
code: [
1952+
'const Hello = (props) => {',
1953+
' return <div>Hello {props.foo}</div>;',
1954+
'};',
1955+
'Hello.propTypes = {',
1956+
' foo: PropTypes.string.isRequired',
1957+
'};',
1958+
'Hello.defaultProps = {',
1959+
' foo: \'bar\'',
1960+
'};'
1961+
].join('\n'),
1962+
options: [{forbidDefaultForRequired: true}],
1963+
errors: [{
1964+
message: 'propType "foo" is required and should not have a defaultProp declaration.'
1965+
}]
1966+
},
1967+
{
1968+
code: [
1969+
'class Hello extends React.Component {',
1970+
' static propTypes = {',
1971+
' foo: PropTypes.string.isRequired',
1972+
' }',
1973+
' static defaultProps = {',
1974+
' foo: \'bar\'',
1975+
' }',
1976+
' render() {',
1977+
' return <div>Hello {this.props.foo}</div>;',
1978+
' }',
1979+
'}'
1980+
].join('\n'),
1981+
parser: 'babel-eslint',
1982+
options: [{forbidDefaultForRequired: true}],
1983+
errors: [{
1984+
message: 'propType "foo" is required and should not have a defaultProp declaration.'
1985+
}]
1986+
},
1987+
{
1988+
code: [
1989+
'class Hello extends React.Component {',
1990+
' static get propTypes () {',
1991+
' return {',
1992+
' foo: PropTypes.string.isRequired',
1993+
' };',
1994+
' }',
1995+
' static get defaultProps() {',
1996+
' return {',
1997+
' foo: \'bar\'',
1998+
' };',
1999+
' }',
2000+
' render() {',
2001+
' return <div>Hello {this.props.foo}</div>;',
2002+
' }',
2003+
'}'
2004+
].join('\n'),
2005+
options: [{forbidDefaultForRequired: true}],
2006+
errors: [{
2007+
message: 'propType "foo" is required and should not have a defaultProp declaration.'
2008+
}]
18992009
}
19002010
]
19012011
});

0 commit comments

Comments
 (0)