func-style.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * @fileoverview Rule to enforce a particular function style
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../types').Rule.RuleModule} */
  10. module.exports = {
  11. meta: {
  12. dialects: ["javascript", "typescript"],
  13. language: "javascript",
  14. type: "suggestion",
  15. defaultOptions: [
  16. "expression",
  17. {
  18. allowArrowFunctions: false,
  19. allowTypeAnnotation: false,
  20. overrides: {},
  21. },
  22. ],
  23. docs: {
  24. description:
  25. "Enforce the consistent use of either `function` declarations or expressions assigned to variables",
  26. recommended: false,
  27. frozen: true,
  28. url: "https://eslint.org/docs/latest/rules/func-style",
  29. },
  30. schema: [
  31. {
  32. enum: ["declaration", "expression"],
  33. },
  34. {
  35. type: "object",
  36. properties: {
  37. allowArrowFunctions: {
  38. type: "boolean",
  39. },
  40. allowTypeAnnotation: {
  41. type: "boolean",
  42. },
  43. overrides: {
  44. type: "object",
  45. properties: {
  46. namedExports: {
  47. enum: ["declaration", "expression", "ignore"],
  48. },
  49. },
  50. additionalProperties: false,
  51. },
  52. },
  53. additionalProperties: false,
  54. },
  55. ],
  56. messages: {
  57. expression: "Expected a function expression.",
  58. declaration: "Expected a function declaration.",
  59. },
  60. },
  61. create(context) {
  62. const [style, { allowArrowFunctions, allowTypeAnnotation, overrides }] =
  63. context.options;
  64. const enforceDeclarations = style === "declaration";
  65. const { namedExports: exportFunctionStyle } = overrides;
  66. const stack = [];
  67. /**
  68. * Checks if a function declaration is part of an overloaded function
  69. * @param {ASTNode} node The function declaration node to check
  70. * @returns {boolean} True if the function is overloaded
  71. */
  72. function isOverloadedFunction(node) {
  73. const functionName = node.id.name;
  74. if (node.parent.type === "ExportNamedDeclaration") {
  75. return node.parent.parent.body.some(
  76. member =>
  77. member.type === "ExportNamedDeclaration" &&
  78. member.declaration?.type === "TSDeclareFunction" &&
  79. member.declaration.id.name === functionName,
  80. );
  81. }
  82. if (node.parent.type === "SwitchCase") {
  83. return node.parent.parent.cases.some(switchCase =>
  84. switchCase.consequent.some(
  85. member =>
  86. member.type === "TSDeclareFunction" &&
  87. member.id.name === functionName,
  88. ),
  89. );
  90. }
  91. return (
  92. Array.isArray(node.parent.body) &&
  93. node.parent.body.some(
  94. member =>
  95. member.type === "TSDeclareFunction" &&
  96. member.id.name === functionName,
  97. )
  98. );
  99. }
  100. const nodesToCheck = {
  101. FunctionDeclaration(node) {
  102. stack.push(false);
  103. if (
  104. !enforceDeclarations &&
  105. node.parent.type !== "ExportDefaultDeclaration" &&
  106. (typeof exportFunctionStyle === "undefined" ||
  107. node.parent.type !== "ExportNamedDeclaration") &&
  108. !isOverloadedFunction(node)
  109. ) {
  110. context.report({ node, messageId: "expression" });
  111. }
  112. if (
  113. node.parent.type === "ExportNamedDeclaration" &&
  114. exportFunctionStyle === "expression" &&
  115. !isOverloadedFunction(node)
  116. ) {
  117. context.report({ node, messageId: "expression" });
  118. }
  119. },
  120. "FunctionDeclaration:exit"() {
  121. stack.pop();
  122. },
  123. FunctionExpression(node) {
  124. stack.push(false);
  125. if (
  126. enforceDeclarations &&
  127. node.parent.type === "VariableDeclarator" &&
  128. (typeof exportFunctionStyle === "undefined" ||
  129. node.parent.parent.parent.type !==
  130. "ExportNamedDeclaration") &&
  131. !(allowTypeAnnotation && node.parent.id.typeAnnotation)
  132. ) {
  133. context.report({
  134. node: node.parent,
  135. messageId: "declaration",
  136. });
  137. }
  138. if (
  139. node.parent.type === "VariableDeclarator" &&
  140. node.parent.parent.parent.type ===
  141. "ExportNamedDeclaration" &&
  142. exportFunctionStyle === "declaration" &&
  143. !(allowTypeAnnotation && node.parent.id.typeAnnotation)
  144. ) {
  145. context.report({
  146. node: node.parent,
  147. messageId: "declaration",
  148. });
  149. }
  150. },
  151. "FunctionExpression:exit"() {
  152. stack.pop();
  153. },
  154. "ThisExpression, Super"() {
  155. if (stack.length > 0) {
  156. stack[stack.length - 1] = true;
  157. }
  158. },
  159. };
  160. if (!allowArrowFunctions) {
  161. nodesToCheck.ArrowFunctionExpression = function () {
  162. stack.push(false);
  163. };
  164. nodesToCheck["ArrowFunctionExpression:exit"] = function (node) {
  165. const hasThisOrSuperExpr = stack.pop();
  166. if (
  167. !hasThisOrSuperExpr &&
  168. node.parent.type === "VariableDeclarator"
  169. ) {
  170. if (
  171. enforceDeclarations &&
  172. (typeof exportFunctionStyle === "undefined" ||
  173. node.parent.parent.parent.type !==
  174. "ExportNamedDeclaration") &&
  175. !(allowTypeAnnotation && node.parent.id.typeAnnotation)
  176. ) {
  177. context.report({
  178. node: node.parent,
  179. messageId: "declaration",
  180. });
  181. }
  182. if (
  183. node.parent.parent.parent.type ===
  184. "ExportNamedDeclaration" &&
  185. exportFunctionStyle === "declaration" &&
  186. !(allowTypeAnnotation && node.parent.id.typeAnnotation)
  187. ) {
  188. context.report({
  189. node: node.parent,
  190. messageId: "declaration",
  191. });
  192. }
  193. }
  194. };
  195. }
  196. return nodesToCheck;
  197. },
  198. };