]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-multiple-empty-lines.js
33ac76f60378941e2d0da22fd9fd929d232322c9
[pve-eslint.git] / eslint / lib / rules / no-multiple-empty-lines.js
1 /**
2 * @fileoverview Disallows multiple blank lines.
3 * implementation adapted from the no-trailing-spaces rule.
4 * @author Greg Cochard
5 */
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = {
13 meta: {
14 type: "layout",
15
16 docs: {
17 description: "disallow multiple empty lines",
18 recommended: false,
19 url: "https://eslint.org/docs/rules/no-multiple-empty-lines"
20 },
21
22 fixable: "whitespace",
23
24 schema: [
25 {
26 type: "object",
27 properties: {
28 max: {
29 type: "integer",
30 minimum: 0
31 },
32 maxEOF: {
33 type: "integer",
34 minimum: 0
35 },
36 maxBOF: {
37 type: "integer",
38 minimum: 0
39 }
40 },
41 required: ["max"],
42 additionalProperties: false
43 }
44 ],
45
46 messages: {
47 blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.",
48 blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.",
49 consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed."
50 }
51 },
52
53 create(context) {
54
55 // Use options.max or 2 as default
56 let max = 2,
57 maxEOF = max,
58 maxBOF = max;
59
60 if (context.options.length) {
61 max = context.options[0].max;
62 maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
63 maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
64 }
65
66 const sourceCode = context.getSourceCode();
67
68 // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue
69 const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines;
70 const templateLiteralLines = new Set();
71
72 //--------------------------------------------------------------------------
73 // Public
74 //--------------------------------------------------------------------------
75
76 return {
77 TemplateLiteral(node) {
78 node.quasis.forEach(literalPart => {
79
80 // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines.
81 for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) {
82 templateLiteralLines.add(ignoredLine);
83 }
84 });
85 },
86 "Program:exit"(node) {
87 return allLines
88
89 // Given a list of lines, first get a list of line numbers that are non-empty.
90 .reduce((nonEmptyLineNumbers, line, index) => {
91 if (line.trim() || templateLiteralLines.has(index + 1)) {
92 nonEmptyLineNumbers.push(index + 1);
93 }
94 return nonEmptyLineNumbers;
95 }, [])
96
97 // Add a value at the end to allow trailing empty lines to be checked.
98 .concat(allLines.length + 1)
99
100 // Given two line numbers of non-empty lines, report the lines between if the difference is too large.
101 .reduce((lastLineNumber, lineNumber) => {
102 let messageId, maxAllowed;
103
104 if (lastLineNumber === 0) {
105 messageId = "blankBeginningOfFile";
106 maxAllowed = maxBOF;
107 } else if (lineNumber === allLines.length + 1) {
108 messageId = "blankEndOfFile";
109 maxAllowed = maxEOF;
110 } else {
111 messageId = "consecutiveBlank";
112 maxAllowed = max;
113 }
114
115 if (lineNumber - lastLineNumber - 1 > maxAllowed) {
116 context.report({
117 node,
118 loc: {
119 start: { line: lastLineNumber + maxAllowed + 1, column: 0 },
120 end: { line: lineNumber, column: 0 }
121 },
122 messageId,
123 data: {
124 max: maxAllowed,
125 pluralizedLines: maxAllowed === 1 ? "line" : "lines"
126 },
127 fix(fixer) {
128 const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 });
129
130 /*
131 * The end of the removal range is usually the start index of the next line.
132 * However, at the end of the file there is no next line, so the end of the
133 * range is just the length of the text.
134 */
135 const lineNumberAfterRemovedLines = lineNumber - maxAllowed;
136 const rangeEnd = lineNumberAfterRemovedLines <= allLines.length
137 ? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 })
138 : sourceCode.text.length;
139
140 return fixer.removeRange([rangeStart, rangeEnd]);
141 }
142 });
143 }
144
145 return lineNumber;
146 }, 0);
147 }
148 };
149 }
150 };