comma-style.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /**
  2. * @fileoverview Comma style - enforces comma styles of two types: last and first
  3. * @author Vignesh Anand aka vegetableman
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. const astUtils = require("./utils/ast-utils");
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. /** @type {import('../types').Rule.RuleModule} */
  12. module.exports = {
  13. meta: {
  14. deprecated: {
  15. message: "Formatting rules are being moved out of ESLint core.",
  16. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  17. deprecatedSince: "8.53.0",
  18. availableUntil: "11.0.0",
  19. replacedBy: [
  20. {
  21. message:
  22. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  23. url: "https://eslint.style/guide/migration",
  24. plugin: {
  25. name: "@stylistic/eslint-plugin",
  26. url: "https://eslint.style",
  27. },
  28. rule: {
  29. name: "comma-style",
  30. url: "https://eslint.style/rules/comma-style",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description: "Enforce consistent comma style",
  38. recommended: false,
  39. url: "https://eslint.org/docs/latest/rules/comma-style",
  40. },
  41. fixable: "code",
  42. schema: [
  43. {
  44. enum: ["first", "last"],
  45. },
  46. {
  47. type: "object",
  48. properties: {
  49. exceptions: {
  50. type: "object",
  51. additionalProperties: {
  52. type: "boolean",
  53. },
  54. },
  55. },
  56. additionalProperties: false,
  57. },
  58. ],
  59. messages: {
  60. unexpectedLineBeforeAndAfterComma:
  61. "Bad line breaking before and after ','.",
  62. expectedCommaFirst: "',' should be placed first.",
  63. expectedCommaLast: "',' should be placed last.",
  64. },
  65. },
  66. create(context) {
  67. const style = context.options[0] || "last",
  68. sourceCode = context.sourceCode;
  69. const exceptions = {
  70. ArrayPattern: true,
  71. ArrowFunctionExpression: true,
  72. CallExpression: true,
  73. FunctionDeclaration: true,
  74. FunctionExpression: true,
  75. ImportDeclaration: true,
  76. ObjectPattern: true,
  77. NewExpression: true,
  78. };
  79. if (
  80. context.options.length === 2 &&
  81. Object.hasOwn(context.options[1], "exceptions")
  82. ) {
  83. const keys = Object.keys(context.options[1].exceptions);
  84. for (let i = 0; i < keys.length; i++) {
  85. exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
  86. }
  87. }
  88. //--------------------------------------------------------------------------
  89. // Helpers
  90. //--------------------------------------------------------------------------
  91. /**
  92. * Modified text based on the style
  93. * @param {string} styleType Style type
  94. * @param {string} text Source code text
  95. * @returns {string} modified text
  96. * @private
  97. */
  98. function getReplacedText(styleType, text) {
  99. switch (styleType) {
  100. case "between":
  101. return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`;
  102. case "first":
  103. return `${text},`;
  104. case "last":
  105. return `,${text}`;
  106. default:
  107. return "";
  108. }
  109. }
  110. /**
  111. * Determines the fixer function for a given style.
  112. * @param {string} styleType comma style
  113. * @param {ASTNode} previousItemToken The token to check.
  114. * @param {ASTNode} commaToken The token to check.
  115. * @param {ASTNode} currentItemToken The token to check.
  116. * @returns {Function} Fixer function
  117. * @private
  118. */
  119. function getFixerFunction(
  120. styleType,
  121. previousItemToken,
  122. commaToken,
  123. currentItemToken,
  124. ) {
  125. const text =
  126. sourceCode.text.slice(
  127. previousItemToken.range[1],
  128. commaToken.range[0],
  129. ) +
  130. sourceCode.text.slice(
  131. commaToken.range[1],
  132. currentItemToken.range[0],
  133. );
  134. const range = [
  135. previousItemToken.range[1],
  136. currentItemToken.range[0],
  137. ];
  138. return function (fixer) {
  139. return fixer.replaceTextRange(
  140. range,
  141. getReplacedText(styleType, text),
  142. );
  143. };
  144. }
  145. /**
  146. * Validates the spacing around single items in lists.
  147. * @param {Token} previousItemToken The last token from the previous item.
  148. * @param {Token} commaToken The token representing the comma.
  149. * @param {Token} currentItemToken The first token of the current item.
  150. * @param {Token} reportItem The item to use when reporting an error.
  151. * @returns {void}
  152. * @private
  153. */
  154. function validateCommaItemSpacing(
  155. previousItemToken,
  156. commaToken,
  157. currentItemToken,
  158. reportItem,
  159. ) {
  160. // if single line
  161. if (
  162. astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
  163. astUtils.isTokenOnSameLine(previousItemToken, commaToken)
  164. ) {
  165. // do nothing.
  166. } else if (
  167. !astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
  168. !astUtils.isTokenOnSameLine(previousItemToken, commaToken)
  169. ) {
  170. const comment = sourceCode.getCommentsAfter(commaToken)[0];
  171. const styleType =
  172. comment &&
  173. comment.type === "Block" &&
  174. astUtils.isTokenOnSameLine(commaToken, comment)
  175. ? style
  176. : "between";
  177. // lone comma
  178. context.report({
  179. node: reportItem,
  180. loc: commaToken.loc,
  181. messageId: "unexpectedLineBeforeAndAfterComma",
  182. fix: getFixerFunction(
  183. styleType,
  184. previousItemToken,
  185. commaToken,
  186. currentItemToken,
  187. ),
  188. });
  189. } else if (
  190. style === "first" &&
  191. !astUtils.isTokenOnSameLine(commaToken, currentItemToken)
  192. ) {
  193. context.report({
  194. node: reportItem,
  195. loc: commaToken.loc,
  196. messageId: "expectedCommaFirst",
  197. fix: getFixerFunction(
  198. style,
  199. previousItemToken,
  200. commaToken,
  201. currentItemToken,
  202. ),
  203. });
  204. } else if (
  205. style === "last" &&
  206. astUtils.isTokenOnSameLine(commaToken, currentItemToken)
  207. ) {
  208. context.report({
  209. node: reportItem,
  210. loc: commaToken.loc,
  211. messageId: "expectedCommaLast",
  212. fix: getFixerFunction(
  213. style,
  214. previousItemToken,
  215. commaToken,
  216. currentItemToken,
  217. ),
  218. });
  219. }
  220. }
  221. /**
  222. * Checks the comma placement with regards to a declaration/property/element
  223. * @param {ASTNode} node The binary expression node to check
  224. * @param {string} property The property of the node containing child nodes.
  225. * @private
  226. * @returns {void}
  227. */
  228. function validateComma(node, property) {
  229. const items = node[property],
  230. arrayLiteral =
  231. node.type === "ArrayExpression" ||
  232. node.type === "ArrayPattern";
  233. if (items.length > 1 || arrayLiteral) {
  234. // seed as opening [
  235. let previousItemToken = sourceCode.getFirstToken(node);
  236. items.forEach(item => {
  237. const commaToken = item
  238. ? sourceCode.getTokenBefore(item)
  239. : previousItemToken,
  240. currentItemToken = item
  241. ? sourceCode.getFirstToken(item)
  242. : sourceCode.getTokenAfter(commaToken),
  243. reportItem = item || currentItemToken;
  244. /*
  245. * This works by comparing three token locations:
  246. * - previousItemToken is the last token of the previous item
  247. * - commaToken is the location of the comma before the current item
  248. * - currentItemToken is the first token of the current item
  249. *
  250. * These values get switched around if item is undefined.
  251. * previousItemToken will refer to the last token not belonging
  252. * to the current item, which could be a comma or an opening
  253. * square bracket. currentItemToken could be a comma.
  254. *
  255. * All comparisons are done based on these tokens directly, so
  256. * they are always valid regardless of an undefined item.
  257. */
  258. if (astUtils.isCommaToken(commaToken)) {
  259. validateCommaItemSpacing(
  260. previousItemToken,
  261. commaToken,
  262. currentItemToken,
  263. reportItem,
  264. );
  265. }
  266. if (item) {
  267. const tokenAfterItem = sourceCode.getTokenAfter(
  268. item,
  269. astUtils.isNotClosingParenToken,
  270. );
  271. previousItemToken = tokenAfterItem
  272. ? sourceCode.getTokenBefore(tokenAfterItem)
  273. : sourceCode.ast.tokens.at(-1);
  274. } else {
  275. previousItemToken = currentItemToken;
  276. }
  277. });
  278. /*
  279. * Special case for array literals that have empty last items, such
  280. * as [ 1, 2, ]. These arrays only have two items show up in the
  281. * AST, so we need to look at the token to verify that there's no
  282. * dangling comma.
  283. */
  284. if (arrayLiteral) {
  285. const lastToken = sourceCode.getLastToken(node),
  286. nextToLastToken = sourceCode.getTokenBefore(lastToken);
  287. if (astUtils.isCommaToken(nextToLastToken)) {
  288. validateCommaItemSpacing(
  289. sourceCode.getTokenBefore(nextToLastToken),
  290. nextToLastToken,
  291. lastToken,
  292. lastToken,
  293. );
  294. }
  295. }
  296. }
  297. }
  298. //--------------------------------------------------------------------------
  299. // Public
  300. //--------------------------------------------------------------------------
  301. const nodes = {};
  302. if (!exceptions.VariableDeclaration) {
  303. nodes.VariableDeclaration = function (node) {
  304. validateComma(node, "declarations");
  305. };
  306. }
  307. if (!exceptions.ObjectExpression) {
  308. nodes.ObjectExpression = function (node) {
  309. validateComma(node, "properties");
  310. };
  311. }
  312. if (!exceptions.ObjectPattern) {
  313. nodes.ObjectPattern = function (node) {
  314. validateComma(node, "properties");
  315. };
  316. }
  317. if (!exceptions.ArrayExpression) {
  318. nodes.ArrayExpression = function (node) {
  319. validateComma(node, "elements");
  320. };
  321. }
  322. if (!exceptions.ArrayPattern) {
  323. nodes.ArrayPattern = function (node) {
  324. validateComma(node, "elements");
  325. };
  326. }
  327. if (!exceptions.FunctionDeclaration) {
  328. nodes.FunctionDeclaration = function (node) {
  329. validateComma(node, "params");
  330. };
  331. }
  332. if (!exceptions.FunctionExpression) {
  333. nodes.FunctionExpression = function (node) {
  334. validateComma(node, "params");
  335. };
  336. }
  337. if (!exceptions.ArrowFunctionExpression) {
  338. nodes.ArrowFunctionExpression = function (node) {
  339. validateComma(node, "params");
  340. };
  341. }
  342. if (!exceptions.CallExpression) {
  343. nodes.CallExpression = function (node) {
  344. validateComma(node, "arguments");
  345. };
  346. }
  347. if (!exceptions.ImportDeclaration) {
  348. nodes.ImportDeclaration = function (node) {
  349. validateComma(node, "specifiers");
  350. };
  351. }
  352. if (!exceptions.NewExpression) {
  353. nodes.NewExpression = function (node) {
  354. validateComma(node, "arguments");
  355. };
  356. }
  357. return nodes;
  358. },
  359. };