class-methods-use-this.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * @fileoverview Rule to enforce that all class methods use 'this'.
  3. * @author Patrick Williams
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. module.exports = {
  14. meta: {
  15. type: "suggestion",
  16. docs: {
  17. description: "enforce that class methods utilize `this`",
  18. category: "Best Practices",
  19. recommended: false,
  20. url: "https://eslint.org/docs/rules/class-methods-use-this"
  21. },
  22. schema: [{
  23. type: "object",
  24. properties: {
  25. exceptMethods: {
  26. type: "array",
  27. items: {
  28. type: "string"
  29. }
  30. }
  31. },
  32. additionalProperties: false
  33. }],
  34. messages: {
  35. missingThis: "Expected 'this' to be used by class {{name}}."
  36. }
  37. },
  38. create(context) {
  39. const config = Object.assign({}, context.options[0]);
  40. const exceptMethods = new Set(config.exceptMethods || []);
  41. const stack = [];
  42. /**
  43. * Initializes the current context to false and pushes it onto the stack.
  44. * These booleans represent whether 'this' has been used in the context.
  45. * @returns {void}
  46. * @private
  47. */
  48. function enterFunction() {
  49. stack.push(false);
  50. }
  51. /**
  52. * Check if the node is an instance method
  53. * @param {ASTNode} node node to check
  54. * @returns {boolean} True if its an instance method
  55. * @private
  56. */
  57. function isInstanceMethod(node) {
  58. return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
  59. }
  60. /**
  61. * Check if the node is an instance method not excluded by config
  62. * @param {ASTNode} node node to check
  63. * @returns {boolean} True if it is an instance method, and not excluded by config
  64. * @private
  65. */
  66. function isIncludedInstanceMethod(node) {
  67. return isInstanceMethod(node) &&
  68. (node.computed || !exceptMethods.has(node.key.name));
  69. }
  70. /**
  71. * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
  72. * Static methods and the constructor are exempt.
  73. * Then pops the context off the stack.
  74. * @param {ASTNode} node A function node that was entered.
  75. * @returns {void}
  76. * @private
  77. */
  78. function exitFunction(node) {
  79. const methodUsesThis = stack.pop();
  80. if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
  81. context.report({
  82. node,
  83. messageId: "missingThis",
  84. data: {
  85. name: astUtils.getFunctionNameWithKind(node)
  86. }
  87. });
  88. }
  89. }
  90. /**
  91. * Mark the current context as having used 'this'.
  92. * @returns {void}
  93. * @private
  94. */
  95. function markThisUsed() {
  96. if (stack.length) {
  97. stack[stack.length - 1] = true;
  98. }
  99. }
  100. return {
  101. FunctionDeclaration: enterFunction,
  102. "FunctionDeclaration:exit": exitFunction,
  103. FunctionExpression: enterFunction,
  104. "FunctionExpression:exit": exitFunction,
  105. ThisExpression: markThisUsed,
  106. Super: markThisUsed
  107. };
  108. }
  109. };