expect-type
Advanced tools
Comparing version 0.7.10 to 0.7.11
@@ -5,2 +5,17 @@ { | ||
{ | ||
"version": "0.7.11", | ||
"tag": "expect-type_v0.7.11", | ||
"date": "Thu, 01 Oct 2020 14:48:13 GMT", | ||
"comments": { | ||
"patch": [ | ||
{ | ||
"comment": "chore: npmignore coverage folder (#184)" | ||
}, | ||
{ | ||
"comment": "fix: distinguish readonly properties (#186)" | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"version": "0.7.10", | ||
@@ -7,0 +22,0 @@ "tag": "expect-type_v0.7.10", |
# Change Log - expect-type | ||
This log was last generated on Fri, 18 Sep 2020 16:56:41 GMT and should not be manually modified. | ||
This log was last generated on Thu, 01 Oct 2020 14:48:13 GMT and should not be manually modified. | ||
## 0.7.11 | ||
Thu, 01 Oct 2020 14:48:13 GMT | ||
### Patches | ||
- chore: npmignore coverage folder (#184) | ||
- fix: distinguish readonly properties (#186) | ||
## 0.7.10 | ||
@@ -6,0 +14,0 @@ Fri, 18 Sep 2020 16:56:41 GMT |
@@ -12,17 +12,38 @@ export declare type Not<T extends boolean> = T extends true ? false : true; | ||
export declare type IsNeverOrAny<T> = Or<[IsNever<T>, IsAny<T>]>; | ||
declare type DeepBrand<T> = IsAny<T> extends true ? Secret : T extends (...args: infer P) => infer R ? { | ||
/** | ||
* Recursively walk a type and replace it with a branded type related to the original. This is useful for | ||
* equality-checking stricter than `A extends B ? B extends A ? true : false : false`, because it detects | ||
* the difference between a few edge-case types that vanilla typescript doesn't by default: | ||
* - `any` vs `unknown` | ||
* - `{ readonly a: string }` vs `{ a: string }` | ||
* - `{ a?: string }` vs `{ a: string | undefined }` | ||
*/ | ||
declare type DeepBrand<T> = IsAny<T> extends true ? Secret : T extends string | number | boolean | symbol | bigint | null | undefined ? T : T extends (...args: infer P) => infer R ? { | ||
type: 'function'; | ||
params: DeepBrand<P>; | ||
return: DeepBrand<R>; | ||
function: Secret; | ||
} : { | ||
[K in keyof T]: DeepBrand<T[K]>; | ||
type: 'object'; | ||
properties: { | ||
[K in keyof T]: DeepBrand<T[K]>; | ||
}; | ||
readonly: ReadonlyKeys<T>; | ||
required: RequiredKeys<T>; | ||
optional: OptionalKeys<T>; | ||
}; | ||
export declare type RequiredKeys<T> = Extract<{ | ||
[K in keyof T]-?: {} extends Pick<T, K> ? never : K; | ||
}[keyof T], keyof T>; | ||
export declare type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>; | ||
export declare type ReadonlyKeys<T> = Extract<{ | ||
[K in keyof T]-?: ReadonlyEquivalent<{ | ||
[_K in K]: T[K]; | ||
}, { | ||
-readonly [_K in K]: T[K]; | ||
}> extends true ? never : K; | ||
}[keyof T], keyof T>; | ||
declare type ReadonlyEquivalent<X, Y> = Extends<(<T>() => T extends X ? true : false), (<T>() => T extends Y ? true : false)>; | ||
export declare type Extends<L, R> = L extends R ? true : false; | ||
export declare type StrictExtends<L, R> = Extends<DeepBrand<L>, DeepBrand<R>>; | ||
export declare type Equal<Left, Right> = And<[ | ||
StrictExtends<Left, Right>, | ||
StrictExtends<Right, Left>, | ||
StrictExtends<keyof Left, keyof Right>, | ||
StrictExtends<keyof Right, keyof Left> | ||
]>; | ||
export declare type Equal<Left, Right> = And<[StrictExtends<Left, Right>, StrictExtends<Right, Left>]>; | ||
export declare type Params<Actual> = Actual extends (...args: infer P) => any ? P : never; | ||
@@ -29,0 +50,0 @@ export declare type ConstructorParams<Actual> = Actual extends new (...args: infer P) => any ? Actual extends new () => any ? P | [] : P : never; |
{ | ||
"name": "expect-type", | ||
"version": "0.7.10", | ||
"version": "0.7.11", | ||
"keywords": [ | ||
@@ -23,9 +23,11 @@ "typescript", | ||
"scripts": { | ||
"build": "run tsc -p .", | ||
"lint": "run eslint --cache .", | ||
"test": "run jest" | ||
"prebuild": "npm run clean", | ||
"build": "rig tsc -p .", | ||
"clean": "rig rimraf dist", | ||
"lint": "rig eslint --cache .", | ||
"test": "rig jest" | ||
}, | ||
"devDependencies": { | ||
"@mmkal/builder": "0.0.1" | ||
"@mmkal/rig": "0.0.1" | ||
} | ||
} |
@@ -282,6 +282,30 @@ # expect-type | ||
```typescript | ||
expectTypeOf<{a: number; b?: number}>().not.toEqualTypeOf<{a: number}>() | ||
expectTypeOf<{a: number; b?: number | null}>().not.toEqualTypeOf<{a: number; b?: number}>() | ||
expectTypeOf<{a: number; b?: number | null}>().toEqualTypeOf<{a: number; b?: number | null}>() | ||
expectTypeOf<{a: string}>().not.toEqualTypeOf<{a: number}>() | ||
``` | ||
Distinguish between missing/null/optional properties: | ||
```typescript | ||
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{}>() | ||
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{a: number}>() | ||
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{a: number | undefined}>() | ||
expectTypeOf<{a?: number | null}>().not.toEqualTypeOf<{a: number | null}>() | ||
expectTypeOf<{a: {b?: number}}>().not.toEqualTypeOf<{a: {}}>() | ||
``` | ||
Detect the difference between regular and readonly properties: | ||
```typescript | ||
type A1 = {readonly a: string; b: string} | ||
type E1 = {a: string; b: string} | ||
expectTypeOf<A1>().toMatchTypeOf<E1>() | ||
expectTypeOf<A1>().not.toEqualTypeOf<E1>() | ||
type A2 = {a: string; b: {readonly c: string}} | ||
type E2 = {a: string; b: {c: string}} | ||
expectTypeOf<A2>().toMatchTypeOf<E2>() | ||
expectTypeOf<A2>().not.toEqualTypeOf<E2>() | ||
``` | ||
<!-- codegen:end --> | ||
@@ -304,3 +328,3 @@ | ||
- a fluent, jest-inspired API, making the difference between `actual` and `expected` clear. This is helpful with complex types and assertions. | ||
- inverting assertions intuitively and easily via `expectType(...).not` | ||
- inverting assertions intuitively and easily via `expectTypeOf(...).not` | ||
- first-class support for: | ||
@@ -318,2 +342,2 @@ - `any` (as well as `unknown` and `never`). | ||
- built into existing tooling. No extra build step, cli tool, IDE extension, or lint plugin is needed. Just import the function and start writing tests. Failures will be at compile time - they'll appear in your IDE and when you run `tsc`. | ||
- simple implementation with no dependencies. ~100 lines of code - [take a look!](./src/index.ts) | ||
- small implementation with no dependencies. <200 lines of code - [take a look!](./src/index.ts) |
@@ -16,19 +16,55 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ | ||
/** | ||
* Recursively walk a type and replace it with a branded type related to the original. This is useful for | ||
* equality-checking stricter than `A extends B ? B extends A ? true : false : false`, because it detects | ||
* the difference between a few edge-case types that vanilla typescript doesn't by default: | ||
* - `any` vs `unknown` | ||
* - `{ readonly a: string }` vs `{ a: string }` | ||
* - `{ a?: string }` vs `{ a: string | undefined }` | ||
*/ | ||
type DeepBrand<T> = IsAny<T> extends true // avoid `any` matching `unknown` | ||
? Secret | ||
: T extends string | number | boolean | symbol | bigint | null | undefined | ||
? T | ||
: T extends (...args: infer P) => infer R // avoid functions with different params/return values matching | ||
? {params: DeepBrand<P>; return: DeepBrand<R>; function: Secret} | ||
: {[K in keyof T]: DeepBrand<T[K]>} | ||
? { | ||
type: 'function' | ||
params: DeepBrand<P> | ||
return: DeepBrand<R> | ||
} | ||
: { | ||
type: 'object' | ||
properties: {[K in keyof T]: DeepBrand<T[K]>} | ||
readonly: ReadonlyKeys<T> | ||
required: RequiredKeys<T> | ||
optional: OptionalKeys<T> | ||
} | ||
export type RequiredKeys<T> = Extract< | ||
{ | ||
[K in keyof T]-?: {} extends Pick<T, K> ? never : K | ||
}[keyof T], | ||
keyof T | ||
> | ||
export type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>> | ||
// adapted from some answers to https://github.com/type-challenges/type-challenges/issues?q=label%3A5+label%3Aanswer | ||
// prettier-ignore | ||
export type ReadonlyKeys<T> = Extract<{ | ||
[K in keyof T]-?: ReadonlyEquivalent< | ||
{ [_K in K]: T[K] }, | ||
{ -readonly [_K in K]: T[K] } | ||
> extends true ? never : K; | ||
}[keyof T], keyof T>; | ||
// prettier-ignore | ||
type ReadonlyEquivalent<X, Y> = Extends< | ||
(<T>() => T extends X ? true : false), | ||
(<T>() => T extends Y ? true : false) | ||
> | ||
export type Extends<L, R> = L extends R ? true : false | ||
export type StrictExtends<L, R> = Extends<DeepBrand<L>, DeepBrand<R>> | ||
export type Equal<Left, Right> = And< | ||
[ | ||
StrictExtends<Left, Right>, | ||
StrictExtends<Right, Left>, | ||
StrictExtends<keyof Left, keyof Right>, | ||
StrictExtends<keyof Right, keyof Left> | ||
] | ||
> | ||
export type Equal<Left, Right> = And<[StrictExtends<Left, Right>, StrictExtends<Right, Left>]> | ||
@@ -35,0 +71,0 @@ export type Params<Actual> = Actual extends (...args: infer P) => any ? P : never |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
35006
354
341