Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
Description
I frequently see code like this:
typeMyUnion='a'|'b';declareconstmyUnion:MyUnion;constf1=()=>(myUnion==='a' ?'a' :'b');f1('a')// 'a'f1('b')// 'b'
The logic inf1
makes an assumption: ifmyUnion
is nota
, it must beb
.
Later on, someone might update theMyUnion
type and this assumption will breakdown:
-type MyUnion = 'a' | 'b';+type MyUnion = 'a' | 'b' | 'c';
The runtime behaviour is clearly incorrect, yet TypeScript will not error to remind us that we need to update the logic inf1
:
f1('a')// 'a'f1('b')// 'b'f1('c')// 'b' ❌
This problem is not specific to the ternary operator but alsoif
andswitch
statements:
constf2=()=>{if(myUnion==='a'){return'a';}else{return'b';}};constf3=()=>{switch(myUnion){case'a':return'a';default:return'b';}};
As we can see, it is not safe to make assumptions about the value that reaches theelse
/default
case because it can change.
Instead we need to explicitly specify all cases:
importassertNeverfrom'assert-never';typeMyUnion='a'|'b';declareconstmyUnion:MyUnion;constf2=()=>{if(myUnion==='a'){return'a';}elseif(myUnion==='b'){return'b';}else{assertNever(myUnion);}};constf3=()=>{switch(myUnion){case'a':return'a';case'b':return'b';}};constf3b=()=>{switch(myUnion){case'a':return'a';case'b':return'b';default:assertNever(myUnion);}};
This way, when the type is eventually widened, TypeScript will generate a type error so we're reminded that we need to update our code:
importassertNeverfrom'assert-never';typeMyUnion='a'|'b'|'c';declareconstmyUnion:MyUnion;constf2=()=>{if(myUnion==='a'){return'a';}elseif(myUnion==='b'){return'b';}else{// Argument of type 'string' is not assignable to parameter of type 'never'.assertNever(myUnion);}};//@noImplicitReturns: true// Not all code paths return a value.constf3=()=>{switch(myUnion){case'a':return'a';case'b':return'b';}};constf3b=()=>{switch(myUnion){case'a':return'a';case'b':return'b';default:// Argument of type 'string' is not assignable to parameter of type 'never'.assertNever(myUnion);}};
I would like to propose a rule that enforces this. The rule would report an error inside a ternary orif
/switch
statement if we're switching over a union type (exceptboolean
) and we have a fallback case (else
/default
). The fix would be to explicitly specify all cases.
I'm really not sure what we would call it.
WDYT?