UNPKG

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