Skip to content

Commit eb81897

Browse files
committed
[Fix] no-arrow-function-lifecycle: fix invalid autofix from a concise arrow method to a regular one
Fixes #3145
1 parent 8f00023 commit eb81897

File tree

3 files changed

+104
-17
lines changed

3 files changed

+104
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
99
* [`no-invalid-html-attribute`]: allow `link` `rel` to have `apple-touch-icon`, `mask-icon` ([#3132][] @ljharb)
1010
* [`no-unused-class-component-methods`]: add `getChildContext` lifecycle method ([#3136][] @yoyo837)
1111
* [`prop-types`]: fix false positives on renames in object destructuring ([#3142][] @golopot)
12+
* [`no-arrow-function-lifecycle`]: fix invalid autofix from a concise arrow method to a regular one ([#3145][] @ljharb)
1213

1314
### Changed
1415
* [readme] fix syntax typo ([#3141][] @moselhy)
1516

17+
[#3145]: https://github.com/yannickcr/eslint-plugin-react/issue/3145
1618
[#3142]: https://github.com/yannickcr/eslint-plugin-react/pull/3142
1719
[#3141]: https://github.com/yannickcr/eslint-plugin-react/pull/3141
1820
[#3136]: https://github.com/yannickcr/eslint-plugin-react/pull/3136

lib/rules/no-arrow-function-lifecycle.js

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ const astUtil = require('../util/ast');
1212
const docsUrl = require('../util/docsUrl');
1313
const lifecycleMethods = require('../util/lifecycleMethods');
1414

15+
function getText(node) {
16+
const params = node.value.params.map((p) => p.name);
17+
18+
if (node.type === 'Property') {
19+
return `: function(${params.join(', ')}) `;
20+
}
21+
22+
if (node.type === 'ClassProperty' || node.type === 'PropertyDefinition') {
23+
return `(${params.join(', ')}) `;
24+
}
25+
26+
return null;
27+
}
28+
1529
module.exports = {
1630
meta: {
1731
docs: {
@@ -25,20 +39,6 @@ module.exports = {
2539
},
2640

2741
create: Components.detect((context, components, utils) => {
28-
function getText(node) {
29-
const params = node.value.params.map((p) => p.name);
30-
31-
if (node.type === 'Property') {
32-
return `: function(${params.join(', ')}) `;
33-
}
34-
35-
if (node.type === 'ClassProperty' || node.type === 'PropertyDefinition') {
36-
return `(${params.join(', ')}) `;
37-
}
38-
39-
return null;
40-
}
41-
4242
/**
4343
* @param {Array} properties list of component properties
4444
*/
@@ -57,16 +57,63 @@ module.exports = {
5757
).indexOf(propertyName) > -1;
5858

5959
if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) {
60-
const range = [node.key.range[1], node.value.body.range[0]];
61-
const text = getText(node);
60+
const body = node.value.body;
61+
const isBlockBody = body.type === 'BlockStatement';
62+
const sourceCode = context.getSourceCode();
63+
64+
let nextComment = [];
65+
let previousComment = [];
66+
let bodyRange;
67+
if (!isBlockBody) {
68+
const previousToken = sourceCode.getTokenBefore(body);
69+
70+
if (sourceCode.getCommentsBefore) {
71+
// eslint >=4.x
72+
previousComment = sourceCode.getCommentsBefore(body);
73+
} else {
74+
// eslint 3.x
75+
const potentialComment = sourceCode.getTokenBefore(body, { includeComments: true });
76+
previousComment = previousToken === potentialComment ? [] : [potentialComment];
77+
}
78+
79+
if (sourceCode.getCommentsAfter) {
80+
// eslint >=4.x
81+
nextComment = sourceCode.getCommentsAfter(body);
82+
} else {
83+
// eslint 3.x
84+
const potentialComment = sourceCode.getTokenAfter(body, { includeComments: true });
85+
const nextToken = sourceCode.getTokenAfter(body);
86+
nextComment = nextToken === potentialComment ? [] : [potentialComment];
87+
}
88+
bodyRange = [
89+
(previousComment.length > 0 ? previousComment[0] : body).range[0],
90+
(nextComment.length > 0 ? nextComment[nextComment.length - 1] : body).range[1],
91+
];
92+
}
93+
const headRange = [
94+
node.key.range[1],
95+
(previousComment.length > 0 ? previousComment[0] : body).range[0],
96+
];
6297

6398
context.report({
6499
node,
65100
message: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.',
66101
data: {
67102
propertyName,
68103
},
69-
fix: (fixer) => fixer.replaceTextRange(range, text),
104+
fix(fixer) {
105+
if (!sourceCode.getCommentsAfter) {
106+
// eslint 3.x
107+
return isBlockBody && fixer.replaceTextRange(headRange, getText(node));
108+
}
109+
return [].concat(
110+
fixer.replaceTextRange(headRange, getText(node)),
111+
isBlockBody ? [] : fixer.replaceTextRange(
112+
bodyRange,
113+
`{ return ${previousComment.map((x) => sourceCode.getText(x)).join('')}${sourceCode.getText(body)}${nextComment.map((x) => sourceCode.getText(x)).join('')}; }`
114+
)
115+
);
116+
},
70117
});
71118
}
72119
});

tests/lib/rules/no-arrow-function-lifecycle.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
'use strict';
77

8+
const semver = require('semver');
89
const RuleTester = require('eslint').RuleTester;
10+
const eslintPkg = require('eslint/package.json');
911
const rule = require('../../../lib/rules/no-arrow-function-lifecycle');
1012

1113
const parsers = require('../../helpers/parsers');
@@ -949,5 +951,41 @@ ruleTester.run('no-arrow-function-lifecycle', rule, {
949951
}
950952
`,
951953
},
954+
{
955+
code: `
956+
class Hello extends React.Component {
957+
render = () => <div />
958+
}
959+
`,
960+
features: ['class fields'],
961+
errors: [{ message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' }],
962+
output: semver.satisfies(eslintPkg.version, '> 3') ? `
963+
class Hello extends React.Component {
964+
render() { return <div />; }
965+
}
966+
` : `
967+
class Hello extends React.Component {
968+
render = () => <div />
969+
}
970+
`,
971+
},
972+
{
973+
code: `
974+
class Hello extends React.Component {
975+
render = () => /*first*/<div />/*second*/
976+
}
977+
`,
978+
features: ['class fields'],
979+
errors: [{ message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' }],
980+
output: semver.satisfies(eslintPkg.version, '> 3') ? `
981+
class Hello extends React.Component {
982+
render() { return /*first*/<div />/*second*/; }
983+
}
984+
` : `
985+
class Hello extends React.Component {
986+
render = () => /*first*/<div />/*second*/
987+
}
988+
`,
989+
},
952990
]),
953991
});

0 commit comments

Comments
 (0)