项目原始demo,不改动
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.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 

342 lines
12 KiB

  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("../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. docs: {
  69. description: "require or disallow trailing commas",
  70. category: "Stylistic Issues",
  71. recommended: false,
  72. url: "https://eslint.org/docs/rules/comma-dangle"
  73. },
  74. fixable: "code",
  75. schema: {
  76. definitions: {
  77. value: {
  78. enum: [
  79. "always-multiline",
  80. "always",
  81. "never",
  82. "only-multiline"
  83. ]
  84. },
  85. valueWithIgnore: {
  86. enum: [
  87. "always-multiline",
  88. "always",
  89. "ignore",
  90. "never",
  91. "only-multiline"
  92. ]
  93. }
  94. },
  95. type: "array",
  96. items: [
  97. {
  98. oneOf: [
  99. {
  100. $ref: "#/definitions/value"
  101. },
  102. {
  103. type: "object",
  104. properties: {
  105. arrays: { $ref: "#/definitions/valueWithIgnore" },
  106. objects: { $ref: "#/definitions/valueWithIgnore" },
  107. imports: { $ref: "#/definitions/valueWithIgnore" },
  108. exports: { $ref: "#/definitions/valueWithIgnore" },
  109. functions: { $ref: "#/definitions/valueWithIgnore" }
  110. },
  111. additionalProperties: false
  112. }
  113. ]
  114. }
  115. ]
  116. },
  117. messages: {
  118. unexpected: "Unexpected trailing comma.",
  119. missing: "Missing trailing comma."
  120. }
  121. },
  122. create(context) {
  123. const options = normalizeOptions(context.options[0]);
  124. const sourceCode = context.getSourceCode();
  125. /**
  126. * Gets the last item of the given node.
  127. * @param {ASTNode} node - The node to get.
  128. * @returns {ASTNode|null} The last node or null.
  129. */
  130. function getLastItem(node) {
  131. switch (node.type) {
  132. case "ObjectExpression":
  133. case "ObjectPattern":
  134. return lodash.last(node.properties);
  135. case "ArrayExpression":
  136. case "ArrayPattern":
  137. return lodash.last(node.elements);
  138. case "ImportDeclaration":
  139. case "ExportNamedDeclaration":
  140. return lodash.last(node.specifiers);
  141. case "FunctionDeclaration":
  142. case "FunctionExpression":
  143. case "ArrowFunctionExpression":
  144. return lodash.last(node.params);
  145. case "CallExpression":
  146. case "NewExpression":
  147. return lodash.last(node.arguments);
  148. default:
  149. return null;
  150. }
  151. }
  152. /**
  153. * Gets the trailing comma token of the given node.
  154. * If the trailing comma does not exist, this returns the token which is
  155. * the insertion point of the trailing comma token.
  156. *
  157. * @param {ASTNode} node - The node to get.
  158. * @param {ASTNode} lastItem - The last item of the node.
  159. * @returns {Token} The trailing comma token or the insertion point.
  160. */
  161. function getTrailingToken(node, lastItem) {
  162. switch (node.type) {
  163. case "ObjectExpression":
  164. case "ArrayExpression":
  165. case "CallExpression":
  166. case "NewExpression":
  167. return sourceCode.getLastToken(node, 1);
  168. default: {
  169. const nextToken = sourceCode.getTokenAfter(lastItem);
  170. if (astUtils.isCommaToken(nextToken)) {
  171. return nextToken;
  172. }
  173. return sourceCode.getLastToken(lastItem);
  174. }
  175. }
  176. }
  177. /**
  178. * Checks whether or not a given node is multiline.
  179. * This rule handles a given node as multiline when the closing parenthesis
  180. * and the last element are not on the same line.
  181. *
  182. * @param {ASTNode} node - A node to check.
  183. * @returns {boolean} `true` if the node is multiline.
  184. */
  185. function isMultiline(node) {
  186. const lastItem = getLastItem(node);
  187. if (!lastItem) {
  188. return false;
  189. }
  190. const penultimateToken = getTrailingToken(node, lastItem);
  191. const lastToken = sourceCode.getTokenAfter(penultimateToken);
  192. return lastToken.loc.end.line !== penultimateToken.loc.end.line;
  193. }
  194. /**
  195. * Reports a trailing comma if it exists.
  196. *
  197. * @param {ASTNode} node - A node to check. Its type is one of
  198. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  199. * ImportDeclaration, and ExportNamedDeclaration.
  200. * @returns {void}
  201. */
  202. function forbidTrailingComma(node) {
  203. const lastItem = getLastItem(node);
  204. if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
  205. return;
  206. }
  207. const trailingToken = getTrailingToken(node, lastItem);
  208. if (astUtils.isCommaToken(trailingToken)) {
  209. context.report({
  210. node: lastItem,
  211. loc: trailingToken.loc.start,
  212. messageId: "unexpected",
  213. fix(fixer) {
  214. return fixer.remove(trailingToken);
  215. }
  216. });
  217. }
  218. }
  219. /**
  220. * Reports the last element of a given node if it does not have a trailing
  221. * comma.
  222. *
  223. * If a given node is `ArrayPattern` which has `RestElement`, the trailing
  224. * comma is disallowed, so report if it exists.
  225. *
  226. * @param {ASTNode} node - A node to check. Its type is one of
  227. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  228. * ImportDeclaration, and ExportNamedDeclaration.
  229. * @returns {void}
  230. */
  231. function forceTrailingComma(node) {
  232. const lastItem = getLastItem(node);
  233. if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
  234. return;
  235. }
  236. if (!isTrailingCommaAllowed(lastItem)) {
  237. forbidTrailingComma(node);
  238. return;
  239. }
  240. const trailingToken = getTrailingToken(node, lastItem);
  241. if (trailingToken.value !== ",") {
  242. context.report({
  243. node: lastItem,
  244. loc: trailingToken.loc.end,
  245. messageId: "missing",
  246. fix(fixer) {
  247. return fixer.insertTextAfter(trailingToken, ",");
  248. }
  249. });
  250. }
  251. }
  252. /**
  253. * If a given node is multiline, reports the last element of a given node
  254. * when it does not have a trailing comma.
  255. * Otherwise, reports a trailing comma if it exists.
  256. *
  257. * @param {ASTNode} node - A node to check. Its type is one of
  258. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  259. * ImportDeclaration, and ExportNamedDeclaration.
  260. * @returns {void}
  261. */
  262. function forceTrailingCommaIfMultiline(node) {
  263. if (isMultiline(node)) {
  264. forceTrailingComma(node);
  265. } else {
  266. forbidTrailingComma(node);
  267. }
  268. }
  269. /**
  270. * Only if a given node is not multiline, reports the last element of a given node
  271. * when it does not have a trailing comma.
  272. * Otherwise, reports a trailing comma if it exists.
  273. *
  274. * @param {ASTNode} node - A node to check. Its type is one of
  275. * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
  276. * ImportDeclaration, and ExportNamedDeclaration.
  277. * @returns {void}
  278. */
  279. function allowTrailingCommaIfMultiline(node) {
  280. if (!isMultiline(node)) {
  281. forbidTrailingComma(node);
  282. }
  283. }
  284. const predicate = {
  285. always: forceTrailingComma,
  286. "always-multiline": forceTrailingCommaIfMultiline,
  287. "only-multiline": allowTrailingCommaIfMultiline,
  288. never: forbidTrailingComma,
  289. ignore: lodash.noop
  290. };
  291. return {
  292. ObjectExpression: predicate[options.objects],
  293. ObjectPattern: predicate[options.objects],
  294. ArrayExpression: predicate[options.arrays],
  295. ArrayPattern: predicate[options.arrays],
  296. ImportDeclaration: predicate[options.imports],
  297. ExportNamedDeclaration: predicate[options.exports],
  298. FunctionDeclaration: predicate[options.functions],
  299. FunctionExpression: predicate[options.functions],
  300. ArrowFunctionExpression: predicate[options.functions],
  301. CallExpression: predicate[options.functions],
  302. NewExpression: predicate[options.functions]
  303. };
  304. }
  305. };