2 * @fileoverview A rule to ensure blank lines within blocks.
3 * @author Mathias Schreck <https://github.com/lo1tuma>
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils = require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
23 description: "require or disallow padding within blocks",
24 category: "Stylistic Issues",
26 url: "https://eslint.org/docs/rules/padded-blocks"
29 fixable: "whitespace",
35 enum: ["always", "never"]
41 enum: ["always", "never"]
44 enum: ["always", "never"]
47 enum: ["always", "never"]
50 additionalProperties: false,
58 allowSingleLineBlocks: {
68 const typeOptions = context.options[0] || "always";
69 const exceptOptions = context.options[1] || {};
71 if (typeof typeOptions === "string") {
72 const shouldHavePadding = typeOptions === "always";
74 options.blocks = shouldHavePadding;
75 options.switches = shouldHavePadding;
76 options.classes = shouldHavePadding;
78 if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) {
79 options.blocks = typeOptions.blocks === "always";
81 if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) {
82 options.switches = typeOptions.switches === "always";
84 if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) {
85 options.classes = typeOptions.classes === "always";
89 if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) {
90 options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true;
93 const ALWAYS_MESSAGE = "Block must be padded by blank lines.",
94 NEVER_MESSAGE = "Block must not be padded by blank lines.";
96 const sourceCode = context.getSourceCode();
99 * Gets the open brace token from a given node.
100 * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace.
101 * @returns {Token} The token of the open brace.
103 function getOpenBrace(node) {
104 if (node.type === "SwitchStatement") {
105 return sourceCode.getTokenBefore(node.cases[0]);
107 return sourceCode.getFirstToken(node);
111 * Checks if the given parameter is a comment node
112 * @param {ASTNode|Token} node An AST node or token
113 * @returns {boolean} True if node is a comment
115 function isComment(node) {
116 return node.type === "Line" || node.type === "Block";
120 * Checks if there is padding between two tokens
121 * @param {Token} first The first token
122 * @param {Token} second The second token
123 * @returns {boolean} True if there is at least a line between the tokens
125 function isPaddingBetweenTokens(first, second) {
126 return second.loc.start.line - first.loc.end.line >= 2;
131 * Checks if the given token has a blank line after it.
132 * @param {Token} token The token to check.
133 * @returns {boolean} Whether or not the token is followed by a blank line.
135 function getFirstBlockToken(token) {
141 first = sourceCode.getTokenAfter(first, { includeComments: true });
142 } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
148 * Checks if the given token is preceeded by a blank line.
149 * @param {Token} token The token to check
150 * @returns {boolean} Whether or not the token is preceeded by a blank line
152 function getLastBlockToken(token) {
158 last = sourceCode.getTokenBefore(last, { includeComments: true });
159 } while (isComment(last) && last.loc.end.line === next.loc.start.line);
165 * Checks if a node should be padded, according to the rule config.
166 * @param {ASTNode} node The AST node to check.
167 * @returns {boolean} True if the node should be padded, false otherwise.
169 function requirePaddingFor(node) {
171 case "BlockStatement":
172 return options.blocks;
173 case "SwitchStatement":
174 return options.switches;
176 return options.classes;
178 /* istanbul ignore next */
180 throw new Error("unreachable");
185 * Checks the given BlockStatement node to be padded if the block is not empty.
186 * @param {ASTNode} node The AST node of a BlockStatement.
187 * @returns {void} undefined.
189 function checkPadding(node) {
190 const openBrace = getOpenBrace(node),
191 firstBlockToken = getFirstBlockToken(openBrace),
192 tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
193 closeBrace = sourceCode.getLastToken(node),
194 lastBlockToken = getLastBlockToken(closeBrace),
195 tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
196 blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
197 blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
199 if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) {
203 if (requirePaddingFor(node)) {
204 if (!blockHasTopPadding) {
207 loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
209 return fixer.insertTextAfter(tokenBeforeFirst, "\n");
211 message: ALWAYS_MESSAGE
214 if (!blockHasBottomPadding) {
217 loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
219 return fixer.insertTextBefore(tokenAfterLast, "\n");
221 message: ALWAYS_MESSAGE
225 if (blockHasTopPadding) {
229 loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
231 return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
233 message: NEVER_MESSAGE
237 if (blockHasBottomPadding) {
241 loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
242 message: NEVER_MESSAGE,
244 return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
253 if (Object.prototype.hasOwnProperty.call(options, "switches")) {
254 rule.SwitchStatement = function(node) {
255 if (node.cases.length === 0) {
262 if (Object.prototype.hasOwnProperty.call(options, "blocks")) {
263 rule.BlockStatement = function(node) {
264 if (node.body.length === 0) {
271 if (Object.prototype.hasOwnProperty.call(options, "classes")) {
272 rule.ClassBody = function(node) {
273 if (node.body.length === 0) {