Skip to content

Commit 34920bb

Browse files
Merge branch 'master' into fix/useContext
2 parents 25ef6c0 + de2ec66 commit 34920bb

15 files changed

+311
-16
lines changed

.eslintrc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
"no-plusplus": 1,
3434
"no-param-reassign": 1,
3535
"no-mixed-operators": 1,
36-
"no-restricted-syntax": 1,
36+
"no-restricted-syntax": [2, {
37+
"selector": "ObjectPattern",
38+
"message": "Object destructuring is not compatible with Node v4"
39+
}],
3740
"strict": [2, "safe"],
3841
"valid-jsdoc": [2, {
3942
"requireReturn": false,

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
77

88
### Added
99
* [`button-has-type`]: support trivial ternary expressions ([#2748][] @Hypnosphi)
10+
* [`jsx-handler-names`]: add `checkInlineFunction` option ([#2761][] @dididy)
1011

12+
### Fixed
13+
* [`function-component-definition`]: ignore object properties ([#2771][] @stefan-wullems)
14+
* [`forbid-component-props`]: Implemented support for "namespaced" components ([#2767][] @mnn)
15+
* [`prefer-read-only-props`]: support Flow `$ReadOnly` ([#2772][], [#2779][], [#2770][] @karolina-benitez)
16+
17+
[#2779]: https://github.com/yannickcr/eslint-plugin-react/pull/2779
18+
[#2772]: https://github.com/yannickcr/eslint-plugin-react/pull/2772
19+
[#2771]: https://github.com/yannickcr/eslint-plugin-react/pull/2771
20+
[#2770]: https://github.com/yannickcr/eslint-plugin-react/pull/2770
21+
[#2767]: https://github.com/yannickcr/eslint-plugin-react/pull/2767
22+
[#2761]: https://github.com/yannickcr/eslint-plugin-react/pull/2761
1123
[#2748]: https://github.com/yannickcr/eslint-plugin-react/pull/2748
1224

1325
## [7.20.6] - 2020.08.12

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ You should also specify settings that will be shared across all the plugin rules
3939
"createClass": "createReactClass", // Regex for Component Factory to use,
4040
// default to "createReactClass"
4141
"pragma": "React", // Pragma to use, default to "React"
42+
"fragment": "React.Fragment", // Fragment to use, default to "React.Fragment"
4243
"version": "detect", // React version. "detect" automatically picks the version you have installed.
4344
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
4445
// default to latest and warns if missing

docs/rules/jsx-handler-names.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ The following patterns are **not** considered warnings:
3131
"react/jsx-handler-names": [<enabled>, {
3232
"eventHandlerPrefix": <eventHandlerPrefix>,
3333
"eventHandlerPropPrefix": <eventHandlerPropPrefix>,
34-
"checkLocalVariables": <boolean>
34+
"checkLocalVariables": <boolean>,
35+
"checkInlineFunction": <boolean>
3536
}]
3637
...
3738
```
3839

3940
* `eventHandlerPrefix`: Prefix for component methods used as event handlers. Defaults to `handle`
4041
* `eventHandlerPropPrefix`: Prefix for props that are used as event handlers. Defaults to `on`
4142
* `checkLocalVariables`: Determines whether event handlers stored as local variables are checked. Defaults to `false`
43+
* `checkInlineFunction`: Determines whether event handlers set as inline functions are checked. Defaults to `false`
4244

4345
## When Not To Use It
4446

lib/rules/forbid-component-props.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ module.exports = {
7878

7979
return {
8080
JSXAttribute(node) {
81-
const tag = node.parent.name.name;
82-
if (tag && tag[0] !== tag[0].toUpperCase()) {
81+
const parentName = node.parent.name;
82+
// Extract a component name when using a "namespace", e.g. `<AntdLayout.Content />`.
83+
const tag = parentName.name || `${parentName.object.name}.${parentName.property.name}`;
84+
const componentName = parentName.name || parentName.property.name;
85+
if (componentName && componentName[0] !== componentName[0].toUpperCase()) {
8386
// This is a DOM node, not a Component, so exit.
8487
return;
8588
}

lib/rules/function-component-definition.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ module.exports = {
156156

157157
function validate(node, functionType) {
158158
if (!components.get(node)) return;
159+
160+
if (node.parent && node.parent.type === 'Property') return;
161+
159162
if (hasName(node) && namedConfig !== functionType) {
160163
report(node, {
161164
message: ERROR_MESSAGES[namedConfig],

lib/rules/jsx-handler-names.js

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ module.exports = {
2727
properties: {
2828
eventHandlerPrefix: {type: 'string'},
2929
eventHandlerPropPrefix: {type: 'string'},
30-
checkLocalVariables: {type: 'boolean'}
30+
checkLocalVariables: {type: 'boolean'},
31+
checkInlineFunction: {type: 'boolean'}
3132
},
3233
additionalProperties: false
3334
}, {
@@ -38,7 +39,8 @@ module.exports = {
3839
type: 'boolean',
3940
enum: [false]
4041
},
41-
checkLocalVariables: {type: 'boolean'}
42+
checkLocalVariables: {type: 'boolean'},
43+
checkInlineFunction: {type: 'boolean'}
4244
},
4345
additionalProperties: false
4446
}, {
@@ -49,7 +51,8 @@ module.exports = {
4951
enum: [false]
5052
},
5153
eventHandlerPropPrefix: {type: 'string'},
52-
checkLocalVariables: {type: 'boolean'}
54+
checkLocalVariables: {type: 'boolean'},
55+
checkInlineFunction: {type: 'boolean'}
5356
},
5457
additionalProperties: false
5558
}, {
@@ -58,6 +61,12 @@ module.exports = {
5861
checkLocalVariables: {type: 'boolean'}
5962
},
6063
additionalProperties: false
64+
}, {
65+
type: 'object',
66+
properties: {
67+
checkInlineFunction: {type: 'boolean'}
68+
},
69+
additionalProperties: false
6170
}
6271
]
6372
}]
@@ -68,6 +77,10 @@ module.exports = {
6877
return prefix === false;
6978
}
7079

80+
function isInlineHandler(node) {
81+
return node.value.expression.type === 'ArrowFunctionExpression';
82+
}
83+
7184
const configuration = context.options[0] || {};
7285

7386
const eventHandlerPrefix = isPrefixDisabled(configuration.eventHandlerPrefix)
@@ -86,14 +99,29 @@ module.exports = {
8699

87100
const checkLocal = !!configuration.checkLocalVariables;
88101

102+
const checkInlineFunction = !!configuration.checkInlineFunction;
103+
89104
return {
90105
JSXAttribute(node) {
91-
if (!node.value || !node.value.expression || (!checkLocal && !node.value.expression.object)) {
106+
if (
107+
!node.value
108+
|| !node.value.expression
109+
|| (
110+
!checkLocal
111+
&& (isInlineHandler(node)
112+
? !node.value.expression.body.callee.object
113+
: !node.value.expression.object
114+
)
115+
)
116+
) {
92117
return;
93118
}
94119

95120
const propKey = typeof node.name === 'object' ? node.name.name : node.name;
96-
const propValue = context.getSourceCode().getText(node.value.expression).replace(/^this\.|.*::/, '');
121+
const expression = node.value.expression;
122+
const propValue = context.getSourceCode()
123+
.getText(checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression)
124+
.replace(/^this\.|.*::/, '');
97125

98126
if (propKey === 'ref') {
99127
return;

lib/rules/jsx-uses-react.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = {
2525

2626
create(context) {
2727
const pragma = pragmaUtil.getFromContext(context);
28+
const fragment = pragmaUtil.getFragmentFromContext(context);
2829

2930
function handleOpeningElement() {
3031
context.markVariableAsUsed(pragma);
@@ -35,7 +36,10 @@ module.exports = {
3536

3637
return {
3738
JSXOpeningElement: handleOpeningElement,
38-
JSXOpeningFragment: handleOpeningElement
39+
JSXOpeningFragment: handleOpeningElement,
40+
JSXFragment() {
41+
context.markVariableAsUsed(fragment);
42+
}
3943
};
4044
}
4145
};

lib/rules/prefer-read-only-props.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function isFlowPropertyType(node) {
1313
}
1414

1515
function isCovariant(node) {
16-
return node.variance && node.variance.kind === 'plus';
16+
return node.variance && node.variance.kind === 'plus' || node.parent.parent.parent.id && node.parent.parent.parent.id.name === '$ReadOnly';
1717
}
1818

1919
// ------------------------------------------------------------------------------

markdown.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
/* eslint-disable no-restricted-syntax */
4+
35
const {rules} = require('./index');
46

57
const ruleListItems = Object.keys(rules)

tests/lib/rules/forbid-component-props.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ ruleTester.run('forbid-component-props', rule, {
104104
options: [{
105105
forbid: [{propName: 'className', allowedFor: ['ReactModal']}]
106106
}]
107+
}, {
108+
code: 'const item = (<AntdLayout.Content className="antdFoo" />);',
109+
options: [{
110+
forbid: [{propName: 'className', allowedFor: ['AntdLayout.Content']}]
111+
}]
112+
}, {
113+
code: 'const item = (<this.ReactModal className="foo" />);',
114+
options: [{
115+
forbid: [{propName: 'className', allowedFor: ['this.ReactModal']}]
116+
}]
107117
}],
108118

109119
invalid: [{

tests/lib/rules/function-component-definition.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,144 @@ ruleTester.run('function-component-definition', rule, {
166166
code: 'function Hello(props): ReactNode { return <p/> }',
167167
options: [{namedComponents: 'function-declaration'}],
168168
parser: parsers.TYPESCRIPT_ESLINT
169+
},
170+
// https://github.com/yannickcr/eslint-plugin-react/issues/2765
171+
{
172+
code: [
173+
'const obj = {',
174+
' serialize: (el) => {',
175+
' return <p/>',
176+
' }',
177+
'}'
178+
].join('\n'),
179+
options: [{namedComponents: 'function-declaration'}]
180+
}, {
181+
code: [
182+
'const obj = {',
183+
' serialize: (el) => {',
184+
' return <p/>',
185+
' }',
186+
'}'
187+
].join('\n'),
188+
options: [{namedComponents: 'arrow-function'}]
189+
}, {
190+
code: [
191+
'const obj = {',
192+
' serialize: (el) => {',
193+
' return <p/>',
194+
' }',
195+
'}'
196+
].join('\n'),
197+
options: [{namedComponents: 'function-expression'}]
198+
},
199+
{
200+
code: [
201+
'const obj = {',
202+
' serialize: function (el) {',
203+
' return <p/>',
204+
' }',
205+
'}'
206+
].join('\n'),
207+
options: [{namedComponents: 'function-declaration'}]
208+
}, {
209+
code: [
210+
'const obj = {',
211+
' serialize: function (el) {',
212+
' return <p/>',
213+
' }',
214+
'}'
215+
].join('\n'),
216+
options: [{namedComponents: 'arrow-function'}]
217+
}, {
218+
code: [
219+
'const obj = {',
220+
' serialize: function (el) {',
221+
' return <p/>',
222+
' }',
223+
'}'
224+
].join('\n'),
225+
options: [{namedComponents: 'function-expression'}]
226+
}, {
227+
code: [
228+
'const obj = {',
229+
' serialize(el) {',
230+
' return <p/>',
231+
' }',
232+
'}'
233+
].join('\n'),
234+
options: [{namedComponents: 'function-declaration'}]
235+
}, {
236+
code: [
237+
'const obj = {',
238+
' serialize(el) {',
239+
' return <p/>',
240+
' }',
241+
'}'
242+
].join('\n'),
243+
options: [{namedComponents: 'arrow-function'}]
244+
}, {
245+
code: [
246+
'const obj = {',
247+
' serialize(el) {',
248+
' return <p/>',
249+
' }',
250+
'}'
251+
].join('\n'),
252+
options: [{namedComponents: 'function-expression'}]
253+
}, {
254+
code: [
255+
'const obj = {',
256+
' serialize(el) {',
257+
' return <p/>',
258+
' }',
259+
'}'
260+
].join('\n'),
261+
options: [{unnamedComponents: 'arrow-function'}]
262+
}, {
263+
code: [
264+
'const obj = {',
265+
' serialize(el) {',
266+
' return <p/>',
267+
' }',
268+
'}'
269+
].join('\n'),
270+
options: [{unnamedComponents: 'function-expression'}]
271+
}, {
272+
code: [
273+
'const obj = {',
274+
' serialize: (el) => {',
275+
' return <p/>',
276+
' }',
277+
'}'
278+
].join('\n'),
279+
options: [{unnamedComponents: 'arrow-function'}]
280+
}, {
281+
code: [
282+
'const obj = {',
283+
' serialize: (el) => {',
284+
' return <p/>',
285+
' }',
286+
'}'
287+
].join('\n'),
288+
options: [{unnamedComponents: 'function-expression'}]
289+
}, {
290+
code: [
291+
'const obj = {',
292+
' serialize: function (el) {',
293+
' return <p/>',
294+
' }',
295+
'}'
296+
].join('\n'),
297+
options: [{unnamedComponents: 'arrow-function'}]
298+
}, {
299+
code: [
300+
'const obj = {',
301+
' serialize: function (el) {',
302+
' return <p/>',
303+
' }',
304+
'}'
305+
].join('\n'),
306+
options: [{unnamedComponents: 'function-expression'}]
169307
}],
170308

171309
invalid: [{

0 commit comments

Comments
 (0)