1 | /**
|
2 | * @fileoverview Rule to require newlines before `return` statement
|
3 | * @author Kai Cataldo
|
4 | * @deprecated
|
5 | */
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Rule Definition
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | module.exports = {
|
13 | meta: {
|
14 | type: "layout",
|
15 |
|
16 | docs: {
|
17 | description: "require an empty line before `return` statements",
|
18 | category: "Stylistic Issues",
|
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 | };
|