destructuring-assignment.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context.
  3. **/
  4. 'use strict';
  5. const Components = require('../util/Components');
  6. const docsUrl = require('../util/docsUrl');
  7. const DEFAULT_OPTION = 'always';
  8. module.exports = {
  9. meta: {
  10. docs: {
  11. description: 'Enforce consistent usage of destructuring assignment of props, state, and context',
  12. category: 'Stylistic Issues',
  13. recommended: false,
  14. url: docsUrl('destructuring-assignment')
  15. },
  16. schema: [{
  17. type: 'string',
  18. enum: [
  19. 'always',
  20. 'never'
  21. ]
  22. }]
  23. },
  24. create: Components.detect((context, components, utils) => {
  25. const configuration = context.options[0] || DEFAULT_OPTION;
  26. /**
  27. * Checks if a prop is being assigned a value props.bar = 'bar'
  28. * @param {ASTNode} node The AST node being checked.
  29. * @returns {Boolean}
  30. */
  31. function isAssignmentToProp(node) {
  32. return (
  33. node.parent &&
  34. node.parent.type === 'AssignmentExpression' &&
  35. node.parent.left === node
  36. );
  37. }
  38. /**
  39. * @param {ASTNode} node We expect either an ArrowFunctionExpression,
  40. * FunctionDeclaration, or FunctionExpression
  41. */
  42. function handleStatelessComponent(node) {
  43. const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
  44. const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern';
  45. if (destructuringProps && components.get(node) && configuration === 'never') {
  46. context.report({
  47. node: node,
  48. message: 'Must never use destructuring props assignment in SFC argument'
  49. });
  50. } else if (destructuringContext && components.get(node) && configuration === 'never') {
  51. context.report({
  52. node: node,
  53. message: 'Must never use destructuring context assignment in SFC argument'
  54. });
  55. }
  56. }
  57. function handleSFCUsage(node) {
  58. // props.aProp || context.aProp
  59. const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentToProp(node);
  60. if (isPropUsed && configuration === 'always') {
  61. context.report({
  62. node: node,
  63. message: `Must use destructuring ${node.object.name} assignment`
  64. });
  65. }
  66. }
  67. function handleClassUsage(node) {
  68. // this.props.Aprop || this.context.aProp || this.state.aState
  69. const isPropUsed = (
  70. node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression' &&
  71. (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state')
  72. );
  73. if (isPropUsed && configuration === 'always') {
  74. context.report({
  75. node: node,
  76. message: `Must use destructuring ${node.object.property.name} assignment`
  77. });
  78. }
  79. }
  80. return {
  81. FunctionDeclaration: handleStatelessComponent,
  82. ArrowFunctionExpression: handleStatelessComponent,
  83. FunctionExpression: handleStatelessComponent,
  84. MemberExpression: function(node) {
  85. const SFCComponent = components.get(context.getScope(node).block);
  86. const classComponent = utils.getParentComponent(node);
  87. if (SFCComponent) {
  88. handleSFCUsage(node);
  89. }
  90. if (classComponent) {
  91. handleClassUsage(node, classComponent);
  92. }
  93. },
  94. VariableDeclarator: function(node) {
  95. const classComponent = utils.getParentComponent(node);
  96. const SFCComponent = components.get(context.getScope(node).block);
  97. const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
  98. // let {foo} = props;
  99. const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
  100. // let {foo} = this.props;
  101. const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
  102. node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
  103. );
  104. if (SFCComponent && destructuringSFC && configuration === 'never') {
  105. context.report({
  106. node: node,
  107. message: `Must never use destructuring ${node.init.name} assignment`
  108. });
  109. }
  110. if (classComponent && destructuringClass && configuration === 'never') {
  111. context.report({
  112. node: node,
  113. message: `Must never use destructuring ${node.init.property.name} assignment`
  114. });
  115. }
  116. }
  117. };
  118. })
  119. };