UNPKG

8.1 kBJavaScriptView Raw
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
12module.exports = {
13 meta: {
14 docs: {
15 description: "disallow multiple empty lines",
16 category: "Stylistic Issues",
17 recommended: false
18 },
19
20 fixable: "whitespace",
21
22 schema: [
23 {
24 type: "object",
25 properties: {
26 max: {
27 type: "integer",
28 minimum: 0
29 },
30 maxEOF: {
31 type: "integer",
32 minimum: 0
33 },
34 maxBOF: {
35 type: "integer",
36 minimum: 0
37 }
38 },
39 required: ["max"],
40 additionalProperties: false
41 }
42 ]
43 },
44
45 create(context) {
46
47 // Use options.max or 2 as default
48 let max = 2,
49 maxEOF,
50 maxBOF;
51
52 // store lines that appear empty but really aren't
53 const notEmpty = [];
54
55 if (context.options.length) {
56 max = context.options[0].max;
57 maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
58 maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
59 }
60
61 const sourceCode = context.getSourceCode();
62
63 //--------------------------------------------------------------------------
64 // Public
65 //--------------------------------------------------------------------------
66
67 return {
68
69 TemplateLiteral(node) {
70 let start = node.loc.start.line;
71 const end = node.loc.end.line;
72
73 while (start <= end) {
74 notEmpty.push(start);
75 start++;
76 }
77 },
78
79 "Program:exit": function checkBlankLines(node) {
80 const lines = sourceCode.lines,
81 fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
82 linesRangeStart = [];
83 let firstNonBlankLine = -1,
84 trimmedLines = [],
85 blankCounter = 0,
86 currentLocation,
87 lastLocation,
88 firstOfEndingBlankLines,
89 diff,
90 rangeStart,
91 rangeEnd;
92
93 /**
94 * Fix code.
95 * @param {RuleFixer} fixer - The fixer of this context.
96 * @returns {Object} The fixing information.
97 */
98 function fix(fixer) {
99 return fixer.removeRange([rangeStart, rangeEnd]);
100 }
101
102 linesRangeStart.push(0);
103 lines.forEach(function(str, i) {
104 const length = i < fullLines.length ? fullLines[i].length : 0,
105 trimmed = str.trim();
106
107 if ((firstNonBlankLine === -1) && (trimmed !== "")) {
108 firstNonBlankLine = i;
109 }
110
111 linesRangeStart.push(linesRangeStart[linesRangeStart.length - 1] + length);
112 trimmedLines.push(trimmed);
113 });
114
115 // add the notEmpty lines in there with a placeholder
116 notEmpty.forEach(function(x, i) {
117 trimmedLines[i] = x;
118 });
119
120 if (typeof maxEOF === "undefined") {
121
122 /*
123 * Swallow the final newline, as some editors add it
124 * automatically and we don't want it to cause an issue
125 */
126 if (trimmedLines[trimmedLines.length - 1] === "") {
127 trimmedLines = trimmedLines.slice(0, -1);
128 }
129
130 firstOfEndingBlankLines = trimmedLines.length;
131 } else {
132
133 // save the number of the first of the last blank lines
134 firstOfEndingBlankLines = trimmedLines.length;
135 while (trimmedLines[firstOfEndingBlankLines - 1] === ""
136 && firstOfEndingBlankLines > 0) {
137 firstOfEndingBlankLines--;
138 }
139 }
140
141 // Aggregate and count blank lines
142 if (firstNonBlankLine > maxBOF) {
143 diff = firstNonBlankLine - maxBOF;
144 rangeStart = linesRangeStart[firstNonBlankLine - diff];
145 rangeEnd = linesRangeStart[firstNonBlankLine];
146 context.report({
147 node,
148 loc: node.loc.start,
149 message: "Too many blank lines at the beginning of file. Max of {{maxBOF}} allowed.",
150 data: {
151 maxBOF
152 },
153 fix
154 });
155 }
156 currentLocation = firstNonBlankLine - 1;
157
158 lastLocation = currentLocation;
159 currentLocation = trimmedLines.indexOf("", currentLocation + 1);
160 while (currentLocation !== -1) {
161 lastLocation = currentLocation;
162 currentLocation = trimmedLines.indexOf("", currentLocation + 1);
163 if (lastLocation === currentLocation - 1) {
164 blankCounter++;
165 } else {
166 const location = {
167 line: lastLocation + 1,
168 column: 0
169 };
170
171 if (lastLocation < firstOfEndingBlankLines) {
172
173 // within the file, not at the end
174 if (blankCounter >= max) {
175 diff = blankCounter - max + 1;
176 rangeStart = linesRangeStart[location.line - diff];
177 rangeEnd = linesRangeStart[location.line];
178
179 context.report({
180 node,
181 loc: location,
182 message: "More than {{max}} blank {{lines}} not allowed.",
183 data: {
184 max,
185 lines: (max === 1 ? "line" : "lines")
186 },
187 fix
188 });
189 }
190 } else {
191
192 // inside the last blank lines
193 if (blankCounter > maxEOF) {
194 diff = blankCounter - maxEOF + 1;
195 rangeStart = linesRangeStart[location.line - diff];
196 rangeEnd = linesRangeStart[location.line - 1];
197 context.report({
198 node,
199 loc: location,
200 message: "Too many blank lines at the end of file. Max of {{maxEOF}} allowed.",
201 data: {
202 maxEOF
203 },
204 fix
205 });
206 }
207 }
208
209 // Finally, reset the blank counter
210 blankCounter = 0;
211 }
212 }
213 }
214 };
215
216 }
217};