Skip to content

prefer-ternary: convert options to object, add onlyAssignments option#2856

Open
ipanasenko wants to merge 4 commits intosindresorhus:mainfrom
ipanasenko:limit-prefer-ternary-to-assignments
Open

prefer-ternary: convert options to object, add onlyAssignments option#2856
ipanasenko wants to merge 4 commits intosindresorhus:mainfrom
ipanasenko:limit-prefer-ternary-to-assignments

Conversation

@ipanasenko
Copy link
Copy Markdown
Contributor

Fixes #1633

Was created with the help from Claude 4.5 Opus

@ipanasenko ipanasenko closed this Feb 3, 2026
@ipanasenko ipanasenko reopened this Feb 3, 2026
@ipanasenko ipanasenko marked this pull request as ready for review February 3, 2026 11:10
@ipanasenko ipanasenko force-pushed the limit-prefer-ternary-to-assignments branch from 3b77a6d to db4e58f Compare February 6, 2026 16:38
@sindresorhus
Copy link
Copy Markdown
Owner

  1. [P1] Autofix can introduce let TDZ runtime errors in rules/prefer-ternary.js:329.
    The new declaration-merging fixer rewrites:
let foo;
if (foo) {
	foo = 1;
} else {
	foo = 2;
}

to:

let foo = foo ? 1 : 2;

which throws (Cannot access 'foo' before initialization). This is a behavior regression.

  1. [P1] Autofix drops comments between declaration and if in rules/prefer-ternary.js:337.
    fixer.removeRange([declarationRange[0], ifRange[0]]) removes everything between nodes, including standalone comments, for example:
let foo;
// keep me
if (test) { ... }

becomes:

let foo = test ? ... : ...;

with the comment deleted.

  1. [P1] Autofix drops TypeScript annotations on merged declarations in rules/prefer-ternary.js:331.
    Using declarations[0].id.name reconstructs only the bare identifier.
    let foo: number; if (...) { foo = 1; } else { foo = 2; } becomes let foo = ...;, losing : number.

@cobaltt7
Copy link
Copy Markdown

cobaltt7 commented Mar 2, 2026

My $0.02: I feel like it makes less sense to limit it to only assignments, and I'd prefer that the rule not warn on yield or await. Most of the annoying examples I've seen that I don't want fixed involve await. Typically that means it's also not an assignment, but something like this I still wouldn't like

let output = await (test ? a() : b());

@ipanasenko
Copy link
Copy Markdown
Contributor Author

@cobaltt7 in this PR it will be fixed like this:

let output = test ? await a() : await b();

@sindresorhus
Copy link
Copy Markdown
Owner

Merge conflict.

__

Incomplete TDZ check — only inspects node.test, not branches

The TDZ guard only checks if the variable appears in the if-condition:

const tokens = sourceCode.getTokens(node.test);
if (tokens.some(token => token.type === 'Identifier' && token.value === variableName)) {
    precedingVariableDeclaration = undefined;
}

This misses cases where the variable is referenced in the assignment branches:

let foo;
if (test) {
    foo = foo + 1;  // reads `foo` (which is `undefined`)
} else {
    foo = 2;
}
// Merged to: let foo = test ? foo + 1 : 2;  ← TDZ error!

The original code is valid (foo is undefined), but the merged version throws because foo is in TDZ during initializer evaluation. The check should also scan consequent.right and alternate.right for references to the variable.


TDZ check uses token matching instead of scope analysis

Using sourceCode.getTokens() and matching on token.type === 'Identifier' is imprecise. It would false-positive on property accesses like obj.foo where foo matches the variable name but isn't actually a reference to it. Scope analysis (sourceCode.getScope() + walking references) would be more correct. The current approach is conservative (skips valid merges rather than creating bugs), so it's not critical, but worth noting.


onlyAssignments name is misleading

The option also allows return statements, not just assignments. The filter at line 256-261:

if (
    onlyAssignments
    && consequent?.type !== 'AssignmentExpression'
    && consequent?.type !== 'ReturnStatement'
) {
    return;
}

Consider a more descriptive name like onlyValueCapture or at minimum document clearly in the docs that returns are also included (the docs do mention this, but the option name is still confusing).

@sindresorhus sindresorhus force-pushed the main branch 4 times, most recently from 814b622 to 0e023e3 Compare April 2, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Limit prefer-ternary to assignments

3 participants