UNPKG

7.87 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to require newlines before `return` statement
3 * @author Kai Cataldo
4 * @deprecated
5 */
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "require an empty line before `return` statements",
16 category: "Stylistic Issues",
17 recommended: false,
18 replacedBy: ["padding-line-between-statements"]
19 },
20 fixable: "whitespace",
21 schema: [],
22 deprecated: true
23 },
24
25 create(context) {
26 const sourceCode = context.getSourceCode();
27
28 //--------------------------------------------------------------------------
29 // Helpers
30 //--------------------------------------------------------------------------
31
32 /**
33 * Tests whether node is preceded by supplied tokens
34 * @param {ASTNode} node - node to check
35 * @param {array} testTokens - array of tokens to test against
36 * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
37 * @private
38 */
39 function isPrecededByTokens(node, testTokens) {
40 const tokenBefore = sourceCode.getTokenBefore(node);
41
42 return testTokens.some(token => tokenBefore.value === token);
43 }
44
45 /**
46 * Checks whether node is the first node after statement or in block
47 * @param {ASTNode} node - node to check
48 * @returns {boolean} Whether or not the node is the first node after statement or in block
49 * @private
50 */
51 function isFirstNode(node) {
52 const parentType = node.parent.type;
53
54 if (node.parent.body) {
55 return Array.isArray(node.parent.body)
56 ? node.parent.body[0] === node
57 : node.parent.body === node;
58 }
59
60 if (parentType === "IfStatement") {
61 return isPrecededByTokens(node, ["else", ")"]);
62 } else if (parentType === "DoWhileStatement") {
63 return isPrecededByTokens(node, ["do"]);
64 } else if (parentType === "SwitchCase") {
65 return isPrecededByTokens(node, [":"]);
66 }
67 return isPrecededByTokens(node, [")"]);
68
69 }
70
71 /**
72 * Returns the number of lines of comments that precede the node
73 * @param {ASTNode} node - node to check for overlapping comments
74 * @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments
75 * @returns {number} Number of lines of comments that precede the node
76 * @private
77 */
78 function calcCommentLines(node, lineNumTokenBefore) {
79 const comments = sourceCode.getCommentsBefore(node);
80 let numLinesComments = 0;
81
82 if (!comments.length) {
83 return numLinesComments;
84 }
85
86 comments.forEach(comment => {
87 numLinesComments++;
88
89 if (comment.type === "Block") {
90 numLinesComments += comment.loc.end.line - comment.loc.start.line;
91 }
92
93 // avoid counting lines with inline comments twice
94 if (comment.loc.start.line === lineNumTokenBefore) {
95 numLinesComments--;
96 }
97
98 if (comment.loc.end.line === node.loc.start.line) {
99 numLinesComments--;
100 }
101 });
102
103 return numLinesComments;
104 }
105
106 /**
107 * Returns the line number of the token before the node that is passed in as an argument
108 * @param {ASTNode} node - The node to use as the start of the calculation
109 * @returns {number} Line number of the token before `node`
110 * @private
111 */
112 function getLineNumberOfTokenBefore(node) {
113 const tokenBefore = sourceCode.getTokenBefore(node);
114 let lineNumTokenBefore;
115
116 /**
117 * Global return (at the beginning of a script) is a special case.
118 * If there is no token before `return`, then we expect no line
119 * break before the return. Comments are allowed to occupy lines
120 * before the global return, just no blank lines.
121 * Setting lineNumTokenBefore to zero in that case results in the
122 * desired behavior.
123 */
124 if (tokenBefore) {
125 lineNumTokenBefore = tokenBefore.loc.end.line;
126 } else {
127 lineNumTokenBefore = 0; // global return at beginning of script
128 }
129
130 return lineNumTokenBefore;
131 }
132
133 /**
134 * Checks whether node is preceded by a newline
135 * @param {ASTNode} node - node to check
136 * @returns {boolean} Whether or not the node is preceded by a newline
137 * @private
138 */
139 function hasNewlineBefore(node) {
140 const lineNumNode = node.loc.start.line;
141 const lineNumTokenBefore = getLineNumberOfTokenBefore(node);
142 const commentLines = calcCommentLines(node, lineNumTokenBefore);
143
144 return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
145 }
146
147 /**
148 * Checks whether it is safe to apply a fix to a given return statement.
149 *
150 * The fix is not considered safe if the given return statement has leading comments,
151 * as we cannot safely determine if the newline should be added before or after the comments.
152 * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211
153 *
154 * @param {ASTNode} node - The return statement node to check.
155 * @returns {boolean} `true` if it can fix the node.
156 * @private
157 */
158 function canFix(node) {
159 const leadingComments = sourceCode.getCommentsBefore(node);
160 const lastLeadingComment = leadingComments[leadingComments.length - 1];
161 const tokenBefore = sourceCode.getTokenBefore(node);
162
163 if (leadingComments.length === 0) {
164 return true;
165 }
166
167 // if the last leading comment ends in the same line as the previous token and
168 // does not share a line with the `return` node, we can consider it safe to fix.
169 // Example:
170 // function a() {
171 // var b; //comment
172 // return;
173 // }
174 if (lastLeadingComment.loc.end.line === tokenBefore.loc.end.line &&
175 lastLeadingComment.loc.end.line !== node.loc.start.line) {
176 return true;
177 }
178
179 return false;
180 }
181
182 //--------------------------------------------------------------------------
183 // Public
184 //--------------------------------------------------------------------------
185
186 return {
187 ReturnStatement(node) {
188 if (!isFirstNode(node) && !hasNewlineBefore(node)) {
189 context.report({
190 node,
191 message: "Expected newline before return statement.",
192 fix(fixer) {
193 if (canFix(node)) {
194 const tokenBefore = sourceCode.getTokenBefore(node);
195 const newlines = node.loc.start.line === tokenBefore.loc.end.line ? "\n\n" : "\n";
196
197 return fixer.insertTextBefore(node, newlines);
198 }
199 return null;
200 }
201 });
202 }
203 }
204 };
205 }
206};