Description
This issue appeared in eslint-plugin-jest
after updating @typescript-eslint
to 2.13.0 (from 2.12.0).
TypeScript Version: 3.7.x-dev.201xxxxx
Search Terms:
typeguard, union
Code
export declare enum AST_NODE_TYPES {
Literal = 'Literal',
TemplateLiteral = 'TemplateLiteral',
}
export type TSNode = StringLiteral | TemplateLiteral;
export interface BaseNode {
stuff: null;
}
export interface LiteralBase extends BaseNode {
raw: string;
value: boolean | number | RegExp | string | null;
regex?: {
pattern: string;
flags: string;
};
}
export interface StringLiteral extends LiteralBase {
type: AST_NODE_TYPES.Literal;
value: string;
}
export interface TemplateLiteral extends BaseNode {
type: AST_NODE_TYPES.TemplateLiteral;
quasis: string[];
}
interface MyStringLiteral<Value extends string = string> extends StringLiteral {
value: Value;
}
interface MyTemplateLiteral<Value extends string = string>
extends TemplateLiteral {
quasis: [Value];
}
type MyStringNode<S extends string = string> =
| MyStringLiteral<S>
| MyTemplateLiteral<S>;
declare function isMyStringNode<V extends string>(
node: TSNode,
specifics?: V,
): node is MyStringNode<V>;
function foo(argument: TSNode) {
if (isMyStringNode(argument)) {
/*
Expected: argument.type is Literal|TemplateLiteral
Actual : argument.type is Literal
This condition will always return 'false' since the types
'AST_NODE_TYPES.Literal' and
'AST_NODE_TYPES.TemplateLiteral' have no overlap.
*/
if (argument.type === AST_NODE_TYPES.TemplateLiteral) {
}
}
}
This is a real life reproduction - it can be made smaller by removing the generics, BaseNode
, etc, but then it becomes counterproductive as you can argue the solution to be "just use StringLiteral
directly".
Expected behavior:
No errors
Actual behavior:
if (argument.type === AST_NODE_TYPES.TemplateLiteral) {
is marked as an error, as TS believes argument.type
can only be AST_NODE_TYPES.Literal
(aka it appears to discard the alternative in the union).
This behaviour is tied to the existence of the value: string
property on StringLiteral
: if you remove that property, everything works as expected (which is what changed between 2.12.0 & 2.13.0: previously Literal
was just an interface w/ type
- now it's a union of <Type>Literal
interfaces; one for each of the values of LiteralBase#value
)
Related Issues:
This is probably a duplicate of #31156, but I've created a new issue as I believe this is a more subtle (ideally fixable) issue