no-multiple-empty-lines.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /**
  2. * @fileoverview Disallows multiple blank lines.
  3. * implementation adapted from the no-trailing-spaces rule.
  4. * @author Greg Cochard
  5. * @deprecated in ESLint v8.53.0
  6. */
  7. "use strict";
  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: "no-multiple-empty-lines",
  30. url: "https://eslint.style/rules/no-multiple-empty-lines",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description: "Disallow multiple empty lines",
  38. recommended: false,
  39. url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines",
  40. },
  41. fixable: "whitespace",
  42. schema: [
  43. {
  44. type: "object",
  45. properties: {
  46. max: {
  47. type: "integer",
  48. minimum: 0,
  49. },
  50. maxEOF: {
  51. type: "integer",
  52. minimum: 0,
  53. },
  54. maxBOF: {
  55. type: "integer",
  56. minimum: 0,
  57. },
  58. },
  59. required: ["max"],
  60. additionalProperties: false,
  61. },
  62. ],
  63. messages: {
  64. blankBeginningOfFile:
  65. "Too many blank lines at the beginning of file. Max of {{max}} allowed.",
  66. blankEndOfFile:
  67. "Too many blank lines at the end of file. Max of {{max}} allowed.",
  68. consecutiveBlank:
  69. "More than {{max}} blank {{pluralizedLines}} not allowed.",
  70. },
  71. },
  72. create(context) {
  73. // Use options.max or 2 as default
  74. let max = 2,
  75. maxEOF = max,
  76. maxBOF = max;
  77. if (context.options.length) {
  78. max = context.options[0].max;
  79. maxEOF =
  80. typeof context.options[0].maxEOF !== "undefined"
  81. ? context.options[0].maxEOF
  82. : max;
  83. maxBOF =
  84. typeof context.options[0].maxBOF !== "undefined"
  85. ? context.options[0].maxBOF
  86. : max;
  87. }
  88. const sourceCode = context.sourceCode;
  89. // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue
  90. const allLines =
  91. sourceCode.lines.at(-1) === ""
  92. ? sourceCode.lines.slice(0, -1)
  93. : sourceCode.lines;
  94. const templateLiteralLines = new Set();
  95. //--------------------------------------------------------------------------
  96. // Public
  97. //--------------------------------------------------------------------------
  98. return {
  99. TemplateLiteral(node) {
  100. node.quasis.forEach(literalPart => {
  101. // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines.
  102. for (
  103. let ignoredLine = literalPart.loc.start.line;
  104. ignoredLine < literalPart.loc.end.line;
  105. ignoredLine++
  106. ) {
  107. templateLiteralLines.add(ignoredLine);
  108. }
  109. });
  110. },
  111. "Program:exit"(node) {
  112. return (
  113. allLines
  114. // Given a list of lines, first get a list of line numbers that are non-empty.
  115. .reduce((nonEmptyLineNumbers, line, index) => {
  116. if (
  117. line.trim() ||
  118. templateLiteralLines.has(index + 1)
  119. ) {
  120. nonEmptyLineNumbers.push(index + 1);
  121. }
  122. return nonEmptyLineNumbers;
  123. }, [])
  124. // Add a value at the end to allow trailing empty lines to be checked.
  125. .concat(allLines.length + 1)
  126. // Given two line numbers of non-empty lines, report the lines between if the difference is too large.
  127. .reduce((lastLineNumber, lineNumber) => {
  128. let messageId, maxAllowed;
  129. if (lastLineNumber === 0) {
  130. messageId = "blankBeginningOfFile";
  131. maxAllowed = maxBOF;
  132. } else if (lineNumber === allLines.length + 1) {
  133. messageId = "blankEndOfFile";
  134. maxAllowed = maxEOF;
  135. } else {
  136. messageId = "consecutiveBlank";
  137. maxAllowed = max;
  138. }
  139. if (lineNumber - lastLineNumber - 1 > maxAllowed) {
  140. context.report({
  141. node,
  142. loc: {
  143. start: {
  144. line:
  145. lastLineNumber + maxAllowed + 1,
  146. column: 0,
  147. },
  148. end: { line: lineNumber, column: 0 },
  149. },
  150. messageId,
  151. data: {
  152. max: maxAllowed,
  153. pluralizedLines:
  154. maxAllowed === 1 ? "line" : "lines",
  155. },
  156. fix(fixer) {
  157. const rangeStart =
  158. sourceCode.getIndexFromLoc({
  159. line: lastLineNumber + 1,
  160. column: 0,
  161. });
  162. /*
  163. * The end of the removal range is usually the start index of the next line.
  164. * However, at the end of the file there is no next line, so the end of the
  165. * range is just the length of the text.
  166. */
  167. const lineNumberAfterRemovedLines =
  168. lineNumber - maxAllowed;
  169. const rangeEnd =
  170. lineNumberAfterRemovedLines <=
  171. allLines.length
  172. ? sourceCode.getIndexFromLoc({
  173. line: lineNumberAfterRemovedLines,
  174. column: 0,
  175. })
  176. : sourceCode.text.length;
  177. return fixer.removeRange([
  178. rangeStart,
  179. rangeEnd,
  180. ]);
  181. },
  182. });
  183. }
  184. return lineNumber;
  185. }, 0)
  186. );
  187. },
  188. };
  189. },
  190. };