Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

prefer-stateless-function.js 12 KiB

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