Visitar URL original
Parentheses around nullish coalescing operator within conditional expression · Issue #4873 · prettier/prettier · GitHub
Skip to content

Parentheses around nullish coalescing operator within conditional expression #4873

Description

@mariusschulz

Prettier 1.13.5
Playground link

--parser babylon

Input:

const value = condition ? (nullableValue ?? fallbackValue) : nonNullableValue;

Output:

const value = condition ? nullableValue ?? fallbackValue : nonNullableValue;

Expected behavior:

I think it would be better if Prettier preserved the parentheses around the arguments of the nullish coalescing operator if nested within a conditional expression:

const value = condition ? (nullableValue ?? fallbackValue) : nonNullableValue;

My concern is that the precedence of ?? isn't immediately obvious, and I think that parentheses would improve readability here.

Activity

  1. added
    help wantedWe're a small group who can't get to every issue promptly. We’d appreciate help fixing this issue!
    on Jul 23, 2018
  2. changed the title [-]Parentheses around nullish coalescing operator within condition expression[/-] [+]Parentheses around nullish coalescing operator within conditional expression[/+] on Jul 23, 2018
  3. suchipi commented on Jul 23, 2018

    @suchipi
    Member

    We don't do this for other operators, like ||. I'm not sure it makes sense to do this for ??.

  4. mariusschulz commented on Jul 23, 2018

    @mariusschulz
    Author

    I've considered this argument as well, but I come to a different conclusion: I do think it's valuable to make the precedence clearer by adding explicit parentheses here — clarity trumps brevity.

  5. j-f1 commented on Jul 23, 2018

    @j-f1
    Member

    IMO this case is a little different because the operator contains a ?, which could be confused with a nested ternary, which parens would clarify. However, we also add parens for actual ternaries, so my point may be moot:

    Prettier 1.13.7
    Playground link

    --parser babylon

    Input:

    const value = condition ? n ? n : fallbackValue : nonNullableValue;

    Output:

    const value = condition ? (n ? n : fallbackValue) : nonNullableValue;
  6. suchipi commented on Jul 24, 2018

    @suchipi
    Member

    @vjeux @cpojer I know facebook started using the nullish coalescing operator recently, do you have any insight into people's reactions with reading it?

  7. vjeux commented on Jul 24, 2018

    @vjeux
    Contributor

    It’s likely a very niche use case, so whichever decision we make isn’t going to impact a lot of code.

    I’m not against adding parenthesis there personally.

  8. vjeux commented on Jul 24, 2018

    @vjeux
    Contributor

    To expand on my comment. This is only an issue if the entire line fits in 80 column. Otherwise it would break at the beginning of the ? and the indentation would make it obvious what precedence is.

    It’s unlikely that you have an assignment, condition, fallback and else value that all fits.

  9. rubennorte commented on Jul 24, 2018

    @rubennorte
    Contributor

    I can see there's some visual effort needed to separate the alternatives of a ternary but I don't think the specific operator makes much difference:

    const value = condition ? nullableValue ?? fallbackValue : nonNullableValue;
    const value = condition ? nullableValue || fallbackValue : nonNullableValue;
    const value = condition ? nullableValue % fallbackValue : nonNullableValue;

    IMO the debate should be wether or not to use parenthesis in binary expressions inside ternaries rather than making an exception for this one. This change was already done for some mathematical expressions, so it might make sense to revisit this case too.

  10. mariusschulz commented on Jul 24, 2018

    @mariusschulz
    Author

    I agree, it's not so much about the specific operator. IMO it would be a good idea to add/preserve parentheses around binary expressions within conditional expressions. 👍

  11. suchipi commented on Jul 24, 2018

    @suchipi
    Member

    I'm fine adding them as long as we're consistent across binary expressions

  12. saithis commented on Sep 17, 2020

    @saithis

    I helped debug some code today where the dev had written something like this:

    const value = valueOrNull ?? condition ? value1 : value2;
    

    javascript sees this as

    const value = (valueOrNull ?? condition) ? value1 : value2;
    

    but the dev thought it would mean this:

    const value = valueOrNull ?? (condition ? value1 : value2);
    

    If prettier would insert the paranthesis there, then the dev could have seen his error earlier. (with format on save)

  13. NilSet commented on Oct 26, 2020

    @NilSet

    A somewhat similar problem came up

    if (maybeValue ?? defaultValue > 2)

    this was assumed to work like

    if ((maybeValue ?? defaultValue) > 2)

    but it actually works like

    if (maybeValue ?? (defaultValue > 2))

    My suggestion for code style here would be to always use parens with nullish coalescing since there is no consistent instinct around how tightly it binds, but prettier will remove the parens in the latter case when that behavior is desired

  14. kylemh commented on Feb 9, 2021

    @kylemh

    I can't tell if this issue is supported or not. I've been burned many times by the precedence problem, and would love if prettier added explicit parentheses to help indicate the problem for me.

  15. jkillian commented on May 26, 2022

    @jkillian

    I also hit this recently, with a ?? operator in the second branch of a ternary. Would love to see Prettier add parens here!

    Input:

    const alwaysConfusing  = someCondition ? val1 : val2 ?? val3;
    const goodBeforePrettier = someCondition ? val1 : (val2 ?? val3);
    const alwaysFine = (someCondition ? val1 : val2) ?? val3;
    

    Output:

    const alwaysConfusing = someCondition ? val1 : val2 ?? val3
    const goodBeforePrettier = someCondition ? val1 : val2 ?? val3
    const alwaysFine = (someCondition ? val1 : val2) ?? val3
    
  16. Merott commented on Jun 5, 2023

    @Merott

    This issue is nearly 5 years old, but still relevant, and it's actually a bug… Let me know if I should open a separate issue.

    Parentheses around nullish coalescing operators aren't merely cosmetic or for readability alone.

    In this example, the parentheses help TypeScript with narrowing the type of the variable that's being assigned:

    export function myFunc(
      config: {
        throughDate?: string
      } & (
        | { baseDate: string; trackedStartDate?: never }
        | { baseDate?: never; trackedStartDate: string }
        | { baseDate: string; trackedStartDate: string }
      ),
    ) {
      const { baseDate, throughDate, trackedStartDate } = config
    
      const wrong = throughDate ?? baseDate ?? trackedStartDate // --> string | undefined
      const right = throughDate ?? (baseDate ?? trackedStartDate) // --> string
    }

    Try it on TS Playground

    But, prettier is removing the parentheses, which then breaks the code.

    I'd say parentheses should be left alone unless they're extraneous, like unnecessary double parentheses:

    This is ok:

    - const right = throughDate ?? ((baseDate ?? trackedStartDate))
    + const right = throughDate ?? (baseDate ?? trackedStartDate)

    This is NOT ok:

    - const right = throughDate ?? (baseDate ?? trackedStartDate)
    + const right = throughDate ?? baseDate ?? trackedStartDate
  17. j-f1 commented on Jun 5, 2023

    @j-f1
    Member

    That feels like a TypeScript bug, @Merott — I confirmed that Prettier does seem to intentionally change the AST here (as it does with every other logical expression, including && and ||). If you swap the types in your function from string (which can be falsy) to "a" (which will always be truthy), and then swap the operator from ?? to ||, you’ll find that both wrong and right are typed as "a". Additionally, I tried out all the possible valid parameters to that function that I could think of and neither wrong nor right ever returned undefined with your original code, so I’m fairly confident this is a deficiency in the type checker. Logically, there’s no behavior difference between (a ?? b) ?? c (which is what you get if you don’t use parens) and a ?? (b ?? c).

  18. added a commit that references this issue on Jun 12, 2024
    7768a73
  19. added a commit that references this issue on Jun 18, 2024
    988b43b
  20. locked as resolved and limited conversation to collaborators on Sep 17, 2024
  21. added theissue type on Mar 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedWe're a small group who can't get to every issue promptly. We’d appreciate help fixing this issue!lang:javascriptIssues affecting JSlocked-due-to-inactivityPlease open a new issue and fill out the template instead of commenting.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions