space-before-function-paren.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * @fileoverview Rule to validate spacing before function paren.
  3. * @author Mathias Schreck <https://github.com/lo1tuma>
  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: "layout",
  16. docs: {
  17. description: "enforce consistent spacing before `function` definition opening parenthesis",
  18. category: "Stylistic Issues",
  19. recommended: false,
  20. url: "https://eslint.org/docs/rules/space-before-function-paren"
  21. },
  22. fixable: "whitespace",
  23. schema: [
  24. {
  25. oneOf: [
  26. {
  27. enum: ["always", "never"]
  28. },
  29. {
  30. type: "object",
  31. properties: {
  32. anonymous: {
  33. enum: ["always", "never", "ignore"]
  34. },
  35. named: {
  36. enum: ["always", "never", "ignore"]
  37. },
  38. asyncArrow: {
  39. enum: ["always", "never", "ignore"]
  40. }
  41. },
  42. additionalProperties: false
  43. }
  44. ]
  45. }
  46. ],
  47. messages: {
  48. unexpectedSpace: "Unexpected space before function parentheses.",
  49. missingSpace: "Missing space before function parentheses."
  50. }
  51. },
  52. create(context) {
  53. const sourceCode = context.getSourceCode();
  54. const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always";
  55. const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {};
  56. /**
  57. * Determines whether a function has a name.
  58. * @param {ASTNode} node The function node.
  59. * @returns {boolean} Whether the function has a name.
  60. */
  61. function isNamedFunction(node) {
  62. if (node.id) {
  63. return true;
  64. }
  65. const parent = node.parent;
  66. return parent.type === "MethodDefinition" ||
  67. (parent.type === "Property" &&
  68. (
  69. parent.kind === "get" ||
  70. parent.kind === "set" ||
  71. parent.method
  72. )
  73. );
  74. }
  75. /**
  76. * Gets the config for a given function
  77. * @param {ASTNode} node The function node
  78. * @returns {string} "always", "never", or "ignore"
  79. */
  80. function getConfigForFunction(node) {
  81. if (node.type === "ArrowFunctionExpression") {
  82. // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar
  83. if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) {
  84. return overrideConfig.asyncArrow || baseConfig;
  85. }
  86. } else if (isNamedFunction(node)) {
  87. return overrideConfig.named || baseConfig;
  88. // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}`
  89. } else if (!node.generator) {
  90. return overrideConfig.anonymous || baseConfig;
  91. }
  92. return "ignore";
  93. }
  94. /**
  95. * Checks the parens of a function node
  96. * @param {ASTNode} node A function node
  97. * @returns {void}
  98. */
  99. function checkFunction(node) {
  100. const functionConfig = getConfigForFunction(node);
  101. if (functionConfig === "ignore") {
  102. return;
  103. }
  104. const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
  105. const leftToken = sourceCode.getTokenBefore(rightToken);
  106. const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
  107. if (hasSpacing && functionConfig === "never") {
  108. context.report({
  109. node,
  110. loc: {
  111. start: leftToken.loc.end,
  112. end: rightToken.loc.start
  113. },
  114. messageId: "unexpectedSpace",
  115. fix(fixer) {
  116. const comments = sourceCode.getCommentsBefore(rightToken);
  117. // Don't fix anything if there's a single line comment between the left and the right token
  118. if (comments.some(comment => comment.type === "Line")) {
  119. return null;
  120. }
  121. return fixer.replaceTextRange(
  122. [leftToken.range[1], rightToken.range[0]],
  123. comments.reduce((text, comment) => text + sourceCode.getText(comment), "")
  124. );
  125. }
  126. });
  127. } else if (!hasSpacing && functionConfig === "always") {
  128. context.report({
  129. node,
  130. loc: rightToken.loc,
  131. messageId: "missingSpace",
  132. fix: fixer => fixer.insertTextAfter(leftToken, " ")
  133. });
  134. }
  135. }
  136. return {
  137. ArrowFunctionExpression: checkFunction,
  138. FunctionDeclaration: checkFunction,
  139. FunctionExpression: checkFunction
  140. };
  141. }
  142. };