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 | 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 | };
|