1/** 2 * @fileoverview Validates spacing before and after semicolon 3 * @author Mathias Schreck 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 and after semicolons", 20 category: "Stylistic Issues", 21 recommended: false, 22 url: "https://eslint.org/docs/rules/semi-spacing" 23 }, 24 25 fixable: "whitespace", 26 27 schema: [ 28 { 29 type: "object", 30 properties: { 31 before: { 32 type: "boolean", 33 default: false 34 }, 35 after: { 36 type: "boolean", 37 default: true 38 } 39 }, 40 additionalProperties: false 41 } 42 ], 43 44 messages: { 45 unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.", 46 unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.", 47 missingWhitespaceBefore: "Missing whitespace before semicolon.", 48 missingWhitespaceAfter: "Missing whitespace after semicolon." 49 } 50 }, 51 52 create(context) { 53 54 const config = context.options[0], 55 sourceCode = context.getSourceCode(); 56 let requireSpaceBefore = false, 57 requireSpaceAfter = true; 58 59 if (typeof config === "object") { 60 requireSpaceBefore = config.before; 61 requireSpaceAfter = config.after; 62 } 63 64 /** 65 * Checks if a given token has leading whitespace. 66 * @param {Object} token The token to check. 67 * @returns {boolean} True if the given token has leading space, false if not. 68 */ 69 function hasLeadingSpace(token) { 70 const tokenBefore = sourceCode.getTokenBefore(token); 71 72 return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token); 73 } 74 75 /** 76 * Checks if a given token has trailing whitespace. 77 * @param {Object} token The token to check. 78 * @returns {boolean} True if the given token has trailing space, false if not. 79 */ 80 function hasTrailingSpace(token) { 81 const tokenAfter = sourceCode.getTokenAfter(token); 82 83 return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); 84 } 85 86 /** 87 * Checks if the given token is the last token in its line. 88 * @param {Token} token The token to check. 89 * @returns {boolean} Whether or not the token is the last in its line. 90 */ 91 function isLastTokenInCurrentLine(token) { 92 const tokenAfter = sourceCode.getTokenAfter(token); 93 94 return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); 95 } 96 97 /** 98 * Checks if the given token is the first token in its line 99 * @param {Token} token The token to check. 100 * @returns {boolean} Whether or not the token is the first in its line. 101 */ 102 function isFirstTokenInCurrentLine(token) { 103 const tokenBefore = sourceCode.getTokenBefore(token); 104 105 return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); 106 } 107 108 /** 109 * Checks if the next token of a given token is a closing parenthesis. 110 * @param {Token} token The token to check. 111 * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. 112 */ 113 function isBeforeClosingParen(token) { 114 const nextToken = sourceCode.getTokenAfter(token); 115 116 return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken)); 117 } 118 119 /** 120 * Report location example : 121 * 122 * for unexpected space `before` 123 * 124 * var a = 'b' ; 125 * ^^^ 126 * 127 * for unexpected space `after` 128 * 129 * var a = 'b'; c = 10; 130 * ^^ 131 * 132 * Reports if the given token has invalid spacing. 133 * @param {Token} token The semicolon token to check. 134 * @param {ASTNode} node The corresponding node of the token. 135 * @returns {void} 136 */ 137 function checkSemicolonSpacing(token, node) { 138 if (astUtils.isSemicolonToken(token)) { 139 if (hasLeadingSpace(token)) { 140 if (!requireSpaceBefore) { 141 const tokenBefore = sourceCode.getTokenBefore(token); 142 const loc = { 143 start: tokenBefore.loc.end, 144 end: token.loc.start 145 }; 146 147 context.report({ 148 node, 149 loc, 150 messageId: "unexpectedWhitespaceBefore", 151 fix(fixer) { 152 153 return fixer.removeRange([tokenBefore.range[1], token.range[0]]); 154 } 155 }); 156 } 157 } else { 158 if (requireSpaceBefore) { 159 const loc = token.loc; 160 161 context.report({ 162 node, 163 loc, 164 messageId: "missingWhitespaceBefore", 165 fix(fixer) { 166 return fixer.insertTextBefore(token, " "); 167 } 168 }); 169 } 170 } 171 172 if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) { 173 if (hasTrailingSpace(token)) { 174 if (!requireSpaceAfter) { 175 const tokenAfter = sourceCode.getTokenAfter(token); 176 const loc = { 177 start: token.loc.end, 178 end: tokenAfter.loc.start 179 }; 180 181 context.report({ 182 node, 183 loc, 184 messageId: "unexpectedWhitespaceAfter", 185 fix(fixer) { 186 187 return fixer.removeRange([token.range[1], tokenAfter.range[0]]); 188 } 189 }); 190 } 191 } else { 192 if (requireSpaceAfter) { 193 const loc = token.loc; 194 195 context.report({ 196 node, 197 loc, 198 messageId: "missingWhitespaceAfter", 199 fix(fixer) { 200 return fixer.insertTextAfter(token, " "); 201 } 202 }); 203 } 204 } 205 } 206 } 207 } 208 209 /** 210 * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. 211 * @param {ASTNode} node The node to check. 212 * @returns {void} 213 */ 214 function checkNode(node) { 215 const token = sourceCode.getLastToken(node); 216 217 checkSemicolonSpacing(token, node); 218 } 219 220 return { 221 VariableDeclaration: checkNode, 222 ExpressionStatement: checkNode, 223 BreakStatement: checkNode, 224 ContinueStatement: checkNode, 225 DebuggerStatement: checkNode, 226 ReturnStatement: checkNode, 227 ThrowStatement: checkNode, 228 ImportDeclaration: checkNode, 229 ExportNamedDeclaration: checkNode, 230 ExportAllDeclaration: checkNode, 231 ExportDefaultDeclaration: checkNode, 232 ForStatement(node) { 233 if (node.init) { 234 checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node); 235 } 236 237 if (node.test) { 238 checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); 239 } 240 } 241 }; 242 } 243}; 244