New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update: add fixer for newline-before-return (fixes #5958) #7050
Changes from 3 commits
52b6ab0
0c352e2
a68f4a1
0b6e24b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ module.exports = { | |
category: "Stylistic Issues", | ||
recommended: false | ||
}, | ||
|
||
fixable: "whitespace", | ||
schema: [] | ||
}, | ||
|
||
|
@@ -103,35 +103,81 @@ 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; | ||
|
||
/** | ||
* 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. | ||
*/ | ||
* Global return (at the beginning of a script) is a special case. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like these spaces were removed, but I'm actually used to seeing them there. The examples in the valid-jsdoc docs also follow this style. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spaces are preferred in the little-known ESLint style guide. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, d'oh! I'm not sure how they got removed, was definitely not intentional :) will fix it! |
||
* 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.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; | ||
} | ||
}); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}" | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test was a duplicate of the one before it, so I removed it. |
||
{ | ||
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}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In some tests (like this one), I've added an |
||
}, | ||
{ | ||
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}" | ||
} | ||
] | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be "code" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be "whitespace" since we're only altering whitespace