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 | ;
|
10 |
|
11 | //------------------------------------------------------------------------------
|
12 | // Rule Definition
|
13 | //------------------------------------------------------------------------------
|
14 |
|
15 | module.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 |
|
171 | module.exports.schema = [
|
172 | {
|
173 | "enum": ["never", "always"]
|
174 | }
|
175 | ];
|