Skip to content

fix(eslint-plugin): [prefer-nullish-coalescing] doesn't report on ternary but on equivalent || #10517

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f663f69
prefer-nullish-coalescing: handle Identifier and UnaryExpression in
OlivierZal Dec 18, 2024
214e56e
remove useless return
OlivierZal Dec 30, 2024
c0ffe54
add tests
OlivierZal Dec 30, 2024
08dcde6
create intermediate constants for clarity
OlivierZal Dec 31, 2024
bde98e9
move line
OlivierZal Dec 31, 2024
d6fa559
add tests
OlivierZal Dec 31, 2024
ec6c08f
add more tests
OlivierZal Dec 31, 2024
5734d33
add ouput
OlivierZal Dec 31, 2024
7ff3d6d
renaming + migrate some utils from rules to util folder
OlivierZal Jan 4, 2025
0274a0c
remove redundant tests
OlivierZal Jan 4, 2025
d92734e
remove useless return
OlivierZal Jan 4, 2025
7a85531
handle MemberExpression
OlivierZal Jan 4, 2025
eca7196
keep no-unnecessary-condition utils update for another PR
OlivierZal Jan 4, 2025
c8d56b3
add tests
OlivierZal Jan 4, 2025
0494327
renaming
OlivierZal Jan 4, 2025
ab5d7d8
add tests for new utils
OlivierZal Jan 4, 2025
d6cc757
re-add utils in no-unnecessary-condition for utils coverage
OlivierZal Jan 5, 2025
ec87f63
anticipate optional chain handling
OlivierZal Jan 5, 2025
cf22e92
Merge branch 'main' into prefer-nullish-coalescing-10470
OlivierZal Jan 6, 2025
23b095b
align behaviour with || one
OlivierZal Jan 19, 2025
5c67422
simplify
OlivierZal Jan 19, 2025
313f5e4
fix eslint
OlivierZal Jan 19, 2025
24424ea
align behaviour on || one
OlivierZal Jan 19, 2025
c2e9ab0
renaming
OlivierZal Jan 19, 2025
0bc81d4
Merge branch 'main' into prefer-nullish-coalescing-10470
OlivierZal Jan 19, 2025
70e5aeb
change break line
OlivierZal Jan 19, 2025
80c0221
Add tests for primitives + ternary
OlivierZal Jan 20, 2025
ddb404c
Add tests for primitives + ternary
OlivierZal Jan 20, 2025
28eeac8
Merge branch 'main' into prefer-nullish-coalescing-10470
OlivierZal Jan 20, 2025
2cfb35c
align options for as much use cases as possible
OlivierZal Jan 21, 2025
993d6b1
add tests for ternary + bool coercion
OlivierZal Jan 21, 2025
4822bb4
fixes and add test ternary + conditional
OlivierZal Jan 21, 2025
2defdf4
suggestion
kirkwaiblinger Jan 21, 2025
dce2427
Merge pull request #2 from kirkwaiblinger/pnc-suggestion
OlivierZal Jan 21, 2025
5bd6b4e
Merge branch 'main' into prefer-nullish-coalescing-10470
OlivierZal Jan 21, 2025
80f3a20
fix lint
OlivierZal Jan 21, 2025
469bd65
Update packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
OlivierZal Jan 22, 2025
b771c7f
nits
OlivierZal Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
"noninteractive",
"Nrwl",
"nullish",
"nullishness",
"nx",
"nx's",
"onboarded",
Expand All @@ -167,11 +168,11 @@
"redeclared",
"reimplement",
"resync",
"ronami",
"Ronen",
"Ribaudo",
"ROADMAP",
"Romain",
"ronami",
"Ronen",
"Rosenwasser",
"ruleset",
"rulesets",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This rule reports when you may consider replacing:

- An `||` operator with `??`
- An `||=` operator with `??=`
- Ternary expressions (`?:`) that are equivalent to `||` or `??` with `??`

:::caution
This rule will not work as expected if [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks) is not enabled.
Expand All @@ -42,7 +43,9 @@ foo === undefined ? 'a string' : foo;

const foo: string | null = 'bar';
foo !== null ? foo : 'a string';
foo ? foo : 'a string';
foo === null ? 'a string' : foo;
!foo ? 'a string' : foo;
```

Correct code for `ignoreTernaryTests: false`:
Expand All @@ -61,6 +64,8 @@ foo ?? 'a string';
const foo: string | null = 'bar';
foo ?? 'a string';
foo ?? 'a string';
foo ?? 'a string';
foo ?? 'a string';
```

### `ignoreConditionalTests`
Expand Down
62 changes: 5 additions & 57 deletions packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Praise] Really nice refactors here. Love to see a -62 line count on a rule 😄

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import {
getParserServices,
getTypeName,
getTypeOfPropertyOfName,
getValueOfLiteralType,
isAlwaysNullish,
isArrayMethodCallWithPredicate,
isIdentifier,
isNullableType,
isPossiblyFalsy,
isPossiblyNullish,
isPossiblyTruthy,
isTypeAnyType,
isTypeFlagSet,
isTypeUnknownType,
Expand All @@ -25,59 +30,7 @@ import {
findTypeGuardAssertedArgument,
} from '../util/assertionFunctionUtils';

// Truthiness utilities
// #region
const valueIsPseudoBigInt = (
value: number | string | ts.PseudoBigInt,
): value is ts.PseudoBigInt => {
return typeof value === 'object';
};

const getValueOfLiteralType = (
type: ts.LiteralType,
): bigint | number | string => {
if (valueIsPseudoBigInt(type.value)) {
return pseudoBigIntToBigInt(type.value);
}
return type.value;
};

const isTruthyLiteral = (type: ts.Type): boolean =>
tsutils.isTrueLiteralType(type) ||
(type.isLiteral() && !!getValueOfLiteralType(type));

const isPossiblyFalsy = (type: ts.Type): boolean =>
tsutils
.unionTypeParts(type)
// Intersections like `string & {}` can also be possibly falsy,
// requiring us to look into the intersection.
.flatMap(type => tsutils.intersectionTypeParts(type))
// PossiblyFalsy flag includes literal values, so exclude ones that
// are definitely truthy
.filter(t => !isTruthyLiteral(t))
.some(type => isTypeFlagSet(type, ts.TypeFlags.PossiblyFalsy));

const isPossiblyTruthy = (type: ts.Type): boolean =>
tsutils
.unionTypeParts(type)
.map(type => tsutils.intersectionTypeParts(type))
.some(intersectionParts =>
// It is possible to define intersections that are always falsy,
// like `"" & { __brand: string }`.
intersectionParts.every(type => !tsutils.isFalsyType(type)),
);

// Nullish utilities
const nullishFlag = ts.TypeFlags.Undefined | ts.TypeFlags.Null;
const isNullishType = (type: ts.Type): boolean =>
isTypeFlagSet(type, nullishFlag);

const isPossiblyNullish = (type: ts.Type): boolean =>
tsutils.unionTypeParts(type).some(isNullishType);

const isAlwaysNullish = (type: ts.Type): boolean =>
tsutils.unionTypeParts(type).every(isNullishType);

function toStaticValue(
type: ts.Type,
):
Expand All @@ -100,10 +53,6 @@ function toStaticValue(
return undefined;
}

function pseudoBigIntToBigInt(value: ts.PseudoBigInt): bigint {
return BigInt((value.negative ? '-' : '') + value.base10Value);
}

const BOOL_OPERATORS = new Set([
'<',
'>',
Expand Down Expand Up @@ -151,7 +100,6 @@ function booleanComparison(
return left >= right;
}
}

// #endregion

export type Options = [
Expand Down
Loading
Loading