generator-star-spacing.js 6.2 KB

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