Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

260 строки
9.3 KiB

  1. /**
  2. * @fileoverview Rule to enforce line breaks after each array element
  3. * @author Jan Peer Stöcklmair <https://github.com/JPeer264>
  4. */
  5. "use strict";
  6. const astUtils = require("../util/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. module.exports = {
  11. meta: {
  12. docs: {
  13. description: "enforce line breaks after each array element",
  14. category: "Stylistic Issues",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/array-element-newline"
  17. },
  18. fixable: "whitespace",
  19. schema: [
  20. {
  21. oneOf: [
  22. {
  23. enum: ["always", "never", "consistent"]
  24. },
  25. {
  26. type: "object",
  27. properties: {
  28. multiline: {
  29. type: "boolean"
  30. },
  31. minItems: {
  32. type: ["integer", "null"],
  33. minimum: 0
  34. }
  35. },
  36. additionalProperties: false
  37. }
  38. ]
  39. }
  40. ],
  41. messages: {
  42. unexpectedLineBreak: "There should be no linebreak here.",
  43. missingLineBreak: "There should be a linebreak after this element."
  44. }
  45. },
  46. create(context) {
  47. const sourceCode = context.getSourceCode();
  48. //----------------------------------------------------------------------
  49. // Helpers
  50. //----------------------------------------------------------------------
  51. /**
  52. * Normalizes a given option value.
  53. *
  54. * @param {string|Object|undefined} providedOption - An option value to parse.
  55. * @returns {{multiline: boolean, minItems: number}} Normalized option object.
  56. */
  57. function normalizeOptionValue(providedOption) {
  58. let consistent = false;
  59. let multiline = false;
  60. let minItems;
  61. const option = providedOption || "always";
  62. if (!option || option === "always" || option.minItems === 0) {
  63. minItems = 0;
  64. } else if (option === "never") {
  65. minItems = Number.POSITIVE_INFINITY;
  66. } else if (option === "consistent") {
  67. consistent = true;
  68. minItems = Number.POSITIVE_INFINITY;
  69. } else {
  70. multiline = Boolean(option.multiline);
  71. minItems = option.minItems || Number.POSITIVE_INFINITY;
  72. }
  73. return { consistent, multiline, minItems };
  74. }
  75. /**
  76. * Normalizes a given option value.
  77. *
  78. * @param {string|Object|undefined} options - An option value to parse.
  79. * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
  80. */
  81. function normalizeOptions(options) {
  82. const value = normalizeOptionValue(options);
  83. return { ArrayExpression: value, ArrayPattern: value };
  84. }
  85. /**
  86. * Reports that there shouldn't be a line break after the first token
  87. * @param {Token} token - The token to use for the report.
  88. * @returns {void}
  89. */
  90. function reportNoLineBreak(token) {
  91. const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
  92. context.report({
  93. loc: {
  94. start: tokenBefore.loc.end,
  95. end: token.loc.start
  96. },
  97. messageId: "unexpectedLineBreak",
  98. fix(fixer) {
  99. if (astUtils.isCommentToken(tokenBefore)) {
  100. return null;
  101. }
  102. if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {
  103. return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
  104. }
  105. /*
  106. * This will check if the comma is on the same line as the next element
  107. * Following array:
  108. * [
  109. * 1
  110. * , 2
  111. * , 3
  112. * ]
  113. *
  114. * will be fixed to:
  115. * [
  116. * 1, 2, 3
  117. * ]
  118. */
  119. const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
  120. if (astUtils.isCommentToken(twoTokensBefore)) {
  121. return null;
  122. }
  123. return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
  124. }
  125. });
  126. }
  127. /**
  128. * Reports that there should be a line break after the first token
  129. * @param {Token} token - The token to use for the report.
  130. * @returns {void}
  131. */
  132. function reportRequiredLineBreak(token) {
  133. const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
  134. context.report({
  135. loc: {
  136. start: tokenBefore.loc.end,
  137. end: token.loc.start
  138. },
  139. messageId: "missingLineBreak",
  140. fix(fixer) {
  141. return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
  142. }
  143. });
  144. }
  145. /**
  146. * Reports a given node if it violated this rule.
  147. *
  148. * @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
  149. * @param {{multiline: boolean, minItems: number}} options - An option object.
  150. * @returns {void}
  151. */
  152. function check(node) {
  153. const elements = node.elements;
  154. const normalizedOptions = normalizeOptions(context.options[0]);
  155. const options = normalizedOptions[node.type];
  156. let elementBreak = false;
  157. /*
  158. * MULTILINE: true
  159. * loop through every element and check
  160. * if at least one element has linebreaks inside
  161. * this ensures that following is not valid (due to elements are on the same line):
  162. *
  163. * [
  164. * 1,
  165. * 2,
  166. * 3
  167. * ]
  168. */
  169. if (options.multiline) {
  170. elementBreak = elements
  171. .filter(element => element !== null)
  172. .some(element => element.loc.start.line !== element.loc.end.line);
  173. }
  174. const linebreaksCount = node.elements.map((element, i) => {
  175. const previousElement = elements[i - 1];
  176. if (i === 0 || element === null || previousElement === null) {
  177. return false;
  178. }
  179. const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
  180. const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
  181. const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
  182. return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement);
  183. }).filter(isBreak => isBreak === true).length;
  184. const needsLinebreaks = (
  185. elements.length >= options.minItems ||
  186. (
  187. options.multiline &&
  188. elementBreak
  189. ) ||
  190. (
  191. options.consistent &&
  192. linebreaksCount > 0 &&
  193. linebreaksCount < node.elements.length
  194. )
  195. );
  196. elements.forEach((element, i) => {
  197. const previousElement = elements[i - 1];
  198. if (i === 0 || element === null || previousElement === null) {
  199. return;
  200. }
  201. const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
  202. const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
  203. const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
  204. if (needsLinebreaks) {
  205. if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
  206. reportRequiredLineBreak(firstTokenOfCurrentElement);
  207. }
  208. } else {
  209. if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
  210. reportNoLineBreak(firstTokenOfCurrentElement);
  211. }
  212. }
  213. });
  214. }
  215. //----------------------------------------------------------------------
  216. // Public
  217. //----------------------------------------------------------------------
  218. return {
  219. ArrayPattern: check,
  220. ArrayExpression: check
  221. };
  222. }
  223. };