array-bracket-newline.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /**
  2. * @fileoverview Rule to enforce linebreaks after open and before close array brackets
  3. * @author Jan Peer Stöcklmair <https://github.com/JPeer264>
  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: "array-bracket-newline",
  30. url: "https://eslint.style/rules/array-bracket-newline",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description:
  38. "Enforce linebreaks after opening and before closing array brackets",
  39. recommended: false,
  40. url: "https://eslint.org/docs/latest/rules/array-bracket-newline",
  41. },
  42. fixable: "whitespace",
  43. schema: [
  44. {
  45. oneOf: [
  46. {
  47. enum: ["always", "never", "consistent"],
  48. },
  49. {
  50. type: "object",
  51. properties: {
  52. multiline: {
  53. type: "boolean",
  54. },
  55. minItems: {
  56. type: ["integer", "null"],
  57. minimum: 0,
  58. },
  59. },
  60. additionalProperties: false,
  61. },
  62. ],
  63. },
  64. ],
  65. messages: {
  66. unexpectedOpeningLinebreak:
  67. "There should be no linebreak after '['.",
  68. unexpectedClosingLinebreak:
  69. "There should be no linebreak before ']'.",
  70. missingOpeningLinebreak: "A linebreak is required after '['.",
  71. missingClosingLinebreak: "A linebreak is required before ']'.",
  72. },
  73. },
  74. create(context) {
  75. const sourceCode = context.sourceCode;
  76. //----------------------------------------------------------------------
  77. // Helpers
  78. //----------------------------------------------------------------------
  79. /**
  80. * Normalizes a given option value.
  81. * @param {string|Object|undefined} option An option value to parse.
  82. * @returns {{multiline: boolean, minItems: number}} Normalized option object.
  83. */
  84. function normalizeOptionValue(option) {
  85. let consistent = false;
  86. let multiline = false;
  87. let minItems;
  88. if (option) {
  89. if (option === "consistent") {
  90. consistent = true;
  91. minItems = Number.POSITIVE_INFINITY;
  92. } else if (option === "always" || option.minItems === 0) {
  93. minItems = 0;
  94. } else if (option === "never") {
  95. minItems = Number.POSITIVE_INFINITY;
  96. } else {
  97. multiline = Boolean(option.multiline);
  98. minItems = option.minItems || Number.POSITIVE_INFINITY;
  99. }
  100. } else {
  101. consistent = false;
  102. multiline = true;
  103. minItems = Number.POSITIVE_INFINITY;
  104. }
  105. return { consistent, multiline, minItems };
  106. }
  107. /**
  108. * Normalizes a given option value.
  109. * @param {string|Object|undefined} options An option value to parse.
  110. * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
  111. */
  112. function normalizeOptions(options) {
  113. const value = normalizeOptionValue(options);
  114. return { ArrayExpression: value, ArrayPattern: value };
  115. }
  116. /**
  117. * Reports that there shouldn't be a linebreak after the first token
  118. * @param {ASTNode} node The node to report in the event of an error.
  119. * @param {Token} token The token to use for the report.
  120. * @returns {void}
  121. */
  122. function reportNoBeginningLinebreak(node, token) {
  123. context.report({
  124. node,
  125. loc: token.loc,
  126. messageId: "unexpectedOpeningLinebreak",
  127. fix(fixer) {
  128. const nextToken = sourceCode.getTokenAfter(token, {
  129. includeComments: true,
  130. });
  131. if (astUtils.isCommentToken(nextToken)) {
  132. return null;
  133. }
  134. return fixer.removeRange([
  135. token.range[1],
  136. nextToken.range[0],
  137. ]);
  138. },
  139. });
  140. }
  141. /**
  142. * Reports that there shouldn't be a linebreak before the last token
  143. * @param {ASTNode} node The node to report in the event of an error.
  144. * @param {Token} token The token to use for the report.
  145. * @returns {void}
  146. */
  147. function reportNoEndingLinebreak(node, token) {
  148. context.report({
  149. node,
  150. loc: token.loc,
  151. messageId: "unexpectedClosingLinebreak",
  152. fix(fixer) {
  153. const previousToken = sourceCode.getTokenBefore(token, {
  154. includeComments: true,
  155. });
  156. if (astUtils.isCommentToken(previousToken)) {
  157. return null;
  158. }
  159. return fixer.removeRange([
  160. previousToken.range[1],
  161. token.range[0],
  162. ]);
  163. },
  164. });
  165. }
  166. /**
  167. * Reports that there should be a linebreak after the first token
  168. * @param {ASTNode} node The node to report in the event of an error.
  169. * @param {Token} token The token to use for the report.
  170. * @returns {void}
  171. */
  172. function reportRequiredBeginningLinebreak(node, token) {
  173. context.report({
  174. node,
  175. loc: token.loc,
  176. messageId: "missingOpeningLinebreak",
  177. fix(fixer) {
  178. return fixer.insertTextAfter(token, "\n");
  179. },
  180. });
  181. }
  182. /**
  183. * Reports that there should be a linebreak before the last token
  184. * @param {ASTNode} node The node to report in the event of an error.
  185. * @param {Token} token The token to use for the report.
  186. * @returns {void}
  187. */
  188. function reportRequiredEndingLinebreak(node, token) {
  189. context.report({
  190. node,
  191. loc: token.loc,
  192. messageId: "missingClosingLinebreak",
  193. fix(fixer) {
  194. return fixer.insertTextBefore(token, "\n");
  195. },
  196. });
  197. }
  198. /**
  199. * Reports a given node if it violated this rule.
  200. * @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node.
  201. * @returns {void}
  202. */
  203. function check(node) {
  204. const elements = node.elements;
  205. const normalizedOptions = normalizeOptions(context.options[0]);
  206. const options = normalizedOptions[node.type];
  207. const openBracket = sourceCode.getFirstToken(node);
  208. const closeBracket = sourceCode.getLastToken(node);
  209. const firstIncComment = sourceCode.getTokenAfter(openBracket, {
  210. includeComments: true,
  211. });
  212. const lastIncComment = sourceCode.getTokenBefore(closeBracket, {
  213. includeComments: true,
  214. });
  215. const first = sourceCode.getTokenAfter(openBracket);
  216. const last = sourceCode.getTokenBefore(closeBracket);
  217. const needsLinebreaks =
  218. elements.length >= options.minItems ||
  219. (options.multiline &&
  220. elements.length > 0 &&
  221. firstIncComment.loc.start.line !==
  222. lastIncComment.loc.end.line) ||
  223. (elements.length === 0 &&
  224. firstIncComment.type === "Block" &&
  225. firstIncComment.loc.start.line !==
  226. lastIncComment.loc.end.line &&
  227. firstIncComment === lastIncComment) ||
  228. (options.consistent &&
  229. openBracket.loc.end.line !== first.loc.start.line);
  230. /*
  231. * Use tokens or comments to check multiline or not.
  232. * But use only tokens to check whether linebreaks are needed.
  233. * This allows:
  234. * var arr = [ // eslint-disable-line foo
  235. * 'a'
  236. * ]
  237. */
  238. if (needsLinebreaks) {
  239. if (astUtils.isTokenOnSameLine(openBracket, first)) {
  240. reportRequiredBeginningLinebreak(node, openBracket);
  241. }
  242. if (astUtils.isTokenOnSameLine(last, closeBracket)) {
  243. reportRequiredEndingLinebreak(node, closeBracket);
  244. }
  245. } else {
  246. if (!astUtils.isTokenOnSameLine(openBracket, first)) {
  247. reportNoBeginningLinebreak(node, openBracket);
  248. }
  249. if (!astUtils.isTokenOnSameLine(last, closeBracket)) {
  250. reportNoEndingLinebreak(node, closeBracket);
  251. }
  252. }
  253. }
  254. //----------------------------------------------------------------------
  255. // Public
  256. //----------------------------------------------------------------------
  257. return {
  258. ArrayPattern: check,
  259. ArrayExpression: check,
  260. };
  261. },
  262. };