Skip to content
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

Merged

Conversation

joaogranado
Copy link
Contributor

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

@eslintbot
Copy link

LGTM

@mention-bot
Copy link

@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.

@nzakas
Copy link
Member

nzakas commented Aug 23, 2017

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.

@not-an-aardvark
Copy link
Member

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).

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 quote-props always adds double quotes around keys, which sometimes creates new quotes errors if the user prefers single quotes. However, this isn't a problem because the quotes autofixer will correct the quotes on the next pass.

We do a similar thing with the brace-style fixer:

/* 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 newline-per-chained-call autofixer outputs lines with incorrect indentation, because the indent rule can fix it on the next pass.

@not-an-aardvark not-an-aardvark added enhancement This change enhances an existing feature of ESLint evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion rule Relates to ESLint's core rules labels Aug 23, 2017
@joaogranado
Copy link
Contributor Author

joaogranado commented Aug 23, 2017

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.

@nzakas
Copy link
Member

nzakas commented Aug 25, 2017

Good catch @not-an-aardvark. No further concerns from me.

Copy link
Member

@not-an-aardvark not-an-aardvark left a 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']()",
Copy link
Member

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']()",
Copy link
Member

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']()",
Copy link
Member

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.)

Copy link
Contributor Author

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();

Copy link
Member

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.

Copy link
Contributor Author

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];

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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)}`);
Copy link
Member

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");
}

@joaogranado joaogranado force-pushed the fixable-newline-per-chained-call branch from b4eabdb to ccaf1b9 Compare August 27, 2017 10:26
@eslintbot
Copy link

LGTM

@joaogranado
Copy link
Contributor Author

@not-an-aardvark I've updated the code with the requested changes.

Copy link
Member

@not-an-aardvark not-an-aardvark left a 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!

@not-an-aardvark
Copy link
Member

cc @eslint/eslint-team -- anyone willing to support this enhancement?

Copy link
Member

@kaicataldo kaicataldo left a 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) {",
Copy link
Member

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 ?",
Copy link
Member

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.

@joaogranado joaogranado force-pushed the fixable-newline-per-chained-call branch from ccaf1b9 to eb1365f Compare September 12, 2017 19:36
@eslintbot
Copy link

LGTM

@joaogranado
Copy link
Contributor Author

@kaicataldo I've updated the tests according to your comment. Good catch BTW! 😄

@kaicataldo
Copy link
Member

Thanks! I'll champion this since we have 3 other supporters. This issue is now accepted.

@kaicataldo kaicataldo self-assigned this Sep 12, 2017
@kaicataldo kaicataldo added accepted There is consensus among the team that this change meets the criteria for inclusion and removed evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion labels Sep 12, 2017
Copy link
Member

@kaicataldo kaicataldo left a 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.

@joaogranado
Copy link
Contributor Author

Thanks!

@kaicataldo kaicataldo merged commit 2731f94 into eslint:master Sep 13, 2017
@kaicataldo
Copy link
Member

Thanks for contributing to ESLint!

@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Mar 13, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Mar 13, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants