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