diff --git a/docs/rules/newline-before-return.md b/docs/rules/newline-before-return.md index 1a3c48584ec..dffffbf5363 100644 --- a/docs/rules/newline-before-return.md +++ b/docs/rules/newline-before-return.md @@ -1,5 +1,7 @@ # require an empty line before `return` statements (newline-before-return) +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. + There is no hard and fast rule about whether empty lines should precede `return` statements in JavaScript. However, clearly delineating where a function is returning can greatly increase the readability and clarity of the code. For example: ```js diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js index 461d7259035..7d297fd0035 100644 --- a/lib/rules/newline-before-return.js +++ b/lib/rules/newline-before-return.js @@ -15,7 +15,7 @@ module.exports = { category: "Stylistic Issues", recommended: false }, - + fixable: "whitespace", schema: [] }, @@ -103,14 +103,13 @@ module.exports = { } /** - * 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 + * 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 hasNewlineBefore(node) { - const tokenBefore = sourceCode.getTokenBefore(node), - lineNumNode = node.loc.start.line; + function getLineNumberOfTokenBefore(node) { + const tokenBefore = sourceCode.getTokenBefore(node); let lineNumTokenBefore; /** @@ -127,11 +126,58 @@ module.exports = { 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.getComments(node).leading; + 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 //-------------------------------------------------------------------------- @@ -141,7 +187,16 @@ module.exports = { if (!isFirstNode(node) && !hasNewlineBefore(node)) { context.report({ node, - message: "Expected newline before return statement." + message: "Expected newline before return statement.", + 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; + } }); } } diff --git a/tests/lib/rules/newline-before-return.js b/tests/lib/rules/newline-before-return.js index 4aee307e8f8..d310455a3c1 100644 --- a/tests/lib/rules/newline-before-return.js +++ b/tests/lib/rules/newline-before-return.js @@ -110,79 +110,171 @@ ruleTester.run("newline-before-return", rule, { ], invalid: [ + { + code: "function a() {\nvar b; return;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; \n\nreturn;\n}" + }, { code: "function a() {\nvar b;\nreturn;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n\nreturn;\n}" }, { code: "function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\nreturn d;\n}\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\n\nreturn d;\n}\n}" + }, + { + code: "function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne(); return d;\n}\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne(); \n\nreturn d;\n}\n}" }, { code: "function a() {\n while (b) {\nc();\nreturn;\n}\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\n while (b) {\nc();\n\nreturn;\n}\n}" }, { code: "function a() {\ndo {\nc();\nreturn;\n} while (b);\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\ndo {\nc();\n\nreturn;\n} while (b);\n}" }, { code: "function a() {\nfor (var b; b < c; b++) {\nc();\nreturn;\n}\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nfor (var b; b < c; b++) {\nc();\n\nreturn;\n}\n}" }, { code: "function a() {\nfor (b in c) {\nd();\nreturn;\n}\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nfor (b in c) {\nd();\n\nreturn;\n}\n}" }, { code: "function a() {\nfor (b of c) {\nd();\nreturn;\n}\n}", parserOptions: { ecmaVersion: 6 }, - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nfor (b of c) {\nd();\n\nreturn;\n}\n}" }, { code: "function a() {\nif (b) {\nc();\n}\n//comment\nreturn b;\n}", - errors: ["Expected newline before return statement."] - }, - { - code: "function a() {\nif (b) {\nc();\n}\n//comment\nreturn b;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) {\nc();\n}\n//comment\nreturn b;\n}" }, { code: "function a() {\n/*comment\ncomment*/\nif (b) {\nc();\nreturn b;\n} else {\n//comment\n\nreturn d;\n}\n/*multi-line\ncomment*/\nreturn e;\n}", - errors: ["Expected newline before return statement.", "Expected newline before return statement."] + errors: ["Expected newline before return statement.", "Expected newline before return statement."], + output: "function a() {\n/*comment\ncomment*/\nif (b) {\nc();\n\nreturn b;\n} else {\n//comment\n\nreturn d;\n}\n/*multi-line\ncomment*/\nreturn e;\n}" }, { code: "function a() {\nif (b) { return; } //comment\nreturn c;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) { return; } //comment\n\nreturn c;\n}" }, { code: "function a() {\nif (b) { return; } /*multi-line\ncomment*/\nreturn c;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) { return; } /*multi-line\ncomment*/\nreturn c;\n}" }, { code: "function a() {\nif (b) { return; }\n/*multi-line\ncomment*/ return c;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) { return; }\n/*multi-line\ncomment*/ return c;\n}" }, { code: "function a() {\nif (b) { return; } /*multi-line\ncomment*/ return c;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nif (b) { return; } /*multi-line\ncomment*/ return c;\n}" }, { code: "var a;\nreturn;", parserOptions: { ecmaFeatures: { globalReturn: true } }, - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "var a;\n\nreturn;" + }, + { + code: "var a; return;", + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: ["Expected newline before return statement."], + output: "var a; \n\nreturn;" }, { code: "function a() {\n{\n//comment\n}\nreturn\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\n{\n//comment\n}\n\nreturn\n}" + }, + { + code: "function a() {\n{\n//comment\n} return\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\n{\n//comment\n} \n\nreturn\n}" }, { code: "function a() {\nvar c;\nwhile (b) {\n c = d; //comment\n}\nreturn c;\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nvar c;\nwhile (b) {\n c = d; //comment\n}\n\nreturn c;\n}" }, { code: "function a() {\nfor (var b; b < c; b++) {\nif (d) {\nbreak; //comment\n}\nreturn;\n}\n}", - errors: ["Expected newline before return statement."] + errors: ["Expected newline before return statement."], + output: "function a() {\nfor (var b; b < c; b++) {\nif (d) {\nbreak; //comment\n}\n\nreturn;\n}\n}" + }, + + // Testing edge cases of the fixer when the `return` statement has leading comments. + // https://github.com/eslint/eslint/issues/5958 + { + code: "function a() {\nvar b; /*multi-line\ncomment*/\nreturn c;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; /*multi-line\ncomment*/\nreturn c;\n}" + }, + { + code: "function a() {\nvar b;\n/*multi-line\ncomment*/ return c;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n/*multi-line\ncomment*/ return c;\n}" + }, + { + code: "function a() {\nvar b; /*multi-line\ncomment*/ return c;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; /*multi-line\ncomment*/ return c;\n}" + }, + { + code: "function a() {\nvar b;\n//comment\nreturn;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n//comment\nreturn;\n}" + }, + { + code: "function a() {\nvar b; //comment\nreturn;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; //comment\n\nreturn;\n}" + }, + { + code: "function a() {\nvar b;\n/* comment */ return;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n/* comment */ return;\n}" + }, + { + code: "function a() {\nvar b;\n//comment\n/* comment */ return;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n//comment\n/* comment */ return;\n}" + }, + { + code: "function a() {\nvar b; /* comment */ return;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; /* comment */ return;\n}" + }, + { + code: "function a() {\nvar b; /* comment */\nreturn;\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; /* comment */\n\nreturn;\n}" + }, + { + code: "function a() {\nvar b;\nreturn; //comment\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b;\n\nreturn; //comment\n}" + }, + { + code: "function a() {\nvar b; return; //comment\n}", + errors: ["Expected newline before return statement."], + output: "function a() {\nvar b; \n\nreturn; //comment\n}" } ] });