Skip to content

Commit e3d3525

Browse files
petersendiditljharb
authored andcommitted
[New] function-component-definition: support namedComponents option being an array
This adds support to the `function-component-definition` rule to have the `namedComponents` rule be an array.
1 parent d5bf8d9 commit e3d3525

File tree

4 files changed

+143
-26
lines changed

4 files changed

+143
-26
lines changed

CHANGELOG.md

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

66
## Unreleased
77

8+
### Added
9+
* [`function-component-definition`]: support namedComponents option being an array ([#3129][] @petersendidit)
10+
811
### Changed
912
* [Refactor] [`no-arrow-function-lifecycle`], [`no-unused-class-component-methods`]: use report/messages convention (@ljharb)
1013
* [Tests] component detection: Add testing scaffolding ([#3149][] @duncanbeevers)
1114
* [New] component detection: track React imports ([#3149][] @duncanbeevers)
1215

1316
[#3149]: https://github.com/yannickcr/eslint-plugin-react/pull/3149
17+
[#3129]: https://github.com/yannickcr/eslint-plugin-react/pull/3129
1418

1519
## [7.27.1] - 2021.11.18
1620

docs/rules/function-component-definition.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ function getComponent() {
3131

3232
## Rule Options
3333

34-
This rule takes an options object as a second parameter where the preferred function type for components can be specified. The first property of the options object is `"namedComponents"` which can be `"function-declaration"`, `"function-expression"`, or `"arrow-function"` and has `'function-declaration'` as its default. The second property is `"unnamedComponents"` that can be either `"function-expression"` or `"arrow-function"`, and has `'function-expression'` as its default.
34+
This rule takes an options object as a second parameter where the preferred function type for components can be specified.
35+
The first property of the options object is `"namedComponents"` which can be `"function-declaration"`, `"function-expression"`, `"arrow-function"`, or an array containing any of those, and has `'function-declaration'` as its default.
36+
The second property is `"unnamedComponents"` that can be either `"function-expression"`, `"arrow-function"`, or an array containing any of those, and has `'function-expression'` as its default.
3537

3638
```js
3739
...
3840
"react/function-component-definition": [<enabled>, {
39-
"namedComponents": "function-declaration" | "function-expression" | "arrow-function",
40-
"unnamedComponents": "function-expression" | "arrow-function"
41+
"namedComponents": "function-declaration" | "function-expression" | "arrow-function" | Array<"function-declaration" | "function-expression" | "arrow-function">,
42+
"unnamedComponents": "function-expression" | "arrow-function" | Array<"function-expression" | "arrow-function">
4143
}]
4244
...
4345
```

lib/rules/function-component-definition.js

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
'use strict';
77

8+
const arrayIncludes = require('array-includes');
89
const Components = require('../util/Components');
910
const docsUrl = require('../util/docsUrl');
1011
const reportC = require('../util/report');
@@ -109,24 +110,44 @@ module.exports = {
109110

110111
messages,
111112

112-
schema: [{
113-
type: 'object',
114-
properties: {
115-
namedComponents: {
116-
enum: ['function-declaration', 'arrow-function', 'function-expression'],
117-
},
118-
unnamedComponents: {
119-
enum: ['arrow-function', 'function-expression'],
113+
schema: [
114+
{
115+
type: 'object',
116+
properties: {
117+
namedComponents: {
118+
oneOf: [
119+
{ enum: ['function-declaration', 'arrow-function', 'function-expression'] },
120+
{
121+
type: 'array',
122+
items: {
123+
type: 'string',
124+
enum: ['function-declaration', 'arrow-function', 'function-expression'],
125+
},
126+
},
127+
],
128+
},
129+
unnamedComponents: {
130+
oneOf: [
131+
{ enum: ['arrow-function', 'function-expression'] },
132+
{
133+
type: 'array',
134+
items: {
135+
type: 'string',
136+
enum: ['arrow-function', 'function-expression'],
137+
},
138+
},
139+
],
140+
},
120141
},
121142
},
122-
}],
143+
],
123144
},
124145

125146
create: Components.detect((context, components) => {
126147
const configuration = context.options[0] || {};
127148

128-
const namedConfig = configuration.namedComponents || 'function-declaration';
129-
const unnamedConfig = configuration.unnamedComponents || 'function-expression';
149+
const namedConfig = [].concat(configuration.namedComponents || 'function-declaration');
150+
const unnamedConfig = [].concat(configuration.unnamedComponents || 'function-expression');
130151

131152
function getFixer(node, options) {
132153
const sourceCode = context.getSourceCode();
@@ -161,24 +182,24 @@ module.exports = {
161182

162183
if (node.parent && node.parent.type === 'Property') return;
163184

164-
if (hasName(node) && namedConfig !== functionType) {
185+
if (hasName(node) && !arrayIncludes(namedConfig, functionType)) {
165186
report(node, {
166-
messageId: namedConfig,
187+
messageId: namedConfig[0],
167188
fixerOptions: {
168-
type: namedConfig,
169-
template: NAMED_FUNCTION_TEMPLATES[namedConfig],
189+
type: namedConfig[0],
190+
template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]],
170191
range: node.type === 'FunctionDeclaration'
171192
? node.range
172193
: node.parent.parent.range,
173194
},
174195
});
175196
}
176-
if (!hasName(node) && unnamedConfig !== functionType) {
197+
if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) {
177198
report(node, {
178-
messageId: unnamedConfig,
199+
messageId: unnamedConfig[0],
179200
fixerOptions: {
180-
type: unnamedConfig,
181-
template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig],
201+
type: unnamedConfig[0],
202+
template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]],
182203
range: node.range,
183204
},
184205
});

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

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ ruleTester.run('function-component-definition', rule, {
7878
options: [{ namedComponents: 'function-declaration' }],
7979
},
8080
{
81-
// shouldn't trigger this rule since functions stating with a lowercase
82-
// letter are not considered components
81+
// shouldn't trigger this rule since functions stating with a lowercase
82+
// letter are not considered components
8383
code: `
8484
const selectAvatarByUserId = (state, id) => {
8585
const user = selectUserById(state, id)
@@ -89,8 +89,8 @@ ruleTester.run('function-component-definition', rule, {
8989
options: [{ namedComponents: 'function-declaration' }],
9090
},
9191
{
92-
// shouldn't trigger this rule since functions stating with a lowercase
93-
// letter are not considered components
92+
// shouldn't trigger this rule since functions stating with a lowercase
93+
// letter are not considered components
9494
code: `
9595
function ensureValidSourceType(sourceType: string) {
9696
switch (sourceType) {
@@ -346,6 +346,54 @@ ruleTester.run('function-component-definition', rule, {
346346
`,
347347
options: [{ unnamedComponents: 'function-expression' }],
348348
},
349+
350+
{
351+
code: 'function Hello(props) { return <div/> }',
352+
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
353+
},
354+
{
355+
code: 'var Hello = function(props) { return <div/> }',
356+
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
357+
},
358+
{
359+
code: 'var Foo = React.memo(function Foo() { return <p/> })',
360+
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
361+
},
362+
{
363+
code: 'function Hello(props: Test) { return <p/> }',
364+
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
365+
features: ['types'],
366+
},
367+
{
368+
code: 'var Hello = function(props: Test) { return <p/> }',
369+
options: [{ namedComponents: ['function-expression', 'function-expression'] }],
370+
features: ['types'],
371+
},
372+
{
373+
code: 'var Hello = (props: Test) => { return <p/> }',
374+
options: [{ namedComponents: ['arrow-function', 'function-expression'] }],
375+
features: ['types'],
376+
},
377+
{
378+
code: `
379+
function wrap(Component) {
380+
return function(props) {
381+
return <div><Component {...props}/></div>;
382+
};
383+
}
384+
`,
385+
options: [{ unnamedComponents: ['arrow-function', 'function-expression'] }],
386+
},
387+
{
388+
code: `
389+
function wrap(Component) {
390+
return (props) => {
391+
return <div><Component {...props}/></div>;
392+
};
393+
}
394+
`,
395+
options: [{ unnamedComponents: ['arrow-function', 'function-expression'] }],
396+
},
349397
]),
350398

351399
invalid: parsers.all([
@@ -879,5 +927,47 @@ ruleTester.run('function-component-definition', rule, {
879927
options: [{ unnamedComponents: 'arrow-function' }],
880928
errors: [{ messageId: 'arrow-function' }],
881929
},
930+
{
931+
code: `
932+
function Hello(props) {
933+
return <div/>;
934+
}
935+
`,
936+
output: `
937+
var Hello = (props) => {
938+
return <div/>;
939+
}
940+
`,
941+
options: [{ namedComponents: ['arrow-function', 'function-expression'] }],
942+
errors: [{ messageId: 'arrow-function' }],
943+
},
944+
{
945+
code: `
946+
var Hello = (props) => {
947+
return <div/>;
948+
};
949+
`,
950+
output: `
951+
function Hello(props) {
952+
return <div/>;
953+
}
954+
`,
955+
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
956+
errors: [{ messageId: 'function-declaration' }],
957+
},
958+
{
959+
code: `
960+
var Hello = (props) => {
961+
return <div/>;
962+
};
963+
`,
964+
output: `
965+
var Hello = function(props) {
966+
return <div/>;
967+
}
968+
`,
969+
options: [{ namedComponents: ['function-expression', 'function-declaration'] }],
970+
errors: [{ messageId: 'function-expression' }],
971+
},
882972
]),
883973
});

0 commit comments

Comments
 (0)