UNPKG

6.21 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to check empty newline after "var" statement
3 * @author Gopal Venkatesan
4 * @copyright 2015 Gopal Venkatesan. All rights reserved.
5 * @copyright 2015 Casey Visco. All rights reserved.
6 * @copyright 2015 Ian VanSchooten. All rights reserved.
7 */
8
9"use strict";
10
11//------------------------------------------------------------------------------
12// Rule Definition
13//------------------------------------------------------------------------------
14
15module.exports = function(context) {
16
17 var ALWAYS_MESSAGE = "Expected blank line after variable declarations.",
18 NEVER_MESSAGE = "Unexpected blank line after variable declarations.";
19
20 // Default `mode` to "always".
21 var mode = context.options[0] === "never" ? "never" : "always";
22
23 // Cache starting and ending line numbers of comments for faster lookup
24 var commentEndLine = context.getAllComments().reduce(function(result, token) {
25 result[token.loc.start.line] = token.loc.end.line;
26 return result;
27 }, {});
28
29
30 //--------------------------------------------------------------------------
31 // Helpers
32 //--------------------------------------------------------------------------
33
34 /**
35 * Determine if provided keyword is a variable declaration
36 * @private
37 * @param {string} keyword - keyword to test
38 * @returns {boolean} True if `keyword` is a type of var
39 */
40 function isVar(keyword) {
41 return keyword === "var" || keyword === "let" || keyword === "const";
42 }
43
44 /**
45 * Determine if provided keyword is a variant of for specifiers
46 * @private
47 * @param {string} keyword - keyword to test
48 * @returns {boolean} True if `keyword` is a variant of for specifier
49 */
50 function isForTypeSpecifier(keyword) {
51 return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
52 }
53
54 /**
55 * Determine if provided keyword is an export specifiers
56 * @private
57 * @param {string} nodeType - nodeType to test
58 * @returns {boolean} True if `nodeType` is an export specifier
59 */
60 function isExportSpecifier(nodeType) {
61 return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
62 nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
63 }
64
65 /**
66 * Determine if provided nodeType is a function specifier
67 * @private
68 * @param {string} nodeType - nodeType to test
69 * @returns {boolean} True if `nodeType` is a function specifier
70 */
71 function isFunctionSpecifier(nodeType) {
72 return nodeType === "FunctionDeclaration" || nodeType === "FunctionExpression" ||
73 nodeType === "ArrowFunctionExpression";
74 }
75
76 /**
77 * Determine if provided node is the last of his parent
78 * @private
79 * @param {ASTNode} node - node to test
80 * @returns {boolean} True if `node` is last of his parent
81 */
82 function isLastNode(node) {
83 return node.parent.body[node.parent.body.length - 1] === node;
84 }
85
86 /**
87 * Determine if a token starts more than one line after a comment ends
88 * @param {token} token The token being checked
89 * @param {integer} commentStartLine The line number on which the comment starts
90 * @returns {boolean} True if `token` does not start immediately after a comment
91 */
92 function hasBlankLineAfterComment(token, commentStartLine) {
93 var commentEnd = commentEndLine[commentStartLine];
94 // If there's another comment, repeat check for blank line
95 if (commentEndLine[commentEnd + 1]) {
96 return hasBlankLineAfterComment(token, commentEnd + 1);
97 }
98 return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
99 }
100
101 /**
102 * Checks that a blank line exists after a variable declaration when mode is
103 * set to "always", or checks that there is no blank line when mode is set
104 * to "never"
105 * @private
106 * @param {ASTNode} node - `VariableDeclaration` node to test
107 * @returns {void}
108 */
109 function checkForBlankLine(node) {
110 var lastToken = context.getLastToken(node),
111 nextToken = context.getTokenAfter(node),
112 nextLineNum = lastToken.loc.end.line + 1,
113 noNextLineToken,
114 hasNextLineComment;
115
116 // Ignore if there is no following statement
117 if (!nextToken) {
118 return;
119 }
120
121 // Ignore if parent of node is a for variant
122 if (isForTypeSpecifier(node.parent.type)) {
123 return;
124 }
125
126 // Ignore if parent of node is an export specifier
127 if (isExportSpecifier(node.parent.type)) {
128 return;
129 }
130
131 // Some coding styles use multiple `var` statements, so do nothing if
132 // the next token is a `var` statement.
133 if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
134 return;
135 }
136
137 // Ignore if it is last statement in a function
138 if (node.parent.parent && isFunctionSpecifier(node.parent.parent.type) && isLastNode(node)) {
139 return;
140 }
141
142 // Next statement is not a `var`...
143 noNextLineToken = nextToken.loc.start.line > nextLineNum;
144 hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
145
146 if (mode === "never" && noNextLineToken && !hasNextLineComment) {
147 context.report(node, NEVER_MESSAGE, { identifier: node.name });
148 }
149
150 // Token on the next line, or comment without blank line
151 if (
152 mode === "always" && (
153 !noNextLineToken ||
154 hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
155 )
156 ) {
157 context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
158 }
159 }
160
161 //--------------------------------------------------------------------------
162 // Public
163 //--------------------------------------------------------------------------
164
165 return {
166 "VariableDeclaration": checkForBlankLine
167 };
168
169};
170
171module.exports.schema = [
172 {
173 "enum": ["never", "always"]
174 }
175];