no-arrow-function-lifecycle.js
4.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* @fileoverview Lifecycle methods should be methods on the prototype, not class fields
* @author Tan Nguyen
*/
'use strict';
const values = require('object.values');
const Components = require('../util/Components');
const astUtil = require('../util/ast');
const componentUtil = require('../util/componentUtil');
const docsUrl = require('../util/docsUrl');
const lifecycleMethods = require('../util/lifecycleMethods');
const report = require('../util/report');
function getText(node) {
const params = node.value.params.map((p) => p.name);
if (node.type === 'Property') {
return `: function(${params.join(', ')}) `;
}
if (node.type === 'ClassProperty' || node.type === 'PropertyDefinition') {
return `(${params.join(', ')}) `;
}
return null;
}
const messages = {
lifecycle: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.',
};
module.exports = {
meta: {
docs: {
description: 'Lifecycle methods should be methods on the prototype, not class fields',
category: 'Best Practices',
recommended: false,
url: docsUrl('no-arrow-function-lifecycle'),
},
messages,
schema: [],
fixable: 'code',
},
create: Components.detect((context, components) => {
/**
* @param {Array} properties list of component properties
*/
function reportNoArrowFunctionLifecycle(properties) {
properties.forEach((node) => {
if (!node || !node.value) {
return;
}
const propertyName = astUtil.getPropertyName(node);
const nodeType = node.value.type;
const isLifecycleMethod = (
node.static && !componentUtil.isES5Component(node, context)
? lifecycleMethods.static
: lifecycleMethods.instance
).indexOf(propertyName) > -1;
if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) {
const body = node.value.body;
const isBlockBody = body.type === 'BlockStatement';
const sourceCode = context.getSourceCode();
let nextComment = [];
let previousComment = [];
let bodyRange;
if (!isBlockBody) {
const previousToken = sourceCode.getTokenBefore(body);
if (sourceCode.getCommentsBefore) {
// eslint >=4.x
previousComment = sourceCode.getCommentsBefore(body);
} else {
// eslint 3.x
const potentialComment = sourceCode.getTokenBefore(body, { includeComments: true });
previousComment = previousToken === potentialComment ? [] : [potentialComment];
}
if (sourceCode.getCommentsAfter) {
// eslint >=4.x
nextComment = sourceCode.getCommentsAfter(body);
} else {
// eslint 3.x
const potentialComment = sourceCode.getTokenAfter(body, { includeComments: true });
const nextToken = sourceCode.getTokenAfter(body);
nextComment = nextToken === potentialComment ? [] : [potentialComment];
}
bodyRange = [
(previousComment.length > 0 ? previousComment[0] : body).range[0],
(nextComment.length > 0 ? nextComment[nextComment.length - 1] : body).range[1]
+ (node.value.body.type === 'ObjectExpression' ? 1 : 0), // to account for a wrapped end paren
];
}
const headRange = [
node.key.range[1],
(previousComment.length > 0 ? previousComment[0] : body).range[0],
];
const hasSemi = node.value.expression && sourceCode.getText(node).slice(node.value.range[1] - node.range[0]) === ';';
report(
context,
messages.lifecycle,
'lifecycle',
{
node,
data: {
propertyName,
},
fix(fixer) {
if (!sourceCode.getCommentsAfter) {
// eslint 3.x
return isBlockBody && fixer.replaceTextRange(headRange, getText(node));
}
return [].concat(
fixer.replaceTextRange(headRange, getText(node)),
isBlockBody ? [] : fixer.replaceTextRange(
[bodyRange[0], bodyRange[1] + (hasSemi ? 1 : 0)],
`{ return ${previousComment.map((x) => sourceCode.getText(x)).join('')}${sourceCode.getText(body)}${nextComment.map((x) => sourceCode.getText(x)).join('')}; }`
)
);
},
}
);
}
});
}
return {
'Program:exit'() {
values(components.list()).forEach((component) => {
const properties = astUtil.getComponentProperties(component.node);
reportNoArrowFunctionLifecycle(properties);
});
},
};
}),
};