no-empty-function.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /**
  2. * @fileoverview Rule to disallow empty functions.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const ALLOW_OPTIONS = Object.freeze([
  14. "functions",
  15. "arrowFunctions",
  16. "generatorFunctions",
  17. "methods",
  18. "generatorMethods",
  19. "getters",
  20. "setters",
  21. "constructors",
  22. "asyncFunctions",
  23. "asyncMethods",
  24. "privateConstructors",
  25. "protectedConstructors",
  26. "decoratedFunctions",
  27. "overrideMethods",
  28. ]);
  29. /**
  30. * Gets the kind of a given function node.
  31. * @param {ASTNode} node A function node to get. This is one of
  32. * an ArrowFunctionExpression, a FunctionDeclaration, or a
  33. * FunctionExpression.
  34. * @returns {string} The kind of the function. This is one of "functions",
  35. * "arrowFunctions", "generatorFunctions", "asyncFunctions", "methods",
  36. * "generatorMethods", "asyncMethods", "getters", "setters", and
  37. * "constructors".
  38. */
  39. function getKind(node) {
  40. const parent = node.parent;
  41. let kind;
  42. if (node.type === "ArrowFunctionExpression") {
  43. return "arrowFunctions";
  44. }
  45. // Detects main kind.
  46. if (parent.type === "Property") {
  47. if (parent.kind === "get") {
  48. return "getters";
  49. }
  50. if (parent.kind === "set") {
  51. return "setters";
  52. }
  53. kind = parent.method ? "methods" : "functions";
  54. } else if (parent.type === "MethodDefinition") {
  55. if (parent.kind === "get") {
  56. return "getters";
  57. }
  58. if (parent.kind === "set") {
  59. return "setters";
  60. }
  61. if (parent.kind === "constructor") {
  62. return "constructors";
  63. }
  64. kind = "methods";
  65. } else {
  66. kind = "functions";
  67. }
  68. // Detects prefix.
  69. let prefix;
  70. if (node.generator) {
  71. prefix = "generator";
  72. } else if (node.async) {
  73. prefix = "async";
  74. } else {
  75. return kind;
  76. }
  77. return prefix + kind[0].toUpperCase() + kind.slice(1);
  78. }
  79. /**
  80. * Checks if a constructor function has parameter properties.
  81. * @param {ASTNode} node The function node to examine.
  82. * @returns {boolean} True if the constructor has parameter properties, false otherwise.
  83. */
  84. function isParameterPropertiesConstructor(node) {
  85. return node.params.some(param => param.type === "TSParameterProperty");
  86. }
  87. //------------------------------------------------------------------------------
  88. // Rule Definition
  89. //------------------------------------------------------------------------------
  90. /** @type {import('../types').Rule.RuleModule} */
  91. module.exports = {
  92. meta: {
  93. dialects: ["javascript", "typescript"],
  94. language: "javascript",
  95. hasSuggestions: true,
  96. type: "suggestion",
  97. defaultOptions: [{ allow: [] }],
  98. docs: {
  99. description: "Disallow empty functions",
  100. recommended: false,
  101. url: "https://eslint.org/docs/latest/rules/no-empty-function",
  102. },
  103. schema: [
  104. {
  105. type: "object",
  106. properties: {
  107. allow: {
  108. type: "array",
  109. items: { enum: ALLOW_OPTIONS },
  110. uniqueItems: true,
  111. },
  112. },
  113. additionalProperties: false,
  114. },
  115. ],
  116. messages: {
  117. unexpected: "Unexpected empty {{name}}.",
  118. suggestComment: "Add comment inside empty {{name}}.",
  119. },
  120. },
  121. create(context) {
  122. const [{ allow }] = context.options;
  123. const sourceCode = context.sourceCode;
  124. /**
  125. * Checks if the given function node is allowed to be empty.
  126. * @param {ASTNode} node The function node to check.
  127. * @returns {boolean} True if the function is allowed to be empty, false otherwise.
  128. */
  129. function isAllowedEmptyFunction(node) {
  130. const kind = getKind(node);
  131. if (allow.includes(kind)) {
  132. return true;
  133. }
  134. if (kind === "constructors") {
  135. if (
  136. (node.parent.accessibility === "private" &&
  137. allow.includes("privateConstructors")) ||
  138. (node.parent.accessibility === "protected" &&
  139. allow.includes("protectedConstructors")) ||
  140. isParameterPropertiesConstructor(node)
  141. ) {
  142. return true;
  143. }
  144. }
  145. if (/(?:g|s)etters|methods$/iu.test(kind)) {
  146. if (
  147. (node.parent.decorators?.length &&
  148. allow.includes("decoratedFunctions")) ||
  149. (node.parent.override && allow.includes("overrideMethods"))
  150. ) {
  151. return true;
  152. }
  153. }
  154. return false;
  155. }
  156. /**
  157. * Reports a given function node if the node matches the following patterns.
  158. *
  159. * - Not allowed by options.
  160. * - The body is empty.
  161. * - The body doesn't have any comments.
  162. * @param {ASTNode} node A function node to report. This is one of
  163. * an ArrowFunctionExpression, a FunctionDeclaration, or a
  164. * FunctionExpression.
  165. * @returns {void}
  166. */
  167. function reportIfEmpty(node) {
  168. const name = astUtils.getFunctionNameWithKind(node);
  169. const innerComments = sourceCode.getTokens(node.body, {
  170. includeComments: true,
  171. filter: astUtils.isCommentToken,
  172. });
  173. if (
  174. !isAllowedEmptyFunction(node) &&
  175. node.body.type === "BlockStatement" &&
  176. node.body.body.length === 0 &&
  177. innerComments.length === 0
  178. ) {
  179. context.report({
  180. node,
  181. loc: node.body.loc,
  182. messageId: "unexpected",
  183. data: { name },
  184. suggest: [
  185. {
  186. messageId: "suggestComment",
  187. data: { name },
  188. fix(fixer) {
  189. const range = [
  190. node.body.range[0] + 1,
  191. node.body.range[1] - 1,
  192. ];
  193. return fixer.replaceTextRange(
  194. range,
  195. " /* empty */ ",
  196. );
  197. },
  198. },
  199. ],
  200. });
  201. }
  202. }
  203. return {
  204. ArrowFunctionExpression: reportIfEmpty,
  205. FunctionDeclaration: reportIfEmpty,
  206. FunctionExpression: reportIfEmpty,
  207. };
  208. },
  209. };