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: make newline-per-chained-call fixable #9149
Update: make newline-per-chained-call fixable #9149
Conversation
LGTM |
@joaogranado, thanks for your PR! By analyzing the history of the files in this pull request, we identified @mysticatea, @rpatil26 and @vitorbal to be potential reviewers. |
Thanks for the pull request. Unfortunately, I think this is one of those rules that we can't support autofix for due to the complexities around calculating the correct indentation for each chain. Generally, we don't support autofix for rules that need to have information from other rules to end up 100% correct (we don't want to introduce additional linting errors when we autofix something). I'll leave this open to see if any other team members believe I'm incorrect here. |
With other rules, I think we've generally decided that it's okay to do something like this because we now have multipass autofixing, so the new linting errors will be fixed on the next pass. For example, the autofixer for We do a similar thing with the /* eslint brace-style: error, indent: error */
if (foo) {bar()}
// brace-style autofix ↓
if (foo) {
bar()
}
// indent autofix ↓
if (foo) {
bar()
} So in this case, I think it's okay if the |
Thanks for your review. Initially, I had the same concern about indentation but as @not-an-aardvark said about multipass autofixing I really don't think this is actually an issue. This rule only concerns new lines on chain calls and therefore it would only introduce conflicts between rules if we care about indentation. Many other "newline" rules use the same approach so I think it is OK. Another point is that if we enforce indentation we could introduce conflicts on projects that (for some reason) don't enforce indentation on chain calls. This particular rule conflicts with prettier, since prettier inlines everything that doesn't match the max-length limit, and considering that this rule is not fixable there is the need to use workarounds in order to remove the warnings. |
Good catch @not-an-aardvark. No further concerns from me. |
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.
Thanks for the pull request! This looks like a good start. I added a few suggestions.
errors: [{ | ||
message: "Expected line break before `['method' + n]`." | ||
}, { | ||
message: "Expected line break before `[aCondition ?`." | ||
}] | ||
}, { | ||
code: "foo.bar()['foo' + \u2029 + 'bar']()", | ||
output: "foo.bar()\n['foo' + \u2029 + 'bar']()", |
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.
Can you add a test where the property is computed and parenthesized?
foo[(bar)]()
errors: [{ | ||
message: "Expected line break before `['method' + n]`." | ||
}, { | ||
message: "Expected line break before `[aCondition ?`." | ||
}] | ||
}, { | ||
code: "foo.bar()['foo' + \u2029 + 'bar']()", | ||
output: "foo.bar()\n['foo' + \u2029 + 'bar']()", |
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.
Can you add a test where the object is parenthesized?
(foo).bar()
errors: [{ | ||
message: "Expected line break before `['method' + n]`." | ||
}, { | ||
message: "Expected line break before `[aCondition ?`." | ||
}] | ||
}, { | ||
code: "foo.bar()['foo' + \u2029 + 'bar']()", | ||
output: "foo.bar()\n['foo' + \u2029 + 'bar']()", |
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.
Can you add a test where there is a comment between the object and the property?
foo . /* comment */ bar();
(I'm not really sure what output I would expect for this case since the code is sort of strange, so pretty much anything would be fine as long as it doesn't delete the comment and doesn't produce invalid syntax.)
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.
You were right about the parenthesized properties/object, so I updated the fix()
with your recommendation, and everything is passing accordingly. Regarding this comment, I would expect this to break before the .
like:
foo
. /* comment */ bar();
In my opinion the comment refers to the property, so it should break before the comment. Without further changes everything to the current implementation, it is working as expected. I added another test with the comment before the dot, and it breaks the line correctly as well:
foo /* comment */
.bar();
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.
For what it's worth, in any case where we're unsure of the expected fix behavior, we can always choose not to fix. I think these cases where block comments make it difficult to infer what the user would really want would be a great place not to fix. Just my two cents.
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.
If we opt to not fix it I think we need to change the rule in order to not report for block comments. The expected behaviour of the autofixer is to perform fixes, and if it leaves unfixed code after running it would break pre commit hooks, etc. There are other rules such as array-element-newline
that fix it anyway like:
/* eslint array-element-newline: error */
const foo = [1,2 /* comment */,3];
// autofixes to:
const foo = [1,
2 /* comment */,
3];
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 don't feel strongly about this particular case, but there is precedent for autofixers not fixing all problems. In general we don't make any guarantees about whether a given autofix will be applied, and some rules aren't autofixable at all. For example, brace-style
doesn't remove the linebreak before the {
in cases like this:
if (foo) // comment
{
bar();
}
And eqeqeq
can only autofix a small number of cases where a typeof
expression or a literal is used.
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.
But in the case of brace-style
it is needed to get the final brace position since it would affect the final output. I think that in this case in this particular case it is ok to fix it anyway since it just fixes the code following the rule as it is, but it is up to you the final decision.
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.
Seems reasonable to me.
context.report({ | ||
node: callee.property, | ||
loc: callee.property.loc.start, | ||
message: "Expected line break before `{{callee}}`.", | ||
data: { | ||
callee: getPropertyText(callee) | ||
}, | ||
fix(fixer) { | ||
return fixer.replaceTextRange([callee.object.range[1], callee.property.range[0]], `\n${getPrefix(callee)}`); |
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 will work incorrectly if the object or the property is parenthesized. callee.object.range
doesn't include any surrounding parentheses, so for text like (foo).bar()
, callee.object.range
will be just the range of the foo
token. Similarly, for text like foo[(bar)]()
, I think the current implementation will produce foo\n[bar)]
, which is invalid syntax.
You could avoid parentheses by doing something like this:
fix(fixer) {
const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
return fixer.insertTextBefore(firstTokenAfterObject, "\n");
}
b4eabdb
to
ccaf1b9
Compare
LGTM |
@not-an-aardvark I've updated the code with the requested changes. |
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.
Looks good to me. Thanks!
cc @eslint/eslint-team -- anyone willing to support this enhancement? |
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 support this enhancement. Just a few small comments, but this LGTM!
" // Do something with response.", | ||
" // Do something with response.", | ||
" // Do something with response.", | ||
"})\n.on('error', function(error) {", |
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.
Nitpick: Since we're actually writing this out with whitespace I think it would be clearer to add a new element to the array instead of inlining a \n
character.
For example:
[
"})",
".on('error', function(error) {",
]
@@ -116,15 +153,40 @@ ruleTester.run("newline-per-chained-call", rule, { | |||
" 'method3' :", | |||
" 'method4']()" | |||
].join("\n"), | |||
output: [ | |||
"anObject.method1().method2()\n['method' + n]()\n[aCondition ?", |
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.
Nitpick: Since we're actually writing this out with whitespace I think it would be clearer to add a new element to the array instead of inlining a \n
character.
ccaf1b9
to
eb1365f
Compare
LGTM |
@kaicataldo I've updated the tests according to your comment. Good catch BTW! 😄 |
Thanks! I'll champion this since we have 3 other supporters. This issue is now accepted. |
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.
LGTM. Thanks for contributing to ESLint!
Leaving this open for another day since it was just accepted.
Thanks! |
Thanks for contributing to ESLint! |
What is the purpose of this pull request? (put an "X" next to item)
[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[x] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:
What changes did you make? (Give an overview)
This updates the newline-per-chained-call by making it fixable and does not affect the rule definition. The result of the fix was tested by adding the
output
according to the error messages.The main motivation for this change is that code formatting tools like prettier perform changes based on the line length, and since this rule is not fixable it warns of exceeding chained calls per line.
Is there anything you'd like reviewers to focus on?
No