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