1/** 2 * @fileoverview A rule to ensure whitespace before blocks. 3 * @author Mathias Schreck <https://github.com/lo1tuma> 4 */ 5 6"use strict"; 7 8const astUtils = require("./utils/ast-utils"); 9 10//------------------------------------------------------------------------------ 11// Rule Definition 12//------------------------------------------------------------------------------ 13 14module.exports = { 15 meta: { 16 type: "layout", 17 18 docs: { 19 description: "enforce consistent spacing before blocks", 20 category: "Stylistic Issues", 21 recommended: false, 22 url: "https://eslint.org/docs/rules/space-before-blocks" 23 }, 24 25 fixable: "whitespace", 26 27 schema: [ 28 { 29 oneOf: [ 30 { 31 enum: ["always", "never"] 32 }, 33 { 34 type: "object", 35 properties: { 36 keywords: { 37 enum: ["always", "never", "off"] 38 }, 39 functions: { 40 enum: ["always", "never", "off"] 41 }, 42 classes: { 43 enum: ["always", "never", "off"] 44 } 45 }, 46 additionalProperties: false 47 } 48 ] 49 } 50 ], 51 52 messages: { 53 unexpectedSpace: "Unexpected space before opening brace.", 54 missingSpace: "Missing space before opening brace." 55 } 56 }, 57 58 create(context) { 59 const config = context.options[0], 60 sourceCode = context.getSourceCode(); 61 let alwaysFunctions = true, 62 alwaysKeywords = true, 63 alwaysClasses = true, 64 neverFunctions = false, 65 neverKeywords = false, 66 neverClasses = false; 67 68 if (typeof config === "object") { 69 alwaysFunctions = config.functions === "always"; 70 alwaysKeywords = config.keywords === "always"; 71 alwaysClasses = config.classes === "always"; 72 neverFunctions = config.functions === "never"; 73 neverKeywords = config.keywords === "never"; 74 neverClasses = config.classes === "never"; 75 } else if (config === "never") { 76 alwaysFunctions = false; 77 alwaysKeywords = false; 78 alwaysClasses = false; 79 neverFunctions = true; 80 neverKeywords = true; 81 neverClasses = true; 82 } 83 84 /** 85 * Checks whether or not a given token is an arrow operator (=>) or a keyword 86 * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`. 87 * @param {Token} token A token to check. 88 * @returns {boolean} `true` if the token is an arrow operator. 89 */ 90 function isConflicted(token) { 91 return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword"; 92 } 93 94 /** 95 * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. 96 * @param {ASTNode|Token} node The AST node of a BlockStatement. 97 * @returns {void} undefined. 98 */ 99 function checkPrecedingSpace(node) { 100 const precedingToken = sourceCode.getTokenBefore(node); 101 102 if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) { 103 const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node); 104 const parent = context.getAncestors().pop(); 105 let requireSpace; 106 let requireNoSpace; 107 108 if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") { 109 requireSpace = alwaysFunctions; 110 requireNoSpace = neverFunctions; 111 } else if (node.type === "ClassBody") { 112 requireSpace = alwaysClasses; 113 requireNoSpace = neverClasses; 114 } else { 115 requireSpace = alwaysKeywords; 116 requireNoSpace = neverKeywords; 117 } 118 119 if (requireSpace && !hasSpace) { 120 context.report({ 121 node, 122 messageId: "missingSpace", 123 fix(fixer) { 124 return fixer.insertTextBefore(node, " "); 125 } 126 }); 127 } else if (requireNoSpace && hasSpace) { 128 context.report({ 129 node, 130 messageId: "unexpectedSpace", 131 fix(fixer) { 132 return fixer.removeRange([precedingToken.range[1], node.range[0]]); 133 } 134 }); 135 } 136 } 137 } 138 139 /** 140 * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. 141 * @param {ASTNode} node The node of a SwitchStatement. 142 * @returns {void} undefined. 143 */ 144 function checkSpaceBeforeCaseBlock(node) { 145 const cases = node.cases; 146 let openingBrace; 147 148 if (cases.length > 0) { 149 openingBrace = sourceCode.getTokenBefore(cases[0]); 150 } else { 151 openingBrace = sourceCode.getLastToken(node, 1); 152 } 153 154 checkPrecedingSpace(openingBrace); 155 } 156 157 return { 158 BlockStatement: checkPrecedingSpace, 159 ClassBody: checkPrecedingSpace, 160 SwitchStatement: checkSpaceBeforeCaseBlock 161 }; 162 163 } 164}; 165