makeNoMethodSetStateRule.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. /**
  2. * @fileoverview Prevent usage of setState in lifecycle methods
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const docsUrl = require('./docsUrl');
  7. // ------------------------------------------------------------------------------
  8. // Rule Definition
  9. // ------------------------------------------------------------------------------
  10. function mapTitle(methodName) {
  11. const map = {
  12. componentDidMount: 'did-mount',
  13. componentDidUpdate: 'did-update',
  14. componentWillUpdate: 'will-update'
  15. };
  16. const title = map[methodName];
  17. if (!title) {
  18. throw Error(`No docsUrl for '${methodName}'`);
  19. }
  20. return `no-${title}-set-state`;
  21. }
  22. function makeNoMethodSetStateRule(methodName, shouldCheckUnsafeCb) {
  23. return {
  24. meta: {
  25. docs: {
  26. description: `Prevent usage of setState in ${methodName}`,
  27. category: 'Best Practices',
  28. recommended: false,
  29. url: docsUrl(mapTitle(methodName))
  30. },
  31. messages: {
  32. noSetState: 'Do not use setState in {{name}}'
  33. },
  34. schema: [{
  35. enum: ['disallow-in-func']
  36. }]
  37. },
  38. create(context) {
  39. const mode = context.options[0] || 'allow-in-func';
  40. function nameMatches(name) {
  41. if (name === methodName) {
  42. return true;
  43. }
  44. if (typeof shouldCheckUnsafeCb === 'function' && shouldCheckUnsafeCb(context)) {
  45. return name === `UNSAFE_${methodName}`;
  46. }
  47. return false;
  48. }
  49. // --------------------------------------------------------------------------
  50. // Public
  51. // --------------------------------------------------------------------------
  52. return {
  53. CallExpression(node) {
  54. const callee = node.callee;
  55. if (
  56. callee.type !== 'MemberExpression'
  57. || callee.object.type !== 'ThisExpression'
  58. || callee.property.name !== 'setState'
  59. ) {
  60. return;
  61. }
  62. const ancestors = context.getAncestors(callee).reverse();
  63. let depth = 0;
  64. ancestors.some((ancestor) => {
  65. if (/Function(Expression|Declaration)$/.test(ancestor.type)) {
  66. depth++;
  67. }
  68. if (
  69. (ancestor.type !== 'Property' && ancestor.type !== 'MethodDefinition' && ancestor.type !== 'ClassProperty')
  70. || !nameMatches(ancestor.key.name)
  71. || (mode !== 'disallow-in-func' && depth > 1)
  72. ) {
  73. return false;
  74. }
  75. context.report({
  76. node: callee,
  77. messageId: 'noSetState',
  78. data: {
  79. name: ancestor.key.name
  80. }
  81. });
  82. return true;
  83. });
  84. }
  85. };
  86. }
  87. };
  88. }
  89. module.exports = makeNoMethodSetStateRule;