prettier icon indicating copy to clipboard operation
prettier copied to clipboard

Removes parentheses in expressions with mixed operators

Open yenbekbay opened this issue 7 years ago • 186 comments

Before:

const foo = (a && b) || c || d;

After:

const foo = a && b || c || d;

The same goes for arithmetic and bitwise operators.

Relevant ESLint rule: http://eslint.org/docs/rules/no-mixed-operators.

yenbekbay avatar Jan 14 '17 10:01 yenbekbay

Can't seem to reproduce this: https://jlongster.github.io/prettier/#%7B%22content%22%3A%22const%20foo%20%3D%20a%20%26%26%20(b%20%7C%7C%20c%20%7C%7C%20d)%3B%5Cn%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3Afalse%2C%22bracketSpacing%22%3Atrue%7D%7D seems to generate the expected code. Maybe you can try updating your version of Prettier.

bnjmnt4n avatar Jan 14 '17 14:01 bnjmnt4n

@demoneaux Oh, you are right. I updated the description with a correct example. It seems that prettier only removes parentheses when it doesn't lead to a change in logic. Nonetheless, I think it's important to keep the parentheses for clarity if that's possible.

yenbekbay avatar Jan 14 '17 15:01 yenbekbay

I agree. It should be opinionated about enforcing clear logic between operators. For example, it's just not clear, unless you remember your math classes from HS, that division happens before addition:

const result = 100 / 10 + 10; // 20 const result = 100 / (10 + 10); // 5 const result = (100 / 10) + 10; // 20

It currently enforces unclear operations, when the strong opinion should be to enforce clarity.

alanimgur avatar Jan 15 '17 21:01 alanimgur

A real-world example from some code I'm working on: https://jlongster.github.io/prettier/#%7B%22content%22%3A%22style.left%20%3D%20left%20-%20(this.state.width%20%2F%202)%20%2B%20(parent.offsetWidth%20%2F%202)%3B%5Cn%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3Afalse%2C%22bracketSpacing%22%3Atrue%2C%22doc%22%3Afalse%7D%7D

Both are correct, but I would say that the original is vastly more readable (and one might even say prettier).

smably avatar Feb 09 '17 19:02 smably

Related: https://github.com/lydell/eslint-config-prettier#no-mixed-operators

lydell avatar Feb 10 '17 17:02 lydell

The create-react-app ESLint config includes the no-mixed-operators warning. So even when the user writes mixed operator code with proper parentheses, Prettier currently converts it to code that causes warnings in create-react-app projects. Since Prettier and create-react-app seem to share some principles and generally work well in combination, it would be a nice design principle if Prettier's formatting rules match create-react-app's ESLint rules.

sander avatar Feb 10 '17 17:02 sander

I think this is one of those cases where prettier should defer to the user's choice. Preserve brackets when they already exist, but don't add new ones.

erbridge avatar Feb 11 '17 12:02 erbridge

@erbridge All of this information is lost from the AST. The AST doesn't have any information about extraneous parens, so it's impossible for us to keep the original structure. And in fact, that's the opposite of what we want to do; this project enforces a consistent style by disregarding the original style.

We need to choose whether or not we want to do this, and have it consistent everywhere. I'm a little worried that it would add a lot of noise when it's only helpful in a few situations, but we can talk about it more.

jlongster avatar Feb 20 '17 14:02 jlongster

@jlongster Understood. My preference is to get rid of all extraneous brackets. As you say, in general, they would be unnecessarily verbose. If extra clarity is needed, users can pull the sub-statements out into their own variables.

erbridge avatar Feb 20 '17 14:02 erbridge

I think that there are many cases when judiciously placed brackets add meaning. For example:

const remainingImageCount = totalImageCount - (currentIndex + 1);

vs

const remainingImageCount = totalImageCount - currentIndex + 1;

I would argue that the first example reads better because is clearly shows the addition applied to currentIndex. By stripping away the brackets, meaning is lost. Sure you could solve this by adding another variable, but I don't think this would make the code more readable.

Undistraction avatar Feb 27 '17 11:02 Undistraction

@Undistraction prettier will keep the parens in that case. Those expressions are semantically different. 5 - (2 + 1) === 2 but 5 - 2 + 1 === 4. Prettier maintains the correct semantics and add parens where needed, naturally.

jlongster avatar Feb 27 '17 14:02 jlongster

@jlongster Of course. Sorry for the noise.

Undistraction avatar Feb 27 '17 15:02 Undistraction

We are now adding parenthesis around && inside of || which seems like the biggest issue people are facing in this issue. I'm closing this issue. If there are other places where you'd like parenthesis to be kept, please feel free to open another issue.

vjeux avatar Mar 01 '17 19:03 vjeux

Can we reopen the issue?

I believe it's still a good thing to keep the parenthesis when 2 operators have the same precedence.

These kind of things might not be an issue to read for you, but for a lot of people, it's way more clear with parenthesis. https://prettier.github.io/prettier/#%7B%22content%22%3A%22(10%20-%205)%20%2B%2025%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3A%22none%22%2C%22bracketSpacing%22%3Atrue%2C%22jsxBracketSameLine%22%3Afalse%2C%22parser%22%3A%22babylon%22%2C%22doc%22%3Afalse%7D%7D

tleunen avatar Mar 15 '17 20:03 tleunen

With division and multiplication it's even easier to confuse precedence:

https://prettier.github.io/prettier/#%7B%22content%22%3A%22function%20shareInPercent(total%2C%20part)%7B%5Cn%20%20return%20(part%20%2F%20total)%20*%20100%3B%5Cn%7D%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3A%22none%22%2C%22bracketSpacing%22%3Atrue%2C%22jsxBracketSameLine%22%3Afalse%2C%22parser%22%3A%22babylon%22%2C%22semi%22%3Afalse%2C%22useTabs%22%3Afalse%2C%22doc%22%3Afalse%7D%7D

Division is a very powerful semantic separator of divisor from dividend in an expression. I have to make an effort to not perceive the multiplication as a part of the divisor.

I suggest putting in all the parens if an expression contains division.

leonid-shevtsov avatar May 02 '17 12:05 leonid-shevtsov

I think this should seriously be considered again. Currently prettier transforms this:

const css = `
 max-width: ${(ITEM_WIDTH * 4) + (ITEM_GUTTER * 3)}px;
`;

into this:

const css = `
 max-width: ${ITEM_WIDTH * 4 + ITEM_GUTTER * 3}px;
`;

which really cannot be the desired outcome? It doesn't provide any clarity on how to read this code - which should be the goal.

I'd love to see parenthesis in calculation expressions were more than one operator is mixed.

stnwk avatar Jun 02 '17 10:06 stnwk

Rule idea: When mixing binary operators, always add parentheses, except for the few ones taught in Maths in school: +, -, *, / and **.

lydell avatar Jun 07 '17 11:06 lydell

By removing parenthesis from expressions prettier introduces ambiguity, which I hope is not the desired outcome. There is simply no way for prettier to understand the context of e.g. mathematical expressions, only the developer is in a place to make sound judgements about parenthesis to group the expression in logical sections.

I strongly disagree that this is what prettier wants to do, it is not just rewriting code without formatting, it's removing actual intent. Simplifying an expression (double parens) is one thing, but removing them is hurtful (for readability). After all, shouldn't prettier prioritize readability and understandability over all other things?

I think gofmt (formatting for Golang) does this kind of behavior well, here are some examples:

a := 10 * 2 + 5 + 20 / 4 * 2 + 9
b := (10 * 2) + 5 + (20 / 4) * 2 + 9
c := (a - (b + (1)))
// becomes
a := 10*2 + 5 + 20/4*2 + 9
b := (10 * 2) + 5 + (20/4)*2 + 9
c := (a - (b + (1)))

Now, we could argue that b has a superfluous parens around 20/4, but we cannot know what the intent is. c on the other hand does have superfluous parens, but what's the benefit in removing them?

Takeaway from golang example is IMO that we can improve visibility (e.g. by grouping operators of different precednece) but we should not try to simplify (syntactically) expressions needlessly. I also think it can make sense to reduce ambiguity by introducing parens (e.g. given the && / || situation) where there might be (visual) ambiguity.

mafredri avatar Jun 16 '17 08:06 mafredri

Here presents my use case in Node.js that encounters this same issue:

Real world use case:

/* A basic line of code to measure time taken of a task performed in Node.js */
const requestStarts = process.hrtime();

/* Do some tasks */
doSomeTasks();

const requestEnds = process.hrtime(requestStarts);
 /* no-mixed-operators rule will work here and enforces brackets in the line below. */
const requestDiff = (requestEnds[0] * 1e3) + (requestEnds[1] * 1e-6);  /* Convert to ms */

console.log(`Time taken - ${requestsDiff.toFixed(3)}ms`);

Expected outcome after Prettier

const requestDiff = (requestEnds[0] * 1e3) + (requestEnds[1] * 1e-6);

Actual outcome after Prettier

 /* no-mixed-operators starts throwing error */
const requestDiff = requestEnds[0] * 1e3 + requestEnds[1] * 1e-6;

motss avatar Jun 19 '17 09:06 motss

1 + 2 * 3 % 6 + 1

take your time

przhkv avatar Jun 21 '17 17:06 przhkv

In general, I love that prettier removes debate about correct formatting from my PRs.

But removing parens? I understand that there may be some challenges with keeping them but please remember the following -

“Programs must be written for people to read, and only incidentally for machines to execute.”
Harold Abelson, Structure and Interpretation of Computer Programs

So you can sit and think and decide that they're unnecessary, but you're not the person i'm trying to communicate with.

mattkime avatar Jun 23 '17 02:06 mattkime

Update here, we do want to add parenthesis back when they help disambiguate code for humans. I haven't had the time to actually go through all the cases and figure out when to actually do it. If someone is interested in tackling this, this would be a welcome addition to prettier.

vjeux avatar Jun 23 '17 02:06 vjeux

I reviewed all the comments raised here and related issues and here's what I think the next steps are:

  • A few months ago, we added parenthesis when mixing && and ||.
  • For +*/-, I don't think that we should put parenthesis. The priority for those are taught in every school in the world. If you find them confusing, I think that taking the time to learn them is going to be useful for your career in the long run.
  • For === inside of === (or !===), we should add parenthesis as it's not clear. a === b === c
  • For * inside of %, we should add parenthesis.

That's the extent of what I have found people report here. If there are more things, please let me know.

If someone is interested in working on it, that would be great!

vjeux avatar Jul 05 '17 21:07 vjeux

For +*/-, I don't think that we should put parenthesis. The priority for those are taught in every school in the world. If you find them confusing, I think that taking the time to learn them is going to be useful for your career in the long run.

I agree with the put part, but the current behavior is to remove, which I do not agree with. I don't think any programmer is going to have a problem with differentiating the priority of +*/-. The real problem lies in code readability. The human mind is prone to simplifying and taking shortcuts, when reading expressions containing multiple operators, it's not a question of knowing the priority, it's a question of how easily you can spot the difference, which segments belong where. In these cases a parenthesis or two or ten can really help disambiguate the statement.

To me it seems like the reason to stick to this choice is that the parens are lost in the AST, but this seems like lousy reasoning to me. It would be OK to mention the behaviour in the README and note that it will be fixed in the future, but making a decision based on current limitations just seems wrong.

Finally, does removing parens make the code any prettier? My opinion is no, it does not.

mafredri avatar Jul 05 '17 22:07 mafredri

@mafredri Just keep in mind that Prettier actually doesn't remove your parentheses per se. It throws away all of your code, and then prints equivalent code pretending it never even saw the original code basically (except for a few special cases). That's what makes sure that Prettier can print code consistently regardless of who wrote it. So the crux of this issue is defining the rules for when Prettier should print parentheses. Thinking about how the input code looked is the wrong approach.

lydell avatar Jul 06 '17 05:07 lydell

When this patch is gonna be released? I hope that this will result in prettier-eslint to be updated because right now it causes conflicts with eslint config.

lukaleli avatar Jul 26 '17 12:07 lukaleli

@vjeux

If you find them confusing, I think that taking the time to learn them is going to be useful for your career in the long run.

IMO, this is a condescending position.

mattkime avatar Jul 26 '17 16:07 mattkime

@lydell

Just keep in mind that Prettier actually doesn't remove your parentheses per se. It throws away all of your code, and then prints equivalent code pretending it never even saw the original code basically (except for a few special cases).

I don't wish argue technicalities here, if parens disappear, the are removed, implementation may be what it is. My hope is that issues can be considered in a broader sense than current limitations of the implementation. Just because a parenthesis doesn't modify the behaviour of the code doesn't mean that it's not valid AST . If it's missing from prettier it's because someone decided it shouldn't be there (for better or worse).

EDIT: I realize the above statement can be somewhat incorrect, AST doesn't need to contain parens, but I hope you can understand the underlying intent. More importantly, AST is for computers, code is for humans 🙂.

So the crux of this issue is defining the rules for when Prettier should print parentheses. Thinking about how the input code looked is the wrong approach.

That is not the point I'm trying to make. To me this seems like the wrong approach since prettier is supposed to be a tool to help humans, not fight them. And IMO unnecessarily1 removing parenthesis is hurtful, rather than beneficial, at least it wont make the code prettier or more readable.

1 Please consider this statement, not from the point of prettier AST, but from the eyes of the developer prettier is supposed to help

mafredri avatar Jul 26 '17 16:07 mafredri

Is this gonna be fixed soon ?

agjs avatar Jul 28 '17 13:07 agjs

This should adhere to the rule applied with no-mixed-operators.

When connected to Eslint, this presents a lint issue when prettier is active that is essentially unresolvable, at least without using // prettier-ignore to manually skip it.

ImAdamTM avatar Aug 08 '17 20:08 ImAdamTM

When using flow with its stuff as comments (so that the source remains valid javascript), removing unnecessary parenthesis nukes its type assertion expressions.

i.e. (x /*: number */) turns into x /*: number */ which isn't the same thing

frou avatar Aug 24 '17 15:08 frou

@frou That has nothing to do with this issue. Besides, that seems to have been fixed. Please double check that you're using the latest Prettier version.

lydell avatar Aug 24 '17 15:08 lydell

@lydell This issue was linked from another one as the locus for unnecessary parenthesis stuff, so my comment was not a total drive-by :)

Your link shows a type on a function parameter. The Type Assertion is a different construct and can be on a line/statement all by itself.

frou avatar Aug 24 '17 15:08 frou

@frou Whoops, yes, you're right. Sorry for the confusion. 👍

lydell avatar Aug 24 '17 15:08 lydell

For +*/-, I don't think that we should put parenthesis. The priority for those are taught in every school in the world. If you find them confusing, I think that taking the time to learn them is going to be useful for your career in the long run.

There's a big difference between "finding them confusing" and wishing to clearly express one's intent.

msakrejda avatar Aug 25 '17 06:08 msakrejda

not fixed for me in prettier 1.6.1 eslintvsprettier

formatOnSave triggers prettier and is reverting this change.

codeofsumit avatar Sep 12 '17 12:09 codeofsumit

@codeofsumit That works as intended in Prettier 1.6.1. It was decided to remove parentheses for the common grade school math operators.

lydell avatar Sep 12 '17 12:09 lydell

@lydell thanks for the update. I'm new to prettier but having the option eslintIntegration: true makes me expect prettier would respect any rules defined in the project specific .eslintrc? Is that not the case?

codeofsumit avatar Sep 12 '17 13:09 codeofsumit

@codeofsumit Prettier does not look at your ESLint config at all. The recommendation is to use eslint-config-prettier, or, if you disagree with Prettier's formatting, prettier-eslint.

lydell avatar Sep 12 '17 15:09 lydell

eslintIntegration: true is a prettier-vscode setting to use prettier-eslint instead of prettier.

azz avatar Sep 13 '17 14:09 azz

@azz well I did set eslintIntegration: true so the case in the above screenshot should work, shouldn't it? 🤔

codeofsumit avatar Sep 14 '17 07:09 codeofsumit

Taking a closer look, no-mixed-operators does not support autofix, so prettier-eslint (and by extension eslintIntegration: true) can't help you. You have to edit your ESLint config: https://github.com/prettier/eslint-config-prettier#no-mixed-operators

lydell avatar Sep 14 '17 08:09 lydell

@lydell uhm wait a minute. I want the eslint rule to be enforced. I don't expect that prettier is adding the brackets, my issue is that prettier removes the brackets when I add them. Can I change this?

codeofsumit avatar Sep 14 '17 10:09 codeofsumit

@codeofsumit There's no way to change this. You have to follow the advice I linked to (refactor your code): https://github.com/prettier/eslint-config-prettier#no-mixed-operators

lydell avatar Sep 14 '17 10:09 lydell

@lydell ah sorry, they're specifically talking about prettier 👍 . Ok thanks. Will find something...

codeofsumit avatar Sep 14 '17 11:09 codeofsumit

@leonid-shevtsov posted this example in this thread before but the importance of it has been buried by comments about aesthetics or readability: https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAzArlMMCW0AEAzgBYCGATnAJJQAKc5YCMAFDBDKQDYA0+ADhRgBKYAB0o+fJRjpykloPIx8AenztOXYfgBU+AIwAGIwG4JAXxA8QEfrmiFkofuRywA6jgAmMYsgAOIxtOACMvX38kACYbQncAcy44AEV0DjhkVG5COBDyUhwuRIBhCABbctJkEChoTJtQgrAAazgYAGVBMETkGHJ0PJAAK0IADwAhZrbO0nK4ABl3TKRsrlybJVzyGtDSUIBPLmhrEFzynCycofRcgBV9p1Xrm28IMCv1odJCGE+N2zoGD8IHRf5wCwWIA

I'll post the example in the comment instead of just the playground. This is how it's originally written (and I how I write code for calculating percentages):

function shareInPercent(total, part){
  return (part / total) * 100;
}

This is what prettier converts it to:

function shareInPercent(total, part) {
  return part / total * 100
}

This is an error. If you have prettier enabled on save, you're lucky to catch it. If you have it on a pre-commit hook, you'll miss it. Granted, you can teach devs to make sure to write it like this (part * 100) / 100 but it's hard to reverse years of using the previous pattern. Across a team, it's bound to cause errors that aren't caught.

I know this may be a difficult problem to solve, but I want to raise it's status of importance because it's causing real errors in people's code.

jefffriesen avatar Oct 23 '17 02:10 jefffriesen

@jefffriesen how is it erroneous? There is no input for part and total that would produce a different result in the example and "prettified" version. Semantically it is correct.

mafredri avatar Oct 23 '17 02:10 mafredri

@mafredri You're right.

screen shot 2017-10-22 at 10 19 54 pm

I definitely have seen errors but I keep blaming it on this. That's kind of embarrassing. Multiplication and division have the same precedence, but in C-style languages, the operation proceeds from left to right. It's still uncomfortable though, because in handwritten math, it looks like what's happening is this:

// If I wrote this by hand:
1 / 2 * 100 = 50

// It would, in my experience, be interpreted as:
1 / (2 * 100) = 0.005

Ok, so this issue is actually a readability problem, not a correctness problem.

jefffriesen avatar Oct 23 '17 04:10 jefffriesen

I didn't mean that prettier breaks code. The code works fine.

It's us humans who have a hard time understanding the expression without brackets because for humans the division operator is a strong separator of what's being divided from what's it divided by.

Since prettier's goal is to make code nicer for humans, it should definitely acknowledge that for humans, division has lower precedence than multiplication.

(Which - actually - can be a great simple solution to this problem: put division into a precedence class of its own, between multiplication and addition.)

leonid-shevtsov avatar Oct 23 '17 05:10 leonid-shevtsov

Here is another simple example to add to the discussion:

Before

const r = (a * b + c) + (x * y + z);

After:

const r = a * b + c + (x * y + z);

I don't think there is any argument for the transformed code being easier to parse than the original. In this case prettier has made the code harder to read - it is less clear that there are two very similar parallel calculations being performed and summed.

Undistraction avatar Oct 23 '17 08:10 Undistraction

Just weighing in here:

I think it's a crutch for prettier to format code like this:

if (x || (y && z)) { }

Programmers should know that && has a higher precedence over ||. Adding the parentheses is a crutch and could likely hinder their understanding of JavaScript. I know this is a bit pedantic, but I wanted to throw my two cents in anyway.

ffxsam avatar Nov 19 '17 05:11 ffxsam

@ffxsam If you read through the thread you'll see that Prettier used to work like you suggest, before taking in the overwhelming community feedback to actually do output parens in that case. This decision has already been made.

lydell avatar Nov 19 '17 09:11 lydell

My opinion on confusing * /:

This

return (part / total) * 100;

Is definitely more readable than this:

return part / total * 100;

And here's why:

It's not only a question of understanding mathematical operators. In my opinion, the second one is more confusing because even when you know how it works, it might look like the one who wrote this code meant something else.

I think a good analogy is switch statements and how everyone dislikes them for the fact that they implicitly fall through if there's no break or return. The problem is that when someone intentionally writes a switch statement that is supposed to fall though, the reader of the code might assume that it's forgotten by mistake.

Same here.

Adding explicit parenthesis shows intention and therefore removes possible confusion.

everdimension avatar Dec 11 '17 20:12 everdimension

I totally get these points, and I think they’re valid. But it seems like more ESLint’s territory, not Prettier’s. As I understand it, Prettier is concerned with formatting, ESLint is concerned with style. Other than changing spacing and line wraps, and the very few stylistic points (dangling commas, single vs double quotes—which are entirely configurable, BTW), Prettier lets the developer decide how to style their code.

In fact, there are some cases where Prettier removes parentheses against the developer’s will, because it decides they’re not necessary. For instance:

const myFunc = (arg) => {
  // code
};

Prettier will change this to:

const myFunc = arg => {
  // code
};

But maybe I was intentionally wrapping my single argument with parentheses so it makes my transition to TypeScript later a little easier (const myFunc = (arg: string) => {}).

In the end, I don't mind so much if parentheses are added to make mixed operators more clear. I'm all for code legibility. My concern is, will this set a precedent where Prettier forces more stylistic choices on the developer? IMO these should be configurable as with trailing-comma, single-quote, etc.

ffxsam avatar Dec 12 '17 15:12 ffxsam

Perhaps this might be a good place for a disabling comment. But not ignore the line fully.

Instead, it might be a good place for a comment like: // prettier preserve-parens ...or something like this.

everdimension avatar Dec 12 '17 15:12 everdimension

@ffxsam Why do you think Prettier should not be concerned about style? Ideally it would only use the AST to print the code (not looking at the original at all) so Prettier should make all the stylistic choices for you (e.g. add parens around single arg arrows or not, or parens around binary expressions). But since the JS community has already embraced a few "blessed" coding styles (like standard), we enabled some options to unblock adoption by a large number of people.. but that doesn't mean everything should be configurable.

Using Prettier is more about giving up control of your formatting (whenever possible).. and if you do want to control, there are tools to format your code after it was prettified (prettier-eslint, prettier-standard, and others) :)

@everdimension I don't think that // prettier-ignore would suffice for this case, if you want that kind of control over formatting.

duailibe avatar Dec 12 '17 15:12 duailibe

This seems to be a related case. Prettier changes this (dummy example):

const newObj = {
    ...(includeOptions ? options : {})
};

to this:

const newObj = {
    ...includeOptions ? options : {}
};

Admittedly, there are other, arguably better, ways to write the original code. Still, the result in this case seems less readable than the original.

jmrog avatar Jan 09 '18 02:01 jmrog

@jmrog we should fix this case. Could you open a separate issue for it?

vjeux avatar Jan 09 '18 02:01 vjeux

@vjeux I just tried to reproduce this in the playground and can't seem to do so. An analogous (but somewhat more complicated) situation just happened in a codebase I'm working on, though; I'll see if I can figure out what exactly may have triggered it (and yes, I'll open an issue if I can do so).

jmrog avatar Jan 09 '18 02:01 jmrog

@vjeux Ah, it seems to happen only when using the TypeScript parser specifically. Is it still appropriate to open a separate issue here in that case?

jmrog avatar Jan 09 '18 02:01 jmrog

Yes — just make sure you select the TS parser in the playground.

j-f1 avatar Jan 09 '18 02:01 j-f1

I'm getting into prettier and the stripping of parens is one reason I may stop using it completely. Parens help me to visually parse my code more easily. I'd like some way to keep them in the code when they are added (no need to automatically add them).

jdheeter avatar Feb 08 '18 21:02 jdheeter

@jdheeter Do you have any specific cases you’d like to see parens printed?

j-f1 avatar Feb 08 '18 21:02 j-f1

When using Await statements to call an API that returns json I often write some code like this: var result = (await client.request(q.user.checkUsername(), {username}).catch(console.log)).User

In the example above, I want to return the .User from the object. Sometimes I might need the object itself, so I can write: var result = (await client.request(q.user.checkUsername(), {username}).catch(console.log)) Which Prettier turns into var result = await client.request(q.user.checkUsername(), {username}).catch(console.log

I'd rather Prettier just leave the Parens, to keep it visually consistent with my other code, and make it easy for me to parse elements from the returned object. Visually, it's easier for me to understand the await statement as a logical operation that happens before other things.

jdheeter avatar Feb 08 '18 22:02 jdheeter

Hi @jdheeter, could you open a new issue around your specific case using the prettier playground to demonstrate? There are instructions for using the playground in the issue template.

suchipi avatar Feb 08 '18 23:02 suchipi

Looks like that’s already kept:

Prettier 1.10.2 Playground link

Input:

async () => {
  var result = (await foo().catch(bar)).baz
}

Output:

async () => {
  var result = (await foo().catch(bar)).baz;
};

j-f1 avatar Feb 08 '18 23:02 j-f1

Here is an example of a situation where I want to keep the parens:

Prettier 1.10.2 Playground link

Input:

async () => {
  var result = (await foo().catch(bar))
}

Output:

async () => {
  var result = await foo().catch(bar);
};

jdheeter avatar Feb 08 '18 23:02 jdheeter

@jdheeter Would you write

var result = (foo + bar)

as well? (Reminds me of lisp.) Why would await expressions be different?

lydell avatar Feb 09 '18 10:02 lydell

I would not.

The result of + is going to be a number or a string and happens sync. The result of an await expression can be anything and needs to happen before any other operations. In my instance, the result is an object which I often need to pick some data out of, (but not always).

I agree that it's a specific stylistic choice, I just assumed Prettier would give me an escape hatch to format my code in a way that is more readable.

jdheeter avatar Feb 10 '18 22:02 jdheeter

I just assumed Prettier would give me an escape hatch to format my code in a way that is [whatever]

There are // prettier-ignore comments. Other than that, there’s no way to format the code whatever way you wish – that’s the whole point of Prettier ;)

lydell avatar Feb 10 '18 22:02 lydell

I agree, it's not configurable in prettier, (like it is in eslint no-mixed-operators rule)

not really happy that

if (!inputColon && inputKey || inputColon && inputValue) return;

is converted in

if ((!inputColon && inputKey) || (inputColon && inputValue)) return;

caub avatar Mar 11 '18 11:03 caub

I'm throwing my hat in the ring in favor of preventing the automatic removal of parentheses with mixed operators. As multiple people before me have already stated, leaving the parentheses helps make the code more readable.

Prettier is an awesome tool, and this is probably the only (minor) thing I don't like about it.

callmeaponte avatar Mar 26 '18 01:03 callmeaponte

Any update on this guys? Just wish it would keep parenthesis for mixed operators like (100 / 5) * 3. Playground

divyanshu013 avatar Apr 12 '18 06:04 divyanshu013

This reminds me of the older issue concerning the automatic removal of parentheses around JSX returned from arrow functions - which was another symptom of Prettier unfortunately favoring code concision over code readability. I'm looking forward to the day I can use Prettier, but until then, it's no-brainer issues like these that give me cold feet. Such a tool simply shouldn't perform any formatting that introduces cognitive load for anyone reading the code.

I'm excited to see things getting better with each release though - all the hard work is greatly appreciated! :)

chocobuckle avatar Apr 13 '18 15:04 chocobuckle

Our team would also appreciate if this were configurable 👍 Parenthesis just help reduce the cognitive load when scanning code, (a / b) * c is a lot easier (in my opinion) to mentally consume than a / b * c

jeffberry avatar Apr 13 '18 18:04 jeffberry

You know, after thinking about this some more, I've come around from my previous stance. In fact, I now think that prettier doesn't go far enough. After all, since multiplication and addition are commutative, in order to ensure a consistent style, prettier should sort the operands:

Why allow both

const volume = width * length * height

and

const volume = length * width * height

when the canonical expression can clearly be arrived at by sorting (by name for variables and magnitude for numbers)?

const volume = height * length * width

msakrejda avatar Apr 13 '18 19:04 msakrejda

I stopped using prettier: consistency != readability I feel validated by the official Google style guide, which suggests using parens to improve readability. https://google.github.io/styleguide/jsguide.html#formatting-grouping-parentheses

jdheeter avatar Apr 13 '18 19:04 jdheeter

@uhoh-itsmaciek due to how floating points are implemented, moving around operands will not product the exact same result. In most JavaScript applications it doesn’t really matter but if you are doing modeling computations (machine learning, physical analysis ...) those kind of things start to matter.

vjeux avatar Apr 13 '18 19:04 vjeux

I absolutely understand the philosophy behind prettier, but this instance I think its a clear case of pedantry over pragmatism. When it comes to bracket removal prettier is making code harder to parse in exactly the places where code needs to be easy to parse.

Undistraction avatar Apr 13 '18 20:04 Undistraction

I don't understand why this is still an issue and not being fixed across one year.

maple-leaf avatar Apr 23 '18 07:04 maple-leaf

Reopening since many people don’t like the way things are formatted right now.

j-f1 avatar Apr 23 '18 11:04 j-f1

This issue is ridiculous. Just fix that..

thalesfsp avatar Apr 27 '18 22:04 thalesfsp

@thalesfsp Prettier is an open-source project. We need people like you to help come up with a heuristic that prints parentheses in cases where they improve readability, doesn’t print them when they don’t improve readability, and isn’t affected by coders’ under- or overuse of parentheses? Remember, many — if not most — of us are volunteers doing this in our spare time.

j-f1 avatar Apr 28 '18 01:04 j-f1

We need people like you to help come up with a heuristic that prints parentheses in cases where they improve readability, doesn’t print them when they don’t improve readability, and isn’t affected by coders’ under- or overuse of parentheses?

At risk of repeating myself, I feel this is the wrong conclusion and I don't really understand why this issue was re-opened if this is the case. Most issues raised here are due to the fact that prettier can't know the developers intent.

IMO the only way forward is to make parenthesis part of the AST, some rules for simplification are allowed (e.g. double parens), but should not remove groupings as intended by dev.

mafredri avatar Apr 28 '18 08:04 mafredri

My suggestion: There should be a option for disabling Prettier from removing the surplus parentheses, e.g. removeSurplusParentheses: true (default), so if I set it to false, Prettier will only format my code and won't remove the surplus parentheses that are used for readibility.

cassmtnr avatar Apr 30 '18 13:04 cassmtnr

@cassianomon from https://github.com/prettier/prettier/issues/187#issuecomment-313303188

Just keep in mind that Prettier actually doesn't remove your parentheses per se. It throws away all of your code, and then prints equivalent code pretending it never even saw the original code basically (except for a few special cases)

duailibe avatar Apr 30 '18 13:04 duailibe

@duailibe That's what is being discussed in this issue, for Prettier to keep the readibility of the code.

from https://github.com/prettier/prettier/issues/187#issuecomment-310554650

“Programs must be written for people to read, and only incidentally for machines to execute.” Harold Abelson, Structure and Interpretation of Computer Programs

cassmtnr avatar Apr 30 '18 13:04 cassmtnr

@cassianomon I know what's being discussed. I pasted that quote to show you that your suggestion doesn't make sense for Prettier, since Prettier does not remove anything from your code, we rewrite entirely. We can't prevent anything from being removed, but we can make it print in a way that makes sense. It's a subtle difference and that's what I'm trying to emphasize.

If you do want those parens printed, please suggest how we can detect when a paren is necessary for readability and when it's not. That's the way we can improve Prettier's output.

duailibe avatar Apr 30 '18 13:04 duailibe

@cassianomon What @duailibe means is that the process doesn't involve taking your code and selectively removing parts. It involves converting it all into an AST, then printing out your code again based on the AST. He isn't saying it literally doesn't remove anything, but that the process doesn't involve selective removal, but a transformation to an AST, then back to JS. It's like its being reduced to the minimum possible code (while still retaining the original meaning) and then rendered, so the extra brackets (which don't change the meaning) are thrown away and no longer exist. So to handle this issue there needs to be a heuristic in place to decide how the bare-bones, bracketless version should be rendered with brackets.

Undistraction avatar Apr 30 '18 14:04 Undistraction

@Undistraction I understood what Prettier does, I insist in my suggestion, since I can't use prettier at my work because it is removing the parentheses that are used for readibility, I think this falls on this issue.

cassmtnr avatar Apr 30 '18 14:04 cassmtnr

Perhaps if people posted one example each of the worst case they've encountered in real world code while using Prettier we could see a pattern and find a better middle ground. Right now it is very had to tell what people actually need. Let's take one small step at a time.

lydell avatar Apr 30 '18 15:04 lydell

@lydell One real world example is if the airbnb style guide (arguably pretty popular itself) is being used by a team as the base eslint configuration for a project. Grade school math or not I still see the benefit in enforcing the avoidance of mixed operators, but that's just personal preference. Either way I think it's fair to say a strong eslint configuration to ensure consistent coding style within a team is a good and more customizable option, not to mention it can be determined on a per project basis.

Exhibit A: Airbnb JavaScript Style Guide / eslint-config-airbnb Exhibit B: no-mixed-operators

ImAdamTM avatar Apr 30 '18 15:04 ImAdamTM

@ImAdamTM We are aware of the conflicts with other style guides. @lydell asked for

one example each of the worst case [you’ve] encountered in real world code

(emphasis mine)

Can you provide an actual code example where Prettier significantly harms readability and how you would format it?

j-f1 avatar Apr 30 '18 15:04 j-f1

@j-f1 thanks for your continued interest in addressing this! I think the original report contains a good example:

const foo = (a && b) || c || d;

is turned into

const foo = a && b || c || d;

and the original is more readable. Okay, technically this is not real world code, but it's not exactly a contrived example either. This comment includes another good example:

const css = `
 max-width: ${(ITEM_WIDTH * 4) + (ITEM_GUTTER * 3)}px;
`;

becomes

const css = `
 max-width: ${ITEM_WIDTH * 4 + ITEM_GUTTER * 3}px;
`;

Again, the original is more readable.

A lot of the "just think about operator precedence when reading code" responses seem to ignore the value that clear formatting brings to the table in the first place.

msakrejda avatar Apr 30 '18 16:04 msakrejda

@ImAdamTM please note that the next (semver-major) version of the airbnb config will have an altered configuration for no-mixed-operators, so that specific case may no longer apply. Additionally, to use the airbnb config with prettier, you have to/will have to use prettier-eslint, so that eslint can clean up some of prettier’s output.

The cases that we find, at airbnb, where the effective removal is problematic, are when the choice to place otherwise-unnecessary parens is intentional by the developer, to disambiguate and make their intentions clear. In these cases, it is impossible to reconstruct the need for these parens from the AST - as such, the only way to “preserve” them is to literally do that: to keep track of them, in a side channel from the ast, and when printing, add them in as needed.

ljharb avatar Apr 30 '18 16:04 ljharb

@uhoh-itsmaciek The &&/|| case has already been changed/fixed.

Prettier 1.12.1 Playground link

Input:

const foo = (a && b) || c || d;

Output:

const foo = (a && b) || c || d;

lydell avatar Apr 30 '18 16:04 lydell

to use the airbnb config with prettier, you have to/will have to use prettier-eslint, so that eslint can clean up some of prettier’s output.

Or eslint-config-prettier :) (Depending on which tool you'd like to "win".)

lydell avatar Apr 30 '18 16:04 lydell

Here is a simple example (from earlier in the thread) which I think is a good one:

Prettier 1.12.1 Playground link

Input:

const r = (a * b + c) + (x * y + z);

Output:

const r = a * b + c + (x * y + z);

Undistraction avatar Apr 30 '18 16:04 Undistraction

Hard to say how real-world that example is though because of all the one-letter variables. At this point I really do think we need real-world examples to be able to make a good decision.

lydell avatar Apr 30 '18 16:04 lydell

Sorry to jump in, but here's two examples in one of my apps where the extra parenthesis make the code more readable to me personally:

1

  • With parenthesis. Notice how the unary plus is being applied to the final result image

  • Without parenthesis. image

2

  • With parenthesis surrounding the division operation. image

  • Without parenthesis. The red squiggly underlines are due to no-mixed-operators rule. image

Yohanna avatar Apr 30 '18 16:04 Yohanna