Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] function-component-definition: support namedComponents option being an array #3129

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
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
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
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
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' }],
},
]),
});