From fd718ddbfc32b31f0437e101083172de46b70190 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky <15040698+mmkal@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:34:30 -0400 Subject: [PATCH] Improve CLI error messages (#16) Fixes #17 Closes #36 (by documenting that `toMatchTypeOf` ~= `toExtend`) Closes #37 (by documenting that helper types are not part of the API surface) Closes #32 Closes #4 Closes #6 (by documenting that you _can't_ use `.not.toBeCallableWith(...)`) Closes #38 Use generics to give better error messages than "Arguments for the rest parameter 'MISMATCH' were not provided" for most `toEqualTypeOf` cases and many `toMatchTypeOf` cases. This trades off some implementation complexity for better end-user error messages. Essentially, write a special `` constraint for each overload of each method, which does some crazy conditional logic, which boil down to: - `` for `toEqualTypeOf` (when we aren't in a `.not` context) - `>` for `toMatchTypeOf` Anyone interested, have a look at the snapshot updates in `errors.test.ts.snap` to see what the real world difference is. Each of these constraints apply only when we know it's going to "fail" - i.e. `Satisfies<...>` is a fairly meaningless helper type that is used to try to show errors at the type-constraint level rather than the `...MISMATCH: MismatchArgs<...>` level which won't give good error messages. When using `.not`, the constraint just becomes `extends unknown`, and you'll have to squint as before. See also: #14 for the better long-term solution, _if_ the TypeScript team decide to merge the throw types PR. See also: #13 for a hopefully-complementary improvement to the information on hover, which will improve the cases this doesn't cover. TODO: - [x] See if the `expectTypeOf({a: 1}).toMatchTypeOf({a: 'one'})` case can also be improved. - [x] Document. The constraints are a bit different to what most users would be used to, so it's worth highlighting the best way to read error messages and clarify when they might default to "Arguments for the rest parameter 'MISMATCH' were not provided" Note: I have publish v0.17.0-1 based on this PR and will hopefully be able to use [that version in vitest](https://github.com/vitest-dev/vitest/pull/4206) as a test before merging. --- README.md | 181 ++++++++++- package.json | 4 +- pnpm-lock.yaml | 389 ++++++++--------------- src/index.ts | 242 ++++++++++++--- test/__snapshots__/errors.test.ts.snap | 410 +++++++++++-------------- test/errors.test.ts | 187 +++++++++-- test/types.test.ts | 91 ++++-- test/usage.test.ts | 88 +++++- 8 files changed, 1010 insertions(+), 582 deletions(-) diff --git a/README.md b/README.md index 66c47ca..4caf6a4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ See below for lots more examples. - [Installation and usage](#installation-and-usage) - [Documentation](#documentation) - [Features](#features) + - [Where is `.toExtend`?](#where-is-toextend) + - [Internal type helpers](#internal-type-helpers) + - [Error messages](#error-messages) + - [Concrete "expected" objects vs typeargs](#concrete-expected-objects-vs-typeargs) - [Within test frameworks](#within-test-frameworks) - [Jest & `eslint-plugin-jest`](#jest--eslint-plugin-jest) - [Similar projects](#similar-projects) @@ -62,7 +66,7 @@ Check an object's type with `.toEqualTypeOf`: expectTypeOf({a: 1}).toEqualTypeOf<{a: number}>() ``` -`.toEqualTypeOf` can check that two concrete objects have equivalent types: +`.toEqualTypeOf` can check that two concrete objects have equivalent types (note: when these assertions _fail_, the error messages can be less informative vs the generic typearg syntax above - see [error messages docs](#error-messages)): ```typescript expectTypeOf({a: 1}).toEqualTypeOf({a: 1}) @@ -81,10 +85,19 @@ expectTypeOf({a: 1}).toEqualTypeOf({a: 2}) expectTypeOf({a: 1, b: 1}).toEqualTypeOf<{a: number}>() ``` -To allow for extra properties, use `.toMatchTypeOf`. This checks that an object "matches" a type. This is similar to jest's `.toMatchObject`: +To allow for extra properties, use `.toMatchTypeOf`. This is roughly equivalent to an `extends` constraint in a function type argument.: ```typescript -expectTypeOf({a: 1, b: 1}).toMatchTypeOf({a: 1}) +expectTypeOf({a: 1, b: 1}).toMatchTypeOf<{a: number}>() +``` + +`.toEqualTypeOf` and `.toMatchTypeOf` both fail on missing properties: + +```typescript +// @ts-expect-error +expectTypeOf({a: 1}).toEqualTypeOf<{a: number; b: number}>() +// @ts-expect-error +expectTypeOf({a: 1}).toMatchTypeOf<{a: number; b: number}>() ``` Another example of the difference between `.toMatchTypeOf` and `.toEqualTypeOf`, using generics. `.toMatchTypeOf` can be used for "is-a" relationships: @@ -151,6 +164,33 @@ expectTypeOf(Promise.resolve(123)).resolves.toBeNumber() expectTypeOf(Symbol(1)).toBeSymbol() ``` +`.toBe...` methods allow for types which extend the expected type: + +```typescript +expectTypeOf().toBeNumber() +expectTypeOf<1>().toBeNumber() + +expectTypeOf().toBeArray() +expectTypeOf().toBeArray() + +expectTypeOf().toBeString() +expectTypeOf<'foo'>().toBeString() + +expectTypeOf().toBeBoolean() +expectTypeOf().toBeBoolean() +``` + +`.toBe...` methods protect against `any`: + +```typescript +const goodIntParser = (s: string) => Number.parseInt(s, 10) +const badIntParser = (s: string) => JSON.parse(s) // uh-oh - works at runtime if the input is a number, but return 'any' + +expectTypeOf(goodIntParser).returns.toBeNumber() +// @ts-expect-error - if you write a test like this, `.toBeNumber()` will let you know your implementation returns `any`. +expectTypeOf(badIntParser).returns.toBeNumber() +``` + Nullable types: ```typescript @@ -280,6 +320,15 @@ const twoArgFunc = (a: number, b: string) => ({a, b}) expectTypeOf(twoArgFunc).parameters.toEqualTypeOf<[number, string]>() ``` +You can't use `.toBeCallableWith` with `.not` - you need to use ts-expect-error:: + +```typescript +const f = (a: number) => [a, a] + +// @ts-expect-error +expectTypeOf(f).toBeCallableWith('foo') +``` + You can also check type guards & type assertions: ```typescript @@ -435,19 +484,139 @@ Known limitation: Intersection types can cause issues with `toEqualTypeOf`: expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>() ``` -To workaround, you can use a mapped type: +To workaround for simple cases, you can use a mapped type: ```typescript type Simplify = {[K in keyof T]: T[K]} expectTypeOf>().toEqualTypeOf<{a: 1; b: 2}>() ``` + +But this won't work if the nesting is deeper in the type. For these situations, you can use the `.branded` helper. Note that this comes at a performance cost, and can cause the compiler to 'give up' if used with excessively deep types, so use sparingly. This helper is under `.branded` because it depply transforms the Actual and Expected types into a pseudo-AST: + +```typescript +// @ts-expect-error +expectTypeOf<{a: {b: 1} & {c: 1}}>().toEqualTypeOf<{a: {b: 1; c: 1}}>() + +expectTypeOf<{a: {b: 1} & {c: 1}}>().branded.toEqualTypeOf<{a: {b: 1; c: 1}}>() +``` + +Be careful with `.branded` for very deep or complex types, though. If possible you should find a way to simplify your test to avoid needing to use it: + +```typescript +// This *should* result in an error, but the "branding" mechanism produces too large a type and TypeScript just gives up! https://github.com/microsoft/TypeScript/issues/50670 +expectTypeOf<() => () => () => () => 1>().branded.toEqualTypeOf<() => () => () => () => 2>() + +// @ts-expect-error the non-branded implementation catches the error as expected. +expectTypeOf<() => () => () => () => 1>().toEqualTypeOf<() => () => () => () => 2>() +``` + +So, if you have an extremely deep type which ALSO has an intersection in it, you're out of luck and this library won't be able to test your type properly: + +```typescript +// @ts-expect-error this fails, but it should succeed. +expectTypeOf<() => () => () => () => {a: 1} & {b: 2}>().toEqualTypeOf< + () => () => () => () => {a: 1; b: 2} +>() + +// this succeeds, but it should fail. +expectTypeOf<() => () => () => () => {a: 1} & {b: 2}>().branded.toEqualTypeOf< + () => () => () => () => {a: 1; c: 2} +>() +``` + +Another limitation: passing `this` references to `expectTypeOf` results in errors.: + +```typescript +class B { + b = 'b' + + foo() { + // @ts-expect-error + expectTypeOf(this).toEqualTypeOf(this) + // @ts-expect-error + expectTypeOf(this).toMatchTypeOf(this) + } +} + +// Instead of the above, try something like this: +expectTypeOf(B).instance.toEqualTypeOf<{b: string; foo: () => void}>() +``` +### Where is `.toExtend`? + +A few people have asked for a method like `toExtend` - this is essentially what `toMatchTypeOf` is. There are some cases where it doesn't _precisely_ match the `extends` operator in TypeScript, but for most practical use cases, you can think of this as the same thing. + +### Internal type helpers + +🚧 This library also exports some helper types for performing boolean operations on types, checking extension/equality in various ways, branding types, and checking for various special types like `never`, `any`, `unknown`. Use at your own risk! Nothing is stopping you using these beyond this warning: + +>All internal types that are not documented here are _not_ part of the supported API surface, and may be renamed, modified, or removed, without warning or documentation in release notes. + +For a dedicated internal type library, feel free to look at the [source code](./src/index.ts) for inspiration - or better, use a library like [type-fest](https://npmjs.com/package/type-fest). + +### Error messages + +When types don't match, `.toEqualTypeOf` and `.toMatchTypeOf` use a special helper type to produce error messages that are as actionable as possible. But there's a bit of an nuance to understanding them. Since the assertions are written "fluently", the failure should be on the "expected" type, not the "actual" type (`expect().toEqualTypeOf()`). This means that type errors can be a little confusing - so this library produces a `MismatchInfo` type to try to make explicit what the expectation is. For example: + +```ts +expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>() +``` + +Is an assertion that will fail, since `{a: 1}` has type `{a: number}` and not `{a: string}`. The error message in this case will read something like this: + +``` +test/test.ts:999:999 - error TS2344: Type '{ a: string; }' does not satisfy the constraint '{ a: \\"Expected: string, Actual: number\\"; }'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type '\\"Expected: string, Actual: number\\"'. + +999 expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>() +``` + +Note that the type constraint reported is a human-readable messaging specifying both the "expected" and "actual" types. Rather than taking the sentence `Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"` literally - just look at the property name (`'a'`) and the message: `Expected: string, Actual: number`. This will tell you what's wrong, in most cases. Extremely complex types will of course be more effort to debug, and may require some experimentation. Please [raise an issue](https://github.com/mmkal/expect-type) if the error messages are actually misleading. + +The `toBe...` methods (like `toBeString`, `toBeNumber`, `toBeVoid` etc.) fail by resolving to a non-callable type when the `Actual` type under test doesn't match up. For example, the failure for an assertion like `expectTypeOf(1).toBeString()` will look something like this: + +``` +test/test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectString' has no call signatures. + +999 expectTypeOf(1).toBeString() + ~~~~~~~~~~ +``` + +The `This expression is not callable` part isn't all that helpful - the meaningful error is the next line, `Type 'ExpectString has no call signatures`. This essentially means you passed a number but asserted it should be a string. + +If TypeScript added support for ["throw" types](https://github.com/microsoft/TypeScript/pull/40468) these error messagess could be improved. Until then they will take a certain amount of squinting. + +#### Concrete "expected" objects vs typeargs + +Error messages for an assertion like this: + +```ts +expectTypeOf({a: 1}).toEqualTypeOf({a: ''}) +``` + +Will be less helpful than for an assertion like this: + +```ts +expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>() +``` + +This is because the TypeScript compiler needs to infer the typearg for the `.toEqualTypeOf({a: ''})` style, and this library can only mark it as a failure by comparing it against a generic `Mismatch` type. So, where possible, use a typearg rather than a concrete type for `.toEqualTypeOf` and `toMatchTypeOf`. If it's much more convenient to compare two concrete types, you can use `typeof`: + +```ts +const one = valueFromFunctionOne({some: {complex: inputs}}) +const two = valueFromFunctionTwo({some: {other: inputs}}) + +expectTypeOf(one).toEqualTypeof() +``` + ### Within test frameworks #### Jest & `eslint-plugin-jest` -If you're using Jest along with `eslint-plugin-jest`, you will get warnings from the [`jest/expect-expect`](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/expect-expect.md) rule, complaining that "Test has no assertions" for tests that only use `expectTypeOf()`. +If you're using Jest along with `eslint-plugin-jest`, you may get warnings from the [`jest/expect-expect`](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/expect-expect.md) rule, complaining that "Test has no assertions" for tests that only use `expectTypeOf()`. To remove this warning, configure the ESlint rule to consider `expectTypeOf` as an assertion: @@ -495,7 +664,7 @@ The key differences in this project are: - nullable types - assertions on types "matching" rather than exact type equality, for "is-a" relationships e.g. `expectTypeOf(square).toMatchTypeOf()` - 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`. -- small implementation with no dependencies. <200 lines of code - [take a look!](./src/index.ts) (tsd, for comparison, is [2.6MB](https://bundlephobia.com/result?p=tsd@0.13.1) because it ships a patched version of typescript). +- small implementation with no dependencies. [Take a look!](./src/index.ts) (tsd, for comparison, is [2.6MB](https://bundlephobia.com/result?p=tsd@0.13.1) because it ships a patched version of typescript). ## Contributing diff --git a/package.json b/package.json index 972faf8..e555d89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expect-type", - "version": "0.16.0", + "version": "0.17.0-2", "engines": { "node": ">=12.0.0" }, @@ -40,7 +40,7 @@ "eslint": "8.23.0", "eslint-plugin-mmkal": "0.0.1-2", "jest": "28.1.3", - "np": "8.0.1", + "np": "^8.0.4", "strip-ansi": "6.0.1", "ts-jest": "28.0.8", "ts-morph": "16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 759e67f..0c724af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,8 +17,8 @@ devDependencies: specifier: 28.1.3 version: 28.1.3(@types/node@14.18.32) np: - specifier: 8.0.1 - version: 8.0.1 + specifier: ^8.0.4 + version: 8.0.4(typescript@4.8.2) strip-ansi: specifier: 6.0.1 version: 6.0.1 @@ -740,6 +740,11 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@ljharb/through@2.3.9: + resolution: {integrity: sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==} + engines: {node: '>= 0.4'} + dev: true + /@microsoft/tsdoc-config@0.16.2: resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} dependencies: @@ -786,8 +791,8 @@ packages: graceful-fs: 4.2.10 dev: true - /@pnpm/npm-conf@2.2.0: - resolution: {integrity: sha512-roLI1ul/GwzwcfcVpZYPdrgW2W/drLriObl1h+yLF5syc8/5ULWw2ALbCHUWF+4YltIqA3xFSbG4IwyJz37e9g==} + /@pnpm/npm-conf@2.2.2: + resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} engines: {node: '>=12'} dependencies: '@pnpm/config.env-replace': 1.1.0 @@ -931,8 +936,8 @@ packages: engines: {node: '>=10'} dev: true - /@sindresorhus/is@5.3.0: - resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} + /@sindresorhus/is@5.6.0: + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} dev: true @@ -1003,10 +1008,10 @@ packages: /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: - '@types/http-cache-semantics': 4.0.1 + '@types/http-cache-semantics': 4.0.2 '@types/keyv': 3.1.4 '@types/node': 14.18.32 - '@types/responselike': 1.0.0 + '@types/responselike': 1.0.1 dev: true /@types/eslint@8.4.7: @@ -1026,8 +1031,8 @@ packages: '@types/node': 18.11.3 dev: true - /@types/http-cache-semantics@4.0.1: - resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + /@types/http-cache-semantics@4.0.2: + resolution: {integrity: sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==} dev: true /@types/istanbul-lib-coverage@2.0.4: @@ -1067,10 +1072,6 @@ packages: '@types/node': 14.18.32 dev: true - /@types/minimist@1.2.2: - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - dev: true - /@types/node@14.18.32: resolution: {integrity: sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==} dev: true @@ -1087,8 +1088,8 @@ packages: resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} dev: true - /@types/responselike@1.0.0: - resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + /@types/responselike@1.0.1: + resolution: {integrity: sha512-TiGnitEDxj2X0j+98Eqk5lv/Cij8oHd32bU4D/Yw6AOq7vvTk0gSD2GPj0G/HkvhMoVsdlhYF4yqqlyPBTM6Sg==} dependencies: '@types/node': 14.18.32 dev: true @@ -1488,17 +1489,15 @@ packages: uri-js: 4.4.1 dev: true - /all-package-names@2.0.661: - resolution: {integrity: sha512-IaRYCsl8hFNTDenxsZkkl5B1EGaDlBi+5JsqvQIt7uGJdQLeVYInqpXXGnTUv7y8xbOb38M5uKE22Z7oFCsu2A==} + /all-package-names@2.0.747: + resolution: {integrity: sha512-m/5vISwZ6uByh84cYisdL3XSkDgdawT3BXJRNtjCYUeLdDl2LaG8J2SMsR0WIOJjMCXrXWiWYdzJibKtO/zcHw==} hasBin: true dependencies: commander-version: 1.1.0 p-lock: 2.1.0 parse-json-object: 2.0.1 progress: 2.0.3 - read-json-safe: 2.0.2 types-json: 1.2.2 - write-json-safe: 2.1.0 dev: true /ansi-align@3.0.1: @@ -1657,11 +1656,6 @@ packages: es-shim-unscopables: 1.0.0 dev: true - /arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true - /ast-types-flow@0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true @@ -1768,13 +1762,13 @@ packages: readable-stream: 3.6.2 dev: true - /boxen@7.1.0: - resolution: {integrity: sha512-ScG8CDo8dj7McqCZ5hz4dIBp20xj4unQ2lXIDa7ff6RcZElCpuNzutdwzKVvRikfNjm7CFAlR3HJHcoHkDOExQ==} + /boxen@7.1.1: + resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.2.0 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -1870,27 +1864,27 @@ packages: engines: {node: '>=14.16'} dev: true - /cacheable-request@10.2.10: - resolution: {integrity: sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==} + /cacheable-request@10.2.13: + resolution: {integrity: sha512-3SD4rrMu1msNGEtNSt8Od6enwdo//U9s4ykmXfA2TD58kcLkCobtCDiby7kNyj7a/Q7lz/mAesAFI54rTdnvBA==} engines: {node: '>=14.16'} dependencies: - '@types/http-cache-semantics': 4.0.1 + '@types/http-cache-semantics': 4.0.2 get-stream: 6.0.1 http-cache-semantics: 4.1.1 - keyv: 4.5.2 + keyv: 4.5.3 mimic-response: 4.0.0 normalize-url: 8.0.0 responselike: 3.0.0 dev: true - /cacheable-request@7.0.2: - resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} + /cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} engines: {node: '>=8'} dependencies: clone-response: 1.0.3 get-stream: 5.2.0 http-cache-semantics: 4.1.1 - keyv: 4.5.2 + keyv: 4.5.3 lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 @@ -1908,21 +1902,11 @@ packages: engines: {node: '>=6'} dev: true - /callsites@4.0.0: - resolution: {integrity: sha512-y3jRROutgpKdz5vzEhWM34TidDU8vkJppF8dszITeb1PQmSqV3DTxyV8G/lyO/DNvtE1YTedehmw9MPZsCBHxQ==} + /callsites@4.1.0: + resolution: {integrity: sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==} engines: {node: '>=12.20'} dev: true - /camelcase-keys@8.0.2: - resolution: {integrity: sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA==} - engines: {node: '>=14.16'} - dependencies: - camelcase: 7.0.1 - map-obj: 4.3.0 - quick-lru: 6.1.1 - type-fest: 2.19.0 - dev: true - /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -1970,8 +1954,8 @@ packages: supports-color: 7.2.0 dev: true - /chalk@5.2.0: - resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true @@ -1980,10 +1964,6 @@ packages: engines: {node: '>=10'} dev: true - /char-to-string@1.0.0: - resolution: {integrity: sha512-9WeQ+l5COfnFkuDmTJ5zXzMhb4xpMPDu24pD50cMvWT+Br9Sni1hlpvdPK/YpZl1+QI15f4pMWfyEzyyIHJr2Q==} - dev: true - /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true @@ -2029,8 +2009,8 @@ packages: restore-cursor: 3.1.0 dev: true - /cli-spinners@2.9.0: - resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} + /cli-spinners@2.9.1: + resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==} engines: {node: '>=6'} dev: true @@ -2051,8 +2031,8 @@ packages: engines: {node: '>= 10'} dev: true - /cli-width@4.0.0: - resolution: {integrity: sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==} + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} dev: true @@ -2162,14 +2142,20 @@ packages: requiresBuild: true dev: true - /cosmiconfig@8.1.3: - resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==} + /cosmiconfig@8.3.6(typescript@4.8.2): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 + typescript: 4.8.2 dev: true /cross-spawn@7.0.3: @@ -2230,21 +2216,6 @@ packages: ms: 2.1.2 dev: true - /decamelize-keys@2.0.1: - resolution: {integrity: sha512-nrNeSCtU2gV3Apcmn/EZ+aR20zKDuNDStV67jPiupokD3sOAFeMzslLMCFdKv1sPqzwoe5ZUhsSW9IAVgKSL/Q==} - engines: {node: '>=14.16'} - dependencies: - decamelize: 6.0.0 - map-obj: 4.3.0 - quick-lru: 6.1.1 - type-fest: 3.11.0 - dev: true - - /decamelize@6.0.0: - resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -2289,7 +2260,7 @@ packages: dependencies: bundle-name: 3.0.0 default-browser-id: 3.0.0 - execa: 7.1.1 + execa: 7.2.0 titleize: 3.0.0 dev: true @@ -2317,11 +2288,11 @@ packages: object-keys: 1.1.1 dev: true - /del@7.0.0: - resolution: {integrity: sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==} + /del@7.1.0: + resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} engines: {node: '>=14.16'} dependencies: - globby: 13.1.4 + globby: 13.2.2 graceful-fs: 4.2.10 is-glob: 4.0.3 is-path-cwd: 3.0.0 @@ -2418,12 +2389,6 @@ packages: once: 1.4.0 dev: true - /ends-with-string@1.0.1: - resolution: {integrity: sha512-TQg80EKJsnNfH9H/5HpAXKJP7YMYFkoUeS9ayMY60zwNoiNecv8D0+a9cycqdM8rhj/hfBqJb+9uLMsYP8d/Bw==} - dependencies: - char-to-string: 1.0.0 - dev: true - /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -3002,8 +2967,8 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa@7.1.1: - resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} + /execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: cross-spawn: 7.0.3 @@ -3089,6 +3054,17 @@ packages: micromatch: 4.0.5 dev: true + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true @@ -3146,10 +3122,6 @@ packages: flat-cache: 3.0.4 dev: true - /file-exists-safe@1.1.0: - resolution: {integrity: sha512-F0gcLPWcrLnYxlU46sf0UdgDsVaYCDyVVVsT1fpfUJ726R+GmS220+6LCPT1xbU8r6lCrwlG1gnKLF7slnObQw==} - dev: true - /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -3339,13 +3311,13 @@ packages: slash: 3.0.0 dev: true - /globby@13.1.4: - resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==} + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.0 + fast-glob: 3.3.1 + ignore: 5.2.4 merge2: 1.4.1 slash: 4.0.0 dev: true @@ -3357,9 +3329,9 @@ packages: '@sindresorhus/is': 4.6.0 '@szmarczak/http-timer': 4.0.6 '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.0 + '@types/responselike': 1.0.1 cacheable-lookup: 5.0.4 - cacheable-request: 7.0.2 + cacheable-request: 7.0.4 decompress-response: 6.0.0 http2-wrapper: 1.0.3 lowercase-keys: 2.0.0 @@ -3371,10 +3343,10 @@ packages: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} dependencies: - '@sindresorhus/is': 5.3.0 + '@sindresorhus/is': 5.6.0 '@szmarczak/http-timer': 5.0.1 cacheable-lookup: 7.0.0 - cacheable-request: 10.2.10 + cacheable-request: 10.2.13 decompress-response: 6.0.0 form-data-encoder: 2.1.4 get-stream: 6.0.1 @@ -3392,11 +3364,6 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true - /has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -3515,7 +3482,7 @@ packages: resolution: {integrity: sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - minimatch: 9.0.1 + minimatch: 9.0.3 dev: true /ignore@5.2.0: @@ -3523,6 +3490,11 @@ packages: engines: {node: '>= 4'} dev: true + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -3631,14 +3603,15 @@ packages: through: 2.3.8 dev: true - /inquirer@9.2.6: - resolution: {integrity: sha512-y71l237eJJKS4rl7sQcEUiMhrR0pB/ZnRMMTxLpjJhWL4hdWCT03a6jJnC1w6qIPSRZWEozuieGt3v7XaEJYFw==} + /inquirer@9.2.11: + resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==} engines: {node: '>=14.18.0'} dependencies: + '@ljharb/through': 2.3.9 ansi-escapes: 4.3.2 - chalk: 5.2.0 + chalk: 5.3.0 cli-cursor: 3.1.0 - cli-width: 4.0.0 + cli-width: 4.1.0 external-editor: 3.1.0 figures: 5.0.0 lodash: 4.17.21 @@ -3648,7 +3621,6 @@ packages: rxjs: 7.8.1 string-width: 4.2.3 strip-ansi: 6.0.1 - through: 2.3.8 wrap-ansi: 6.2.0 dev: true @@ -3801,7 +3773,7 @@ packages: /is-name-taken@2.0.0: resolution: {integrity: sha512-W+FUWF5g7ONVJTx3rldZeVizmPzrMMUdscpSQ96vyYerx+4b2NcqaujLJJDWruGzE0FjzGZO9RFIipOGxx/WIw==} dependencies: - all-package-names: 2.0.661 + all-package-names: 2.0.747 package-name-conflict: 1.0.3 validate-npm-package-name: 3.0.0 dev: true @@ -3855,11 +3827,6 @@ packages: engines: {node: '>=12'} dev: true - /is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - dev: true - /is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} dev: true @@ -4614,17 +4581,12 @@ packages: object.assign: 4.1.4 dev: true - /keyv@4.5.2: - resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} + /keyv@4.5.3: + resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} dependencies: json-buffer: 3.0.1 dev: true - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true - /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -4644,7 +4606,7 @@ packages: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} dependencies: - package-json: 8.1.0 + package-json: 8.1.1 dev: true /leven@3.1.0: @@ -4784,7 +4746,7 @@ packages: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 is-unicode-supported: 1.3.0 dev: true @@ -4843,27 +4805,9 @@ packages: tmpl: 1.0.5 dev: true - /map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: true - - /meow@12.0.1: - resolution: {integrity: sha512-/QOqMALNoKQcJAOOdIXjNLtfcCdLXbMFyB1fOOPdm6RzfBTlsuodOCTBDjVbeUSmgDQb8UI2oONqYGtq1PKKKA==} + /meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 8.0.2 - decamelize: 6.0.0 - decamelize-keys: 2.0.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 5.0.0 - read-pkg-up: 9.1.0 - redent: 4.0.0 - trim-newlines: 5.0.0 - type-fest: 3.11.0 - yargs-parser: 21.1.1 dev: true /merge-stream@2.0.0: @@ -4931,22 +4875,13 @@ packages: brace-expansion: 2.0.1 dev: true - /minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 dev: true - /minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true - /minimist@1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} dev: true @@ -5016,17 +4951,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.11.0 - semver: 7.5.1 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-package-data@5.0.0: - resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dependencies: - hosted-git-info: 6.1.1 - is-core-module: 2.11.0 - semver: 7.5.1 + semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -5045,24 +4970,24 @@ packages: engines: {node: '>=14.16'} dev: true - /np@8.0.1: - resolution: {integrity: sha512-5Vd3p5LlymIUhQjKIVhcf/Zh1/Mpeuo4agQ53LWXokPnGlWH3SAjZx5jmy56B904uwnKXyM/220GJae5p7/NhA==} + /np@8.0.4(typescript@4.8.2): + resolution: {integrity: sha512-a4s1yESHcIwsrk/oaTekfbhb1R/2z2yyfVLX6Atl54w/9+QR01qeYyK3vMWgJ0UY+kYsGzQXausgvUX0pkmIMg==} engines: {git: '>=2.11.0', node: '>=16.6.0', npm: '>=7.19.0', yarn: '>=1.7.0'} hasBin: true dependencies: - chalk: 5.2.0 - cosmiconfig: 8.1.3 - del: 7.0.0 + chalk: 5.3.0 + cosmiconfig: 8.3.6(typescript@4.8.2) + del: 7.1.0 escape-goat: 4.0.0 escape-string-regexp: 5.0.0 - execa: 7.1.1 + execa: 7.2.0 exit-hook: 3.2.0 github-url-from-git: 1.5.0 has-yarn: 3.0.0 hosted-git-info: 6.1.1 ignore-walk: 6.0.3 import-local: 3.1.0 - inquirer: 9.2.6 + inquirer: 9.2.11 is-installed-globally: 0.4.0 is-interactive: 2.0.0 is-scoped: 3.0.0 @@ -5070,23 +4995,24 @@ packages: listr: 0.14.3 listr-input: 0.2.1 log-symbols: 5.1.0 - meow: 12.0.1 + meow: 12.1.1 new-github-release-url: 2.0.0 npm-name: 7.1.0 onetime: 6.0.0 open: 9.1.0 ow: 1.1.1 p-memoize: 7.1.1 - p-timeout: 6.1.1 + p-timeout: 6.1.2 path-exists: 5.0.0 pkg-dir: 7.0.0 read-pkg-up: 9.1.0 rxjs: 7.8.1 - semver: 7.5.1 + semver: 7.5.4 symbol-observable: 4.0.0 terminal-link: 3.0.0 update-notifier: 6.0.2 transitivePeerDependencies: + - typescript - zen-observable - zenObservable dev: true @@ -5240,7 +5166,7 @@ packages: bl: 4.1.0 chalk: 4.1.2 cli-cursor: 3.1.0 - cli-spinners: 2.9.0 + cli-spinners: 2.9.1 is-interactive: 1.0.0 is-unicode-supported: 0.1.0 log-symbols: 4.1.0 @@ -5262,8 +5188,8 @@ packages: resolution: {integrity: sha512-sJBRCbS5vh1Jp9EOgwp1Ws3c16lJrUkJYlvWTYC03oyiYVwS/ns7lKRWow4w4XjDyTrA2pplQv4B2naWSR6yDA==} engines: {node: '>=14.16'} dependencies: - '@sindresorhus/is': 5.3.0 - callsites: 4.0.0 + '@sindresorhus/is': 5.6.0 + callsites: 4.1.0 dot-prop: 7.2.0 lodash.isequal: 4.5.0 vali-date: 1.0.0 @@ -5342,11 +5268,11 @@ packages: engines: {node: '>=14.16'} dependencies: mimic-fn: 4.0.0 - type-fest: 3.11.0 + type-fest: 3.13.1 dev: true - /p-timeout@6.1.1: - resolution: {integrity: sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w==} + /p-timeout@6.1.2: + resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} engines: {node: '>=14.16'} dev: true @@ -5355,14 +5281,14 @@ packages: engines: {node: '>=6'} dev: true - /package-json@8.1.0: - resolution: {integrity: sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==} + /package-json@8.1.1: + resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} dependencies: got: 12.6.1 registry-auth-token: 5.0.2 registry-url: 6.0.1 - semver: 7.5.1 + semver: 7.5.4 dev: true /package-name-conflict@1.0.3: @@ -5569,11 +5495,6 @@ packages: engines: {node: '>=10'} dev: true - /quick-lru@6.1.1: - resolution: {integrity: sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==} - engines: {node: '>=12'} - dev: true - /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -5607,13 +5528,6 @@ packages: read-file-safe: 1.0.10 dev: true - /read-json-safe@2.0.2: - resolution: {integrity: sha512-8YIzUwt++g+w5t495q+AIYSOMHgVs3elDf+WJC0XEBs+Zo/1l8vNn2Ha1evKdYY3J6DhjiOZdVyBqkrFIaD20Q==} - dependencies: - parse-json-object: 2.0.1 - read-file-safe: 1.0.10 - dev: true - /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -5661,14 +5575,6 @@ packages: util-deprecate: 1.0.2 dev: true - /redent@4.0.0: - resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} - engines: {node: '>=12'} - dependencies: - indent-string: 5.0.0 - strip-indent: 4.0.0 - dev: true - /regenerator-runtime@0.13.10: resolution: {integrity: sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==} dev: true @@ -5703,7 +5609,7 @@ packages: resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} engines: {node: '>=14'} dependencies: - '@pnpm/npm-conf': 2.2.0 + '@pnpm/npm-conf': 2.2.2 dev: true /registry-url@6.0.1: @@ -5713,10 +5619,6 @@ packages: rc: 1.2.8 dev: true - /remove-file-safe@1.0.1: - resolution: {integrity: sha512-YzBF0IfrPG9PMGCZsQjMNeIyi9+PPYLDHTcU+bDhgZhfaO6WFpA75+kB5eEkg9C3Puomo6Y50Ll8sCem2TiBdA==} - dev: true - /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5847,7 +5749,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.5.2 + tslib: 2.6.2 dev: true /safe-buffer@5.2.1: @@ -5881,7 +5783,7 @@ packages: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} dependencies: - semver: 7.5.1 + semver: 7.5.4 dev: true /semver@5.7.1: @@ -5902,8 +5804,8 @@ packages: lru-cache: 6.0.0 dev: true - /semver@7.5.1: - resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: @@ -6081,12 +5983,6 @@ packages: safe-buffer: 5.2.1 dev: true - /stringify-json-object@1.0.7: - resolution: {integrity: sha512-swTYj+6RL1e4a6whybHPSw+/7X54fyEKbK/fK6uL81PMHohHawfpZDwtFU49vw/Mey6j19m9emN1YA2VE4aCog==} - dependencies: - types-json: 1.2.2 - dev: true - /strip-ansi@3.0.1: resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} engines: {node: '>=0.10.0'} @@ -6149,13 +6045,6 @@ packages: min-indent: 1.0.1 dev: true - /strip-indent@4.0.0: - resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} - engines: {node: '>=12'} - dependencies: - min-indent: 1.0.1 - dev: true - /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -6231,13 +6120,6 @@ packages: supports-hyperlinks: 2.3.0 dev: true - /terminating-newline@1.2.4: - resolution: {integrity: sha512-zJ7ECSI+Ackgbck4h0n8tUg5Q/3vRpVV3T7ITb9w1NY6ezrA0oY0LTDEu1m3hstev7Ok4ZssS+UkL/PIXMz9vQ==} - dependencies: - detect-newline: 3.1.0 - ends-with-string: 1.0.1 - dev: true - /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -6283,11 +6165,6 @@ packages: is-number: 7.0.0 dev: true - /trim-newlines@5.0.0: - resolution: {integrity: sha512-kstfs+hgwmdsOadN3KgA+C68wPJwnZq4DN6WMDCvZapDWEF34W2TyPKN2v2+BJnZgIz5QOfxFeldLyYvdgRAwg==} - engines: {node: '>=14.16'} - dev: true - /ts-jest@28.0.8(@babel/core@7.19.6)(jest@28.1.3)(typescript@4.8.2): resolution: {integrity: sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -6342,8 +6219,8 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib@2.5.2: - resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: true /tsutils@3.21.0(typescript@4.8.2): @@ -6398,8 +6275,8 @@ packages: engines: {node: '>=12.20'} dev: true - /type-fest@3.11.0: - resolution: {integrity: sha512-JaPw5U9ixP0XcpUbQoVSbxSDcK/K4nww20C3kjm9yE6cDRRhptU28AH60VWf9ltXmCrIfIbtt9J+2OUk2Uqiaw==} + /type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} dev: true @@ -6468,8 +6345,8 @@ packages: resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} engines: {node: '>=14.16'} dependencies: - boxen: 7.1.0 - chalk: 5.2.0 + boxen: 7.1.1 + chalk: 5.3.0 configstore: 6.0.0 has-yarn: 3.0.0 import-lazy: 4.0.0 @@ -6479,7 +6356,7 @@ packages: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.1.0 - semver: 7.5.1 + semver: 7.5.4 semver-diff: 4.0.0 xdg-basedir: 5.1.0 dev: true @@ -6602,10 +6479,6 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /write-dir-safe@1.0.1: - resolution: {integrity: sha512-wnY2yzWbfawmHs6sbAOsqLf+yEPS3I+9/kRrL9zAESb56KQa7gVG8ofxtZcdIAqsb47lHPXqEMOhzXoFJkE9Pw==} - dev: true - /write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} dependencies: @@ -6623,22 +6496,6 @@ packages: signal-exit: 3.0.7 dev: true - /write-file-safe@1.3.1: - resolution: {integrity: sha512-9WC/i6b/WDNEtFFftvcnOwBD9QHmofAcURq1KsSxGsAHHTrQNbuwXApE11UcLuX0d49SU7W6CaV9Nqgaive8tw==} - dependencies: - file-exists-safe: 1.1.0 - remove-file-safe: 1.0.1 - terminating-newline: 1.2.4 - write-dir-safe: 1.0.1 - dev: true - - /write-json-safe@2.1.0: - resolution: {integrity: sha512-YORxvFx9nGlVcyhIHhUoApjeaf3Dom5KmnfQVOvDclfiS6yYhXhFXEmycksyhDlzzjKaLIwRUZDYyTg0jYlaRQ==} - dependencies: - stringify-json-object: 1.0.7 - write-file-safe: 1.3.1 - dev: true - /xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} diff --git a/src/index.ts b/src/index.ts index ded8020..88eb32d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,46 @@ export type IsAny = [T] extends [Secret] ? Not> : false export type IsUnknown = [unknown] extends [T] ? Not> : false export type IsNeverOrAny = Or<[IsNever, IsAny]> +export type PrintType = IsUnknown extends true + ? 'unknown' + : IsNever extends true + ? 'never' + : IsAny extends true + ? never // special case, can't use `'any'` because that would match `any` + : boolean extends T + ? 'boolean' + : T extends boolean + ? `literal boolean: ${T}` + : string extends T + ? 'string' + : T extends string + ? `literal string: ${T}` + : number extends T + ? 'number' + : T extends number + ? `literal number: ${T}` + : T extends null + ? 'null' + : T extends undefined + ? 'undefined' + : T extends (...args: any[]) => any + ? 'function' + : '...' + +// Helper for showing end-user a hint why their type assertion is failing. +// This swaps "leaf" types with a literal message about what the actual and expected types are. +// Needs to check for Not> because otherwise LeafTypeOf returns never, which extends everything 🤔 +export type MismatchInfo = And<[Extends, '...'>, Not>]> extends true + ? { + [K in keyof Actual | keyof Expected]: MismatchInfo< + K extends keyof Actual ? Actual[K] : never, + K extends keyof Expected ? Expected[K] : never + > + } + : StrictEqualUsingBranding extends true + ? Actual + : `Expected: ${PrintType}, Actual: ${PrintType>}` + /** * 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 @@ -19,6 +59,9 @@ export type IsNeverOrAny = Or<[IsNever, IsAny]> * - `any` vs `unknown` * - `{ readonly a: string }` vs `{ a: string }` * - `{ a?: string }` vs `{ a: string | undefined }` + * + * Note: not very performant for complex types - this should only be used when you know you need it. If doing + * an equality check, it's almost always better to use `StrictEqualUsingTSInternalIdenticalToOperator`. */ export type DeepBrand = IsNever extends true ? {type: 'never'} @@ -43,6 +86,7 @@ export type DeepBrand = IsNever extends true params: DeepBrand

return: DeepBrand this: DeepBrand> + props: DeepBrand> } : T extends any[] ? { @@ -81,20 +125,30 @@ type ReadonlyEquivalent = Extends< (() => T extends Y ? true : false) > +/** Returns true if `L extends R`. Explicitly checks for `never` since that can give unexpected results. */ export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false -export type StrictExtends = Extends, DeepBrand> +export type ExtendsUsingBranding = Extends, DeepBrand> +export type ExtendsExcludingAnyOrNever = IsAny extends true ? IsAny : Extends -type StrictEqual = (() => T extends (L & T) | T ? true : false) extends () => T extends (R & T) | T - ? true - : false +// much history: https://github.com/microsoft/TypeScript/issues/55188#issuecomment-1656328122 +type StrictEqualUsingTSInternalIdenticalToOperator = (() => T extends (L & T) | T ? true : false) extends < + T, +>() => T extends (R & T) | T ? true : false ? IsNever extends IsNever ? true : false : false -export type Equal = Branded extends true - ? And<[StrictExtends, StrictExtends]> - : StrictEqual +export type StrictEqualUsingBranding = And< + [ExtendsUsingBranding, ExtendsUsingBranding] +> + +export type HopefullyPerformantEqual = StrictEqualUsingTSInternalIdenticalToOperator< + Left, + Right +> extends true + ? true + : StrictEqualUsingBranding export type Params = Actual extends (...args: infer P) => any ? P : never export type ConstructorParams = Actual extends new (...args: infer P) => any @@ -103,51 +157,165 @@ export type ConstructorParams = Actual extends new (...args: infer P) => : P : never +const mismatch = Symbol('mismatch') +type Mismatch = {[mismatch]: 'mismatch'} +/** A type which should match anything passed as a value but *doesn't* match `Mismatch` - helps TypeScript select the right overload for `toEqualTypeOf` and `toMatchTypeOf`. */ +const avalue = Symbol('avalue') +type AValue = {[avalue]?: undefined} | string | number | boolean | symbol | bigint | null | undefined | void type MismatchArgs = Eq< ActualResult, ExpectedResult > extends true ? [] - : [never] + : [Mismatch] -export interface ExpectTypeOfOptions { + export interface ExpectTypeOfOptions { positive: boolean branded: boolean } -export interface ExpectTypeOf { - toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeUnknown: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeNever: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeFunction: (...MISMATCH: MismatchArgs any>, Options['positive']>) => true - toBeObject: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeArray: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeNumber: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeBoolean: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeVoid: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeSymbol: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeNull: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeUndefined: (...MISMATCH: MismatchArgs, Options['positive']>) => true - toBeNullable: ( - ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> - ) => true + +const inverted = Symbol('inverted') +type Inverted = {[inverted]: T} + +const expectNull = Symbol('expectNull') +type ExpectNull = {[expectNull]: T; result: ExtendsExcludingAnyOrNever} +const expectUndefined = Symbol('expectUndefined') +type ExpectUndefined = {[expectUndefined]: T; result: ExtendsExcludingAnyOrNever} +const expectNumber = Symbol('expectNumber') +type ExpectNumber = {[expectNumber]: T; result: ExtendsExcludingAnyOrNever} +const expectString = Symbol('expectString') +type ExpectString = {[expectString]: T; result: ExtendsExcludingAnyOrNever} +const expectBoolean = Symbol('expectBoolean') +type ExpectBoolean = {[expectBoolean]: T; result: ExtendsExcludingAnyOrNever} +const expectVoid = Symbol('expectVoid') +type ExpectVoid = {[expectVoid]: T; result: ExtendsExcludingAnyOrNever} + +const expectFunction = Symbol('expectFunction') +type ExpectFunction = {[expectFunction]: T; result: ExtendsExcludingAnyOrNever any>} +const expectObject = Symbol('expectObject') +type ExpectObject = {[expectObject]: T; result: ExtendsExcludingAnyOrNever} +const expectArray = Symbol('expectArray') +type ExpectArray = {[expectArray]: T; result: ExtendsExcludingAnyOrNever} +const expectSymbol = Symbol('expectSymbol') +type ExpectSymbol = {[expectSymbol]: T; result: ExtendsExcludingAnyOrNever} + +const expectAny = Symbol('expectAny') +type ExpectAny = {[expectAny]: T; result: IsAny} +const expectUnknown = Symbol('expectUnknown') +type ExpectUnknown = {[expectUnknown]: T; result: IsUnknown} +const expectNever = Symbol('expectNever') +type ExpectNever = {[expectNever]: T; result: IsNever} + +const expectNullable = Symbol('expectNullable') +type ExpectNullable = {[expectNullable]: T; result: Not>>} + +type Scolder< + Expecter extends {result: boolean}, + Options extends {positive: boolean}, +> = Expecter['result'] extends Options['positive'] + ? () => true + : Options['positive'] extends true + ? Expecter + : Inverted + +export interface PositiveExpectTypeOf extends BaseExpectTypeOf { + toEqualTypeOf: { + < + Expected extends StrictEqualUsingTSInternalIdenticalToOperator extends true + ? unknown + : MismatchInfo, + >( + value: Expected & AValue, // reason for `& AValue`: make sure this is only the selected overload when the end-user passes a value for an inferred typearg. The `Mismatch` type does match `AValue`. + ...MISMATCH: MismatchArgs, true> + ): true + < + Expected extends StrictEqualUsingTSInternalIdenticalToOperator extends true + ? unknown + : MismatchInfo, + >( + ...MISMATCH: MismatchArgs, true> + ): true + } + toMatchTypeOf: { - (...MISMATCH: MismatchArgs, Options['positive']>): true - (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true + extends true ? unknown : MismatchInfo>( + value: Expected & AValue, // reason for `& AValue`: make sure this is only the selected overload when the end-user passes a value for an inferred typearg. The `Mismatch` type does match `AValue`. + ...MISMATCH: MismatchArgs, true> + ): true + extends true ? unknown : MismatchInfo>( + ...MISMATCH: MismatchArgs, true> + ): true + } + + toHaveProperty: ( + key: K, + ...MISMATCH: MismatchArgs, true> + ) => K extends keyof Actual ? PositiveExpectTypeOf : true + + not: NegativeExpectTypeOf + + branded: { + toEqualTypeOf: < + Expected extends StrictEqualUsingBranding extends true + ? unknown + : MismatchInfo, + >( + ...MISMATCH: MismatchArgs, true> + ) => true } +} + +export interface NegativeExpectTypeOf extends BaseExpectTypeOf { toEqualTypeOf: { - (...MISMATCH: MismatchArgs, Options['positive']>): true ( - expected: Expected, - ...MISMATCH: MismatchArgs, Options['positive']> + value: Expected & AValue, + ...MISMATCH: MismatchArgs, false> + ): true + (...MISMATCH: MismatchArgs, false>): true + } + + toMatchTypeOf: { + ( + value: Expected & AValue, // reason for `& AValue`: make sure this is only the selected overload when the end-user passes a value for an inferred typearg. The `Mismatch` type does match `AValue`. + ...MISMATCH: MismatchArgs, false> ): true + (...MISMATCH: MismatchArgs, false>): true + } + + toHaveProperty: ( + key: K, + ...MISMATCH: MismatchArgs, false> + ) => true + + branded: { + toEqualTypeOf: ( + ...MISMATCH: MismatchArgs, false> + ) => true } +} + +export type ExpectTypeOf = Options['positive'] extends true + ? PositiveExpectTypeOf + : NegativeExpectTypeOf + +export interface BaseExpectTypeOf { + toBeAny: Scolder, Options> + toBeUnknown: Scolder, Options> + toBeNever: Scolder, Options> + toBeFunction: Scolder, Options> + toBeObject: Scolder, Options> + toBeArray: Scolder, Options> + toBeNumber: Scolder, Options> + toBeString: Scolder, Options> + toBeBoolean: Scolder, Options> + toBeVoid: Scolder, Options> + toBeSymbol: Scolder, Options> + toBeNull: Scolder, Options> + toBeUndefined: Scolder, Options> + toBeNullable: Scolder, Options> + toBeCallableWith: Options['positive'] extends true ? (...args: Params) => true : never toBeConstructibleWith: Options['positive'] extends true ? (...args: ConstructorParams) => true : never - toHaveProperty: ( - key: K, - ...MISMATCH: MismatchArgs, Options['positive']> - ) => K extends keyof Actual ? ExpectTypeOf : true extract: (v?: V) => ExpectTypeOf, Options> exclude: (v?: V) => ExpectTypeOf, Options> parameter: >(number: K) => ExpectTypeOf[K], Options> @@ -167,8 +335,6 @@ export interface ExpectTypeOf { ? never : ExpectTypeOf : never - branded: Omit, 'branded'> - not: Omit; branded: Options['branded']}>, 'not'> } const fn: any = () => true @@ -215,7 +381,7 @@ export const expectTypeOf: _ExpectTypeOf = ( 'asserts', 'branded', ] as const - type Keys = keyof ExpectTypeOf + type Keys = keyof PositiveExpectTypeOf | keyof NegativeExpectTypeOf type FunctionsDict = Record, any> const obj: FunctionsDict = { diff --git a/test/__snapshots__/errors.test.ts.snap b/test/__snapshots__/errors.test.ts.snap index bc3087a..9207b7a 100644 --- a/test/__snapshots__/errors.test.ts.snap +++ b/test/__snapshots__/errors.test.ts.snap @@ -1,301 +1,206 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`usage test 1`] = ` -"test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. +exports[`usage.test.ts 1`] = ` +"test/usage.test.ts:999:999 - error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: number; b: \\"Expected: never, Actual: number\\"; }'. + Property 'b' is missing in type '{ a: number; }' but required in type '{ a: number; b: \\"Expected: never, Actual: number\\"; }'. 999 expectTypeOf({a: 1, b: 1}).toEqualTypeOf<{a: number}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ a: number; b: number; }' does not satisfy the constraint '{ a: number; b: \\"Expected: number, Actual: never\\"; }'. + Types of property 'b' are incompatible. + Type 'number' is not assignable to type '\\"Expected: number, Actual: never\\"'. + +999 expectTypeOf({a: 1}).toEqualTypeOf<{a: number; b: number}>() + ~~~~~~~~~~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ a: number; b: number; }' does not satisfy the constraint '{ a: number; b: \\"Expected: number, Actual: never\\"; }'. + Types of property 'b' are incompatible. + Type 'number' is not assignable to type '\\"Expected: number, Actual: never\\"'. + +999 expectTypeOf({a: 1}).toMatchTypeOf<{a: number; b: number}>() + ~~~~~~~~~~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'Apple' does not satisfy the constraint '{ name: \\"Expected: literal string: Apple, Actual: never\\"; type: \\"Fruit\\"; edible: \\"Expected: literal boolean: true, Actual: literal boolean: false\\"; }'. + Types of property 'name' are incompatible. + Type '\\"Apple\\"' is not assignable to type '\\"Expected: literal string: Apple, Actual: never\\"'. 999 expectTypeOf().toMatchTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'Fruit' does not satisfy the constraint '{ name: \\"Expected: never, Actual: literal string: Apple\\"; type: \\"Fruit\\"; edible: \\"Expected: boolean, Actual: never\\"; }'. + Property 'name' is missing in type 'Fruit' but required in type '{ name: \\"Expected: never, Actual: literal string: Apple\\"; type: \\"Fruit\\"; edible: \\"Expected: boolean, Actual: never\\"; }'. 999 expectTypeOf().toEqualTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 2 arguments, but got 1. + ~~~~~ +test/usage.test.ts:999:999 - error TS2554: Expected 0 arguments, but got 1. 999 expectTypeOf({a: 1}).toMatchTypeOf({b: 1}) - ~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'Apple' does not satisfy the constraint '{ name: \\"Expected: literal string: Apple, Actual: never\\"; type: \\"Fruit\\"; edible: \\"Expected: literal boolean: true, Actual: literal boolean: false\\"; }'. + Types of property 'name' are incompatible. + Type '\\"Apple\\"' is not assignable to type '\\"Expected: literal string: Apple, Actual: never\\"'. 999 expectTypeOf().toMatchTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'Fruit' does not satisfy the constraint '{ name: \\"Expected: never, Actual: literal string: Apple\\"; type: \\"Fruit\\"; edible: \\"Expected: boolean, Actual: never\\"; }'. + Property 'name' is missing in type 'Fruit' but required in type '{ name: \\"Expected: never, Actual: literal string: Apple\\"; type: \\"Fruit\\"; edible: \\"Expected: boolean, Actual: never\\"; }'. 999 expectTypeOf().toEqualTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNumber' has no call signatures. 999 expectTypeOf().toBeNumber() - ~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeNumber: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ deeply: { nested: unknown; }; }' does not satisfy the constraint '{ deeply: { nested: \\"Expected: unknown, Actual: never\\"; }; }'. + The types of 'deeply.nested' are incompatible between these types. + Type 'unknown' is not assignable to type '\\"Expected: unknown, Actual: never\\"'. 999 expectTypeOf<{deeply: {nested: any}}>().toEqualTypeOf<{deeply: {nested: unknown}}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNumber' has no call signatures. - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. +999 expectTypeOf(badIntParser).returns.toBeNumber() + ~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNull' has no call signatures. 999 expectTypeOf(undefined).toBeNull() - ~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeNull: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectUndefined' has no call signatures. 999 expectTypeOf(null).toBeUndefined() - ~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeUndefined: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectUnknown' has no call signatures. 999 expectTypeOf(1).toBeUnknown() - ~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeUnknown: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectAny' has no call signatures. 999 expectTypeOf(1).toBeAny() - ~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNever' has no call signatures. 999 expectTypeOf(1).toBeNever() - ~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeNever: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNull' has no call signatures. 999 expectTypeOf(1).toBeNull() - ~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeNull: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectUndefined' has no call signatures. 999 expectTypeOf(1).toBeUndefined() - ~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeUndefined: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectNullable' has no call signatures. 999 expectTypeOf(1).toBeNullable() - ~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'number' does not satisfy the constraint '\\"Expected: number, Actual: string\\"'. 999 expectTypeOf().toMatchTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 2 arguments, but got 1. + ~~~~~~ +test/usage.test.ts:999:999 - error TS2345: Argument of type '\\"xxl\\"' is not assignable to parameter of type '\\"xs\\" | \\"sm\\" | \\"md\\"'. 999 expectTypeOf>().exclude().toHaveProperty('xxl') - ~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 ...MISMATCH: MismatchArgs, Options['positive']> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 2 arguments, but got 1. + ~~~~~ +test/usage.test.ts:999:999 - error TS2345: Argument of type '\\"c\\"' is not assignable to parameter of type '\\"a\\" | \\"b\\"'. 999 expectTypeOf(obj).toHaveProperty('c') - ~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 ...MISMATCH: MismatchArgs, Options['positive']> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectString' has no call signatures. 999 expectTypeOf(obj).toHaveProperty('a').toBeString() - ~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'HasParam' does not satisfy the constraint '\\"Expected: function, Actual: never\\"'. 999 expectTypeOf().toEqualTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectAny<(a: number) => number[]>' has no call signatures. 999 expectTypeOf(f).toBeAny() - ~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~ +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectAny' has no call signatures. 999 expectTypeOf(f).returns.toBeAny() - ~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. + ~~~~~~~ +test/usage.test.ts:999:999 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'Mismatch'. 999 expectTypeOf(f).parameter(0).toEqualTypeOf('1') ~~~ -test/usage.test.ts:999:999 - error TS2345: Argument of type '(this: { name: string; }, message: string) => string' is not assignable to parameter of type 'never'. +test/usage.test.ts:999:999 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + +999 expectTypeOf(f).toBeCallableWith('foo') + ~~~~~ +test/usage.test.ts:999:999 - error TS2345: Argument of type '(this: { name: string; }, message: string) => string' is not assignable to parameter of type 'Mismatch'. 999 expectTypeOf(greetFormal).toEqualTypeOf(greetCasual) ~~~~~~~~~~~ -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. +test/usage.test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectString' has no call signatures. 999 expectTypeOf([1, 2, 3]).items.toBeString() - ~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type 'number[]' does not satisfy the constraint '{ [x: number]: never; [iterator]: \\"Expected: function, Actual: never\\"; [unscopables]: () => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; length: number; toString: () => string; concat: { (...items: ConcatArray[]): any[]; (...items: any[]): any[]; }; indexOf: (searchElement: any, fromIndex?: number | undefined) => number; lastIndexOf: (searchElement: any, fromIndex?: number | undefined) => number; slice: (start?: number | undefined, end?: number | undefined) => any[]; includes: (searchElement: any, fromIndex?: number | undefined) => boolean; toLocaleString: () => string; join: (separator?: string | undefined) => string; every: { (predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any) => void; map: (callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any) => U[]; filter: { (predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; }; reduce: { (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any; (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any; (callbackfn: (previousValue: U, currentValue: any, currentIndex: number, array: any[]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any; (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any; (callbackfn: (previousValue: U, currentValue: any, currentIndex: number, array: any[]) => U, initialValue: U): U; }; find: \\"Expected: function, Actual: never\\"; findIndex: (predicate: (value: any, index: number, obj: any[]) => unknown, thisArg?: any) => number; entries: () => IterableIterator<[number, any]>; keys: () => IterableIterator; values: \\"Expected: function, Actual: never\\"; pop: \\"Expected: function, Actual: never\\"; push: (...items: any[]) => number; reverse: () => any[]; shift: \\"Expected: function, Actual: never\\"; sort: (compareFn?: ((a: any, b: any) => number) | undefined) => any[]; splice: { (start: number, deleteCount?: number | undefined): any[]; (start: number, deleteCount: number, ...items: any[]): any[]; }; unshift: (...items: any[]) => number; fill: (value: any, start?: number | undefined, end?: number | undefined) => any[]; copyWithin: (target: number, start: number, end?: number | undefined) => any[]; }'. + Types of property '[iterator]' are incompatible. + Type '() => IterableIterator' is not assignable to type '\\"Expected: function, Actual: never\\"'. 999 expectTypeOf().toEqualTypeOf() - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: \\"Expected: number, Actual: string\\"; }'. + Types of property 'a' are incompatible. + Type 'number' is not assignable to type '\\"Expected: number, Actual: string\\"'. 999 expectTypeOf<{a: string}>().toEqualTypeOf<{a: number}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{}' does not satisfy the constraint '{ a: \\"Expected: never, Actual: number\\" | \\"Expected: never, Actual: undefined\\"; }'. + Property 'a' is missing in type '{}' but required in type '{ a: \\"Expected: never, Actual: number\\" | \\"Expected: never, Actual: undefined\\"; }'. 999 expectTypeOf<{a?: number}>().toEqualTypeOf<{}>() - ~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: \\"Expected: number, Actual: undefined\\"; }'. + Types of property 'a' are incompatible. + Type 'number' is not assignable to type '\\"Expected: number, Actual: undefined\\"'. 999 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. + ~~~~~~~~~~~ test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. 999 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number | undefined}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. +test/usage.test.ts:999:999 - error TS2344: Type '{ a: number | null; }' does not satisfy the constraint '{ a: \\"Expected: number, Actual: undefined\\" | \\"Expected: null, Actual: undefined\\"; }'. + Types of property 'a' are incompatible. + Type 'number | null' is not assignable to type '\\"Expected: number, Actual: undefined\\" | \\"Expected: null, Actual: undefined\\"'. + Type 'null' is not assignable to type '\\"Expected: number, Actual: undefined\\" | \\"Expected: null, Actual: undefined\\"'. 999 expectTypeOf<{a?: number | null}>().toEqualTypeOf<{a: number | null}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. -test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + ~~~~~~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2344: Type '{ a: {}; }' does not satisfy the constraint '{ a: { b: \\"Expected: never, Actual: number\\" | \\"Expected: never, Actual: undefined\\"; }; }'. + Types of property 'a' are incompatible. + Property 'b' is missing in type '{}' but required in type '{ b: \\"Expected: never, Actual: number\\" | \\"Expected: never, Actual: undefined\\"; }'. 999 expectTypeOf<{a: {b?: number}}>().toEqualTypeOf<{a: {}}>() - ~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided. + ~~~~~~~ test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. 999 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~ src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. @@ -303,8 +208,8 @@ test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. ~~~~~~~~~~~~~~~~~~~ src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. @@ -312,8 +217,8 @@ test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. ~~~~~~~~~~~~~~~~~~~~~~~~~ src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. @@ -321,7 +226,66 @@ test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided." + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. +test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + +999 expectTypeOf<{a: {b: 1} & {c: 1}}>().toEqualTypeOf<{a: {b: 1; c: 1}}>() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:999:999 + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. +test/usage.test.ts:999:999 - error TS2344: Type '() => () => () => () => 2' does not satisfy the constraint '() => () => () => () => 1'. + Call signature return types '() => () => () => 2' and '() => () => () => 1' are incompatible. + Call signature return types '() => () => 2' and '() => () => 1' are incompatible. + Call signature return types '() => 2' and '() => 1' are incompatible. + Type '2' is not assignable to type '1'. + +999 expectTypeOf<() => () => () => () => 1>().toEqualTypeOf<() => () => () => () => 2>() + ~~~~~~~~~~~~~~~~~~~~~~~~~ +test/usage.test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + +999 expectTypeOf<() => () => () => () => {a: 1} & {b: 2}>().toEqualTypeOf< + ~~~~~~~~~~~~~~ +480 () => () => () => () => {a: 1; b: 2} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +481 >() + ~~~~~ + + src/index.ts:999:999 + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. +test/usage.test.ts:999:999 - error TS2769: No overload matches this call. + Overload 1 of 2, '(value: ((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & AValue, ...MISMATCH: MismatchArgs extends IsNever<...> ? true : false) extends true ? unknown : MismatchInfo<...>>, true>): true', gave the following error. + Argument of type 'this' is not assignable to parameter of type '((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & AValue'. + Type 'B' is not assignable to type '((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & AValue'. + Type 'B' is not assignable to type '((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'this' is not assignable to type '((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'B' is not assignable to type '((IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'B' is not assignable to type '(IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo'. + Type 'this' is not assignable to type '(IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo'. + Type 'B' is not assignable to type '(IsNever extends IsNever ? true : false) extends true ? unknown : MismatchInfo'. + Argument of type '[this]' is not assignable to parameter of type 'MismatchArgs extends true ? unknown : MismatchInfo>, true>'. + +999 expectTypeOf(this).toEqualTypeOf(this) + ~~~~ + +test/usage.test.ts:999:999 - error TS2769: No overload matches this call. + Overload 1 of 2, '(value: (Extends extends true ? unknown : MismatchInfo) & AValue, ...MISMATCH: MismatchArgs extends true ? unknown : MismatchInfo<...>>, true>): true', gave the following error. + Argument of type 'this' is not assignable to parameter of type '(Extends extends true ? unknown : MismatchInfo) & AValue'. + Type 'B' is not assignable to type '(Extends extends true ? unknown : MismatchInfo) & AValue'. + Type 'B' is not assignable to type '(Extends extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'this' is not assignable to type '(Extends extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'B' is not assignable to type '(Extends extends true ? unknown : MismatchInfo) & { [avalue]?: undefined; }'. + Type 'B' is not assignable to type 'Extends extends true ? unknown : MismatchInfo'. + Type 'this' is not assignable to type 'Extends extends true ? unknown : MismatchInfo'. + Type 'B' is not assignable to type 'Extends extends true ? unknown : MismatchInfo'. + Argument of type '[this]' is not assignable to parameter of type 'MismatchArgs extends true ? unknown : MismatchInfo>, true>'. + +999 expectTypeOf(this).toMatchTypeOf(this) + ~~~~" `; diff --git a/test/errors.test.ts b/test/errors.test.ts index 6570c4d..4bffac3 100644 --- a/test/errors.test.ts +++ b/test/errors.test.ts @@ -3,62 +3,199 @@ import {tsErrors, tsFileErrors} from './ts-output' test('toEqualTypeOf<...>() error message', async () => { expect(tsErrors(`expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>()`)).toMatchInlineSnapshot(` - "test/test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + "test/test.ts:999:999 - error TS2344: Type '{ a: string; }' does not satisfy the constraint '{ a: \\"Expected: string, Actual: number\\"; }'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type '\\"Expected: string, Actual: number\\"'. 999 expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided." + ~~~~~~~~~~~" `) }) test('toEqualTypeOf(...) error message', async () => { expect(tsErrors(`expectTypeOf({a: 1}).toEqualTypeOf({a: 'one'})`)).toMatchInlineSnapshot(` - "test/test.ts:999:999 - error TS2345: Argument of type '{ a: string; }' is not assignable to parameter of type 'never'. + "test/test.ts:999:999 - error TS2345: Argument of type '{ a: string; }' is not assignable to parameter of type 'Mismatch'. + Object literal may only specify known properties, and 'a' does not exist in type 'Mismatch'. 999 expectTypeOf({a: 1}).toEqualTypeOf({a: 'one'}) - ~~~~~~~~~~" + ~~~~~~~~" + `) +}) + +test('toEqualTypeOf special types', async () => { + expect( + tsErrors( + `expectTypeOf<{a: any}>().toEqualTypeOf<{a: 1}>()`, + `expectTypeOf<{a: never}>().toEqualTypeOf<{a: 1}>()`, + `expectTypeOf<{a: unknown}>().toEqualTypeOf<{a: 1}>()`, + `expectTypeOf<{a: 1}>().toEqualTypeOf<{a: any}>()`, + `expectTypeOf<{a: 1}>().toEqualTypeOf<{a: never}>()`, + `expectTypeOf<{a: 1}>().toEqualTypeOf<{a: unknown}>()`, + ), + ).toMatchInlineSnapshot(` + "test/test.ts:999:999 - error TS2344: Type '{ a: 1; }' does not satisfy the constraint '{ a: never; }'. + Types of property 'a' are incompatible. + Type 'number' is not assignable to type 'never'. + + 999 expectTypeOf<{a: any}>().toEqualTypeOf<{a: 1}>() + ~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: 1; }' does not satisfy the constraint '{ a: \\"Expected: literal number: 1, Actual: never\\"; }'. + Types of property 'a' are incompatible. + Type '1' is not assignable to type '\\"Expected: literal number: 1, Actual: never\\"'. + + 999 expectTypeOf<{a: never}>().toEqualTypeOf<{a: 1}>() + ~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: 1; }' does not satisfy the constraint '{ a: \\"Expected: literal number: 1, Actual: unknown\\"; }'. + Types of property 'a' are incompatible. + Type '1' is not assignable to type '\\"Expected: literal number: 1, Actual: unknown\\"'. + + 999 expectTypeOf<{a: unknown}>().toEqualTypeOf<{a: 1}>() + ~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: any; }' does not satisfy the constraint '{ a: never; }'. + Types of property 'a' are incompatible. + Type 'any' is not assignable to type 'never'. + + 999 expectTypeOf<{a: 1}>().toEqualTypeOf<{a: any}>() + ~~~~~~~~ + test/test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + + 999 expectTypeOf<{a: 1}>().toEqualTypeOf<{a: never}>() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:999:999 + 999 ...MISMATCH: MismatchArgs, true> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/test.ts:999:999 - error TS2344: Type '{ a: unknown; }' does not satisfy the constraint '{ a: \\"Expected: unknown, Actual: never\\"; }'. + Types of property 'a' are incompatible. + Type 'unknown' is not assignable to type '\\"Expected: unknown, Actual: never\\"'. + + 999 expectTypeOf<{a: 1}>().toEqualTypeOf<{a: unknown}>() + ~~~~~~~~~~~~" + `) +}) + +test('toEqualTypeOf with literals', async () => { + expect( + tsErrors( + `expectTypeOf<{a: string}>().toEqualTypeOf<{a: 'abc'}>()`, + `expectTypeOf<{a: 'abc'}>().toEqualTypeOf<{a: string}>()`, + `expectTypeOf<{a: 'abc'}>().toEqualTypeOf<{a: 'xyz'}>()`, + `expectTypeOf<{a: number}>().toEqualTypeOf<{a: 1}>()`, + `expectTypeOf<{a: 1}>().toEqualTypeOf<{a: number}>()`, + `expectTypeOf<{a: 1}>().toEqualTypeOf<{a: 2}>()`, + `expectTypeOf<{a: boolean}>().toEqualTypeOf<{a: true}>()`, + `expectTypeOf<{a: true}>().toEqualTypeOf<{a: boolean}>()`, + `expectTypeOf<{a: true}>().toEqualTypeOf<{a: false}>()`, + ), + ).toMatchInlineSnapshot(` + "test/test.ts:999:999 - error TS2344: Type '{ a: \\"abc\\"; }' does not satisfy the constraint '{ a: \\"Expected: literal string: abc, Actual: string\\"; }'. + Types of property 'a' are incompatible. + Type '\\"abc\\"' is not assignable to type '\\"Expected: literal string: abc, Actual: string\\"'. + + 999 expectTypeOf<{a: string}>().toEqualTypeOf<{a: 'abc'}>() + ~~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: string; }' does not satisfy the constraint '{ a: \\"Expected: string, Actual: never\\"; }'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type '\\"Expected: string, Actual: never\\"'. + + 999 expectTypeOf<{a: 'abc'}>().toEqualTypeOf<{a: string}>() + ~~~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: \\"xyz\\"; }' does not satisfy the constraint '{ a: \\"Expected: literal string: xyz, Actual: literal string: abc\\"; }'. + Types of property 'a' are incompatible. + Type '\\"xyz\\"' is not assignable to type '\\"Expected: literal string: xyz, Actual: literal string: abc\\"'. + + 999 expectTypeOf<{a: 'abc'}>().toEqualTypeOf<{a: 'xyz'}>() + ~~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: 1; }' does not satisfy the constraint '{ a: \\"Expected: literal number: 1, Actual: number\\"; }'. + Types of property 'a' are incompatible. + Type '1' is not assignable to type '\\"Expected: literal number: 1, Actual: number\\"'. + + 999 expectTypeOf<{a: number}>().toEqualTypeOf<{a: 1}>() + ~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: number; }' does not satisfy the constraint '{ a: \\"Expected: number, Actual: never\\"; }'. + Types of property 'a' are incompatible. + Type 'number' is not assignable to type '\\"Expected: number, Actual: never\\"'. + + 999 expectTypeOf<{a: 1}>().toEqualTypeOf<{a: number}>() + ~~~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: 2; }' does not satisfy the constraint '{ a: \\"Expected: literal number: 2, Actual: literal number: 1\\"; }'. + Types of property 'a' are incompatible. + Type '2' is not assignable to type '\\"Expected: literal number: 2, Actual: literal number: 1\\"'. + + 999 expectTypeOf<{a: 1}>().toEqualTypeOf<{a: 2}>() + ~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: true; }' does not satisfy the constraint '{ a: \\"Expected: literal boolean: true, Actual: literal boolean: false\\"; }'. + Types of property 'a' are incompatible. + Type 'true' is not assignable to type '\\"Expected: literal boolean: true, Actual: literal boolean: false\\"'. + + 999 expectTypeOf<{a: boolean}>().toEqualTypeOf<{a: true}>() + ~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: boolean; }' does not satisfy the constraint '{ a: \\"Expected: boolean, Actual: never\\"; }'. + Types of property 'a' are incompatible. + Type 'boolean' is not assignable to type '\\"Expected: boolean, Actual: never\\"'. + + 999 expectTypeOf<{a: true}>().toEqualTypeOf<{a: boolean}>() + ~~~~~~~~~~~~ + test/test.ts:999:999 - error TS2344: Type '{ a: false; }' does not satisfy the constraint '{ a: \\"Expected: literal boolean: false, Actual: literal boolean: true\\"; }'. + Types of property 'a' are incompatible. + Type 'false' is not assignable to type '\\"Expected: literal boolean: false, Actual: literal boolean: true\\"'. + + 999 expectTypeOf<{a: true}>().toEqualTypeOf<{a: false}>() + ~~~~~~~~~~" `) }) test('toMatchTypeOf<...>() error message', async () => { expect(tsErrors(`expectTypeOf({a: 1}).toMatchTypeOf<{a: string}>()`)).toMatchInlineSnapshot(` - "test/test.ts:999:999 - error TS2554: Expected 1 arguments, but got 0. + "test/test.ts:999:999 - error TS2344: Type '{ a: string; }' does not satisfy the constraint '{ a: \\"Expected: string, Actual: number\\"; }'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type '\\"Expected: string, Actual: number\\"'. 999 expectTypeOf({a: 1}).toMatchTypeOf<{a: string}>() - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - src/index.ts:999:999 - 999 (...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided." + ~~~~~~~~~~~" `) }) test('toMatchTypeOf(...) error message', async () => { expect(tsErrors(`expectTypeOf({a: 1}).toMatchTypeOf({a: 'one'})`)).toMatchInlineSnapshot(` - "test/test.ts:999:999 - error TS2554: Expected 2 arguments, but got 1. + "test/test.ts:999:999 - error TS2554: Expected 0 arguments, but got 1. 999 expectTypeOf({a: 1}).toMatchTypeOf({a: 'one'}) - ~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~" + `) +}) - src/index.ts:999:999 - 999 (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Arguments for the rest parameter 'MISMATCH' were not provided." +test('toBeString', async () => { + const badString = `expectTypeOf(1).toBeString()` + expect(tsErrors(badString)).toMatchInlineSnapshot(` + "test/test.ts:999:999 - error TS2349: This expression is not callable. + Type 'ExpectString' has no call signatures. + + 999 expectTypeOf(1).toBeString() + ~~~~~~~~~~" + `) +}) + +test('toBeNullable', async () => { + const okAssertion = `expectTypeOf<1 | undefined>().toBeNullable()` + expect(tsErrors(okAssertion + '\n' + okAssertion.replace('.toBe', '.not.toBe'))).toMatchInlineSnapshot(` + "test/test.ts:999:999 - error TS2349: This expression is not callable. + Type 'Inverted>' has no call signatures. + + 999 expectTypeOf<1 | undefined>().not.toBeNullable() + ~~~~~~~~~~~~" `) }) -test('usage test', () => { +test('usage.test.ts', () => { // remove all `.not`s and `// @ts-expect-error`s from the main test file and snapshot the errors const usageTestFile = fs - .readFileSync(__filename.replace('errors.test.ts', 'usage.test.ts')) + .readFileSync(__dirname + '/usage.test.ts') .toString() .split('\n') - .map(line => line.replace('// @ts-expect-error', '// error on next line:').replace('.not.', '.')) + .map(line => line.replace('// @ts-expect-error', '// error expected on next line:')) + .map(line => line.replace('.not.', '.')) .join('\n') expect(tsFileErrors({filepath: 'test/usage.test.ts', content: usageTestFile})).toMatchSnapshot() }) diff --git a/test/types.test.ts b/test/types.test.ts index 2e57ed4..c549320 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -38,16 +38,16 @@ test('boolean type logic', () => { expectTypeOf>().toEqualTypeOf() expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() }) test(`never types don't sneak by`, () => { @@ -70,6 +70,33 @@ test(`never types don't sneak by`, () => { expectTypeOf().toMatchTypeOf<{foo: string}>() }) +test("any/never types don't break toEqualTypeOf or toMatchTypeOf", () => { + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf<{a: number}>().toEqualTypeOf() + // @ts-expect-error + expectTypeOf<{a: number}>().toEqualTypeOf() + // @ts-expect-error + expectTypeOf<{a: number}>().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf({a: 1, b: 1}) + // @ts-expect-error + expectTypeOf().toEqualTypeOf({a: 1, b: 1}) + // @ts-expect-error + expectTypeOf().toEqualTypeOf({a: 1, b: 1}) +}) + test('intersections do not currently work properly', () => { // @ts-expect-error limitation of new implementation https://github.com/mmkal/expect-type/pull/21 expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>() @@ -116,7 +143,7 @@ test('parity with IsExact from conditional-type-checks', () => { /** shim conditional-type-check's `assert` */ const assert = (_result: T) => true /** shim conditional-type-check's `IsExact` using `Equal` */ - type IsExact = a.Equal + type IsExact = a.StrictEqualUsingBranding // basic test for `assert` shim: expectTypeOf(assert).toBeCallableWith(true) @@ -186,9 +213,11 @@ test('parity with IsExact from conditional-type-checks', () => { }) test('Equal works with functions', () => { - expectTypeOf void, () => string>>().toEqualTypeOf() - expectTypeOf void, (s: string) => void>>().toEqualTypeOf() - expectTypeOf () => () => void, () => (s: string) => () => void>>().toEqualTypeOf() + expectTypeOf void, () => string>>().toEqualTypeOf() + expectTypeOf void, (s: string) => void>>().toEqualTypeOf() + expectTypeOf< + a.StrictEqualUsingBranding<() => () => () => void, () => (s: string) => () => void> + >().toEqualTypeOf() }) test(`undefined isn't removed from unions`, () => { @@ -220,7 +249,7 @@ test('Distinguish between functions whose return types differ by readonly prop', // Self-identity expectTypeOf().toEqualTypeOf() - expectTypeOf(original).branded.toEqualTypeOf(original) + expectTypeOf(original).branded.toEqualTypeOf() expectTypeOf().toEqualTypeOf() expectTypeOf(different).toEqualTypeOf(different) // @ts-expect-error @@ -629,7 +658,7 @@ test('Works arounds tsc bug not handling intersected types for this form of equi // The workaround is the new optional .branded modifier. expectTypeOf<{foo: number} & {bar: string}>().branded.toEqualTypeOf<{foo: number; bar: string}>() - expectTypeOf(one).branded.toEqualTypeOf(two) + expectTypeOf(one).branded.toEqualTypeOf() // @ts-expect-error expectTypeOf<{foo: number} & {bar: string}>().branded.not.toEqualTypeOf<{foo: number; bar: string}>() // @ts-expect-error @@ -644,12 +673,38 @@ test('Distinguish between identical types that are AND`d together', () => { expectTypeOf<{foo: number} & {foo: number}>().not.toEqualTypeOf<{foo: number} & {foo: number}>() // @ts-expect-error expectTypeOf<{foo: number} & {foo: number}>().not.toEqualTypeOf<{foo: number}>() + + expectTypeOf<{a: {b: 1} & {c: 1}}>().branded.toEqualTypeOf<{a: {b: 1; c: 1}}>() + expectTypeOf<() => () => () => {a: 1} & {b: 1}>().not.toEqualTypeOf<() => () => () => {a: 1; c: 1}>() + + expectTypeOf<{foo: number} & {foo: number}>().toEqualTypeOf<{foo: number} & {foo: number}>() + expectTypeOf<(() => 1) & {x: 1}>().not.toEqualTypeOf<() => 1>() + expectTypeOf<(() => 1) & {x: 1}>().not.toEqualTypeOf<() => 1>() }) test('limitations', () => { // these *shouldn't* fail, but kept here to document missing behaviours. Once fixed, remove the expect-error comments to make sure they can't regress // @ts-expect-error typescript can't handle the truth: https://github.com/mmkal/expect-type/issues/5 https://github.com/microsoft/TypeScript/issues/50670 - expectTypeOf () => () => void, () => () => () => string>>().toEqualTypeOf() + expectTypeOf () => () => void, () => () => () => string>>().toEqualTypeOf() - expectTypeOf<(() => 1) & {x: 1}>().not.toEqualTypeOf<() => 1>() + // @ts-expect-error toEqualTypeOf relies on TypeScript's internal `toBeIdentical` function which falls down with intersection types, but is otherwise accurate and performant: https://github.com/microsoft/TypeScript/issues/55188#issuecomment-1656328122 + expectTypeOf<{a: {b: 1} & {c: 1}}>().toEqualTypeOf<{a: {b: 1; c: 1}}>() + // use `.branded` to get around this, at the cost of performance. + expectTypeOf<{a: {b: 1} & {c: 1}}>().branded.toEqualTypeOf<{a: {b: 1; c: 1}}>() +}) + +test('PrintType', () => { + expectTypeOf>().toEqualTypeOf<'boolean'>() + expectTypeOf>().toEqualTypeOf<'string'>() + expectTypeOf>().toEqualTypeOf<'number'>() + expectTypeOf>().toEqualTypeOf<'never'>() + expectTypeOf>().toEqualTypeOf<'unknown'>() + expectTypeOf>().toEqualTypeOf<'literal number: 1'>() + expectTypeOf>().toEqualTypeOf<'literal string: a'>() + expectTypeOf>().toEqualTypeOf<'literal boolean: true'>() + expectTypeOf>().toEqualTypeOf<'literal boolean: false'>() + expectTypeOf>().toEqualTypeOf<'null'>() + expectTypeOf>().toEqualTypeOf<'undefined'>() + expectTypeOf {}>>().toEqualTypeOf<'function'>() + expectTypeOf>().toBeNever() }) diff --git a/test/usage.test.ts b/test/usage.test.ts index 950fd30..80e8c92 100644 --- a/test/usage.test.ts +++ b/test/usage.test.ts @@ -8,7 +8,7 @@ test("Check an object's type with `.toEqualTypeOf`", () => { expectTypeOf({a: 1}).toEqualTypeOf<{a: number}>() }) -test('`.toEqualTypeOf` can check that two concrete objects have equivalent types', () => { +test('`.toEqualTypeOf` can check that two concrete objects have equivalent types (note: when these assertions _fail_, the error messages can be less informative vs the generic typearg syntax above - see [error messages docs](#error-messages))', () => { expectTypeOf({a: 1}).toEqualTypeOf({a: 1}) }) @@ -21,8 +21,15 @@ test('`.toEqualTypeOf` fails on extra properties', () => { expectTypeOf({a: 1, b: 1}).toEqualTypeOf<{a: number}>() }) -test('To allow for extra properties, use `.toMatchTypeOf`. This checks that an object "matches" a type. This is similar to jest\'s `.toMatchObject`', () => { - expectTypeOf({a: 1, b: 1}).toMatchTypeOf({a: 1}) +test('To allow for extra properties, use `.toMatchTypeOf`. This is roughly equivalent to an `extends` constraint in a function type argument.', () => { + expectTypeOf({a: 1, b: 1}).toMatchTypeOf<{a: number}>() +}) + +test('`.toEqualTypeOf` and `.toMatchTypeOf` both fail on missing properties', () => { + // @ts-expect-error + expectTypeOf({a: 1}).toEqualTypeOf<{a: number; b: number}>() + // @ts-expect-error + expectTypeOf({a: 1}).toMatchTypeOf<{a: number; b: number}>() }) test('Another example of the difference between `.toMatchTypeOf` and `.toEqualTypeOf`, using generics. `.toMatchTypeOf` can be used for "is-a" relationships', () => { @@ -78,6 +85,29 @@ test('Test for basic javascript types', () => { expectTypeOf(Symbol(1)).toBeSymbol() }) +test('`.toBe...` methods allow for types which extend the expected type', () => { + expectTypeOf().toBeNumber() + expectTypeOf<1>().toBeNumber() + + expectTypeOf().toBeArray() + expectTypeOf().toBeArray() + + expectTypeOf().toBeString() + expectTypeOf<'foo'>().toBeString() + + expectTypeOf().toBeBoolean() + expectTypeOf().toBeBoolean() +}) + +test('`.toBe...` methods protect against `any`', () => { + const goodIntParser = (s: string) => Number.parseInt(s, 10) + const badIntParser = (s: string) => JSON.parse(s) // uh-oh - works at runtime if the input is a number, but return 'any' + + expectTypeOf(goodIntParser).returns.toBeNumber() + // @ts-expect-error - if you write a test like this, `.toBeNumber()` will let you know your implementation returns `any`. + expectTypeOf(badIntParser).returns.toBeNumber() +}) + test('Nullable types', () => { expectTypeOf(undefined).toBeUndefined() expectTypeOf(undefined).toBeNullable() @@ -189,6 +219,13 @@ test('More examples of ways to work with functions - parameters using `.paramete expectTypeOf(twoArgFunc).parameters.toEqualTypeOf<[number, string]>() }) +test("You can't use `.toBeCallableWith` with `.not` - you need to use ts-expect-error:", () => { + const f = (a: number) => [a, a] + + // @ts-expect-error + expectTypeOf(f).toBeCallableWith('foo') +}) + test('You can also check type guards & type assertions', () => { const assertNumber = (v: any): asserts v is number => { if (typeof v !== 'number') { @@ -316,8 +353,51 @@ test('Known limitation: Intersection types can cause issues with `toEqualTypeOf` expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>() }) -test('To workaround, you can use a mapped type', () => { +test('To workaround for simple cases, you can use a mapped type', () => { type Simplify = {[K in keyof T]: T[K]} expectTypeOf>().toEqualTypeOf<{a: 1; b: 2}>() }) + +test("But this won't work if the nesting is deeper in the type. For these situations, you can use the `.branded` helper. Note that this comes at a performance cost, and can cause the compiler to 'give up' if used with excessively deep types, so use sparingly. This helper is under `.branded` because it depply transforms the Actual and Expected types into a pseudo-AST", () => { + // @ts-expect-error + expectTypeOf<{a: {b: 1} & {c: 1}}>().toEqualTypeOf<{a: {b: 1; c: 1}}>() + + expectTypeOf<{a: {b: 1} & {c: 1}}>().branded.toEqualTypeOf<{a: {b: 1; c: 1}}>() +}) + +test('Be careful with `.branded` for very deep or complex types, though. If possible you should find a way to simplify your test to avoid needing to use it', () => { + // This *should* result in an error, but the "branding" mechanism produces too large a type and TypeScript just gives up! https://github.com/microsoft/TypeScript/issues/50670 + expectTypeOf<() => () => () => () => 1>().branded.toEqualTypeOf<() => () => () => () => 2>() + + // @ts-expect-error the non-branded implementation catches the error as expected. + expectTypeOf<() => () => () => () => 1>().toEqualTypeOf<() => () => () => () => 2>() +}) + +test("So, if you have an extremely deep type which ALSO has an intersection in it, you're out of luck and this library won't be able to test your type properly", () => { + // @ts-expect-error this fails, but it should succeed. + expectTypeOf<() => () => () => () => {a: 1} & {b: 2}>().toEqualTypeOf< + () => () => () => () => {a: 1; b: 2} + >() + + // this succeeds, but it should fail. + expectTypeOf<() => () => () => () => {a: 1} & {b: 2}>().branded.toEqualTypeOf< + () => () => () => () => {a: 1; c: 2} + >() +}) + +test('Another limitation: passing `this` references to `expectTypeOf` results in errors.', () => { + class B { + b = 'b' + + foo() { + // @ts-expect-error + expectTypeOf(this).toEqualTypeOf(this) + // @ts-expect-error + expectTypeOf(this).toMatchTypeOf(this) + } + } + + // Instead of the above, try something like this: + expectTypeOf(B).instance.toEqualTypeOf<{b: string; foo: () => void}>() +})