1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | module.exports = {
|
13 | meta: {
|
14 | docs: {
|
15 | description: "require or disallow padding within blocks",
|
16 | category: "Stylistic Issues",
|
17 | recommended: false,
|
18 | url: "https://eslint.org/docs/rules/padded-blocks"
|
19 | },
|
20 |
|
21 | fixable: "whitespace",
|
22 |
|
23 | schema: [
|
24 | {
|
25 | oneOf: [
|
26 | {
|
27 | enum: ["always", "never"]
|
28 | },
|
29 | {
|
30 | type: "object",
|
31 | properties: {
|
32 | blocks: {
|
33 | enum: ["always", "never"]
|
34 | },
|
35 | switches: {
|
36 | enum: ["always", "never"]
|
37 | },
|
38 | classes: {
|
39 | enum: ["always", "never"]
|
40 | }
|
41 | },
|
42 | additionalProperties: false,
|
43 | minProperties: 1
|
44 | }
|
45 | ]
|
46 | }
|
47 | ]
|
48 | },
|
49 |
|
50 | create(context) {
|
51 | const options = {};
|
52 | const config = context.options[0] || "always";
|
53 |
|
54 | if (typeof config === "string") {
|
55 | const shouldHavePadding = config === "always";
|
56 |
|
57 | options.blocks = shouldHavePadding;
|
58 | options.switches = shouldHavePadding;
|
59 | options.classes = shouldHavePadding;
|
60 | } else {
|
61 | if (config.hasOwnProperty("blocks")) {
|
62 | options.blocks = config.blocks === "always";
|
63 | }
|
64 | if (config.hasOwnProperty("switches")) {
|
65 | options.switches = config.switches === "always";
|
66 | }
|
67 | if (config.hasOwnProperty("classes")) {
|
68 | options.classes = config.classes === "always";
|
69 | }
|
70 | }
|
71 |
|
72 | const ALWAYS_MESSAGE = "Block must be padded by blank lines.",
|
73 | NEVER_MESSAGE = "Block must not be padded by blank lines.";
|
74 |
|
75 | const sourceCode = context.getSourceCode();
|
76 |
|
77 | |
78 |
|
79 |
|
80 |
|
81 |
|
82 | function getOpenBrace(node) {
|
83 | if (node.type === "SwitchStatement") {
|
84 | return sourceCode.getTokenBefore(node.cases[0]);
|
85 | }
|
86 | return sourceCode.getFirstToken(node);
|
87 | }
|
88 |
|
89 | |
90 |
|
91 |
|
92 |
|
93 |
|
94 | function isComment(node) {
|
95 | return node.type === "Line" || node.type === "Block";
|
96 | }
|
97 |
|
98 | |
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | function isPaddingBetweenTokens(first, second) {
|
105 | return second.loc.start.line - first.loc.end.line >= 2;
|
106 | }
|
107 |
|
108 |
|
109 | |
110 |
|
111 |
|
112 |
|
113 |
|
114 | function getFirstBlockToken(token) {
|
115 | let prev,
|
116 | first = token;
|
117 |
|
118 | do {
|
119 | prev = first;
|
120 | first = sourceCode.getTokenAfter(first, { includeComments: true });
|
121 | } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
|
122 |
|
123 | return first;
|
124 | }
|
125 |
|
126 | |
127 |
|
128 |
|
129 |
|
130 |
|
131 | function getLastBlockToken(token) {
|
132 | let last = token,
|
133 | next;
|
134 |
|
135 | do {
|
136 | next = last;
|
137 | last = sourceCode.getTokenBefore(last, { includeComments: true });
|
138 | } while (isComment(last) && last.loc.end.line === next.loc.start.line);
|
139 |
|
140 | return last;
|
141 | }
|
142 |
|
143 | |
144 |
|
145 |
|
146 |
|
147 |
|
148 | function requirePaddingFor(node) {
|
149 | switch (node.type) {
|
150 | case "BlockStatement":
|
151 | return options.blocks;
|
152 | case "SwitchStatement":
|
153 | return options.switches;
|
154 | case "ClassBody":
|
155 | return options.classes;
|
156 |
|
157 |
|
158 | default:
|
159 | throw new Error("unreachable");
|
160 | }
|
161 | }
|
162 |
|
163 | |
164 |
|
165 |
|
166 |
|
167 |
|
168 | function checkPadding(node) {
|
169 | const openBrace = getOpenBrace(node),
|
170 | firstBlockToken = getFirstBlockToken(openBrace),
|
171 | tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
|
172 | closeBrace = sourceCode.getLastToken(node),
|
173 | lastBlockToken = getLastBlockToken(closeBrace),
|
174 | tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
|
175 | blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
|
176 | blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
|
177 |
|
178 | if (requirePaddingFor(node)) {
|
179 | if (!blockHasTopPadding) {
|
180 | context.report({
|
181 | node,
|
182 | loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
|
183 | fix(fixer) {
|
184 | return fixer.insertTextAfter(tokenBeforeFirst, "\n");
|
185 | },
|
186 | message: ALWAYS_MESSAGE
|
187 | });
|
188 | }
|
189 | if (!blockHasBottomPadding) {
|
190 | context.report({
|
191 | node,
|
192 | loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
|
193 | fix(fixer) {
|
194 | return fixer.insertTextBefore(tokenAfterLast, "\n");
|
195 | },
|
196 | message: ALWAYS_MESSAGE
|
197 | });
|
198 | }
|
199 | } else {
|
200 | if (blockHasTopPadding) {
|
201 |
|
202 | context.report({
|
203 | node,
|
204 | loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
|
205 | fix(fixer) {
|
206 | return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
|
207 | },
|
208 | message: NEVER_MESSAGE
|
209 | });
|
210 | }
|
211 |
|
212 | if (blockHasBottomPadding) {
|
213 |
|
214 | context.report({
|
215 | node,
|
216 | loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
|
217 | message: NEVER_MESSAGE,
|
218 | fix(fixer) {
|
219 | return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
|
220 | }
|
221 | });
|
222 | }
|
223 | }
|
224 | }
|
225 |
|
226 | const rule = {};
|
227 |
|
228 | if (options.hasOwnProperty("switches")) {
|
229 | rule.SwitchStatement = function(node) {
|
230 | if (node.cases.length === 0) {
|
231 | return;
|
232 | }
|
233 | checkPadding(node);
|
234 | };
|
235 | }
|
236 |
|
237 | if (options.hasOwnProperty("blocks")) {
|
238 | rule.BlockStatement = function(node) {
|
239 | if (node.body.length === 0) {
|
240 | return;
|
241 | }
|
242 | checkPadding(node);
|
243 | };
|
244 | }
|
245 |
|
246 | if (options.hasOwnProperty("classes")) {
|
247 | rule.ClassBody = function(node) {
|
248 | if (node.body.length === 0) {
|
249 | return;
|
250 | }
|
251 | checkPadding(node);
|
252 | };
|
253 | }
|
254 |
|
255 | return rule;
|
256 | }
|
257 | };
|