generator-star-spacing.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * @fileoverview Rule to check the spacing around the * in generator functions.
  3. * @author Jamund Ferguson
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. const OVERRIDE_SCHEMA = {
  10. oneOf: [
  11. {
  12. enum: ["before", "after", "both", "neither"]
  13. },
  14. {
  15. type: "object",
  16. properties: {
  17. before: { type: "boolean" },
  18. after: { type: "boolean" }
  19. },
  20. additionalProperties: false
  21. }
  22. ]
  23. };
  24. /** @type {import('../shared/types').Rule} */
  25. module.exports = {
  26. meta: {
  27. type: "layout",
  28. docs: {
  29. description: "Enforce consistent spacing around `*` operators in generator functions",
  30. recommended: false,
  31. url: "https://eslint.org/docs/rules/generator-star-spacing"
  32. },
  33. fixable: "whitespace",
  34. schema: [
  35. {
  36. oneOf: [
  37. {
  38. enum: ["before", "after", "both", "neither"]
  39. },
  40. {
  41. type: "object",
  42. properties: {
  43. before: { type: "boolean" },
  44. after: { type: "boolean" },
  45. named: OVERRIDE_SCHEMA,
  46. anonymous: OVERRIDE_SCHEMA,
  47. method: OVERRIDE_SCHEMA
  48. },
  49. additionalProperties: false
  50. }
  51. ]
  52. }
  53. ],
  54. messages: {
  55. missingBefore: "Missing space before *.",
  56. missingAfter: "Missing space after *.",
  57. unexpectedBefore: "Unexpected space before *.",
  58. unexpectedAfter: "Unexpected space after *."
  59. }
  60. },
  61. create(context) {
  62. const optionDefinitions = {
  63. before: { before: true, after: false },
  64. after: { before: false, after: true },
  65. both: { before: true, after: true },
  66. neither: { before: false, after: false }
  67. };
  68. /**
  69. * Returns resolved option definitions based on an option and defaults
  70. * @param {any} option The option object or string value
  71. * @param {Object} defaults The defaults to use if options are not present
  72. * @returns {Object} the resolved object definition
  73. */
  74. function optionToDefinition(option, defaults) {
  75. if (!option) {
  76. return defaults;
  77. }
  78. return typeof option === "string"
  79. ? optionDefinitions[option]
  80. : Object.assign({}, defaults, option);
  81. }
  82. const modes = (function(option) {
  83. const defaults = optionToDefinition(option, optionDefinitions.before);
  84. return {
  85. named: optionToDefinition(option.named, defaults),
  86. anonymous: optionToDefinition(option.anonymous, defaults),
  87. method: optionToDefinition(option.method, defaults)
  88. };
  89. }(context.options[0] || {}));
  90. const sourceCode = context.getSourceCode();
  91. /**
  92. * Checks if the given token is a star token or not.
  93. * @param {Token} token The token to check.
  94. * @returns {boolean} `true` if the token is a star token.
  95. */
  96. function isStarToken(token) {
  97. return token.value === "*" && token.type === "Punctuator";
  98. }
  99. /**
  100. * Gets the generator star token of the given function node.
  101. * @param {ASTNode} node The function node to get.
  102. * @returns {Token} Found star token.
  103. */
  104. function getStarToken(node) {
  105. return sourceCode.getFirstToken(
  106. (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node,
  107. isStarToken
  108. );
  109. }
  110. /**
  111. * capitalize a given string.
  112. * @param {string} str the given string.
  113. * @returns {string} the capitalized string.
  114. */
  115. function capitalize(str) {
  116. return str[0].toUpperCase() + str.slice(1);
  117. }
  118. /**
  119. * Checks the spacing between two tokens before or after the star token.
  120. * @param {string} kind Either "named", "anonymous", or "method"
  121. * @param {string} side Either "before" or "after".
  122. * @param {Token} leftToken `function` keyword token if side is "before", or
  123. * star token if side is "after".
  124. * @param {Token} rightToken Star token if side is "before", or identifier
  125. * token if side is "after".
  126. * @returns {void}
  127. */
  128. function checkSpacing(kind, side, leftToken, rightToken) {
  129. if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
  130. const after = leftToken.value === "*";
  131. const spaceRequired = modes[kind][side];
  132. const node = after ? leftToken : rightToken;
  133. const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`;
  134. context.report({
  135. node,
  136. messageId,
  137. fix(fixer) {
  138. if (spaceRequired) {
  139. if (after) {
  140. return fixer.insertTextAfter(node, " ");
  141. }
  142. return fixer.insertTextBefore(node, " ");
  143. }
  144. return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
  145. }
  146. });
  147. }
  148. }
  149. /**
  150. * Enforces the spacing around the star if node is a generator function.
  151. * @param {ASTNode} node A function expression or declaration node.
  152. * @returns {void}
  153. */
  154. function checkFunction(node) {
  155. if (!node.generator) {
  156. return;
  157. }
  158. const starToken = getStarToken(node);
  159. const prevToken = sourceCode.getTokenBefore(starToken);
  160. const nextToken = sourceCode.getTokenAfter(starToken);
  161. let kind = "named";
  162. if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
  163. kind = "method";
  164. } else if (!node.id) {
  165. kind = "anonymous";
  166. }
  167. // Only check before when preceded by `function`|`static` keyword
  168. if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
  169. checkSpacing(kind, "before", prevToken, starToken);
  170. }
  171. checkSpacing(kind, "after", starToken, nextToken);
  172. }
  173. return {
  174. FunctionDeclaration: checkFunction,
  175. FunctionExpression: checkFunction
  176. };
  177. }
  178. };