Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

comma-dangle.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /**
  2. * @fileoverview Rule to forbid or enforce dangling commas.
  3. * @author Ian Christian Myers
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const lodash = require("lodash");
  10. const astUtils = require("../util/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. const DEFAULT_OPTIONS = Object.freeze({
  15. arrays: "never",
  16. objects: "never",
  17. imports: "never",
  18. exports: "never",
  19. functions: "ignore"
  20. });
  21. /**
  22. * Checks whether or not a trailing comma is allowed in a given node.
  23. * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
  24. *
  25. * @param {ASTNode} lastItem - The node of the last element in the given node.
  26. * @returns {boolean} `true` if a trailing comma is allowed.
  27. */
  28. function isTrailingCommaAllowed(lastItem) {
  29. return !(
  30. lastItem.type === "RestElement" ||
  31. lastItem.type === "RestProperty" ||
  32. lastItem.type === "ExperimentalRestProperty"
  33. );
  34. }
  35. /**
  36. * Normalize option value.
  37. *
  38. * @param {string|Object|undefined} optionValue - The 1st option value to normalize.
  39. * @returns {Object} The normalized option value.
  40. */
  41. function normalizeOptions(optionValue) {
  42. if (typeof optionValue === "string") {
  43. return {
  44. arrays: optionValue,
  45. objects: optionValue,
  46. imports: optionValue,
  47. exports: optionValue,
  48. // For backward compatibility, always ignore functions.
  49. functions: "ignore"
  50. };
  51. }
  52. if (typeof optionValue === "object" && optionValue !== null) {
  53. return {
  54. arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
  55. objects: optionValue.objects || DEFAULT_OPTIONS.objects,
  56. imports: optionValue.imports || DEFAULT_OPTIONS.imports,
  57. exports: optionValue.exports || DEFAULT_OPTIONS.exports,
  58. functions: optionValue.functions || DEFAULT_OPTIONS.functions
  59. };
  60. }
  61. return DEFAULT_OPTIONS;
  62. }
  63. //------------------------------------------------------------------------------
  64. // Rule Definition
  65. //------------------------------------------------------------------------------
  66. module.exports = {
  67. meta: {
  68. type: "layout",
  69. docs: {
  70. description: "require or disallow trailing commas",
  71. category: "Stylistic Issues",
  72. recommended: false,
  73. url: "https://eslint.org/docs/rules/comma-dangle"
  74. },
  75. fixable: "code",
  76. schema: {
  77. definitions: {
  78. value: {
  79. enum: [
  80. "always-multiline",
  81. "always",
  82. "never",
  83. "only-multiline"
  84. ]
  85. },
  86. valueWithIgnore: {
  87. enum: [
  88. "always-multiline",
  89. "always",
  90. "ignore",
  91. "never",
  92. "only-multiline"
  93. ]
  94. }
  95. },
  96. type: "array",
  97. items: [
  98. {
  99. oneOf: [
  100. {
  101. $ref: "#/definitions/value"
  102. },
  103. {
  104. type: "object",
  105. properties: {
  106. arrays: { $ref: "#/definitions/valueWithIgnore" },
  107. objects: { $ref: "#/definitions/valueWithIgnore" },
  108. imports: { $ref: "#/definitions/valueWithIgnore" },
  109. exports: { $ref: "#/definitions/valueWithIgnore" },
  110. functions: { $ref: "#/definitions/valueWithIgnore" }
  111. },
  112. additionalProperties: false
  113. }
  114. ]
  115. }
  116. ]
  117. },
  118. messages: {
  119. unexpected: "Unexpected trailing comma.",
  120. missing: "Missing trailing comma."
  121. }
  122. },
  123. create(context) {
  124. const options = normalizeOptions(context.options[0]);
  125. const sourceCode = context.getSourceCode();
  126. /**
  127. * Gets the last item of the given node.
  128. * @param {ASTNode} node - The node to get.
  129. * @returns {ASTNode|null} The last node or null.
  130. */
  131. function getLastItem(node) {
  132. switch (node.type) {
  133. case "ObjectExpression":
  134. case "ObjectPattern":
  135. return lodash.last(node.properties);
  136. case "ArrayExpression":
  137. case "ArrayPattern":
  138. return lodash.last(node.elements);
  139. case "ImportDeclaration":
  140. case "ExportNamedDeclaration":
  141. return lodash.last(node.specifiers);
  142. case "FunctionDeclaration":
  143. case "FunctionExpression":
  144. case "ArrowFunctionExpression":
  145. return lodash.last(node.params);
  146. case "CallExpression":
  147. case "NewExpression":
  148. return lodash.last(node.arguments);
  149. default:
  150. return null;
  151. }
  152. }
  153. /**
  154. * Gets the trailing comma token of the given node.
  155. * If the trailing comma does not exist, this returns the token which is
  156. * the insertion point of the trailing comma token.
  157. *
  158. * @param {ASTNode} node - The node to get.
  159. * @param {ASTNode} lastItem - The last item of the node.
  160. * @returns {Token} The trailing comma token or the insertion point.
  161. */
  162. function getTrailingToken(node, lastItem) {
  163. switch (node.type) {
  164. case "ObjectExpression":
  165. case "ArrayExpression":
  166. case "CallExpression":
  167. case "NewExpression":
  168. return sourceCode.getLastToken(node, 1);
  169. default: {
  170. const nextToken = sourceCode.getTokenAfter(lastItem);
  171. if (astUtils.isCommaToken(nextToken)) {
  172. return nextToken;
  173. }
  174. return sourceCode.getLastToken(lastItem);
  175. }
  176. }
  177. }
  178. /**
  179. * Checks whether or not a given node is multiline.
  180. * This rule handles a given node as multiline when the closing parenthesis
  181. * and the last element are not on the same line.
  182. *
  183. * @param {ASTNode} node - A node to check.
  184. * @returns {boolean} `true` if the node is multiline.
  185. */
  186. function isMultiline(node) {
  187. const lastItem = getLastItem(node);
  188. if (!lastItem) {
  189. return false;
  190. }
  191. const penultimateToken = getTrailingToken(node, lastItem);
  192. const lastToken = sourceCode.getTokenAfter(penultimateToken);
  193. return lastToken.loc.end.line !== penultimateToken.loc.end.line;
  194. }
  195. /**
  196. * Reports a trailing comma if it exists.
  197. *
  198. * @param {ASTNode} node - A node to check. Its type is one of
  199. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  200. * ImportDeclaration, and ExportNamedDeclaration.
  201. * @returns {void}
  202. */
  203. function forbidTrailingComma(node) {
  204. const lastItem = getLastItem(node);
  205. if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
  206. return;
  207. }
  208. const trailingToken = getTrailingToken(node, lastItem);
  209. if (astUtils.isCommaToken(trailingToken)) {
  210. context.report({
  211. node: lastItem,
  212. loc: trailingToken.loc.start,
  213. messageId: "unexpected",
  214. fix(fixer) {
  215. return fixer.remove(trailingToken);
  216. }
  217. });
  218. }
  219. }
  220. /**
  221. * Reports the last element of a given node if it does not have a trailing
  222. * comma.
  223. *
  224. * If a given node is `ArrayPattern` which has `RestElement`, the trailing
  225. * comma is disallowed, so report if it exists.
  226. *
  227. * @param {ASTNode} node - A node to check. Its type is one of
  228. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  229. * ImportDeclaration, and ExportNamedDeclaration.
  230. * @returns {void}
  231. */
  232. function forceTrailingComma(node) {
  233. const lastItem = getLastItem(node);
  234. if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
  235. return;
  236. }
  237. if (!isTrailingCommaAllowed(lastItem)) {
  238. forbidTrailingComma(node);
  239. return;
  240. }
  241. const trailingToken = getTrailingToken(node, lastItem);
  242. if (trailingToken.value !== ",") {
  243. context.report({
  244. node: lastItem,
  245. loc: trailingToken.loc.end,
  246. messageId: "missing",
  247. fix(fixer) {
  248. return fixer.insertTextAfter(trailingToken, ",");
  249. }
  250. });
  251. }
  252. }
  253. /**
  254. * If a given node is multiline, reports the last element of a given node
  255. * when it does not have a trailing comma.
  256. * Otherwise, reports a trailing comma if it exists.
  257. *
  258. * @param {ASTNode} node - A node to check. Its type is one of
  259. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  260. * ImportDeclaration, and ExportNamedDeclaration.
  261. * @returns {void}
  262. */
  263. function forceTrailingCommaIfMultiline(node) {
  264. if (isMultiline(node)) {
  265. forceTrailingComma(node);
  266. } else {
  267. forbidTrailingComma(node);
  268. }
  269. }
  270. /**
  271. * Only if a given node is not multiline, reports the last element of a given node
  272. * when it does not have a trailing comma.
  273. * Otherwise, reports a trailing comma if it exists.
  274. *
  275. * @param {ASTNode} node - A node to check. Its type is one of
  276. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  277. * ImportDeclaration, and ExportNamedDeclaration.
  278. * @returns {void}
  279. */
  280. function allowTrailingCommaIfMultiline(node) {
  281. if (!isMultiline(node)) {
  282. forbidTrailingComma(node);
  283. }
  284. }
  285. const predicate = {
  286. always: forceTrailingComma,
  287. "always-multiline": forceTrailingCommaIfMultiline,
  288. "only-multiline": allowTrailingCommaIfMultiline,
  289. never: forbidTrailingComma,
  290. ignore: lodash.noop
  291. };
  292. return {
  293. ObjectExpression: predicate[options.objects],
  294. ObjectPattern: predicate[options.objects],
  295. ArrayExpression: predicate[options.arrays],
  296. ArrayPattern: predicate[options.arrays],
  297. ImportDeclaration: predicate[options.imports],
  298. ExportNamedDeclaration: predicate[options.exports],
  299. FunctionDeclaration: predicate[options.functions],
  300. FunctionExpression: predicate[options.functions],
  301. ArrowFunctionExpression: predicate[options.functions],
  302. CallExpression: predicate[options.functions],
  303. NewExpression: predicate[options.functions]
  304. };
  305. }
  306. };