├── .gitignore ├── .npmrc ├── API.md ├── LICENSE.md ├── README.md ├── package.json ├── pnpm-lock.yaml ├── scripts ├── API.source.md ├── clean.mts └── generate.ts ├── src ├── Equal.test.ts ├── Equal.ts ├── Expect.test.ts ├── Expect.ts ├── ExpectFalse.test.ts ├── ExpectFalse.ts ├── Extends.test.ts ├── Extends.ts ├── FalseCases.test.ts ├── FalseCases.ts ├── IsAny.test.ts ├── IsAny.ts ├── IsNever.test.ts ├── IsNever.ts ├── IsTuple.test.ts ├── IsTuple.ts ├── IsUnion.test.ts ├── IsUnion.ts ├── IsUnknown.test.ts ├── IsUnknown.ts ├── NotEqual.test.ts ├── NotEqual.ts ├── SimpleEqual.test.ts ├── SimpleEqual.ts ├── TrueCases.test.ts ├── TrueCases.ts └── index.ts ├── tsconfig.base.json ├── tsconfig.build.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Guide 2 | 3 | - [API Guide](#api-guide) 4 | - [Equal](#equal) 5 | - [NotEqual](#notequal) 6 | - [Expect](#expect) 7 | - [ExpectFalse](#expectfalse) 8 | - [Extends](#extends) 9 | - [IsAny](#isany) 10 | - [IsNever](#isnever) 11 | - [IsTuple](#istuple) 12 | - [IsUnion](#isunion) 13 | - [IsUnknown](#isunknown) 14 | - [SimpleEqual](#simpleequals) 15 | - [TrueCases](#truecases) 16 | - [FalseCases](#falsecases) 17 | 18 | ## Equal 19 | 20 | returns `true` if the provided arguments resolve to the same TypeScript value. 21 | 22 | `Equal` is the cornerstone of TypeScript type testing. 23 | 24 | ## NotEqual 25 | 26 | The opposite of `Equal`, will return false if the two inputs are not equal. 27 | 28 | ## Expect 29 | 30 | errors at TypeScript compile-time if passed a value that is not `true`: 31 | 32 | the following will not error and will return `true`; 33 | 34 | ```ts 35 | Expect; 36 | ``` 37 | 38 | all other inputs will return `false`. 39 | 40 | all other inputs will error, except for `never`. 41 | 42 | ## ExpectFalse 43 | 44 | A type that will error if passed anything other than literal `false` or `never` 45 | 46 | The following will not error: 47 | 48 | ```ts 49 | IsFalse; 50 | IsFalse; 51 | ``` 52 | 53 | The following will error: 54 | 55 | ```ts 56 | IsFalse; 57 | IsFalse; 58 | IsFalse<1 | false>; 59 | IsFalse<'false'>; 60 | IsFalse<''>; 61 | IsFalse<0>; 62 | IsFalse; 63 | IsFalse; 64 | IsFalse; 65 | ``` 66 | 67 | ## Extends 68 | 69 | ONLY use this type if you know exactly what you're doing. 70 | 71 | This type appears to return `true` if and only if `A extends B`, however, if you look at the tests you'll find that this is unfortunately not quite exactly how TypeScript works. There are many edge cases that return surprising results due to how TypeScript is structured. Of course these results may make sense eventually if you look deeply enough at them, but the hazard is always present. 72 | 73 | For example, distributivity is in play any time you set up `extends` clauses, which means that it can actually return _MULTIPLE_ paths at once (yielding a return type of `boolean`) or even _NO PATHS_ (yielding a return type of `never`). 74 | 75 | ```ts 76 | Extends<1 & 2, never> //=> never 77 | Extends //=> never 78 | Extends //=> never 79 | 80 | Extends //=> boolean 81 | Extends<1 | 2, 1> //=> boolean 82 | Extends //=> boolean 83 | Extends //=> boolean 84 | 85 | Extends<[], unknown[]> //=> true 86 | Extends //=> false 87 | 88 | Extends //=> boolean 89 | Extends<{}, any> //=> true 90 | 91 | Extends //=> never 92 | Extends<1, never> //=> false 93 | 94 | Extends<{}, unknown> //=> true 95 | Extends //=> false 96 | ``` 97 | 98 | ## IsAny 99 | 100 | Returns `true` if the input is of type `any` and false for all other inputs. 101 | 102 | The following will return true: 103 | 104 | ```ts 105 | IsAny; 106 | ``` 107 | 108 | The following will return false: 109 | 110 | ```ts 111 | IsAny; 112 | IsAny; 113 | IsAny; 114 | IsAny; 115 | ``` 116 | 117 | Note unions with `any` resolve to `any`, so: 118 | 119 | ```ts 120 | IsAny; 121 | ``` 122 | 123 | returns `true`; 124 | 125 | ## IsNever 126 | 127 | returns `true` when passed a literal `never`: 128 | 129 | ```ts 130 | IsNever; 131 | ``` 132 | 133 | returns `false` for all other values 134 | 135 | ## IsTuple 136 | 137 | returns `true` for types that are Tuples 138 | 139 | the following all return `true`: 140 | 141 | ```ts 142 | IsTuple<[]>; 143 | IsTuple<[number]>; 144 | IsTuple; 145 | ``` 146 | 147 | the following all return `false`: 148 | 149 | ```ts 150 | IsTuple<{ length: 1 }>; 151 | IsTuple; 152 | IsTuple; 153 | ``` 154 | 155 | ## IsUnion 156 | 157 | returns `true` when passed something that resolves to a union 158 | 159 | the following all return `true`: 160 | 161 | ```ts 162 | IsUnion; 163 | IsUnion<{ a: string } | { a: number }>; 164 | IsUnion; 165 | IsUnion<'a' | 'b' | 'c' | 'd'>; 166 | IsUnion; 167 | ``` 168 | 169 | the following all return `false`: 170 | 171 | ```ts 172 | IsUnion; 173 | IsUnion<{ a: string | number }>; 174 | IsUnion<[string | number]>; 175 | ``` 176 | 177 | _Note_ how TypeScript treats types that resolve to a non-union. 178 | 179 | the following all return `false`: 180 | 181 | ```ts 182 | IsUnion; // resolves to `string` 183 | IsUnion; // resolves to `unknown` 184 | IsUnion; // resolves to `any` 185 | IsUnion; // `resolves to `string` 186 | IsUnion; // `never` is an empty union 187 | ``` 188 | 189 | ## IsUnknown 190 | 191 | This predicate tests for whether a given value is `unknown`. 192 | 193 | It will return `true` for `unknown` (and any value that resolves to unknown such as `never | unknown` or `{} | unknown`). 194 | It will return `false` for all other values (including values that resolve to something that is not unknown such as `any | unknown`). 195 | 196 | ## SimpleEqual 197 | 198 | ONLY use this type if you know exactly what you're doing. You probably want `Equal` instead. 199 | 200 | This type appears to return `true` if and only if `A extends B` _and_ `B extends A`. However, if you look at the tests you'll find that this is unfortunately not quite exactly how TypeScript works. There are many edge cases that return surprising results due to how TypeScript is structured. Of course these results may make sense eventually if you look deeply enough at them, but the hazard is always present. 201 | 202 | For example, distributivity is in play any time you set up `extends` clauses, which means that it can actually return _MULTIPLE_ paths at once (yielding a return type of `boolean`) or even _NO PATHS_ (yielding a return type of `never`). 203 | 204 | ```ts 205 | SimpleEqual<1 & 2, never> //=> never (`Equal` returns `true`) 206 | SimpleEqual //=> never (`Equal` returns `false`) 207 | SimpleEqual //=> boolean (`Equal` returns `true`) 208 | SimpleEqual //=> boolean (`Equal` returns `false`) 209 | SimpleEqual<1 | 2, 1> //=> boolean (`Equal` returns `false`) 210 | ``` 211 | 212 | ## TrueCases 213 | 214 | A helper type that will allow you to test many cases at once with minimal boilerplate. 215 | 216 | instead of 217 | 218 | ```ts 219 | type TrueCases = [ 220 | Expect, true>>, 221 | Expect, true>>, 222 | Expect, true>>, 223 | Expect, true>>, 224 | ]; 225 | ``` 226 | 227 | you can write: 228 | 229 | ```ts 230 | type T = TrueCases<[ 231 | IsUnion, 232 | IsUnion<{ a: string } | { a: number }>, 233 | IsUnion, 234 | IsUnion<'a' | 'b' | 'c' | 'd'>, 235 | ]>; 236 | ``` 237 | 238 | The drawback of this type is that the error message is not as friendly if one of the test cases has an error: 239 | 240 | ```text 241 | Type '[true, true, false, true]' does not satisfy the constraint 'readonly true[]'. 242 | Type 'boolean' is not assignable to type 'true'.ts(2344) 243 | ``` 244 | 245 | Whereas with inline `Expect` and `Equal` you'd get an error just on the line of the failing test. 246 | 247 | If the tradeoff of debuggability is desirable to you, then use this type. 248 | 249 | ## FalseCases 250 | 251 | A helper type that will allow you to test many cases at once with minimal boilerplate. 252 | 253 | instead of 254 | 255 | ```ts 256 | type FalseCases = [ 257 | Expect, false>>, 258 | Expect, false>>, 259 | Expect, false>>, 260 | Expect, false>>, 261 | Expect, false>>, 262 | Expect, false>>, 263 | ]; 264 | ``` 265 | 266 | you can write: 267 | 268 | ```ts 269 | type F = FalseCases<[ 270 | IsNever<''>, 271 | IsNever, 272 | IsNever, 273 | IsNever<[]>, 274 | IsNever<{}>, 275 | IsNever, 276 | ]>; 277 | ``` 278 | 279 | The drawback of this type is that the error message is not as friendly if one of the test cases has an error: 280 | 281 | ```text 282 | Type '[false, false, true, false, false, false]' does not satisfy the constraint 'readonly false[]'. 283 | Type 'boolean' is not assignable to type 'false'.ts(2344) 284 | ``` 285 | 286 | Whereas with inline `Expect` and `Equal` you'd get an error just on the line of the failing test. 287 | 288 | If the tradeoff of debuggability is desirable to you, then use this type. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2023 Dimitri Mitropoulos 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # type-testing 2 | 3 | > 🌱 A micro library for testing your TypeScript types. 4 | 5 | Sometimes your project's TypeScript types really matter. More and more often, the TypeScript types _themselves_ are a core part of the product. But there hasn't been a good way to make sure the types don't subtly break from day to day. This is the problem `type-testing` solves. 6 | 7 | A lot of popular projects with incredible type inferencing already use the code in `type-testing`. For example [Zod](https://github.com/colinhacks/zod/blob/master/src/helpers/util.ts#L2), [Tan](https://github.com/TanStack/query/blob/eeec5f77bc9a703ffb6a6d283dcedada34aa3c75/packages/react-query/src/__tests__/useQuery.types.test.tsx#L3-L11)[Stack](https://github.com/TanStack/query/blob/eeec5f77bc9a703ffb6a6d283dcedada34aa3c75/packages/solid-query/src/__tests__/createQuery.types.test.tsx#L3) [Query](https://github.com/TanStack/query/blob/eeec5f77bc9a703ffb6a6d283dcedada34aa3c75/packages/vue-query/src/__tests__/test-utils.ts#L54), [zustand](https://github.com/pmndrs/zustand/blob/fbfcdc54e679cf1cb6d887078b4b9b19319417e9/tests/types.test.tsx#L106), [tRPC](https://github.com/trpc/trpc/blob/main/packages/tests/server/inferenceUtils.ts#L116), [MUI](https://github.com/mui/material-ui/blob/master/packages/mui-styled-engine/src/index.d.ts#L65), [type-fest](https://github.com/sindresorhus/type-fest/blob/main/source/is-equal.d.ts#L26), [ts-reset](https://github.com/total-typescript/ts-reset/blob/main/src/tests/utils.ts#L7), and the [TypeScript Challenges](https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts#L7) all have variants of the same code. We collected that code here and wrote tests for them (yes: test inception). 8 | 9 | ## Goals 10 | 11 | 1. Bring this commonly copy-pasta'd code into one place where the utilities are tested and correct. 12 | 2. Demonstrate how to test types in TypeScript project. 13 | 14 | | Before testing your types | After using `type-testing` | 15 | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| 16 | | :dizzy_face: Your inferred types work great on Monday, but by Friday you realize they don't infer correctly anymore. | :green_salad: At 11:53am on Wednesday, a type test fails and you realize you should probably stop to eat some lunch before you break anything else. | 17 | | :see_no_evil: You want to make awesome TypeScript types.. but every time you try to add or fix something, you are left wondering if you broke something else. | :mechanical_arm: You can refactor with confidence. | 18 | | :crossed_fingers: Your open source project can be tough to keep up with. You want to accept community PRs but you're not sure if they possibly break something. | :partying_face: A community PR won't break anything, and you can request new tests that cover any areas of concern. | 19 | | :clown_face: You wonder about what will happen if there's some weird TypeScript edge-case where `never` or `unknown` or `any` is passed to your type by an end-user. | :mage: You don't have to be a TypeScript wizard to write great TypeScript types. You can write a test and be sure things will work as expected! | 20 | 21 | ## Quickstart 22 | 23 | What if you have a fancy TypeScript function with an inferred type in your library: 24 | 25 | ```ts 26 | const shoutItOutLoud = (str: T) => ( 27 | `${str.toUpperCase() as Uppercase}!!!` as const 28 | ); 29 | ``` 30 | 31 | The type of this function isn't just `string`, it's _the actual result_ as a TypeScript string literal. But how do you write a test for that behavior of your library? 32 | 33 | Now you can write a test for the type for your function! 34 | 35 | ```ts 36 | import { Expect, Equal } from "type-testing"; 37 | 38 | const hello = shoutItOutLoud('hello'); 39 | type test_hello = Expect>; 40 | ``` 41 | 42 | You can do this right alongside your regular tests! 43 | 44 | ```ts 45 | import { Expect, Equal } from 'type-testing'; 46 | import { shoutItOutLoud } from './shout'; 47 | 48 | describe('shoutItOudLoud', () => { 49 | it('has the intended API surface', () => { 50 | const hello = shoutItOutLoud('hello'); 51 | 52 | // this tests the type 53 | type test_hello = Expect>; 54 | 55 | // this tests the runtime behavior 56 | expect(hello).toEqual('HELLO!!!'); 57 | }); 58 | }); 59 | ``` 60 | 61 | > [!IMPORTANT] 62 | > Now, if your fancy type isn't **exactly** correct, your test suite will fail! 63 | > 64 | > just like you'd want it to 65 | 66 | ## Example: type-only libraries 67 | 68 | Consider, though, that many libraries are shipping TypeScript types _as part of the product_. In this case, they have very little way to be sure that the types they're shipping to their users are working as intended. Consider the above but written in types: 69 | 70 | ```ts 71 | type ShoutItOutLoud = `${Uppercase}!!!`; 72 | type Hello = 'hello'; 73 | 74 | // Compiler error! this should be `HELLO!!!` (all caps) 75 | type test_Hello = Expect, 'HeLLO!!!'>> 76 | ``` 77 | 78 | > protip: you can see some great examples of what these kinds of test looks like [in this very repo itself](./src/Equal.test.ts). 79 | 80 | ### Example: testing functions 81 | 82 | Now, you can write tests that will lock-in the intended behavior _of your types_. 83 | 84 | If someone changes a type parameter or return type accidentally, now you'll know about it right away! 85 | 86 | ```ts 87 | type test_params = Expect, 89 | [string] 90 | >>; 91 | 92 | type test_return = Expect, 94 | `${Uppercase}!!!` 95 | >>; 96 | ``` 97 | 98 | ## :family_man_woman_girl_boy: Your New Type Testing Family 99 | 100 | [See API Guide](./API.md) 101 | 102 | - [`Equal`](./API.md#equal) 103 | - [`NotEqual`](./API.md#notequal) 104 | - [`Expect`](./API.md#expect) 105 | - [`ExpectFalse`](./API.md#expectfalse) 106 | - [`Extends`](./API.md#extends) 107 | - [`IsAny`](./API.md#isany) 108 | - [`IsNever`](./API.md#isnever) 109 | - [`IsTuple`](./API.md#istuple) 110 | - [`IsUnion`](./API.md#isunion) 111 | - [`TrueCases`](./API.md#truecases) 112 | - [`SimpleEqual`](./API.md#simpleEquals) 113 | - [`FalseCases`](./API.md#falsecases) 114 | 115 | Also, if you just install and start using the library you'll discover that every type has lots of JSDoc description to help you along the way! 116 | 117 | ## FAQ 118 | 119 | ### Why not just rely on explicit return types? 120 | 121 | Lots of reasons. 122 | 123 | 1. Because a lot of times you can't. What you ship is often an abstraction (i.e. generic), so you really need to write a specific unit test to make sure that your end users of your library have the right type experience (whether those users are external to your team or not). 124 | 1. Because being able to write these tests while you're working enables you to do [TDD](https://en.wikipedia.org/wiki/Test-driven_development) with the types themselves. Even if you don't do full-blown 100% TDD, it's pretty useful to be able to be sure that you've got your core use-cases covered. Then, you can refactor and improve your code with a lot more confidence. 125 | 1. Because return types [can lie](https://youtu.be/I6V2FkW1ozQ?t=439). 126 | 127 | ### Is this a new idea? 128 | 129 | Nope! It's been knocking around in the TypeScript community for a while, but there has yet to be someone to write tests for these types (ironically) and package them into a micro library. 130 | 131 | ### Can `Expect` and `Equal` be combined to a type `ExpectEqual`? 132 | 133 | Unfortunately, no. The power of this approach taken in this library is that it will error at build time while being type checked, and currently there's no way to combine the two utilities. 134 | 135 | If you do happen to find a way... we're all waiting to hear about it! File an issue!! 136 | 137 | ### Where are all the aliases? 138 | 139 | > "no API is the best API". 140 | 141 | You may have seen a version of this code copied around that contained aliases for `Expect` like `IsTrue` and `ExpectTrue`, as well as aliases for `ExpectFalse` like `IsFalse`. The hope is to avoid people having to learn or memorize the quirks of different assertion APIs. This is still a pretty cutting-edge part of the TypeScript world, but if you look around, you're going to find that 98% of the time (or more!) you just need `Expect` and `Equal`. 142 | 143 | ### What about \_\_\_\_\_ other way of testing types? 144 | 145 | You might be familiar with other projects that attempt to do something similar. Here's a quick overview: 146 | 147 | - [`eslint-plugin-expect-type`](https://www.npmjs.com/package/eslint-plugin-expect-type) is powerful, but relies on comments and requires ESLint. This means that refactoring and renaming will get out of sync because the tests themselves aren't actually code. On top of that, there's a problem with the order of unions in TypeScript not being stable from release-to-release, which causes very annoying false positive test failures that you have to manually fix. 148 | - [`tsd`](https://github.com/SamVerschueren/tsd) is nice, but it's not for the type layer itself. For that matter, there are a few nice alternatives if you can integrate this into your test runner, e.g. Jest's [Expect](https://github.com/facebook/jest/blob/main/packages/expect/src/types.ts#L99) type is built into it's assertion functions. The same goes for [`expect-type`](https://github.com/mmkal/expect-type). 149 | - [`ts-expect`](https://github.com/TypeStrong/ts-expect) is probably the most similar currently existing thing but it is built more for things that have runtime manifestations (i.e. things that JavaScript, unlike TypeScript types). The code in this library is already battle tested and used by lots of production projects. 150 | 151 | ### What about ESLint/TypeScript complaining of unused variables? 152 | 153 | If you have [noUnusedLocals](https://www.typescriptlang.org/tsconfig#noUnusedLocals) enabled, you can safely disable it just for your [`tsconfig.eslint.json`](https://typescript-eslint.io/linting/typed-linting/monorepos#one-root-tsconfigjson). It can still be left on for building for production. 154 | 155 | For ESLint, there's a more powerful way to do it which is just to turn off this specific pattern for test files: 156 | 157 | ```ts 158 | /** @type { import('@typescript-eslint/utils').TSESLint.Linter.Config } */ 159 | const config = { 160 | overrides: [ 161 | { 162 | files: ['**/*.test.ts', '**/*.test.tsx'], 163 | rules: { 164 | '@typescript-eslint/no-unused-vars': [ 165 | 'error', 166 | { 167 | vars: 'all', 168 | varsIgnorePattern: 'test_.*', // TypeScript type tests 169 | argsIgnorePattern: '_', 170 | }, 171 | ], 172 | }, 173 | }, 174 | ], 175 | }; 176 | ``` 177 | 178 | ### So, it looks like TypeScript has jumped the shark, eh? 🦈🌊🦈 179 | 180 | Well. This level of type testing isn't "for everyone". It's largely for library authors or projects that have very powerful inferred types. 181 | 182 | If: 183 | 184 | - you don't use a lot of generics 185 | - you aren't using TypeScript `strict` mode 186 | - you use `any` a lot 187 | - your codebase has hundreds of `// @ts-ignore` lines 188 | - your library or project doesn't mind breaking changes at the type layer 189 | 190 | ...then this library might not be something you'd benefit from introducing. 191 | 192 | But as we start seeing projects like Zod, where the types "working" is fully half of the entire project (or type-fest where it's the _whole_ project).. it starts to feel a little lopsided to be able to write tests and assertions about the JavaScript part, but not as much about the TypeScript part. 193 | 194 | ### Why not just pass the type to the generic of `expect`? 195 | 196 | Depending on your testing library, you may be able to do something like this: 197 | 198 | ```ts 199 | expect<'HELLO!!!'>(hello).toEqual('HELLO!!!'); 200 | ``` 201 | 202 | This approach can work, but there are lots of libraries that operate exclusively at the type level. They could never use such an approach because they don't have any runtime code with which to test. 203 | 204 | You also put yourself at the mercy of the error message generated by the expect types. With type-testing, it's quite clear that the types are wrong, and therefore much more clear what you need to fix. 205 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "type-testing", 3 | "version": "0.2.0", 4 | "engines": { 5 | "node": ">= 20.x" 6 | }, 7 | "repository": "https://github.com/MichiganTypeScript/type-testing", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "description": "🌱 A micro library for testing your TypeScript types", 13 | "scripts": { 14 | "test:code": "tsc", 15 | "test:build": "attw --pack", 16 | "test": "pnpm run test:code && pnpm run test:build", 17 | "build:markdown": "tsx ./scripts/generate.ts", 18 | "clean": "tsx ./scripts/clean.mts", 19 | "build:code": "tsc --project tsconfig.build.json", 20 | "build": "pnpm clean && pnpm build:code && pnpm build:markdown" 21 | }, 22 | "license": "MIT", 23 | "keywords": [ 24 | "typescript", 25 | "ts", 26 | "types", 27 | "utility", 28 | "util", 29 | "utilities", 30 | "testing", 31 | "type", 32 | "generics" 33 | ], 34 | "devDependencies": { 35 | "@arethetypeswrong/cli": "^0.11.0", 36 | "@types/node": "^20.8.2", 37 | "tsx": "^3.13.0", 38 | "typescript": "^5.2.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@arethetypeswrong/cli': 9 | specifier: ^0.11.0 10 | version: 0.11.0 11 | '@types/node': 12 | specifier: ^20.8.2 13 | version: 20.8.2 14 | tsx: 15 | specifier: ^3.13.0 16 | version: 3.13.0 17 | typescript: 18 | specifier: ^5.2.2 19 | version: 5.2.2 20 | 21 | packages: 22 | 23 | /@andrewbranch/untar.js@1.0.2: 24 | resolution: {integrity: sha512-hL80MHK3b++pEp6K23+Nl5r5D1F19DRagp2ruCBIv4McyCiLKq67vUNvEQY1aGCAKNZ8GxV23n5MhOm7RwO8Pg==} 25 | dev: true 26 | 27 | /@arethetypeswrong/cli@0.11.0: 28 | resolution: {integrity: sha512-0GxlV58x8DaiWRUx0JWc3glr8iGsjK+KATuubH8qRqjw4aIGB8BBQ8Bv2Bvs9DfGuojBgbVWhOTe8+3BepYZ6Q==} 29 | hasBin: true 30 | dependencies: 31 | '@arethetypeswrong/core': 0.10.2 32 | chalk: 4.1.2 33 | cli-table3: 0.6.3 34 | commander: 10.0.1 35 | marked: 5.1.2 36 | marked-terminal: 5.2.0(marked@5.1.2) 37 | node-fetch: 2.7.0 38 | semver: 7.5.4 39 | transitivePeerDependencies: 40 | - encoding 41 | dev: true 42 | 43 | /@arethetypeswrong/core@0.10.2: 44 | resolution: {integrity: sha512-jL1MPpZKuMkm0EbZn1OLgdTO5hci7g79EUSELEnATdFyTgbPUxJwTXi1Kdont/BG05BZCcxZnOQgYO/whVmwdA==} 45 | dependencies: 46 | '@andrewbranch/untar.js': 1.0.2 47 | fetch-ponyfill: 7.1.0 48 | fflate: 0.7.4 49 | semver: 7.5.4 50 | typescript: 5.2.2 51 | validate-npm-package-name: 5.0.0 52 | transitivePeerDependencies: 53 | - encoding 54 | dev: true 55 | 56 | /@colors/colors@1.5.0: 57 | resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} 58 | engines: {node: '>=0.1.90'} 59 | requiresBuild: true 60 | dev: true 61 | optional: true 62 | 63 | /@esbuild/android-arm64@0.18.20: 64 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} 65 | engines: {node: '>=12'} 66 | cpu: [arm64] 67 | os: [android] 68 | requiresBuild: true 69 | dev: true 70 | optional: true 71 | 72 | /@esbuild/android-arm@0.18.20: 73 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} 74 | engines: {node: '>=12'} 75 | cpu: [arm] 76 | os: [android] 77 | requiresBuild: true 78 | dev: true 79 | optional: true 80 | 81 | /@esbuild/android-x64@0.18.20: 82 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} 83 | engines: {node: '>=12'} 84 | cpu: [x64] 85 | os: [android] 86 | requiresBuild: true 87 | dev: true 88 | optional: true 89 | 90 | /@esbuild/darwin-arm64@0.18.20: 91 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} 92 | engines: {node: '>=12'} 93 | cpu: [arm64] 94 | os: [darwin] 95 | requiresBuild: true 96 | dev: true 97 | optional: true 98 | 99 | /@esbuild/darwin-x64@0.18.20: 100 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} 101 | engines: {node: '>=12'} 102 | cpu: [x64] 103 | os: [darwin] 104 | requiresBuild: true 105 | dev: true 106 | optional: true 107 | 108 | /@esbuild/freebsd-arm64@0.18.20: 109 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} 110 | engines: {node: '>=12'} 111 | cpu: [arm64] 112 | os: [freebsd] 113 | requiresBuild: true 114 | dev: true 115 | optional: true 116 | 117 | /@esbuild/freebsd-x64@0.18.20: 118 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} 119 | engines: {node: '>=12'} 120 | cpu: [x64] 121 | os: [freebsd] 122 | requiresBuild: true 123 | dev: true 124 | optional: true 125 | 126 | /@esbuild/linux-arm64@0.18.20: 127 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} 128 | engines: {node: '>=12'} 129 | cpu: [arm64] 130 | os: [linux] 131 | requiresBuild: true 132 | dev: true 133 | optional: true 134 | 135 | /@esbuild/linux-arm@0.18.20: 136 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} 137 | engines: {node: '>=12'} 138 | cpu: [arm] 139 | os: [linux] 140 | requiresBuild: true 141 | dev: true 142 | optional: true 143 | 144 | /@esbuild/linux-ia32@0.18.20: 145 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} 146 | engines: {node: '>=12'} 147 | cpu: [ia32] 148 | os: [linux] 149 | requiresBuild: true 150 | dev: true 151 | optional: true 152 | 153 | /@esbuild/linux-loong64@0.18.20: 154 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} 155 | engines: {node: '>=12'} 156 | cpu: [loong64] 157 | os: [linux] 158 | requiresBuild: true 159 | dev: true 160 | optional: true 161 | 162 | /@esbuild/linux-mips64el@0.18.20: 163 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} 164 | engines: {node: '>=12'} 165 | cpu: [mips64el] 166 | os: [linux] 167 | requiresBuild: true 168 | dev: true 169 | optional: true 170 | 171 | /@esbuild/linux-ppc64@0.18.20: 172 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} 173 | engines: {node: '>=12'} 174 | cpu: [ppc64] 175 | os: [linux] 176 | requiresBuild: true 177 | dev: true 178 | optional: true 179 | 180 | /@esbuild/linux-riscv64@0.18.20: 181 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} 182 | engines: {node: '>=12'} 183 | cpu: [riscv64] 184 | os: [linux] 185 | requiresBuild: true 186 | dev: true 187 | optional: true 188 | 189 | /@esbuild/linux-s390x@0.18.20: 190 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} 191 | engines: {node: '>=12'} 192 | cpu: [s390x] 193 | os: [linux] 194 | requiresBuild: true 195 | dev: true 196 | optional: true 197 | 198 | /@esbuild/linux-x64@0.18.20: 199 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} 200 | engines: {node: '>=12'} 201 | cpu: [x64] 202 | os: [linux] 203 | requiresBuild: true 204 | dev: true 205 | optional: true 206 | 207 | /@esbuild/netbsd-x64@0.18.20: 208 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} 209 | engines: {node: '>=12'} 210 | cpu: [x64] 211 | os: [netbsd] 212 | requiresBuild: true 213 | dev: true 214 | optional: true 215 | 216 | /@esbuild/openbsd-x64@0.18.20: 217 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} 218 | engines: {node: '>=12'} 219 | cpu: [x64] 220 | os: [openbsd] 221 | requiresBuild: true 222 | dev: true 223 | optional: true 224 | 225 | /@esbuild/sunos-x64@0.18.20: 226 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} 227 | engines: {node: '>=12'} 228 | cpu: [x64] 229 | os: [sunos] 230 | requiresBuild: true 231 | dev: true 232 | optional: true 233 | 234 | /@esbuild/win32-arm64@0.18.20: 235 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} 236 | engines: {node: '>=12'} 237 | cpu: [arm64] 238 | os: [win32] 239 | requiresBuild: true 240 | dev: true 241 | optional: true 242 | 243 | /@esbuild/win32-ia32@0.18.20: 244 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} 245 | engines: {node: '>=12'} 246 | cpu: [ia32] 247 | os: [win32] 248 | requiresBuild: true 249 | dev: true 250 | optional: true 251 | 252 | /@esbuild/win32-x64@0.18.20: 253 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} 254 | engines: {node: '>=12'} 255 | cpu: [x64] 256 | os: [win32] 257 | requiresBuild: true 258 | dev: true 259 | optional: true 260 | 261 | /@types/node@20.8.2: 262 | resolution: {integrity: sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==} 263 | dev: true 264 | 265 | /ansi-escapes@6.2.0: 266 | resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} 267 | engines: {node: '>=14.16'} 268 | dependencies: 269 | type-fest: 3.13.1 270 | dev: true 271 | 272 | /ansi-regex@5.0.1: 273 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 274 | engines: {node: '>=8'} 275 | dev: true 276 | 277 | /ansi-styles@4.3.0: 278 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 279 | engines: {node: '>=8'} 280 | dependencies: 281 | color-convert: 2.0.1 282 | dev: true 283 | 284 | /ansicolors@0.3.2: 285 | resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} 286 | dev: true 287 | 288 | /buffer-from@1.1.2: 289 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 290 | dev: true 291 | 292 | /builtins@5.0.1: 293 | resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} 294 | dependencies: 295 | semver: 7.5.4 296 | dev: true 297 | 298 | /cardinal@2.1.1: 299 | resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} 300 | hasBin: true 301 | dependencies: 302 | ansicolors: 0.3.2 303 | redeyed: 2.1.1 304 | dev: true 305 | 306 | /chalk@4.1.2: 307 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 308 | engines: {node: '>=10'} 309 | dependencies: 310 | ansi-styles: 4.3.0 311 | supports-color: 7.2.0 312 | dev: true 313 | 314 | /chalk@5.3.0: 315 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 316 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 317 | dev: true 318 | 319 | /cli-table3@0.6.3: 320 | resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} 321 | engines: {node: 10.* || >= 12.*} 322 | dependencies: 323 | string-width: 4.2.3 324 | optionalDependencies: 325 | '@colors/colors': 1.5.0 326 | dev: true 327 | 328 | /color-convert@2.0.1: 329 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 330 | engines: {node: '>=7.0.0'} 331 | dependencies: 332 | color-name: 1.1.4 333 | dev: true 334 | 335 | /color-name@1.1.4: 336 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 337 | dev: true 338 | 339 | /commander@10.0.1: 340 | resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} 341 | engines: {node: '>=14'} 342 | dev: true 343 | 344 | /emoji-regex@8.0.0: 345 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 346 | dev: true 347 | 348 | /esbuild@0.18.20: 349 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} 350 | engines: {node: '>=12'} 351 | hasBin: true 352 | requiresBuild: true 353 | optionalDependencies: 354 | '@esbuild/android-arm': 0.18.20 355 | '@esbuild/android-arm64': 0.18.20 356 | '@esbuild/android-x64': 0.18.20 357 | '@esbuild/darwin-arm64': 0.18.20 358 | '@esbuild/darwin-x64': 0.18.20 359 | '@esbuild/freebsd-arm64': 0.18.20 360 | '@esbuild/freebsd-x64': 0.18.20 361 | '@esbuild/linux-arm': 0.18.20 362 | '@esbuild/linux-arm64': 0.18.20 363 | '@esbuild/linux-ia32': 0.18.20 364 | '@esbuild/linux-loong64': 0.18.20 365 | '@esbuild/linux-mips64el': 0.18.20 366 | '@esbuild/linux-ppc64': 0.18.20 367 | '@esbuild/linux-riscv64': 0.18.20 368 | '@esbuild/linux-s390x': 0.18.20 369 | '@esbuild/linux-x64': 0.18.20 370 | '@esbuild/netbsd-x64': 0.18.20 371 | '@esbuild/openbsd-x64': 0.18.20 372 | '@esbuild/sunos-x64': 0.18.20 373 | '@esbuild/win32-arm64': 0.18.20 374 | '@esbuild/win32-ia32': 0.18.20 375 | '@esbuild/win32-x64': 0.18.20 376 | dev: true 377 | 378 | /esprima@4.0.1: 379 | resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} 380 | engines: {node: '>=4'} 381 | hasBin: true 382 | dev: true 383 | 384 | /fetch-ponyfill@7.1.0: 385 | resolution: {integrity: sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==} 386 | dependencies: 387 | node-fetch: 2.6.13 388 | transitivePeerDependencies: 389 | - encoding 390 | dev: true 391 | 392 | /fflate@0.7.4: 393 | resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} 394 | dev: true 395 | 396 | /fsevents@2.3.3: 397 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 398 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 399 | os: [darwin] 400 | requiresBuild: true 401 | dev: true 402 | optional: true 403 | 404 | /get-tsconfig@4.7.2: 405 | resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} 406 | dependencies: 407 | resolve-pkg-maps: 1.0.0 408 | dev: true 409 | 410 | /has-flag@4.0.0: 411 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 412 | engines: {node: '>=8'} 413 | dev: true 414 | 415 | /is-fullwidth-code-point@3.0.0: 416 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 417 | engines: {node: '>=8'} 418 | dev: true 419 | 420 | /lodash@4.17.21: 421 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 422 | dev: true 423 | 424 | /lru-cache@6.0.0: 425 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 426 | engines: {node: '>=10'} 427 | dependencies: 428 | yallist: 4.0.0 429 | dev: true 430 | 431 | /marked-terminal@5.2.0(marked@5.1.2): 432 | resolution: {integrity: sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==} 433 | engines: {node: '>=14.13.1 || >=16.0.0'} 434 | peerDependencies: 435 | marked: ^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 436 | dependencies: 437 | ansi-escapes: 6.2.0 438 | cardinal: 2.1.1 439 | chalk: 5.3.0 440 | cli-table3: 0.6.3 441 | marked: 5.1.2 442 | node-emoji: 1.11.0 443 | supports-hyperlinks: 2.3.0 444 | dev: true 445 | 446 | /marked@5.1.2: 447 | resolution: {integrity: sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==} 448 | engines: {node: '>= 16'} 449 | hasBin: true 450 | dev: true 451 | 452 | /node-emoji@1.11.0: 453 | resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} 454 | dependencies: 455 | lodash: 4.17.21 456 | dev: true 457 | 458 | /node-fetch@2.6.13: 459 | resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==} 460 | engines: {node: 4.x || >=6.0.0} 461 | peerDependencies: 462 | encoding: ^0.1.0 463 | peerDependenciesMeta: 464 | encoding: 465 | optional: true 466 | dependencies: 467 | whatwg-url: 5.0.0 468 | dev: true 469 | 470 | /node-fetch@2.7.0: 471 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 472 | engines: {node: 4.x || >=6.0.0} 473 | peerDependencies: 474 | encoding: ^0.1.0 475 | peerDependenciesMeta: 476 | encoding: 477 | optional: true 478 | dependencies: 479 | whatwg-url: 5.0.0 480 | dev: true 481 | 482 | /redeyed@2.1.1: 483 | resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} 484 | dependencies: 485 | esprima: 4.0.1 486 | dev: true 487 | 488 | /resolve-pkg-maps@1.0.0: 489 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 490 | dev: true 491 | 492 | /semver@7.5.4: 493 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 494 | engines: {node: '>=10'} 495 | hasBin: true 496 | dependencies: 497 | lru-cache: 6.0.0 498 | dev: true 499 | 500 | /source-map-support@0.5.21: 501 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 502 | dependencies: 503 | buffer-from: 1.1.2 504 | source-map: 0.6.1 505 | dev: true 506 | 507 | /source-map@0.6.1: 508 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 509 | engines: {node: '>=0.10.0'} 510 | dev: true 511 | 512 | /string-width@4.2.3: 513 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 514 | engines: {node: '>=8'} 515 | dependencies: 516 | emoji-regex: 8.0.0 517 | is-fullwidth-code-point: 3.0.0 518 | strip-ansi: 6.0.1 519 | dev: true 520 | 521 | /strip-ansi@6.0.1: 522 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 523 | engines: {node: '>=8'} 524 | dependencies: 525 | ansi-regex: 5.0.1 526 | dev: true 527 | 528 | /supports-color@7.2.0: 529 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 530 | engines: {node: '>=8'} 531 | dependencies: 532 | has-flag: 4.0.0 533 | dev: true 534 | 535 | /supports-hyperlinks@2.3.0: 536 | resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} 537 | engines: {node: '>=8'} 538 | dependencies: 539 | has-flag: 4.0.0 540 | supports-color: 7.2.0 541 | dev: true 542 | 543 | /tr46@0.0.3: 544 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 545 | dev: true 546 | 547 | /tsx@3.13.0: 548 | resolution: {integrity: sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==} 549 | hasBin: true 550 | dependencies: 551 | esbuild: 0.18.20 552 | get-tsconfig: 4.7.2 553 | source-map-support: 0.5.21 554 | optionalDependencies: 555 | fsevents: 2.3.3 556 | dev: true 557 | 558 | /type-fest@3.13.1: 559 | resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} 560 | engines: {node: '>=14.16'} 561 | dev: true 562 | 563 | /typescript@5.2.2: 564 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} 565 | engines: {node: '>=14.17'} 566 | hasBin: true 567 | dev: true 568 | 569 | /validate-npm-package-name@5.0.0: 570 | resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} 571 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 572 | dependencies: 573 | builtins: 5.0.1 574 | dev: true 575 | 576 | /webidl-conversions@3.0.1: 577 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 578 | dev: true 579 | 580 | /whatwg-url@5.0.0: 581 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 582 | dependencies: 583 | tr46: 0.0.3 584 | webidl-conversions: 3.0.1 585 | dev: true 586 | 587 | /yallist@4.0.0: 588 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 589 | dev: true 590 | -------------------------------------------------------------------------------- /scripts/API.source.md: -------------------------------------------------------------------------------- 1 | # API Guide 2 | 3 | - [API Guide](#api-guide) 4 | - [Equal](#equal) 5 | - [NotEqual](#notequal) 6 | - [Expect](#expect) 7 | - [ExpectFalse](#expectfalse) 8 | - [Extends](#extends) 9 | - [IsAny](#isany) 10 | - [IsNever](#isnever) 11 | - [IsTuple](#istuple) 12 | - [IsUnion](#isunion) 13 | - [IsUnknown](#isunknown) 14 | - [SimpleEqual](#simpleequals) 15 | - [TrueCases](#truecases) 16 | - [FalseCases](#falsecases) 17 | 18 | ## Equal 19 | 20 | 21 | 22 | ## NotEqual 23 | 24 | 25 | 26 | ## Expect 27 | 28 | 29 | 30 | ## ExpectFalse 31 | 32 | 33 | 34 | ## Extends 35 | 36 | 37 | 38 | ## IsAny 39 | 40 | 41 | 42 | ## IsNever 43 | 44 | 45 | 46 | ## IsTuple 47 | 48 | 49 | 50 | ## IsUnion 51 | 52 | 53 | 54 | ## IsUnknown 55 | 56 | 57 | 58 | ## SimpleEqual 59 | 60 | 61 | 62 | ## TrueCases 63 | 64 | 65 | 66 | ## FalseCases 67 | 68 | -------------------------------------------------------------------------------- /scripts/clean.mts: -------------------------------------------------------------------------------- 1 | import { rm } from 'fs/promises'; 2 | import { join } from 'path'; 3 | 4 | const path = join(process.cwd(), 'dist'); 5 | 6 | await rm(path, { recursive: true, force: true }); -------------------------------------------------------------------------------- /scripts/generate.ts: -------------------------------------------------------------------------------- 1 | import { createProgram, forEachChild, isJSDocCommentContainingNode, isTypeAliasDeclaration } from 'typescript'; 2 | import { readFileSync, writeFileSync } from 'fs'; 3 | 4 | type TypeName = string; 5 | type JSDoc = string; 6 | 7 | const generate = () => { 8 | const declarations: Record = {} 9 | 10 | const program = createProgram(['./src/index.ts'], {}); 11 | program.getTypeChecker(); 12 | 13 | program.getSourceFiles() 14 | .filter(({ fileName }) => fileName.includes('/type-testing/src/')) 15 | .forEach(sourceFile => { 16 | forEachChild(sourceFile, node => { 17 | if (isTypeAliasDeclaration(node)) { 18 | node.getChildren().forEach(child => { 19 | if (isJSDocCommentContainingNode(child)) { 20 | // @ts-expect-error 21 | declarations[node.name.text] = child.comment; 22 | } 23 | }) 24 | } 25 | }) 26 | }); 27 | 28 | let readme = readFileSync('./scripts/API.source.md', 'utf-8'); 29 | 30 | const regex = /\<\!-- Insert JSDoc: (?.*) --\>/gm; 31 | readme = readme.split('\n').flatMap(line => { 32 | try { 33 | const { typeName } = line.matchAll(regex).next().value.groups; 34 | console.log('replacing: ', typeName); 35 | return declarations[typeName] 36 | } catch (error) { 37 | } 38 | 39 | return line; 40 | }).join('\n'); 41 | 42 | 43 | writeFileSync('./API.md', readme); 44 | }; 45 | 46 | generate(); -------------------------------------------------------------------------------- /src/Equal.test.ts: -------------------------------------------------------------------------------- 1 | // note: every other test utilizes this type 2 | 3 | import { Equal } from "./Equal"; 4 | import { Expect } from "./Expect"; 5 | 6 | type Cases = [ 7 | Expect, true>>, 8 | Expect, true>>, 9 | Expect, true>>, 10 | Expect, true>>, 11 | Expect, true>>, 12 | Expect, true>>, 13 | Expect, true>>, 14 | Expect, true>>, 15 | Expect, true>>, 16 | Expect, true>>, 17 | Expect, true>>, 18 | 19 | /// Union 20 | Expect, false>>, 21 | Expect, false>>, 22 | Expect, false>>, 23 | Expect, false>>, 24 | 25 | /// Intersection 26 | Expect, false>>, // intersection detection :) 27 | Expect, true>>, // expected behavior of resolution 28 | Expect, true>>, // expected behavior of resolution 29 | 30 | /// Literal Values 31 | Expect, false>>, // literal numbers 32 | Expect, false>>, // literal string 33 | Expect, false>>, // literal bigint 34 | Expect, false>>, // literal bigint vs literal number 35 | Expect, false>>, // literal boolean vs boolean 36 | Expect, false>>, // literal string vs string 37 | Expect, false>>, // literal number vs number 38 | Expect, false>>, // literal bigint vs bigint 39 | Expect, false>>, // tuple order matters 40 | Expect, false>>, // empty tuple detection 41 | Expect, false>>, // any vs {} 42 | Expect, false>>, // any vs literal number 43 | Expect, false>>, 44 | Expect, false>>, 45 | 46 | Expect, false>>, 47 | Expect, false>>, 48 | Expect, false>>, 49 | Expect, false>>, 50 | ]; 51 | 52 | type Errors = [ 53 | Expect, 56 | any 57 | >>, 58 | 59 | Expect, 62 | any 63 | >>, 64 | ]; -------------------------------------------------------------------------------- /src/Equal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * returns `true` if the provided arguments resolve to the same TypeScript value. 3 | * 4 | * `Equal` is the cornerstone of TypeScript type testing. 5 | */ 6 | export type Equal = 7 | (() => T extends A ? 1 : 2) extends 8 | (() => T extends B ? 1 : 2) 9 | ? true 10 | : false; 11 | -------------------------------------------------------------------------------- /src/Expect.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from './Equal'; 2 | import { Expect } from './Expect'; 3 | 4 | type Cases = [ 5 | Expect, true>>, 6 | Expect, false>>, 7 | ]; 8 | 9 | type Errors = [ 10 | Expect, 13 | false 14 | >>, 15 | 16 | Expect, 19 | false 20 | >>, 21 | 22 | Expect, 25 | false 26 | >>, 27 | 28 | Expect, 31 | false 32 | >>, 33 | 34 | Expect, 37 | false 38 | >>, 39 | 40 | Expect, 43 | false 44 | >>, 45 | 46 | Expect, 49 | false 50 | >>, 51 | 52 | Expect, 55 | false 56 | >>, 57 | 58 | Expect, 61 | false 62 | >>, 63 | 64 | Expect, 67 | false 68 | >>, 69 | 70 | Expect, 73 | any 74 | >>, 75 | ]; 76 | -------------------------------------------------------------------------------- /src/Expect.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | 3 | /** 4 | * errors at TypeScript compile-time if passed a value that is not `true`: 5 | * 6 | * the following will not error and will return `true`; 7 | * 8 | * ```ts 9 | * Expect; 10 | * ``` 11 | * 12 | * all other inputs will return `false`. 13 | * 14 | * all other inputs will error, except for `never`. 15 | */ 16 | export type Expect = Equal; 17 | -------------------------------------------------------------------------------- /src/ExpectFalse.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from './Equal'; 2 | import { Expect } from './Expect'; 3 | import { ExpectFalse } from './ExpectFalse'; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | Expect, false>>, 8 | ]; 9 | 10 | type Errors = [ 11 | Expect, 14 | false 15 | >>, 16 | 17 | Expect, 20 | false 21 | >>, 22 | 23 | Expect, 26 | false 27 | >>, 28 | 29 | Expect, 32 | false 33 | >>, 34 | 35 | Expect, 38 | false 39 | >>, 40 | 41 | Expect, 44 | false 45 | >>, 46 | 47 | Expect, 50 | false 51 | >>, 52 | 53 | Expect, 56 | false 57 | >>, 58 | 59 | Expect, 62 | false 63 | >>, 64 | 65 | Expect, 68 | false 69 | >>, 70 | 71 | Expect, 74 | any 75 | >>, 76 | ]; 77 | -------------------------------------------------------------------------------- /src/ExpectFalse.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | 3 | /** 4 | * A type that will error if passed anything other than literal `false` or `never` 5 | * 6 | * The following will not error: 7 | * 8 | * ```ts 9 | * IsFalse; 10 | * IsFalse; 11 | * ``` 12 | * 13 | * The following will error: 14 | * 15 | * ```ts 16 | * IsFalse; 17 | * IsFalse; 18 | * IsFalse<1 | false>; 19 | * IsFalse<'false'>; 20 | * IsFalse<''>; 21 | * IsFalse<0>; 22 | * IsFalse; 23 | * IsFalse; 24 | * IsFalse; 25 | * ``` 26 | */ 27 | export type ExpectFalse = Equal; -------------------------------------------------------------------------------- /src/Extends.test.ts: -------------------------------------------------------------------------------- 1 | // note: every other test utilizes this type 2 | 3 | import { Equal } from "./Equal"; 4 | import { Expect } from "./Expect"; 5 | import { Extends } from "./Extends"; 6 | 7 | type Cases = [ 8 | Expect, true>>, 9 | Expect, true>>, 10 | Expect, true>>, 11 | Expect, true>>, 12 | Expect, true>>, 13 | Expect, true>>, 14 | Expect, true>>, 15 | Expect, true>>, 16 | 17 | // perhaps a surprising result 18 | Expect, never>>, 19 | Expect, true>>, 20 | Expect, true>>, 21 | 22 | /// Union 23 | 24 | // perhaps a surprising result 25 | Expect, boolean>>, 26 | Expect, true>>, 27 | Expect, true>>, 28 | Expect, true>>, 29 | 30 | /// Intersection 31 | Expect, true>>, 32 | Expect, never>>, 33 | Expect, never>>, 34 | 35 | /// Literal Values 36 | Expect, false>>, 37 | Expect, false>>, 38 | Expect, false>>, 39 | Expect, false>>, 40 | Expect, true>>, 41 | Expect, true>>, 42 | Expect, true>>, 43 | Expect, true>>, 44 | Expect, false>>, 45 | Expect, true>>, 46 | Expect, boolean>>, 47 | Expect, boolean>>, 48 | Expect, false>>, 49 | Expect, never>>, 50 | 51 | Expect, boolean>>, 52 | Expect, false>>, 53 | Expect, false>>, 54 | Expect, never>>, 55 | 56 | /// Literal Values (flipped) 57 | Expect, boolean>>, 58 | Expect, false>>, 59 | Expect, false>>, 60 | Expect, false>>, 61 | Expect, false>>, 62 | Expect, true>>, 63 | Expect, true>>, 64 | Expect, true>>, 65 | Expect, false>>, 66 | 67 | Expect, never>>, 68 | Expect, true>>, 69 | Expect, false>>, 70 | Expect, never>>, 71 | ]; 72 | 73 | type Errors = [ 74 | Expect, 77 | any 78 | >>, 79 | 80 | Expect, 83 | any 84 | >>, 85 | ]; -------------------------------------------------------------------------------- /src/Extends.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ONLY use this type if you know exactly what you're doing. 3 | * 4 | * This type appears to return `true` if and only if `A extends B`, however, if you look at the tests you'll find that this is unfortunately not quite exactly how TypeScript works. There are many edge cases that return surprising results due to how TypeScript is structured. Of course these results may make sense eventually if you look deeply enough at them, but the hazard is always present. 5 | * 6 | * For example, distributivity is in play any time you set up `extends` clauses, which means that it can actually return _MULTIPLE_ paths at once (yielding a return type of `boolean`) or even _NO PATHS_ (yielding a return type of `never`). 7 | * 8 | * ```ts 9 | * Extends<1 & 2, never> //=> never 10 | * Extends //=> never 11 | * Extends //=> never 12 | * 13 | * Extends //=> boolean 14 | * Extends<1 | 2, 1> //=> boolean 15 | * Extends //=> boolean 16 | * Extends //=> boolean 17 | * 18 | * Extends<[], unknown[]> //=> true 19 | * Extends //=> false 20 | * 21 | * Extends //=> boolean 22 | * Extends<{}, any> //=> true 23 | * 24 | * Extends //=> never 25 | * Extends<1, never> //=> false 26 | * 27 | * Extends<{}, unknown> //=> true 28 | * Extends //=> false 29 | * ``` 30 | * 31 | * @deprecated It's marked deprecated because the risk of accidental misuse is so high unless you're absolutely certain you know what you're doing. There are rare but present situations when you want this type instead of Equal, hence it's inclusion in the library. 32 | */ 33 | export type Extends = A extends B ? true : false; 34 | -------------------------------------------------------------------------------- /src/FalseCases.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | import { Expect } from "./Expect"; 3 | import { FalseCases } from "./FalseCases"; 4 | import { IsNever } from "./IsNever"; 5 | 6 | type EEF = [ 7 | Expect, false>>, 8 | Expect, false>>, 9 | // @ts-expect-error(2344) errors at the level of the individual case 10 | Expect, false>>, 11 | Expect, false>>, 12 | Expect, false>>, 13 | Expect, false>>, 14 | 15 | Expect, false>>, 16 | Expect, false>>, 17 | Expect, false>>, 18 | Expect, false>>, 19 | Expect, false>>, 20 | ]; 21 | 22 | // @ts-expect-error(2344) must be ignored at the top level 23 | type Bad = FalseCases<[ 24 | // cannot be ignored here, at the individual case 25 | IsNever, 26 | ]>; 27 | 28 | type F = FalseCases<[ 29 | IsNever<''>, 30 | IsNever, 31 | IsNever<[]>, 32 | IsNever<{}>, 33 | IsNever, 34 | 35 | FalseCases<[]>, 36 | FalseCases<[never]>, 37 | FalseCases<[any]>, 38 | FalseCases, 39 | FalseCases, 40 | ]>; 41 | 42 | type Errors = [ 43 | Expect, 46 | any 47 | >>, 48 | 49 | Expect, 52 | false 53 | >>, 54 | ] -------------------------------------------------------------------------------- /src/FalseCases.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | import { IsTuple } from "./IsTuple"; 3 | 4 | /** 5 | * A helper type that will allow you to test many cases at once with minimal boilerplate. 6 | * 7 | * instead of 8 | * 9 | * ```ts 10 | * type FalseCases = [ 11 | * Expect, false>>, 12 | * Expect, false>>, 13 | * Expect, false>>, 14 | * Expect, false>>, 15 | * Expect, false>>, 16 | * Expect, false>>, 17 | * ]; 18 | * ``` 19 | * 20 | * you can write: 21 | * 22 | * ```ts 23 | * type F = FalseCases<[ 24 | * IsNever<''>, 25 | * IsNever, 26 | * IsNever, 27 | * IsNever<[]>, 28 | * IsNever<{}>, 29 | * IsNever, 30 | * ]>; 31 | * ``` 32 | * 33 | * The drawback of this type is that the error message is not as friendly if one of the test cases has an error: 34 | * 35 | * ```text 36 | * Type '[false, false, true, false, false, false]' does not satisfy the constraint 'readonly false[]'. 37 | * Type 'boolean' is not assignable to type 'false'.ts(2344) 38 | * ``` 39 | * 40 | * Whereas with inline `Expect` and `Equal` you'd get an error just on the line of the failing test. 41 | * 42 | * If the tradeoff of debuggability is desirable to you, then use this type. 43 | */ 44 | export type FalseCases = 45 | Equal extends true 46 | ? IsTuple extends true 47 | ? true 48 | : false 49 | : false; -------------------------------------------------------------------------------- /src/IsAny.test.ts: -------------------------------------------------------------------------------- 1 | import { IsAny } from './IsAny'; 2 | import { Expect } from './Expect'; 3 | import { Equal } from './Equal'; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | Expect, true>>, // a type resolving to any 8 | 9 | Expect, false>>, 10 | Expect, false>>, 11 | Expect, false>>, 12 | Expect, false>>, 13 | ]; 14 | 15 | type Errors = [ 16 | Expect, 19 | any 20 | >>, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/IsAny.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns `true` if the input is of type `any` and false for all other inputs. 3 | * 4 | * The following will return true: 5 | * 6 | * ```ts 7 | * IsAny; 8 | * ``` 9 | * 10 | * The following will return false: 11 | * 12 | * ```ts 13 | * IsAny; 14 | * IsAny; 15 | * IsAny; 16 | * IsAny; 17 | * ``` 18 | * 19 | * Note unions with `any` resolve to `any`, so: 20 | * 21 | * ```ts 22 | * IsAny; 23 | * ``` 24 | * 25 | * returns `true`; 26 | */ 27 | export type IsAny = 0 extends (1 & T) ? true : false; 28 | -------------------------------------------------------------------------------- /src/IsNever.test.ts: -------------------------------------------------------------------------------- 1 | import { IsNever } from './IsNever'; 2 | import { Expect } from './Expect'; 3 | import { Equal } from './Equal'; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | Expect, true>>, 8 | 9 | Expect, false>>, 10 | Expect, false>>, 11 | Expect, false>>, 12 | Expect, false>>, 13 | Expect, false>>, 14 | Expect, false>>, 15 | ]; 16 | 17 | type Errors = [ 18 | // @ts-expect-error(2314) only accepts one argument 19 | IsNever<1, 2>, 20 | ]; 21 | -------------------------------------------------------------------------------- /src/IsNever.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * returns `true` when passed a literal `never`: 3 | * 4 | * ```ts 5 | * IsNever; 6 | * ``` 7 | * 8 | * returns `false` for all other values 9 | */ 10 | export type IsNever = [T] extends [never] ? true : false; -------------------------------------------------------------------------------- /src/IsTuple.test.ts: -------------------------------------------------------------------------------- 1 | import { IsTuple } from './IsTuple'; 2 | import { Expect } from './Expect'; 3 | import { Equal } from './Equal'; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | Expect, true>>, 8 | Expect, true>>, 9 | 10 | Expect, false>>, 11 | Expect, false>>, 12 | Expect, false>>, 13 | ]; 14 | 15 | type Errors = [ 16 | Expect, 19 | any 20 | >>, 21 | ] 22 | -------------------------------------------------------------------------------- /src/IsTuple.ts: -------------------------------------------------------------------------------- 1 | import { IsNever } from "./IsNever"; 2 | 3 | /** 4 | * returns `true` for types that are Tuples 5 | * 6 | * the following all return `true`: 7 | * 8 | * ```ts 9 | * IsTuple<[]>; 10 | * IsTuple<[number]>; 11 | * IsTuple; 12 | * ``` 13 | * 14 | * the following all return `false`: 15 | * 16 | * ```ts 17 | * IsTuple<{ length: 1 }>; 18 | * IsTuple; 19 | * IsTuple; 20 | * ``` 21 | */ 22 | export type IsTuple = 23 | IsNever extends true 24 | ? false 25 | : T extends readonly unknown[] 26 | ? number extends T["length"] 27 | ? false 28 | : true 29 | : false 30 | ; -------------------------------------------------------------------------------- /src/IsUnion.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from './Equal'; 2 | import { Expect } from './Expect'; 3 | import { IsUnion } from './IsUnion'; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | Expect, true>>, 8 | Expect, true>>, 9 | Expect, true>>, 10 | Expect, true>>, 11 | 12 | Expect, false>>, 13 | Expect, false>>, 14 | Expect, false>>, 15 | 16 | // Cases where T resolves to a non-union type. 17 | Expect, false>>, 18 | Expect, false>>, 19 | Expect, false>>, 20 | Expect, false>>, 21 | Expect, false>>, 22 | ]; 23 | 24 | type Errors = [ 25 | // @ts-expect-error(2314) only accepts one argument 26 | IsUnion<1, 2> 27 | ]; -------------------------------------------------------------------------------- /src/IsUnion.ts: -------------------------------------------------------------------------------- 1 | type IsUnionInternal = 2 | [T] extends [never] // IsNever check 3 | ? false 4 | : T extends never // force distributivity 5 | ? false 6 | : [Copy] extends [T] // distributed union equality check 7 | ? false 8 | : true 9 | ; 10 | 11 | /** 12 | * returns `true` when passed something that resolves to a union 13 | * 14 | * the following all return `true`: 15 | * 16 | * ```ts 17 | * IsUnion; 18 | * IsUnion<{ a: string } | { a: number }>; 19 | * IsUnion; 20 | * IsUnion<'a' | 'b' | 'c' | 'd'>; 21 | * IsUnion; 22 | * ``` 23 | * 24 | * the following all return `false`: 25 | * 26 | * ```ts 27 | * IsUnion; 28 | * IsUnion<{ a: string | number }>; 29 | * IsUnion<[string | number]>; 30 | * ``` 31 | * 32 | * _Note_ how TypeScript treats types that resolve to a non-union. 33 | * 34 | * the following all return `false`: 35 | * 36 | * ```ts 37 | * IsUnion; // resolves to `string` 38 | * IsUnion; // resolves to `unknown` 39 | * IsUnion; // resolves to `any` 40 | * IsUnion; // `resolves to `string` 41 | * IsUnion; // `never` is an empty union 42 | * ``` 43 | */ 44 | export type IsUnion = IsUnionInternal; 45 | -------------------------------------------------------------------------------- /src/IsUnknown.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | import { Expect } from "./Expect"; 3 | import { IsUnknown } from "./IsUnknown"; 4 | 5 | type Cases = [ 6 | Expect, true>>, 7 | 8 | Expect, true>>, 9 | Expect, true>>, 10 | Expect, true>>, 11 | 12 | Expect, false>>, 13 | Expect, false>>, 14 | Expect, false>>, 15 | Expect, false>>, 16 | Expect, false>>, 17 | Expect, false>>, 18 | Expect, false>>, 19 | 20 | Expect, false>>, 21 | Expect, false>>, 22 | Expect, false>>, 23 | Expect, false>>, 24 | ]; 25 | 26 | -------------------------------------------------------------------------------- /src/IsUnknown.ts: -------------------------------------------------------------------------------- 1 | import { IsAny } from "./IsAny"; 2 | import { IsNever } from "./IsNever"; 3 | 4 | /** 5 | * This predicate tests for whether a given value is `unknown`. 6 | * 7 | * It will return `true` for `unknown` (and any value that resolves to unknown such as `never | unknown` or `{} | unknown`). 8 | * It will return `false` for all other values (including values that resolve to something that is not unknown such as `any | unknown`). 9 | */ 10 | export type IsUnknown = 11 | [unknown] extends [T] 12 | ? IsAny extends true 13 | ? false 14 | : IsNever extends true 15 | ? false 16 | : true 17 | : false; -------------------------------------------------------------------------------- /src/NotEqual.test.ts: -------------------------------------------------------------------------------- 1 | // Note, see tests for Equal (the underlying code is the same other than a condition flip and the choice was made to not duplicate the tests for no substantive benefit) -------------------------------------------------------------------------------- /src/NotEqual.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The opposite of `Equal`, will return false if the two inputs are not equal. 3 | */ 4 | export type NotEqual = 5 | (() => T extends A ? 1 : 2) extends 6 | (() => T extends B ? 1 : 2) ? false : true; 7 | -------------------------------------------------------------------------------- /src/SimpleEqual.test.ts: -------------------------------------------------------------------------------- 1 | // note: every other test utilizes this type 2 | 3 | import { Equal } from "./Equal"; 4 | import { Expect } from "./Expect"; 5 | import { SimpleEqual } from "./SimpleEqual"; 6 | 7 | type Cases = [ 8 | Expect, true>>, 9 | Expect, true>>, 10 | Expect, true>>, 11 | Expect, true>>, 12 | Expect, true>>, 13 | Expect, true>>, 14 | Expect, true>>, 15 | Expect, true>>, 16 | 17 | // !!!!! behavioral difference with `Equal` !!!!! 18 | Expect, never>>, 19 | Expect, true>>, 20 | 21 | // !!!!! behavioral difference with `Equal` !!!!! 22 | Expect, boolean>>, 23 | 24 | /// Union 25 | 26 | // !!!!! behavioral difference with `Equal` !!!!! 27 | Expect, boolean>>, 28 | Expect, false>>, 29 | Expect, false>>, 30 | Expect, false>>, 31 | 32 | /// Intersection 33 | 34 | // !!!!! behavioral difference with `Equal` !!!!! 35 | Expect, true>>, 36 | 37 | // !!!!! behavioral difference with `Equal` !!!!! 38 | Expect, never>>, 39 | 40 | // !!!!! behavioral difference with `Equal` !!!!! 41 | Expect, never>>, 42 | 43 | /// Literal Values 44 | Expect, false>>, // literal numbers 45 | Expect, false>>, // literal string 46 | Expect, false>>, // literal bigint 47 | Expect, false>>, // literal bigint vs literal number 48 | 49 | // !!!!! behavioral difference with `Equal` !!!!! 50 | Expect, boolean>>, // literal boolean vs boolean 51 | Expect, false>>, // literal string vs string 52 | Expect, false>>, // literal number vs number 53 | Expect, false>>, // literal bigint vs bigint 54 | Expect, false>>, // tuple order matters 55 | Expect, false>>, // empty tuple detection 56 | 57 | // !!!!! behavioral difference with `Equal` !!!!! 58 | Expect, boolean>>, 59 | 60 | // !!!!! behavioral difference with `Equal` !!!!! 61 | Expect, boolean>>, 62 | Expect, false>>, 63 | 64 | // !!!!! behavioral difference with `Equal` !!!!! 65 | Expect, never>>, 66 | 67 | Expect, false>>, 68 | Expect, false>>, 69 | Expect, false>>, 70 | 71 | // !!!!! behavioral difference with `Equal` !!!!! 72 | Expect, never>>, 73 | ]; 74 | 75 | type Errors = [ 76 | Expect, 79 | any 80 | >>, 81 | 82 | Expect, 85 | any 86 | >>, 87 | ]; -------------------------------------------------------------------------------- /src/SimpleEqual.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ONLY use this type if you know exactly what you're doing. You probably want `Equal` instead. 3 | * 4 | * This type appears to return `true` if and only if `A extends B` _and_ `B extends A`. However, if you look at the tests you'll find that this is unfortunately not quite exactly how TypeScript works. There are many edge cases that return surprising results due to how TypeScript is structured. Of course these results may make sense eventually if you look deeply enough at them, but the hazard is always present. 5 | * 6 | * For example, distributivity is in play any time you set up `extends` clauses, which means that it can actually return _MULTIPLE_ paths at once (yielding a return type of `boolean`) or even _NO PATHS_ (yielding a return type of `never`). 7 | * 8 | * ```ts 9 | * SimpleEqual<1 & 2, never> //=> never (`Equal` returns `true`) 10 | * SimpleEqual //=> never (`Equal` returns `false`) 11 | * SimpleEqual //=> boolean (`Equal` returns `true`) 12 | * SimpleEqual //=> boolean (`Equal` returns `false`) 13 | * SimpleEqual<1 | 2, 1> //=> boolean (`Equal` returns `false`) 14 | * ``` 15 | * 16 | * @deprecated It's marked deprecated because the risk of accidental misuse is so high unless you're absolutely certain you know what you're doing. There are rare but present situations when you want this type instead of Equal, hence it's inclusion in the library. 17 | */ 18 | export type SimpleEqual = 19 | A extends B 20 | ? B extends A 21 | ? true // both extend each other 22 | : false // `B` does not extend `A` 23 | : false; // `A` does not extend `B` -------------------------------------------------------------------------------- /src/TrueCases.test.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | import { Expect } from "./Expect"; 3 | import { IsUnion } from "./IsUnion"; 4 | import { TrueCases } from "./TrueCases"; 5 | 6 | type EET = [ 7 | Expect, true>>, 8 | // @ts-expect-error(2344) errors at the level of the individual case 9 | Expect, true>>, 10 | Expect, true>>, 11 | Expect, true>>, 12 | ]; 13 | 14 | // @ts-expect-error(2344) must be ignored at the top level 15 | type T = TrueCases<[ 16 | IsUnion, 17 | IsUnion<1>, 18 | IsUnion, 19 | IsUnion<'a' | 'b' | 'c' | 'd'>, 20 | ]>; 21 | -------------------------------------------------------------------------------- /src/TrueCases.ts: -------------------------------------------------------------------------------- 1 | import { Equal } from "./Equal"; 2 | import { IsTuple } from "./IsTuple"; 3 | 4 | /** 5 | * A helper type that will allow you to test many cases at once with minimal boilerplate. 6 | * 7 | * instead of 8 | * 9 | * ```ts 10 | * type TrueCases = [ 11 | * Expect, true>>, 12 | * Expect, true>>, 13 | * Expect, true>>, 14 | * Expect, true>>, 15 | * ]; 16 | * ``` 17 | * 18 | * you can write: 19 | * 20 | * ```ts 21 | * type T = TrueCases<[ 22 | * IsUnion, 23 | * IsUnion<{ a: string } | { a: number }>, 24 | * IsUnion, 25 | * IsUnion<'a' | 'b' | 'c' | 'd'>, 26 | * ]>; 27 | * ``` 28 | * 29 | * The drawback of this type is that the error message is not as friendly if one of the test cases has an error: 30 | * 31 | * ```text 32 | * Type '[true, true, false, true]' does not satisfy the constraint 'readonly true[]'. 33 | * Type 'boolean' is not assignable to type 'true'.ts(2344) 34 | * ``` 35 | * 36 | * Whereas with inline `Expect` and `Equal` you'd get an error just on the line of the failing test. 37 | * 38 | * If the tradeoff of debuggability is desirable to you, then use this type. 39 | */ 40 | export type TrueCases = 41 | false extends Equal 42 | ? false extends IsTuple 43 | ? false 44 | : true 45 | : true; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type { Equal } from './Equal'; 2 | export type { Expect } from './Expect'; 3 | export type { ExpectFalse } from './ExpectFalse'; 4 | export type { Extends } from './Extends'; 5 | export type { FalseCases } from './FalseCases'; 6 | export type { IsAny } from './IsAny'; 7 | export type { IsNever } from './IsNever'; 8 | export type { IsTuple } from './IsTuple'; 9 | export type { IsUnion } from './IsUnion'; 10 | export type { IsUnknown } from './IsUnknown'; 11 | export type { NotEqual } from './NotEqual'; 12 | export type { SimpleEqual } from './SimpleEqual'; 13 | export type { TrueCases } from './TrueCases'; -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "strict": true 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "emitDeclarationOnly": true 7 | }, 8 | "exclude": ["**/*.test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | } 6 | } 7 | --------------------------------------------------------------------------------