Blame view
node_modules/eslint/lib/rules/newline-after-var.js
9.36 KB
f7563de62
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
/** * @fileoverview Rule to check empty newline after "var" statement * @author Gopal Venkatesan */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const astUtils = require("../ast-utils"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { docs: { description: "require or disallow an empty line after variable declarations", category: "Stylistic Issues", recommended: false }, schema: [ { enum: ["never", "always"] } ], fixable: "whitespace" }, create(context) { const ALWAYS_MESSAGE = "Expected blank line after variable declarations.", NEVER_MESSAGE = "Unexpected blank line after variable declarations."; const sourceCode = context.getSourceCode(); // Default `mode` to "always". const mode = context.options[0] === "never" ? "never" : "always"; // Cache starting and ending line numbers of comments for faster lookup const commentEndLine = sourceCode.getAllComments().reduce((result, token) => { result[token.loc.start.line] = token.loc.end.line; return result; }, {}); //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- /** * Gets a token from the given node to compare line to the next statement. * * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy. * * - The last token is semicolon. * - The semicolon is on a different line from the previous token of the semicolon. * * This behavior would address semicolon-less style code. e.g.: * * var foo = 1 * * ;(a || b).doSomething() * * @param {ASTNode} node - The node to get. * @returns {Token} The token to compare line to the next statement. */ function getLastToken(node) { const lastToken = sourceCode.getLastToken(node); if (lastToken.type === "Punctuator" && lastToken.value === ";") { const prevToken = sourceCode.getTokenBefore(lastToken); if (prevToken.loc.end.line !== lastToken.loc.start.line) { return prevToken; } } return lastToken; } /** * Determine if provided keyword is a variable declaration * @private * @param {string} keyword - keyword to test * @returns {boolean} True if `keyword` is a type of var */ function isVar(keyword) { return keyword === "var" || keyword === "let" || keyword === "const"; } /** * Determine if provided keyword is a variant of for specifiers * @private * @param {string} keyword - keyword to test * @returns {boolean} True if `keyword` is a variant of for specifier */ function isForTypeSpecifier(keyword) { return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; } /** * Determine if provided keyword is an export specifiers * @private * @param {string} nodeType - nodeType to test * @returns {boolean} True if `nodeType` is an export specifier */ function isExportSpecifier(nodeType) { return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; } /** * Determine if provided node is the last of their parent block. * @private * @param {ASTNode} node - node to test * @returns {boolean} True if `node` is last of their parent block. */ function isLastNode(node) { const token = sourceCode.getTokenAfter(node); return !token || (token.type === "Punctuator" && token.value === "}"); } /** * Gets the last line of a group of consecutive comments * @param {number} commentStartLine The starting line of the group * @returns {number} The number of the last comment line of the group */ function getLastCommentLineOfBlock(commentStartLine) { const currentCommentEnd = commentEndLine[commentStartLine]; return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd; } /** * Determine if a token starts more than one line after a comment ends * @param {token} token The token being checked * @param {integer} commentStartLine The line number on which the comment starts * @returns {boolean} True if `token` does not start immediately after a comment */ function hasBlankLineAfterComment(token, commentStartLine) { return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1; } /** * Checks that a blank line exists after a variable declaration when mode is * set to "always", or checks that there is no blank line when mode is set * to "never" * @private * @param {ASTNode} node - `VariableDeclaration` node to test * @returns {void} */ function checkForBlankLine(node) { /* * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will * sometimes be second-last if there is a semicolon on a different line. */ const lastToken = getLastToken(node), /* * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken * is the last token of the node. */ nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node), nextLineNum = lastToken.loc.end.line + 1; // Ignore if there is no following statement if (!nextToken) { return; } // Ignore if parent of node is a for variant if (isForTypeSpecifier(node.parent.type)) { return; } // Ignore if parent of node is an export specifier if (isExportSpecifier(node.parent.type)) { return; } // Some coding styles use multiple `var` statements, so do nothing if // the next token is a `var` statement. if (nextToken.type === "Keyword" && isVar(nextToken.value)) { return; } // Ignore if it is last statement in a block if (isLastNode(node)) { return; } // Next statement is not a `var`... const noNextLineToken = nextToken.loc.start.line > nextLineNum; const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); if (mode === "never" && noNextLineToken && !hasNextLineComment) { context.report({ node, message: NEVER_MESSAGE, data: { identifier: node.name }, fix(fixer) { const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")} ${linesBetween[linesBetween.length - 1]}`); } }); } // Token on the next line, or comment without blank line if ( mode === "always" && ( !noNextLineToken || hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) ) ) { context.report({ node, message: ALWAYS_MESSAGE, data: { identifier: node.name }, fix(fixer) { if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) { return fixer.insertTextBefore(nextToken, " "); } return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], " "); } }); } } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { VariableDeclaration: checkForBlankLine }; } }; |