Permalink
Cannot retrieve contributors at this time
| /** | |
| * @fileoverview Rule to require newlines before `return` statement | |
| * @author Kai Cataldo | |
| * @deprecated | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "layout", | |
| docs: { | |
| description: "require an empty line before `return` statements", | |
| category: "Stylistic Issues", | |
| recommended: false, | |
| url: "https://eslint.org/docs/rules/newline-before-return" | |
| }, | |
| fixable: "whitespace", | |
| schema: [], | |
| messages: { | |
| expected: "Expected newline before return statement." | |
| }, | |
| deprecated: true, | |
| replacedBy: ["padding-line-between-statements"] | |
| }, | |
| create(context) { | |
| const sourceCode = context.getSourceCode(); | |
| //-------------------------------------------------------------------------- | |
| // Helpers | |
| //-------------------------------------------------------------------------- | |
| /** | |
| * Tests whether node is preceded by supplied tokens | |
| * @param {ASTNode} node node to check | |
| * @param {Array} testTokens array of tokens to test against | |
| * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens | |
| * @private | |
| */ | |
| function isPrecededByTokens(node, testTokens) { | |
| const tokenBefore = sourceCode.getTokenBefore(node); | |
| return testTokens.some(token => tokenBefore.value === token); | |
| } | |
| /** | |
| * Checks whether node is the first node after statement or in block | |
| * @param {ASTNode} node node to check | |
| * @returns {boolean} Whether or not the node is the first node after statement or in block | |
| * @private | |
| */ | |
| function isFirstNode(node) { | |
| const parentType = node.parent.type; | |
| if (node.parent.body) { | |
| return Array.isArray(node.parent.body) | |
| ? node.parent.body[0] === node | |
| : node.parent.body === node; | |
| } | |
| if (parentType === "IfStatement") { | |
| return isPrecededByTokens(node, ["else", ")"]); | |
| } | |
| if (parentType === "DoWhileStatement") { | |
| return isPrecededByTokens(node, ["do"]); | |
| } | |
| if (parentType === "SwitchCase") { | |
| return isPrecededByTokens(node, [":"]); | |
| } | |
| return isPrecededByTokens(node, [")"]); | |
| } | |
| /** | |
| * Returns the number of lines of comments that precede the node | |
| * @param {ASTNode} node node to check for overlapping comments | |
| * @param {number} lineNumTokenBefore line number of previous token, to check for overlapping comments | |
| * @returns {number} Number of lines of comments that precede the node | |
| * @private | |
| */ | |
| function calcCommentLines(node, lineNumTokenBefore) { | |
| const comments = sourceCode.getCommentsBefore(node); | |
| let numLinesComments = 0; | |
| if (!comments.length) { | |
| return numLinesComments; | |
| } | |
| comments.forEach(comment => { | |
| numLinesComments++; | |
| if (comment.type === "Block") { | |
| numLinesComments += comment.loc.end.line - comment.loc.start.line; | |
| } | |
| // avoid counting lines with inline comments twice | |
| if (comment.loc.start.line === lineNumTokenBefore) { | |
| numLinesComments--; | |
| } | |
| if (comment.loc.end.line === node.loc.start.line) { | |
| numLinesComments--; | |
| } | |
| }); | |
| return numLinesComments; | |
| } | |
| /** | |
| * Returns the line number of the token before the node that is passed in as an argument | |
| * @param {ASTNode} node The node to use as the start of the calculation | |
| * @returns {number} Line number of the token before `node` | |
| * @private | |
| */ | |
| function getLineNumberOfTokenBefore(node) { | |
| const tokenBefore = sourceCode.getTokenBefore(node); | |
| let lineNumTokenBefore; | |
| /** | |
| * Global return (at the beginning of a script) is a special case. | |
| * If there is no token before `return`, then we expect no line | |
| * break before the return. Comments are allowed to occupy lines | |
| * before the global return, just no blank lines. | |
| * Setting lineNumTokenBefore to zero in that case results in the | |
| * desired behavior. | |
| */ | |
| if (tokenBefore) { | |
| lineNumTokenBefore = tokenBefore.loc.end.line; | |
| } else { | |
| lineNumTokenBefore = 0; // global return at beginning of script | |
| } | |
| return lineNumTokenBefore; | |
| } | |
| /** | |
| * Checks whether node is preceded by a newline | |
| * @param {ASTNode} node node to check | |
| * @returns {boolean} Whether or not the node is preceded by a newline | |
| * @private | |
| */ | |
| function hasNewlineBefore(node) { | |
| const lineNumNode = node.loc.start.line; | |
| const lineNumTokenBefore = getLineNumberOfTokenBefore(node); | |
| const commentLines = calcCommentLines(node, lineNumTokenBefore); | |
| return (lineNumNode - lineNumTokenBefore - commentLines) > 1; | |
| } | |
| /** | |
| * Checks whether it is safe to apply a fix to a given return statement. | |
| * | |
| * The fix is not considered safe if the given return statement has leading comments, | |
| * as we cannot safely determine if the newline should be added before or after the comments. | |
| * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211 | |
| * @param {ASTNode} node The return statement node to check. | |
| * @returns {boolean} `true` if it can fix the node. | |
| * @private | |
| */ | |
| function canFix(node) { | |
| const leadingComments = sourceCode.getCommentsBefore(node); | |
| const lastLeadingComment = leadingComments[leadingComments.length - 1]; | |
| const tokenBefore = sourceCode.getTokenBefore(node); | |
| if (leadingComments.length === 0) { | |
| return true; | |
| } | |
| /* | |
| * if the last leading comment ends in the same line as the previous token and | |
| * does not share a line with the `return` node, we can consider it safe to fix. | |
| * Example: | |
| * function a() { | |
| * var b; //comment | |
| * return; | |
| * } | |
| */ | |
| if (lastLeadingComment.loc.end.line === tokenBefore.loc.end.line && | |
| lastLeadingComment.loc.end.line !== node.loc.start.line) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Public | |
| //-------------------------------------------------------------------------- | |
| return { | |
| ReturnStatement(node) { | |
| if (!isFirstNode(node) && !hasNewlineBefore(node)) { | |
| context.report({ | |
| node, | |
| messageId: "expected", | |
| fix(fixer) { | |
| if (canFix(node)) { | |
| const tokenBefore = sourceCode.getTokenBefore(node); | |
| const newlines = node.loc.start.line === tokenBefore.loc.end.line ? "\n\n" : "\n"; | |
| return fixer.insertTextBefore(node, newlines); | |
| } | |
| return null; | |
| } | |
| }); | |
| } | |
| } | |
| }; | |
| } | |
| }; |