ts-pattern
Advanced tools
Comparing version 0.1.2 to 0.1.3
/** | ||
* # Pattern matching | ||
**/ | ||
declare enum PatternType { | ||
String = "@match/string", | ||
Number = "@match/number", | ||
Boolean = "@match/boolean", | ||
Guard = "@match/guard", | ||
Not = "@match/not" | ||
} | ||
/** | ||
* GuardValue returns the value guarded by a type guard function. | ||
* ### Catch All wildcard | ||
* `__` refers to a wildcard pattern, matching any value | ||
*/ | ||
declare type GuardValue<F> = F extends (value: any) => value is infer b ? b : F extends (value: infer a) => unknown ? a : never; | ||
export declare const __: { | ||
readonly string: PatternType.String; | ||
readonly number: PatternType.Number; | ||
readonly boolean: PatternType.Boolean; | ||
}; | ||
/** type alias for the catch all string */ | ||
declare type __ = '__CATCH_ALL__'; | ||
declare type __ = typeof __; | ||
declare type Primitives = number | boolean | string | undefined | null | symbol | bigint; | ||
/** | ||
* ### Catch All wildcard | ||
* `__` refers to a wildcard pattern, matching any value | ||
* GuardValue returns the value guarded by a type guard function. | ||
*/ | ||
export declare const __: __; | ||
declare type GuardValue<F> = F extends (value: any) => value is infer b ? b : F extends (value: infer a) => unknown ? a : never; | ||
declare type GuardFunction<a> = (value: a) => unknown; | ||
declare type GuardPattern<a> = { | ||
patternKind: PatternType.Guard; | ||
when: GuardFunction<a>; | ||
}; | ||
declare type NotPattern<a> = { | ||
patternKind: PatternType.Not; | ||
pattern: Pattern<a>; | ||
}; | ||
declare type SpecialPattern<a> = a extends number ? typeof __.number | __ : a extends string ? typeof __.string | __ : a extends boolean ? typeof __.boolean | __ : __; | ||
/** | ||
@@ -20,5 +42,5 @@ * ### Pattern | ||
*/ | ||
export declare type Pattern<a> = a extends number ? a | NumberConstructor | __ : a extends string ? a | StringConstructor | __ : a extends boolean ? a | BooleanConstructor | __ : a extends [infer b, infer c, infer d, infer e, infer f] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>, Pattern<f>] | __ : a extends [infer b, infer c, infer d, infer e] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>] | __ : a extends [infer b, infer c, infer d] ? [Pattern<b>, Pattern<c>, Pattern<d>] | __ : a extends [infer b, infer c] ? [Pattern<b>, Pattern<c>] | __ : a extends (infer b)[] ? Pattern<b>[] | __ : a extends Map<infer k, infer v> ? Map<k, Pattern<v>> | __ : a extends Set<infer v> ? Set<Pattern<v>> | __ : { | ||
export declare type Pattern<a> = GuardPattern<a> | NotPattern<a | any> | SpecialPattern<a> | (a extends Primitives ? a : a extends [infer b, infer c, infer d, infer e, infer f] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>, Pattern<f>] : a extends [infer b, infer c, infer d, infer e] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>] : a extends [infer b, infer c, infer d] ? [Pattern<b>, Pattern<c>, Pattern<d>] : a extends [infer b, infer c] ? [Pattern<b>, Pattern<c>] : a extends (infer b)[] ? Pattern<b>[] : a extends Map<infer k, infer v> ? Map<k, Pattern<v>> : a extends Set<infer v> ? Set<Pattern<v>> : a extends object ? { | ||
[k in keyof a]?: Pattern<a[k]>; | ||
} | __; | ||
} : a); | ||
/** | ||
@@ -29,5 +51,8 @@ * ### InvertPattern | ||
*/ | ||
declare type InvertPattern<p> = p extends NumberConstructor ? number : p extends StringConstructor ? string : p extends BooleanConstructor ? boolean : p extends [infer pb, infer pc, infer pd, infer pe, infer pf] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>, InvertPattern<pf>] : p extends [infer pb, infer pc, infer pd, infer pe] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>] : p extends [infer pb, infer pc, infer pd] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>] : p extends [infer pb, infer pc] ? [InvertPattern<pb>, InvertPattern<pc>] : p extends (infer pp)[] ? InvertPattern<pp>[] : p extends Map<infer pk, infer pv> ? Map<pk, InvertPattern<pv>> : p extends Set<infer pv> ? Set<InvertPattern<pv>> : p extends __ ? __ : { | ||
declare type InvertPattern<p> = p extends typeof __.number ? number : p extends typeof __.string ? string : p extends typeof __.boolean ? boolean : p extends __ ? __ : p extends GuardPattern<infer pb> ? GuardValue<p['when']> : p extends NotPattern<infer pb> ? { | ||
valueKind: PatternType.Not; | ||
pattern: InvertPattern<pb>; | ||
} : p extends Primitives ? p : p extends [infer pb, infer pc, infer pd, infer pe, infer pf] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>, InvertPattern<pf>] : p extends [infer pb, infer pc, infer pd, infer pe] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>] : p extends [infer pb, infer pc, infer pd] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>] : p extends [infer pb, infer pc] ? [InvertPattern<pb>, InvertPattern<pc>] : p extends (infer pp)[] ? InvertPattern<pp>[] : p extends Map<infer pk, infer pv> ? Map<pk, InvertPattern<pv>> : p extends Set<infer pv> ? Set<InvertPattern<pv>> : p extends object ? { | ||
[k in keyof p]: InvertPattern<p[k]>; | ||
}; | ||
} : p; | ||
/** | ||
@@ -41,5 +66,22 @@ * ### LeastUpperBound | ||
*/ | ||
declare type LeastUpperBound<a, b> = [a, b] extends [[infer aa, infer ab, infer ac, infer ad, infer ae], [infer ba, infer bb, infer bc, infer bd, infer be]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>, LeastUpperBound<ad, bd>, LeastUpperBound<ae, be>] : [a, b] extends [[infer aa, infer ab, infer ac, infer ad], [infer ba, infer bb, infer bc, infer bd]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>, LeastUpperBound<ad, bd>] : [a, b] extends [[infer aa, infer ab, infer ac], [infer ba, infer bb, infer bc]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>] : [a, b] extends [[infer aa, infer ab], [infer ba, infer bb]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>] : [a, b] extends [(infer aa)[], (infer ba)[]] ? LeastUpperBound<aa, ba>[] : [a, b] extends [Map<infer ak, infer av>, Map<infer bk, infer bv>] ? Map<LeastUpperBound<ak, bk>, LeastUpperBound<av, bv>> : [a, b] extends [Set<infer av>, Set<infer bv>] ? Set<LeastUpperBound<av, bv>> : b extends __ ? a : a extends __ ? b : b extends a ? b : a extends b ? a : never; | ||
declare type MatchedValue<a, p extends Pattern<a>> = LeastUpperBound<a, InvertPattern<p>>; | ||
declare type LeastUpperBound<a, b> = b extends a ? b : a extends b ? a : never; | ||
declare type ExtractMostPreciseValue<a, b> = [a, b] extends [[infer a1, infer a2, infer a3, infer a4, infer a5], [infer b1, infer b2, infer b3, infer b4, infer b5]] ? [ExtractMostPreciseValue<a1, b1>, ExtractMostPreciseValue<a2, b2>, ExtractMostPreciseValue<a3, b3>, ExtractMostPreciseValue<a4, b4>, ExtractMostPreciseValue<a5, b5>] : [a, b] extends [[infer a1, infer a2, infer a3, infer a4], [infer b1, infer b2, infer b3, infer b4]] ? [ExtractMostPreciseValue<a1, b1>, ExtractMostPreciseValue<a2, b2>, ExtractMostPreciseValue<a3, b3>, ExtractMostPreciseValue<a4, b4>] : [a, b] extends [[infer a1, infer a2, infer a3], [infer b1, infer b2, infer b3]] ? [ExtractMostPreciseValue<a1, b1>, ExtractMostPreciseValue<a2, b2>, ExtractMostPreciseValue<a3, b3>] : [a, b] extends [[infer a1, infer a2], [infer b1, infer b2]] ? [ExtractMostPreciseValue<a1, b1>, ExtractMostPreciseValue<a2, b2>] : [a, b] extends [(infer a1)[], (infer b1)[]] ? ExtractMostPreciseValue<a1, b1>[] : [a, b] extends [Map<infer ak, infer av>, Map<infer bk, infer bv>] ? Map<ExtractMostPreciseValue<ak, bk>, ExtractMostPreciseValue<av, bv>> : [a, b] extends [Set<infer av>, Set<infer bv>] ? Set<ExtractMostPreciseValue<av, bv>> : b extends __ ? a : b extends { | ||
valueKind: PatternType.Not; | ||
pattern: infer b1; | ||
} ? Exclude<a, b1> : [a, b] extends [object, object] ? ObjectExtractMostPreciseValue<a, b> : LeastUpperBound<a, b>; | ||
/** | ||
* if a key of an object has the never type, | ||
* returns never, otherwise returns the type of object | ||
**/ | ||
declare type ExcludeIfContainsNever<a> = ValueOf<{ | ||
[k in keyof a]: a[k] extends never ? 'exclude' : 'include'; | ||
}> extends 'include' ? a : never; | ||
declare type ObjectExtractMostPreciseValue<a, b> = b extends a ? b : a extends b ? a : ExcludeIfContainsNever<{ | ||
[k in keyof a]: k extends keyof b ? ExtractMostPreciseValue<a[k], b[k]> : a[k]; | ||
}>; | ||
declare type ValueOf<a> = a[keyof a]; | ||
declare type MatchedValue<a, p extends Pattern<a>> = ExtractMostPreciseValue<a, InvertPattern<p>>; | ||
export declare const when: <a>(predicate: GuardFunction<a>) => GuardPattern<a>; | ||
export declare const not: <a>(pattern: Pattern<a>) => NotPattern<a>; | ||
/** | ||
* ### match | ||
@@ -46,0 +88,0 @@ * Entry point to create pattern matching code branches. It returns an |
@@ -26,3 +26,11 @@ "use strict"; | ||
exports.__esModule = true; | ||
exports.match = exports.__ = void 0; | ||
exports.match = exports.not = exports.when = exports.__ = void 0; | ||
var PatternType; | ||
(function (PatternType) { | ||
PatternType["String"] = "@match/string"; | ||
PatternType["Number"] = "@match/number"; | ||
PatternType["Boolean"] = "@match/boolean"; | ||
PatternType["Guard"] = "@match/guard"; | ||
PatternType["Not"] = "@match/not"; | ||
})(PatternType || (PatternType = {})); | ||
/** | ||
@@ -32,3 +40,15 @@ * ### Catch All wildcard | ||
*/ | ||
exports.__ = '__CATCH_ALL__'; | ||
exports.__ = { | ||
string: PatternType.String, | ||
number: PatternType.Number, | ||
boolean: PatternType.Boolean | ||
}; | ||
exports.when = function (predicate) { return ({ | ||
patternKind: PatternType.Guard, | ||
when: predicate | ||
}); }; | ||
exports.not = function (pattern) { return ({ | ||
patternKind: PatternType.Not, | ||
pattern: pattern | ||
}); }; | ||
/** | ||
@@ -78,3 +98,12 @@ * ### match | ||
}; | ||
var wildcards = [String, Boolean, Number]; | ||
var isGuardPattern = function (x) { | ||
var pattern = x; | ||
return (pattern && | ||
pattern.patternKind === PatternType.Guard && | ||
typeof pattern.when === 'function'); | ||
}; | ||
var isNotPattern = function (x) { | ||
var pattern = x; | ||
return pattern && pattern.patternKind === PatternType.Not; | ||
}; | ||
// tells us if the value matches a given pattern. | ||
@@ -84,9 +113,13 @@ var matchPattern = function (pattern) { return function (value) { | ||
return true; | ||
if (pattern === String) | ||
if (pattern === exports.__.string) | ||
return typeof value === 'string'; | ||
if (pattern === Boolean) | ||
if (pattern === exports.__.boolean) | ||
return typeof value === 'boolean'; | ||
if (pattern === Number) { | ||
if (pattern === exports.__.number) { | ||
return typeof value === 'number' && !Number.isNaN(value); | ||
} | ||
if (isGuardPattern(pattern)) | ||
return Boolean(pattern.when(value)); | ||
if (isNotPattern(pattern)) | ||
return !matchPattern(pattern.pattern)(value); | ||
if (typeof pattern !== typeof value) | ||
@@ -115,3 +148,3 @@ return false; | ||
? patternValues.every(function (subPattern) { | ||
return wildcards.includes(subPattern) | ||
return Object.values(exports.__).includes(subPattern) | ||
? matchPattern([subPattern])(allValues_1) | ||
@@ -118,0 +151,0 @@ : value.has(subPattern); |
{ | ||
"name": "ts-pattern", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Typescript pattern matching library", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
# Typescript Pattern | ||
A complete pattern matching library for typescript. | ||
```ts | ||
import { match, __, when, not } from 'pattern'; | ||
match({ type: 'hello' }) | ||
.with(__, () => 'ok') | ||
.with(__.string, () => 'ok') | ||
.with( | ||
when((x) => true), | ||
() => 'ok' | ||
) | ||
.with(not('hello'), () => 'ok') | ||
.with(not(__.string), () => 'ok') | ||
.with(not(when((x) => true)), () => 'ok') | ||
.with({ type: __ }, () => 'ok') | ||
.with({ type: __.string }, () => 'ok') | ||
.with({ type: when((x) => true) }, () => 'ok') | ||
.with({ type: not('hello') }, () => 'ok') | ||
.with({ type: not(__.string) }, () => 'ok') | ||
.with({ type: not(when((x) => true)) }, () => 'ok'); | ||
``` |
### Roadmap | ||
- [ ] Forbid impossible patterns like `{ status: 'error', data: (data) => data.length > 3 }` (error have no data) | ||
- [ ] Find a way to enforce exhaustive pattern matching | ||
@@ -4,0 +6,0 @@ - [ ] Add `not(value)` in patterns. |
246
src/index.ts
@@ -5,3 +5,33 @@ /** | ||
enum PatternType { | ||
String = '@match/string', | ||
Number = '@match/number', | ||
Boolean = '@match/boolean', | ||
Guard = '@match/guard', | ||
Not = '@match/not', | ||
} | ||
/** | ||
* ### Catch All wildcard | ||
* `__` refers to a wildcard pattern, matching any value | ||
*/ | ||
export const __ = { | ||
string: PatternType.String, | ||
number: PatternType.Number, | ||
boolean: PatternType.Boolean, | ||
} as const; | ||
/** type alias for the catch all string */ | ||
type __ = typeof __; | ||
type Primitives = | ||
| number | ||
| boolean | ||
| string | ||
| undefined | ||
| null | ||
| symbol | ||
| bigint; | ||
/** | ||
* GuardValue returns the value guarded by a type guard function. | ||
@@ -15,10 +45,22 @@ */ | ||
/** type alias for the catch all string */ | ||
type __ = '__CATCH_ALL__'; | ||
/** | ||
* ### Catch All wildcard | ||
* `__` refers to a wildcard pattern, matching any value | ||
*/ | ||
export const __: __ = '__CATCH_ALL__'; | ||
type GuardFunction<a> = (value: a) => unknown; | ||
type GuardPattern<a> = { | ||
patternKind: PatternType.Guard; | ||
when: GuardFunction<a>; | ||
}; | ||
type NotPattern<a> = { | ||
patternKind: PatternType.Not; | ||
pattern: Pattern<a>; | ||
}; | ||
type SpecialPattern<a> = a extends number | ||
? typeof __.number | __ | ||
: a extends string | ||
? typeof __.string | __ | ||
: a extends boolean | ||
? typeof __.boolean | __ | ||
: __; | ||
/** | ||
@@ -29,23 +71,25 @@ * ### Pattern | ||
*/ | ||
export type Pattern<a> = a extends number | ||
? a | NumberConstructor | __ | ||
: a extends string | ||
? a | StringConstructor | __ | ||
: a extends boolean | ||
? a | BooleanConstructor | __ | ||
: a extends [infer b, infer c, infer d, infer e, infer f] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>, Pattern<f>] | __ | ||
: a extends [infer b, infer c, infer d, infer e] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>] | __ | ||
: a extends [infer b, infer c, infer d] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>] | __ | ||
: a extends [infer b, infer c] | ||
? [Pattern<b>, Pattern<c>] | __ | ||
: a extends (infer b)[] | ||
? Pattern<b>[] | __ | ||
: a extends Map<infer k, infer v> | ||
? Map<k, Pattern<v>> | __ | ||
: a extends Set<infer v> | ||
? Set<Pattern<v>> | __ | ||
: { [k in keyof a]?: Pattern<a[k]> } | __; | ||
export type Pattern<a> = | ||
| GuardPattern<a> | ||
| NotPattern<a | any> | ||
| SpecialPattern<a> | ||
| (a extends Primitives | ||
? a | ||
: a extends [infer b, infer c, infer d, infer e, infer f] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>, Pattern<f>] | ||
: a extends [infer b, infer c, infer d, infer e] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>] | ||
: a extends [infer b, infer c, infer d] | ||
? [Pattern<b>, Pattern<c>, Pattern<d>] | ||
: a extends [infer b, infer c] | ||
? [Pattern<b>, Pattern<c>] | ||
: a extends (infer b)[] | ||
? Pattern<b>[] | ||
: a extends Map<infer k, infer v> | ||
? Map<k, Pattern<v>> | ||
: a extends Set<infer v> | ||
? Set<Pattern<v>> | ||
: a extends object | ||
? { [k in keyof a]?: Pattern<a[k]> } | ||
: a); | ||
@@ -57,8 +101,19 @@ /** | ||
*/ | ||
type InvertPattern<p> = p extends NumberConstructor | ||
type InvertPattern<p> = p extends typeof __.number | ||
? number | ||
: p extends StringConstructor | ||
: p extends typeof __.string | ||
? string | ||
: p extends BooleanConstructor | ||
: p extends typeof __.boolean | ||
? boolean | ||
: p extends __ | ||
? __ | ||
: p extends GuardPattern<infer pb> | ||
? GuardValue<p['when']> | ||
: p extends NotPattern<infer pb> | ||
? { | ||
valueKind: PatternType.Not; | ||
pattern: InvertPattern<pb>; | ||
} | ||
: p extends Primitives | ||
? p | ||
: p extends [infer pb, infer pc, infer pd, infer pe, infer pf] | ||
@@ -84,5 +139,5 @@ ? [ | ||
? Set<InvertPattern<pv>> | ||
: p extends __ | ||
? __ | ||
: { [k in keyof p]: InvertPattern<p[k]> }; | ||
: p extends object | ||
? { [k in keyof p]: InvertPattern<p[k]> } | ||
: p; | ||
@@ -97,47 +152,78 @@ /** | ||
*/ | ||
type LeastUpperBound<a, b> = [a, b] extends [ | ||
[infer aa, infer ab, infer ac, infer ad, infer ae], | ||
[infer ba, infer bb, infer bc, infer bd, infer be] | ||
type LeastUpperBound<a, b> = b extends a ? b : a extends b ? a : never; | ||
type ExtractMostPreciseValue<a, b> = [a, b] extends [ | ||
[infer a1, infer a2, infer a3, infer a4, infer a5], | ||
[infer b1, infer b2, infer b3, infer b4, infer b5] | ||
] // quintupple | ||
? [ | ||
LeastUpperBound<aa, ba>, | ||
LeastUpperBound<ab, bb>, | ||
LeastUpperBound<ac, bc>, | ||
LeastUpperBound<ad, bd>, | ||
LeastUpperBound<ae, be> | ||
ExtractMostPreciseValue<a1, b1>, | ||
ExtractMostPreciseValue<a2, b2>, | ||
ExtractMostPreciseValue<a3, b3>, | ||
ExtractMostPreciseValue<a4, b4>, | ||
ExtractMostPreciseValue<a5, b5> | ||
] | ||
: [a, b] extends [ | ||
[infer aa, infer ab, infer ac, infer ad], | ||
[infer ba, infer bb, infer bc, infer bd] | ||
] // quadrupple | ||
[infer a1, infer a2, infer a3, infer a4], | ||
[infer b1, infer b2, infer b3, infer b4] | ||
] // qua4rupple | ||
? [ | ||
LeastUpperBound<aa, ba>, | ||
LeastUpperBound<ab, bb>, | ||
LeastUpperBound<ac, bc>, | ||
LeastUpperBound<ad, bd> | ||
ExtractMostPreciseValue<a1, b1>, | ||
ExtractMostPreciseValue<a2, b2>, | ||
ExtractMostPreciseValue<a3, b3>, | ||
ExtractMostPreciseValue<a4, b4> | ||
] | ||
: [a, b] extends [ | ||
[infer aa, infer ab, infer ac], | ||
[infer ba, infer bb, infer bc] | ||
[infer a1, infer a2, infer a3], | ||
[infer b1, infer b2, infer b3] | ||
] // tripple | ||
? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>] | ||
: [a, b] extends [[infer aa, infer ab], [infer ba, infer bb]] // tupple | ||
? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>] | ||
: [a, b] extends [(infer aa)[], (infer ba)[]] | ||
? LeastUpperBound<aa, ba>[] | ||
? [ | ||
ExtractMostPreciseValue<a1, b1>, | ||
ExtractMostPreciseValue<a2, b2>, | ||
ExtractMostPreciseValue<a3, b3> | ||
] | ||
: [a, b] extends [[infer a1, infer a2], [infer b1, infer b2]] // tupple | ||
? [ExtractMostPreciseValue<a1, b1>, ExtractMostPreciseValue<a2, b2>] | ||
: [a, b] extends [(infer a1)[], (infer b1)[]] | ||
? ExtractMostPreciseValue<a1, b1>[] | ||
: [a, b] extends [Map<infer ak, infer av>, Map<infer bk, infer bv>] | ||
? Map<LeastUpperBound<ak, bk>, LeastUpperBound<av, bv>> | ||
? Map<ExtractMostPreciseValue<ak, bk>, ExtractMostPreciseValue<av, bv>> | ||
: [a, b] extends [Set<infer av>, Set<infer bv>] | ||
? Set<LeastUpperBound<av, bv>> | ||
? Set<ExtractMostPreciseValue<av, bv>> | ||
: b extends __ | ||
? a | ||
: a extends __ | ||
: b extends { valueKind: PatternType.Not; pattern: infer b1 } | ||
? Exclude<a, b1> | ||
: [a, b] extends [object, object] | ||
? ObjectExtractMostPreciseValue<a, b> | ||
: LeastUpperBound<a, b>; | ||
/** | ||
* if a key of an object has the never type, | ||
* returns never, otherwise returns the type of object | ||
**/ | ||
type ExcludeIfContainsNever<a> = ValueOf< | ||
{ | ||
[k in keyof a]: a[k] extends never ? 'exclude' : 'include'; | ||
} | ||
> extends 'include' | ||
? a | ||
: never; | ||
type ObjectExtractMostPreciseValue<a, b> = b extends a | ||
? b | ||
: b extends a | ||
? b | ||
: a extends b | ||
? a | ||
: never; | ||
: ExcludeIfContainsNever< | ||
{ | ||
[k in keyof a]: k extends keyof b | ||
? ExtractMostPreciseValue<a[k], b[k]> | ||
: a[k]; | ||
} | ||
>; | ||
type MatchedValue<a, p extends Pattern<a>> = LeastUpperBound< | ||
type ValueOf<a> = a[keyof a]; | ||
type MatchedValue<a, p extends Pattern<a>> = ExtractMostPreciseValue< | ||
a, | ||
@@ -147,2 +233,12 @@ InvertPattern<p> | ||
export const when = <a>(predicate: GuardFunction<a>): GuardPattern<a> => ({ | ||
patternKind: PatternType.Guard, | ||
when: predicate, | ||
}); | ||
export const not = <a>(pattern: Pattern<a>): NotPattern<a> => ({ | ||
patternKind: PatternType.Not, | ||
pattern, | ||
}); | ||
/** | ||
@@ -266,4 +362,16 @@ * ### match | ||
const wildcards = [String, Boolean, Number]; | ||
const isGuardPattern = (x: unknown): x is GuardPattern<unknown> => { | ||
const pattern = x as GuardPattern<unknown>; | ||
return ( | ||
pattern && | ||
pattern.patternKind === PatternType.Guard && | ||
typeof pattern.when === 'function' | ||
); | ||
}; | ||
const isNotPattern = (x: unknown): x is NotPattern<unknown> => { | ||
const pattern = x as NotPattern<unknown>; | ||
return pattern && pattern.patternKind === PatternType.Not; | ||
}; | ||
// tells us if the value matches a given pattern. | ||
@@ -274,7 +382,9 @@ const matchPattern = <a, p extends Pattern<a>>(pattern: p) => ( | ||
if (pattern === __) return true; | ||
if (pattern === String) return typeof value === 'string'; | ||
if (pattern === Boolean) return typeof value === 'boolean'; | ||
if (pattern === Number) { | ||
if (pattern === __.string) return typeof value === 'string'; | ||
if (pattern === __.boolean) return typeof value === 'boolean'; | ||
if (pattern === __.number) { | ||
return typeof value === 'number' && !Number.isNaN(value); | ||
} | ||
if (isGuardPattern(pattern)) return Boolean(pattern.when(value)); | ||
if (isNotPattern(pattern)) return !matchPattern(pattern.pattern)(value); | ||
@@ -306,3 +416,3 @@ if (typeof pattern !== typeof value) return false; | ||
? patternValues.every((subPattern) => | ||
wildcards.includes(subPattern) | ||
Object.values(__).includes(subPattern) | ||
? matchPattern<any, Pattern<any>>([subPattern])(allValues) | ||
@@ -309,0 +419,0 @@ : value.has(subPattern) |
@@ -1,2 +0,2 @@ | ||
import { match, __, Pattern } from '../src'; | ||
import { match, __, when, not, Pattern } from '../src'; | ||
@@ -11,6 +11,6 @@ type Option<a> = { kind: 'none' } | { kind: 'some'; value: a }; | ||
type State = | ||
| { status: 'idle'; data?: undefined; error?: undefined } | ||
| { status: 'loading'; data?: undefined; error?: undefined } | ||
| { status: 'success'; data: string; error?: undefined } | ||
| { status: 'error'; data?: undefined; error: Error }; | ||
| { status: 'idle' } | ||
| { status: 'loading' } | ||
| { status: 'success'; data: string } | ||
| { status: 'error'; error: Error }; | ||
@@ -24,5 +24,5 @@ type Event = | ||
describe('types', () => { | ||
it('Pattern type should typecheck', () => { | ||
type Input = [State, Event]; | ||
type Input = [State, Event]; | ||
it('wildcard patterns should typecheck', () => { | ||
let pattern: Pattern<Input>; | ||
@@ -32,2 +32,3 @@ pattern = __; | ||
pattern = [{ status: 'success', data: '' }, __]; | ||
pattern = [{ status: 'success', data: __.string }, __]; | ||
pattern = [{ status: 'success', data: __ }, __]; | ||
@@ -41,2 +42,84 @@ pattern = [{ status: 'error', error: new Error() }, __]; | ||
}); | ||
it('guard patterns should typecheck', () => { | ||
const pattern1: Pattern<Input> = when(() => true); | ||
const pattern2: Pattern<Input> = when((x) => { | ||
const inferenceCheck: Input = x; | ||
return true; | ||
}); | ||
const pattern3: Pattern<Input> = [ | ||
when((state) => { | ||
const inferenceCheck: State = state; | ||
return !!state; | ||
}), | ||
when((event) => { | ||
const inferenceCheck: Event = event; | ||
return !!event; | ||
}), | ||
]; | ||
const pattern3_1: Pattern<Input> = [ | ||
__, | ||
{ type: when((t: Event['type']) => true) }, | ||
]; | ||
const pattern4: Pattern<Input> = [ | ||
{ | ||
status: 'success', | ||
data: when((d) => { | ||
const inferenceCheck: string = d; | ||
return true; | ||
}), | ||
}, | ||
__, | ||
]; | ||
const pattern4_1: Pattern<Input> = [{ status: 'error', data: '' }, __]; | ||
const pattern5: Pattern<Input> = [ | ||
__, | ||
{ type: when((t: Event['type']) => true) }, | ||
]; | ||
const isFetch = (type: string): type is 'fetch' => type === 'fetch'; | ||
const pattern6: Pattern<Input> = [__, { type: when(isFetch) }]; | ||
const pattern7: Pattern<{ x: string }> = { | ||
x: when((x) => { | ||
const inferenceCheck: string = x; | ||
return true; | ||
}), | ||
}; | ||
const pattern8: Pattern<[{ x: string }]> = [ | ||
{ | ||
x: when((x) => { | ||
const inferenceCheck: string = x; | ||
return true; | ||
}), | ||
}, | ||
]; | ||
const pattern9: Pattern<[{ x: string }, { y: number }]> = [ | ||
{ | ||
x: when((x) => { | ||
const inferenceCheck: string = x; | ||
return true; | ||
}), | ||
}, | ||
{ | ||
y: when((y) => { | ||
const inferenceCheck: number = y; | ||
return true; | ||
}), | ||
}, | ||
]; | ||
const pattern10: Pattern<string | number> = when((x) => { | ||
const inferenceCheck: string | number = x; | ||
return true; | ||
}); | ||
}); | ||
}); | ||
@@ -125,3 +208,3 @@ | ||
}) | ||
.with([{ id: Number, title: String }], (blogs) => { | ||
.with([{ id: __.number, title: __.string }], (blogs) => { | ||
const inferenceCheck: { id: number; title: string }[] = blogs; | ||
@@ -149,6 +232,6 @@ return { | ||
.with([], () => 0) | ||
.with([Number, Number], ([x, y]) => x + y) | ||
.with([Number, Number, Number], ([x, y, z]) => x + y + z) | ||
.with([__.number, __.number], ([x, y]) => x + y) | ||
.with([__.number, __.number, __.number], ([x, y, z]) => x + y + z) | ||
.with( | ||
[Number, Number, Number, Number], | ||
[__.number, __.number, __.number, __.number], | ||
([x, y, z, w]) => x + y + z + w | ||
@@ -185,3 +268,3 @@ ) | ||
}) | ||
.with([String, Number], (x) => { | ||
.with([__.string, __.number], (x) => { | ||
const inferenceCheck: [string, number] = x; | ||
@@ -256,3 +339,3 @@ return `not matching`; | ||
const userPattern = { name: String }; | ||
const userPattern = { name: __.string }; | ||
@@ -314,2 +397,4 @@ const res = match<Map<string, { name: string }>, { name: string }>( | ||
expect(containsGabAndYo(new Set([2]))).toEqual([false, false]); | ||
expect(containsGabAndYo(new Set([__.number]))).toEqual([false, false]); | ||
expect(containsGabAndYo(new Set([__.string]))).toEqual([false, false]); | ||
}); | ||
@@ -327,7 +412,7 @@ }); | ||
const res = match<any, Blog | Error>(httpResult) | ||
.with({ id: Number, title: String }, (r) => ({ | ||
.with({ id: __.number, title: __.string }, (r) => ({ | ||
id: r.id, | ||
title: r.title, | ||
})) | ||
.with({ errorMessage: String }, (r) => new Error(r.errorMessage)) | ||
.with({ errorMessage: __.string }, (r) => new Error(r.errorMessage)) | ||
.otherwise(() => new Error('Client parse error')) | ||
@@ -425,3 +510,6 @@ .run(); | ||
(x) => x.data.length > 3, | ||
() => true | ||
(x) => { | ||
const inferenceCheck: { status: 'success'; data: string } = x; | ||
return true; | ||
} | ||
) | ||
@@ -434,2 +522,92 @@ .otherwise(() => false) | ||
}); | ||
describe('pattern containing a when close', () => { | ||
it('type of value in predicate should be infered', () => { | ||
type Vec3 = { x: number; y: number; z: number }; | ||
const vec: Vec3 = { x: 20, y: 4, z: 2 }; | ||
const get = (vec: Vec3) => | ||
match<Vec3, boolean>(vec) | ||
.with( | ||
{ | ||
x: when((x): x is 20 => { | ||
const inferenceCheck: number = x; | ||
return x === 20; | ||
}), | ||
y: __.number, | ||
}, | ||
(vec) => { | ||
const inferenceCheck: Vec3 = vec; | ||
return vec.y > 2; | ||
} | ||
) | ||
.with( | ||
when(() => true), | ||
(x) => { | ||
const inferenceCheck: Vec3 = vec; | ||
return false; | ||
} | ||
) | ||
.run(); | ||
expect(get(vec)).toEqual(true); | ||
expect(get({ x: 2, y: 1, z: 0 })).toEqual(false); | ||
}); | ||
}); | ||
describe('pattern containing a not close', () => { | ||
it('should work at the top level', () => { | ||
const get = (x: unknown) => | ||
match<unknown, string>(x) | ||
.with(not(__.number), (x) => { | ||
const inferenceCheck: unknown = x; | ||
return 'not a number'; | ||
}) | ||
.with(not(__.string), (x) => { | ||
const inferenceCheck: unknown = x; | ||
return 'not a string'; | ||
}) | ||
.run(); | ||
expect(get(20)).toEqual('not a string'); | ||
expect(get('hello')).toEqual('not a number'); | ||
}); | ||
it('should work in a nested structure', () => { | ||
type DS = { x: string | number; y: string | number }; | ||
const get = (x: DS) => | ||
match<DS, string>(x) | ||
.with({ y: __.number, x: not(__.string) }, (x) => { | ||
const inferenceCheck: { x: number; y: number } = x; | ||
return 'yes'; | ||
}) | ||
.with(__, () => 'no') | ||
.run(); | ||
expect(get({ x: 2, y: 2 })).toEqual('yes'); | ||
expect(get({ y: 2, x: 'hello' })).toEqual('no'); | ||
}); | ||
it('should discriminate union types correctly', () => { | ||
const one = 'one'; | ||
const two = 'two'; | ||
const get = (x: 'one' | 'two') => | ||
match<'one' | 'two', string>(x) | ||
.with(not(one), (x) => { | ||
const inferenceCheck: 'two' = x; | ||
return 'not 1'; | ||
}) | ||
.with(not(two), (x) => { | ||
const inferenceCheck: 'one' = x; | ||
return 'not 2'; | ||
}) | ||
.run(); | ||
expect(get('two')).toEqual('not 1'); | ||
expect(get('one')).toEqual('not 2'); | ||
}); | ||
it('type of value in predicate should be infered', () => {}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
44286
1231
25