Skip to content

Commit

Permalink
[New] function-component-definition: support namedComponents option…
Browse files Browse the repository at this point in the history
… being an array

This adds support to the `function-component-definition`  rule to have the `namedComponents` rule be an array.
  • Loading branch information
petersendidit authored and ljharb committed Nov 11, 2021
1 parent d5bf8d9 commit e3d3525
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 26 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel

## Unreleased

### Added
* [`function-component-definition`]: support namedComponents option being an array ([#3129][] @petersendidit)

### Changed
* [Refactor] [`no-arrow-function-lifecycle`], [`no-unused-class-component-methods`]: use report/messages convention (@ljharb)
* [Tests] component detection: Add testing scaffolding ([#3149][] @duncanbeevers)
* [New] component detection: track React imports ([#3149][] @duncanbeevers)

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

## [7.27.1] - 2021.11.18

Expand Down
8 changes: 5 additions & 3 deletions docs/rules/function-component-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ function getComponent() {

## Rule Options

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.
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"`, `"arrow-function"`, or an array containing any of those, and has `'function-declaration'` as its default.
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.

```js
...
"react/function-component-definition": [<enabled>, {
"namedComponents": "function-declaration" | "function-expression" | "arrow-function",
"unnamedComponents": "function-expression" | "arrow-function"
"namedComponents": "function-declaration" | "function-expression" | "arrow-function" | Array<"function-declaration" | "function-expression" | "arrow-function">,
"unnamedComponents": "function-expression" | "arrow-function" | Array<"function-expression" | "arrow-function">
}]
...
```
Expand Down
59 changes: 40 additions & 19 deletions lib/rules/function-component-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

'use strict';

const arrayIncludes = require('array-includes');
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const reportC = require('../util/report');
Expand Down Expand Up @@ -109,24 +110,44 @@ module.exports = {

messages,

schema: [{
type: 'object',
properties: {
namedComponents: {
enum: ['function-declaration', 'arrow-function', 'function-expression'],
},
unnamedComponents: {
enum: ['arrow-function', 'function-expression'],
schema: [
{
type: 'object',
properties: {
namedComponents: {
oneOf: [
{ enum: ['function-declaration', 'arrow-function', 'function-expression'] },
{
type: 'array',
items: {
type: 'string',
enum: ['function-declaration', 'arrow-function', 'function-expression'],
},
},
],
},
unnamedComponents: {
oneOf: [
{ enum: ['arrow-function', 'function-expression'] },
{
type: 'array',
items: {
type: 'string',
enum: ['arrow-function', 'function-expression'],
},
},
],
},
},
},
}],
],
},

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

const namedConfig = configuration.namedComponents || 'function-declaration';
const unnamedConfig = configuration.unnamedComponents || 'function-expression';
const namedConfig = [].concat(configuration.namedComponents || 'function-declaration');
const unnamedConfig = [].concat(configuration.unnamedComponents || 'function-expression');

function getFixer(node, options) {
const sourceCode = context.getSourceCode();
Expand Down Expand Up @@ -161,24 +182,24 @@ module.exports = {

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

if (hasName(node) && namedConfig !== functionType) {
if (hasName(node) && !arrayIncludes(namedConfig, functionType)) {
report(node, {
messageId: namedConfig,
messageId: namedConfig[0],
fixerOptions: {
type: namedConfig,
template: NAMED_FUNCTION_TEMPLATES[namedConfig],
type: namedConfig[0],
template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]],
range: node.type === 'FunctionDeclaration'
? node.range
: node.parent.parent.range,
},
});
}
if (!hasName(node) && unnamedConfig !== functionType) {
if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) {
report(node, {
messageId: unnamedConfig,
messageId: unnamedConfig[0],
fixerOptions: {
type: unnamedConfig,
template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig],
type: unnamedConfig[0],
template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]],
range: node.range,
},
});
Expand Down
98 changes: 94 additions & 4 deletions tests/lib/rules/function-component-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ ruleTester.run('function-component-definition', rule, {
options: [{ namedComponents: 'function-declaration' }],
},
{
// shouldn't trigger this rule since functions stating with a lowercase
// letter are not considered components
// shouldn't trigger this rule since functions stating with a lowercase
// letter are not considered components
code: `
const selectAvatarByUserId = (state, id) => {
const user = selectUserById(state, id)
Expand All @@ -89,8 +89,8 @@ ruleTester.run('function-component-definition', rule, {
options: [{ namedComponents: 'function-declaration' }],
},
{
// shouldn't trigger this rule since functions stating with a lowercase
// letter are not considered components
// shouldn't trigger this rule since functions stating with a lowercase
// letter are not considered components
code: `
function ensureValidSourceType(sourceType: string) {
switch (sourceType) {
Expand Down Expand Up @@ -346,6 +346,54 @@ ruleTester.run('function-component-definition', rule, {
`,
options: [{ unnamedComponents: 'function-expression' }],
},

{
code: 'function Hello(props) { return <div/> }',
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
},
{
code: 'var Hello = function(props) { return <div/> }',
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
},
{
code: 'var Foo = React.memo(function Foo() { return <p/> })',
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
},
{
code: 'function Hello(props: Test) { return <p/> }',
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
features: ['types'],
},
{
code: 'var Hello = function(props: Test) { return <p/> }',
options: [{ namedComponents: ['function-expression', 'function-expression'] }],
features: ['types'],
},
{
code: 'var Hello = (props: Test) => { return <p/> }',
options: [{ namedComponents: ['arrow-function', 'function-expression'] }],
features: ['types'],
},
{
code: `
function wrap(Component) {
return function(props) {
return <div><Component {...props}/></div>;
};
}
`,
options: [{ unnamedComponents: ['arrow-function', 'function-expression'] }],
},
{
code: `
function wrap(Component) {
return (props) => {
return <div><Component {...props}/></div>;
};
}
`,
options: [{ unnamedComponents: ['arrow-function', 'function-expression'] }],
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -879,5 +927,47 @@ ruleTester.run('function-component-definition', rule, {
options: [{ unnamedComponents: 'arrow-function' }],
errors: [{ messageId: 'arrow-function' }],
},
{
code: `
function Hello(props) {
return <div/>;
}
`,
output: `
var Hello = (props) => {
return <div/>;
}
`,
options: [{ namedComponents: ['arrow-function', 'function-expression'] }],
errors: [{ messageId: 'arrow-function' }],
},
{
code: `
var Hello = (props) => {
return <div/>;
};
`,
output: `
function Hello(props) {
return <div/>;
}
`,
options: [{ namedComponents: ['function-declaration', 'function-expression'] }],
errors: [{ messageId: 'function-declaration' }],
},
{
code: `
var Hello = (props) => {
return <div/>;
};
`,
output: `
var Hello = function(props) {
return <div/>;
}
`,
options: [{ namedComponents: ['function-expression', 'function-declaration'] }],
errors: [{ messageId: 'function-expression' }],
},
]),
});

0 comments on commit e3d3525

Please sign in to comment.