source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 12.6 KB
Line 
1/**
2 * @fileoverview Enforce stateless components to be written as a pure function
3 * @author Yannick Croissant
4 * @author Alberto Rodríguez
5 * @copyright 2015 Alberto Rodríguez. All rights reserved.
6 */
7
8'use strict';
9
10const values = require('object.values');
11
12const Components = require('../util/Components');
13const testReactVersion = require('../util/version').testReactVersion;
14const astUtil = require('../util/ast');
15const componentUtil = require('../util/componentUtil');
16const docsUrl = require('../util/docsUrl');
17const report = require('../util/report');
18const eslintUtil = require('../util/eslint');
19
20const getScope = eslintUtil.getScope;
21const getText = eslintUtil.getText;
22
23// ------------------------------------------------------------------------------
24// Rule Definition
25// ------------------------------------------------------------------------------
26
27const messages = {
28 componentShouldBePure: 'Component should be written as a pure function',
29};
30
31/** @type {import('eslint').Rule.RuleModule} */
32module.exports = {
33 meta: {
34 docs: {
35 description: 'Enforce stateless components to be written as a pure function',
36 category: 'Stylistic Issues',
37 recommended: false,
38 url: docsUrl('prefer-stateless-function'),
39 },
40
41 messages,
42
43 schema: [{
44 type: 'object',
45 properties: {
46 ignorePureComponents: {
47 default: false,
48 type: 'boolean',
49 },
50 },
51 additionalProperties: false,
52 }],
53 },
54
55 create: Components.detect((context, components, utils) => {
56 const configuration = context.options[0] || {};
57 const ignorePureComponents = configuration.ignorePureComponents || false;
58
59 // --------------------------------------------------------------------------
60 // Public
61 // --------------------------------------------------------------------------
62
63 /**
64 * Checks whether a given array of statements is a single call of `super`.
65 * @see eslint no-useless-constructor rule
66 * @param {ASTNode[]} body - An array of statements to check.
67 * @returns {boolean} `true` if the body is a single call of `super`.
68 */
69 function isSingleSuperCall(body) {
70 return (
71 body.length === 1
72 && body[0].type === 'ExpressionStatement'
73 && body[0].expression.type === 'CallExpression'
74 && body[0].expression.callee.type === 'Super'
75 );
76 }
77
78 /**
79 * Checks whether a given node is a pattern which doesn't have any side effects.
80 * Default parameters and Destructuring parameters can have side effects.
81 * @see eslint no-useless-constructor rule
82 * @param {ASTNode} node - A pattern node.
83 * @returns {boolean} `true` if the node doesn't have any side effects.
84 */
85 function isSimple(node) {
86 return node.type === 'Identifier' || node.type === 'RestElement';
87 }
88
89 /**
90 * Checks whether a given array of expressions is `...arguments` or not.
91 * `super(...arguments)` passes all arguments through.
92 * @see eslint no-useless-constructor rule
93 * @param {ASTNode[]} superArgs - An array of expressions to check.
94 * @returns {boolean} `true` if the superArgs is `...arguments`.
95 */
96 function isSpreadArguments(superArgs) {
97 return (
98 superArgs.length === 1
99 && superArgs[0].type === 'SpreadElement'
100 && superArgs[0].argument.type === 'Identifier'
101 && superArgs[0].argument.name === 'arguments'
102 );
103 }
104
105 /**
106 * Checks whether given 2 nodes are identifiers which have the same name or not.
107 * @see eslint no-useless-constructor rule
108 * @param {ASTNode} ctorParam - A node to check.
109 * @param {ASTNode} superArg - A node to check.
110 * @returns {boolean} `true` if the nodes are identifiers which have the same
111 * name.
112 */
113 function isValidIdentifierPair(ctorParam, superArg) {
114 return (
115 ctorParam.type === 'Identifier'
116 && superArg.type === 'Identifier'
117 && ctorParam.name === superArg.name
118 );
119 }
120
121 /**
122 * Checks whether given 2 nodes are a rest/spread pair which has the same values.
123 * @see eslint no-useless-constructor rule
124 * @param {ASTNode} ctorParam - A node to check.
125 * @param {ASTNode} superArg - A node to check.
126 * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
127 * same values.
128 */
129 function isValidRestSpreadPair(ctorParam, superArg) {
130 return (
131 ctorParam.type === 'RestElement'
132 && superArg.type === 'SpreadElement'
133 && isValidIdentifierPair(ctorParam.argument, superArg.argument)
134 );
135 }
136
137 /**
138 * Checks whether given 2 nodes have the same value or not.
139 * @see eslint no-useless-constructor rule
140 * @param {ASTNode} ctorParam - A node to check.
141 * @param {ASTNode} superArg - A node to check.
142 * @returns {boolean} `true` if the nodes have the same value or not.
143 */
144 function isValidPair(ctorParam, superArg) {
145 return (
146 isValidIdentifierPair(ctorParam, superArg)
147 || isValidRestSpreadPair(ctorParam, superArg)
148 );
149 }
150
151 /**
152 * Checks whether the parameters of a constructor and the arguments of `super()`
153 * have the same values or not.
154 * @see eslint no-useless-constructor rule
155 * @param {ASTNode[]} ctorParams - The parameters of a constructor to check.
156 * @param {ASTNode} superArgs - The arguments of `super()` to check.
157 * @returns {boolean} `true` if those have the same values.
158 */
159 function isPassingThrough(ctorParams, superArgs) {
160 if (ctorParams.length !== superArgs.length) {
161 return false;
162 }
163
164 for (let i = 0; i < ctorParams.length; ++i) {
165 if (!isValidPair(ctorParams[i], superArgs[i])) {
166 return false;
167 }
168 }
169
170 return true;
171 }
172
173 /**
174 * Checks whether the constructor body is a redundant super call.
175 * @see eslint no-useless-constructor rule
176 * @param {Array} body - constructor body content.
177 * @param {Array} ctorParams - The params to check against super call.
178 * @returns {boolean} true if the constructor body is redundant
179 */
180 function isRedundantSuperCall(body, ctorParams) {
181 return (
182 isSingleSuperCall(body)
183 && ctorParams.every(isSimple)
184 && (
185 isSpreadArguments(body[0].expression.arguments)
186 || isPassingThrough(ctorParams, body[0].expression.arguments)
187 )
188 );
189 }
190
191 /**
192 * Check if a given AST node have any other properties the ones available in stateless components
193 * @param {ASTNode} node The AST node being checked.
194 * @returns {Boolean} True if the node has at least one other property, false if not.
195 */
196 function hasOtherProperties(node) {
197 const properties = astUtil.getComponentProperties(node);
198 return properties.some((property) => {
199 const name = astUtil.getPropertyName(property);
200 const isDisplayName = name === 'displayName';
201 const isPropTypes = name === 'propTypes' || ((name === 'props') && property.typeAnnotation);
202 const contextTypes = name === 'contextTypes';
203 const defaultProps = name === 'defaultProps';
204 const isUselessConstructor = property.kind === 'constructor'
205 && !!property.value.body
206 && isRedundantSuperCall(property.value.body.body, property.value.params);
207 const isRender = name === 'render';
208 return !isDisplayName && !isPropTypes && !contextTypes && !defaultProps && !isUselessConstructor && !isRender;
209 });
210 }
211
212 /**
213 * Mark component as pure as declared
214 * @param {ASTNode} node The AST node being checked.
215 */
216 function markSCUAsDeclared(node) {
217 components.set(node, {
218 hasSCU: true,
219 });
220 }
221
222 /**
223 * Mark childContextTypes as declared
224 * @param {ASTNode} node The AST node being checked.
225 */
226 function markChildContextTypesAsDeclared(node) {
227 components.set(node, {
228 hasChildContextTypes: true,
229 });
230 }
231
232 /**
233 * Mark a setState as used
234 * @param {ASTNode} node The AST node being checked.
235 */
236 function markThisAsUsed(node) {
237 components.set(node, {
238 useThis: true,
239 });
240 }
241
242 /**
243 * Mark a props or context as used
244 * @param {ASTNode} node The AST node being checked.
245 */
246 function markPropsOrContextAsUsed(node) {
247 components.set(node, {
248 usePropsOrContext: true,
249 });
250 }
251
252 /**
253 * Mark a ref as used
254 * @param {ASTNode} node The AST node being checked.
255 */
256 function markRefAsUsed(node) {
257 components.set(node, {
258 useRef: true,
259 });
260 }
261
262 /**
263 * Mark return as invalid
264 * @param {ASTNode} node The AST node being checked.
265 */
266 function markReturnAsInvalid(node) {
267 components.set(node, {
268 invalidReturn: true,
269 });
270 }
271
272 /**
273 * Mark a ClassDeclaration as having used decorators
274 * @param {ASTNode} node The AST node being checked.
275 */
276 function markDecoratorsAsUsed(node) {
277 components.set(node, {
278 useDecorators: true,
279 });
280 }
281
282 function visitClass(node) {
283 if (ignorePureComponents && componentUtil.isPureComponent(node, context)) {
284 markSCUAsDeclared(node);
285 }
286
287 if (node.decorators && node.decorators.length) {
288 markDecoratorsAsUsed(node);
289 }
290 }
291
292 return {
293 ClassDeclaration: visitClass,
294 ClassExpression: visitClass,
295
296 // Mark `this` destructuring as a usage of `this`
297 VariableDeclarator(node) {
298 // Ignore destructuring on other than `this`
299 if (!node.id || node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'ThisExpression') {
300 return;
301 }
302 // Ignore `props` and `context`
303 const useThis = node.id.properties.some((property) => {
304 const name = astUtil.getPropertyName(property);
305 return name !== 'props' && name !== 'context';
306 });
307 if (!useThis) {
308 markPropsOrContextAsUsed(node);
309 return;
310 }
311 markThisAsUsed(node);
312 },
313
314 // Mark `this` usage
315 MemberExpression(node) {
316 if (node.object.type !== 'ThisExpression') {
317 if (node.property && node.property.name === 'childContextTypes') {
318 const component = utils.getRelatedComponent(node);
319 if (!component) {
320 return;
321 }
322 markChildContextTypesAsDeclared(component.node);
323 }
324 return;
325 // Ignore calls to `this.props` and `this.context`
326 }
327 if (
328 (node.property.name || node.property.value) === 'props'
329 || (node.property.name || node.property.value) === 'context'
330 ) {
331 markPropsOrContextAsUsed(node);
332 return;
333 }
334 markThisAsUsed(node);
335 },
336
337 // Mark `ref` usage
338 JSXAttribute(node) {
339 const name = getText(context, node.name);
340 if (name !== 'ref') {
341 return;
342 }
343 markRefAsUsed(node);
344 },
345
346 // Mark `render` that do not return some JSX
347 ReturnStatement(node) {
348 let blockNode;
349 let scope = getScope(context, node);
350 while (scope) {
351 blockNode = scope.block && scope.block.parent;
352 if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) {
353 break;
354 }
355 scope = scope.upper;
356 }
357 const isRender = blockNode && blockNode.key && blockNode.key.name === 'render';
358 const allowNull = testReactVersion(context, '>= 15.0.0'); // Stateless components can return null since React 15
359 const isReturningJSX = utils.isReturningJSX(node, !allowNull);
360 const isReturningNull = node.argument && (node.argument.value === null || node.argument.value === false);
361 if (
362 !isRender
363 || (allowNull && (isReturningJSX || isReturningNull))
364 || (!allowNull && isReturningJSX)
365 ) {
366 return;
367 }
368 markReturnAsInvalid(node);
369 },
370
371 'Program:exit'() {
372 const list = components.list();
373 values(list)
374 .filter((component) => (
375 !hasOtherProperties(component.node)
376 && !component.useThis
377 && !component.useRef
378 && !component.invalidReturn
379 && !component.hasChildContextTypes
380 && !component.useDecorators
381 && !component.hasSCU
382 && (
383 componentUtil.isES5Component(component.node, context)
384 || componentUtil.isES6Component(component.node, context)
385 )
386 ))
387 .forEach((component) => {
388 report(context, messages.componentShouldBePure, 'componentShouldBePure', {
389 node: component.node,
390 });
391 });
392 },
393 };
394 }),
395};
Note: See TracBrowser for help on using the repository browser.