├── .github └── CODEOWNERS ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package.json ├── scripts ├── commitDocsIfChanged.sh ├── generateDocumentation.ts ├── preparePublish.ts └── testTsVersions.sh ├── src ├── impl.ts ├── impl │ └── objects.ts ├── index.ts ├── types.ts └── types │ ├── conditionals.ts │ ├── functions.ts │ ├── numbers.ts │ ├── objects.ts │ ├── predicates.ts │ ├── strings.ts │ ├── tuples.ts │ └── utils.ts ├── test ├── conditionals │ ├── And.test.ts │ ├── If.test.ts │ ├── Nand.test.ts │ ├── Not.test.ts │ ├── Or.test.ts │ └── Xor.test.ts ├── functions │ ├── AnyFunc.test.ts │ ├── ArgsAsTuple.test.ts │ ├── ConstructorFunction.test.ts │ ├── OverwriteReturn.test.ts │ └── Predicate.test.ts ├── helpers │ └── assert.ts ├── impl │ └── objects │ │ ├── isKeyOf.test.ts │ │ ├── objectKeys.test.ts │ │ └── taggedObject.test.ts ├── numbers │ ├── Add.test.ts │ ├── IsOne.test.ts │ ├── IsZero.test.ts │ ├── NumberEqual.test.ts │ ├── NumberToString.test.ts │ └── Sub.test.ts ├── objects │ ├── AllKeys.test.ts │ ├── AllRequired.test.ts │ ├── CombineObjects.test.ts │ ├── ConstructorFor.test.ts │ ├── DeepPartial.test.ts │ ├── DeepReadonly.test.ts │ ├── DiffKeys.test.ts │ ├── ElementwiseIntersect.test.ts │ ├── GetKey.test.ts │ ├── Intersect.test.ts │ ├── KeysByType.test.ts │ ├── Merge.test.ts │ ├── ObjectType.test.ts │ ├── Omit.test.ts │ ├── Optional.test.ts │ ├── Overwrite.test.ts │ ├── Required.test.ts │ ├── SharedKeys.test.ts │ ├── StrictUnion.test.ts │ ├── UnionKeys.test.ts │ └── UnionizeProperties.test.ts ├── prototypes.test.ts ├── strings │ ├── DropString.test.ts │ ├── IsNever.test.ts │ └── StringEqual.test.ts ├── tuples │ ├── IntersectTuple.test.ts │ ├── Length.test.ts │ ├── UnionizeTuple.test.ts │ └── Vector.test.ts └── utils │ ├── NoDistribute.test.ts │ ├── NoInfer.test.ts │ ├── Nominal.test.ts │ ├── Nullable.test.ts │ ├── PromiseOr.test.ts │ └── UnionToIntersection.test.ts ├── tsconfig.json └── tslint.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @andnp @Retsam 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | package-lock.json 3 | node_modules 4 | dist/ 5 | edition*/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "node" 5 | install: 6 | - npm install 7 | script: 8 | - npm run test:all 9 | deploy: 10 | provider: script 11 | skip_cleanup: true 12 | script: 13 | - npm run release 14 | on: 15 | branch: master 16 | notifications: 17 | email: 18 | on_success: never 19 | on_failure: always 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributions are welcome here! 4 | We especially appreciate those in the form of bug reports, feature requests, and pull requests on github. 5 | 6 | ## Testing 7 | 8 | To run tests locally, you must manually install a particular version of typescript: 9 | ```bash 10 | npm install 11 | npm install typescript@3.0.3 12 | 13 | npm test 14 | ``` 15 | To run tests against all supported versions of typescript: 16 | ```bash 17 | npm install 18 | npm run test:all 19 | ``` 20 | 21 | All contributions need to be accompanied with a test case. 22 | If fixing a bug provide a test case that would fail before the bug fix, but passes now that the bug has been fixed. 23 | As this library primarily deals with compile-time types, most test cases will be trivial at runtime so it is critical that the `tsc` build passes without errors for the tests to do their job. 24 | 25 | ## Documentation 26 | 27 | All new features need to provide documentation in the form of js-docs. 28 | The `README.md` is automatically generated based on the js-docs (so don't edit it manually!), and will pick up any changes on the `pre-push` hook. 29 | Any helper types that do not need public facing documentation can be ignored by adding `no-doc` to the first line of the js-doc comment. 30 | For example: 31 | ```typescript 32 | /** 33 | * no-doc 34 | * This helpful helper helps do anything. 35 | */ 36 | export type HelpfulHelper = any; 37 | ``` 38 | 39 | ## Commitlint 40 | 41 | All commit messages are linted to ensure that they meet "conventional commit" standards. 42 | More information can be found on the linter [here](https://github.com/marionebl/commitlint/tree/master/%40commitlint/config-conventional) and on the standard [here](https://www.conventionalcommits.org/en/v1.0.0-beta.2/). 43 | 44 | ## Publishing and Versioning 45 | 46 | Publishing and versioning are done automatically using semantic-release. 47 | More information can be found [here](https://semantic-release.gitbook.io/semantic-release/). 48 | New versions of the library will be published moments after a PR is merged. 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andy Patterson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimplyTyped 2 | 3 | 4 | [![Build Status](https://travis-ci.org/andnp/SimplyTyped.svg?branch=NumberPerformance)](https://travis-ci.org/andnp/SimplyTyped) 5 | 6 | Yet another typing library. 7 | This differs by aiming to be less experimental than others, driven by industry use cases. 8 | 9 | Many of the exposed types are a very thin layer above built in functionality. 10 | The goal is to provide all of the building blocks necessary to make concise, yet complex types. 11 | 12 | ``` 13 | npm install --save-dev simplytyped 14 | ``` 15 | 16 | To use with [Deno](https://deno.land), import `https://unpkg.com/simplytyped/edition-deno/index.ts` 17 | 18 | ## Table of Contents 19 | 20 | **[Objects](#objects)** 21 | 22 | [AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [ElementwiseIntersect](#elementwiseintersect) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [TryKey](#trykey) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) 23 | 24 | **[Utils](#utils)** 25 | 26 | [NoDistribute](#nodistribute) - [NoInfer](#noinfer) - [Nominal](#nominal) - [Nullable](#nullable) - [PromiseOr](#promiseor) - [UnionToIntersection](#uniontointersection) 27 | 28 | **[Functions](#functions)** 29 | 30 | [AnyFunc](#anyfunc) - [ArgsAsTuple](#argsastuple) - [ConstructorFunction](#constructorfunction) - [OverwriteReturn](#overwritereturn) - [Predicate](#predicate) 31 | 32 | **[Strings](#strings)** 33 | 34 | [DropString](#dropstring) - [StringEqual](#stringequal) - [UnionContains](#unioncontains) 35 | 36 | **[Tuples](#tuples)** 37 | 38 | [IntersectTuple](#intersecttuple) - [Length](#length) - [UnionizeTuple](#unionizetuple) 39 | 40 | **[Numbers](#numbers)** 41 | 42 | [Add](#add) - [IsOne](#isone) - [IsZero](#iszero) - [Next](#next) - [NumberEqual](#numberequal) - [Numbers](#numbers) - [NumberToString](#numbertostring) - [Prev](#prev) - [Sub](#sub) 43 | 44 | **[Conditionals](#conditionals)** 45 | 46 | [And](#and) - [If](#if) - [Nand](#nand) - [Not](#not) - [Or](#or) - [Xor](#xor) 47 | 48 | **[Predicates](#predicates)** 49 | 50 | [IsAny](#isany) - [IsArray](#isarray) - [IsBoolean](#isboolean) - [IsFunction](#isfunction) - [IsNever](#isnever) - [IsNil](#isnil) - [IsNull](#isnull) - [IsNumber](#isnumber) - [IsObject](#isobject) - [IsString](#isstring) - [IsStringFunction](#isstringfunction) - [IsType](#istype) - [IsUndefined](#isundefined) 51 | 52 | **[Runtime](#runtime)** 53 | 54 | [isKeyOf](#iskeyof) - [objectKeys](#objectkeys) - [Readonly](#readonly) - [taggedObject](#taggedobject) 55 | 56 | ## Objects 57 | 58 | ### AllKeys 59 | Gets all keys between two objects. 60 | ```ts 61 | test('Can get all keys between objects', t => { 62 | type a = { w: number, x: string }; 63 | type b = { x: number, z: boolean }; 64 | 65 | type got = AllKeys; 66 | type expected = 'w' | 'x' | 'z'; 67 | 68 | assert(t); 69 | assert(t); 70 | }); 71 | ``` 72 | 73 | ### AllRequired 74 | Marks all keys as required. 75 | ```ts 76 | test('Can make all fields of options object required (not optional and not nullable)', t => { 77 | type x = { a?: string, b: number | undefined }; 78 | type got = AllRequired; 79 | type expected = { a: string, b: number }; 80 | 81 | assert(t); 82 | assert(t); 83 | }); 84 | ``` 85 | 86 | ### CombineObjects 87 | Takes two objects and returns their intersection. 88 | This combines all keys and uses `ObjectType` to "clean up" the resultant object. 89 | Useful for making extremely complex types look nice in VSCode. 90 | ```ts 91 | test('Can combine two objects (without pesky & in vscode)', t => { 92 | type a = { x: number, y: 'hi' }; 93 | type b = { z: number }; 94 | 95 | type got = CombineObjects; 96 | type expected = { 97 | x: number, 98 | y: 'hi', 99 | z: number 100 | }; 101 | 102 | assert(t); 103 | assert(t); 104 | }); 105 | ``` 106 | 107 | ### DeepPartial 108 | Uses `Partial` to make every parameter of an object optional (`| undefined`). 109 | Iterates through arrays of objects and nested objects. 110 | ```ts 111 | test('Can get a deep partial object', t => { 112 | type a = { 113 | b: { 114 | c: number 115 | }, 116 | d: string 117 | }; 118 | 119 | type got = DeepPartial; 120 | type expected = { 121 | b?: { 122 | c?: number 123 | }, 124 | d?: string 125 | }; 126 | 127 | assert(t); 128 | assert(t); 129 | }); 130 | 131 | test('Can get a deep partial object with arrays', t => { 132 | type a = { 133 | b: Array<{ 134 | c: number, 135 | }>, 136 | }; 137 | 138 | type got = DeepPartial; 139 | type expected = { 140 | b?: Array<{ 141 | c?: number, 142 | }>, 143 | }; 144 | 145 | assert(t); 146 | assert(t); 147 | }); 148 | 149 | test('Can get a deep partial object with functions', t => { 150 | type x = { 151 | a: () => 22, 152 | b: string, 153 | c: { 154 | d: number, 155 | }, 156 | }; 157 | 158 | type expected = { 159 | a?: () => 22, 160 | b?: string, 161 | c?: { 162 | d?: number, 163 | }, 164 | }; 165 | 166 | type got = DeepPartial; 167 | 168 | assert(t); 169 | assert(t); 170 | }); 171 | ``` 172 | 173 | ### DeepReadonly 174 | Uses `Readonly` to make every parameter of an object - and its sub-objects recursively - readonly. 175 | ```ts 176 | test('Can make nested object readonly', t => { 177 | type x = { x: { a: 1, b: 'hi' }, y: 'hey' }; 178 | 179 | type expected = { readonly x: Readonly<{ a: 1, b: 'hi' }>, readonly y: 'hey' }; 180 | type got = DeepReadonly; 181 | 182 | assert(t); 183 | assert(t); 184 | }); 185 | 186 | test('Can make nested object with arrays readonly', t => { 187 | type x = { x: [{ a: 1, b: 'hi' }], y: 'hey' }; 188 | 189 | type expected = { readonly x: ReadonlyArray>, readonly y: 'hey' }; 190 | type got = DeepReadonly; 191 | 192 | assert(t); 193 | assert(t); 194 | }); 195 | 196 | test('Can make an object with functions readonly', t => { 197 | type x = { 198 | a: () => 22, 199 | b: string, 200 | c: { 201 | d: boolean, 202 | }, 203 | }; 204 | 205 | type expected = { 206 | readonly a: () => 22, 207 | readonly b: string, 208 | readonly c: { 209 | readonly d: boolean, 210 | }, 211 | }; 212 | type got = DeepReadonly; 213 | 214 | assert(t); 215 | assert(t); 216 | }); 217 | ``` 218 | 219 | ### DiffKeys 220 | Gets all of the keys that are different between two objects. 221 | This is a set difference between `keyof T` and `keyof U`. 222 | Note that calling this with arguments reversed will have different results. 223 | ```ts 224 | test('Can get all keys that are different between objects', t => { 225 | type a = { x: number, y: string }; 226 | type b = { y: string, z: number }; 227 | 228 | type gotA = DiffKeys; 229 | type gotB = DiffKeys; 230 | 231 | assert(t); 232 | assert(t); 233 | }); 234 | ``` 235 | 236 | ### ElementwiseIntersect 237 | Takes two objects and returns their element-wise intersection. 238 | *Note*: this removes any key-level information, such as optional or readonly keys. 239 | ```ts 240 | test('Can combine two objects elementwise', t => { 241 | type a = { x: number, y: 'hi' }; 242 | type b = { z: number, y: 'there' }; 243 | 244 | type got = ElementwiseIntersect; 245 | type expected = { 246 | x: number, 247 | y: 'hi' & 'there', 248 | z: number, 249 | }; 250 | 251 | assert(t); 252 | assert(t); 253 | }); 254 | 255 | test('Can combine two objects with private members elementwise', t => { 256 | class A { 257 | a: number = 1; 258 | private x: number = 2; 259 | y: 'hi' = 'hi'; 260 | private z: 'hey' = 'hey'; 261 | } 262 | 263 | class B { 264 | a: 22 = 22; 265 | private x: number = 2; 266 | y: 'there' = 'there'; 267 | private z: 'friend' = 'friend'; 268 | } 269 | 270 | type got = ElementwiseIntersect; 271 | type expected = { 272 | a: 22, 273 | y: 'hi' & 'there', 274 | }; 275 | 276 | assert(t); 277 | assert(t); 278 | }); 279 | ``` 280 | 281 | ### GetKey 282 | Gets the value of specified property on any object without compile time error (`Property 'b' does not exist on type '{ a: string; }'.`) and the like. 283 | Returns `never` if the key is not on the object. 284 | It helps to use `If { 287 | type obj = { x: number, y: string }; 288 | type expected = number; 289 | type got = GetKey; 290 | 291 | assert(t); 292 | assert(t); 293 | }); 294 | 295 | test('Will get `never` if key does not exist', t => { 296 | type obj = { x: number, y: string }; 297 | type expected = never; 298 | type got = GetKey; 299 | 300 | assert(t); 301 | assert(t); 302 | }); 303 | ``` 304 | 305 | ### HasKey 306 | Returns `True` if a key, `K`, is present in a type, `T`, else `False`. 307 | 308 | 309 | ### Intersect 310 | Returns only the shared properties between two objects. 311 | All shared properties must be the same type. 312 | ```ts 313 | test('Can get an object with only shared properties', t => { 314 | type a = { x: number, y: string }; 315 | type b = { y: string, z: string }; 316 | 317 | type expected = { y: string }; 318 | type got = Intersect; 319 | 320 | assert(t); 321 | assert(t); 322 | }); 323 | ``` 324 | 325 | ### KeysByType 326 | Gets all keys that point to a given type. 327 | ```ts 328 | test('Can filter object keys by right side type', t => { 329 | type obj = { 330 | a: 1, 331 | b: 2, 332 | c: 3, 333 | }; 334 | 335 | type expected = 'a' | 'b'; 336 | type got = KeysByType; 337 | 338 | assert(t); 339 | assert(t); 340 | }); 341 | ``` 342 | 343 | ### Merge 344 | Much like `_.merge` in javascript, this returns an object with all keys present between both objects, but conflicts resolved by rightmost object. 345 | ```ts 346 | test('Can merge two objects, resolving matching keys by rightmost object', t => { 347 | type a = { x: number, y: string }; 348 | type b = { y: number, z: string }; 349 | 350 | type got = Merge; 351 | type expected = { x: number, y: number, z: string }; 352 | 353 | assert(t); 354 | assert(t); 355 | }); 356 | 357 | test('Can merge an object containing all strings as keys', t => { 358 | type a = { 359 | y: string; 360 | [s: string]: string; 361 | }; 362 | type b = { x: number, y: number }; 363 | 364 | type got = Merge; 365 | type expected = { x: number, y: number } & Record; 366 | 367 | assert(t); 368 | assert(t); 369 | }); 370 | ``` 371 | 372 | ### ObjectKeys 373 | Objects can be indexed by multiple types: `string`, `number`, `symbol`. 374 | For safe compatibility with typescript version, this type will always 375 | have the correct set of object key types for the current version of TS. 376 | 377 | This is useful for functions that must take a key, instead of `K extends string`, 378 | use `K extends ObjectKeys`. 379 | 380 | 381 | ### ObjectType 382 | Takes any type and makes it an object type. 383 | Useful when combined with `&` intersection types. 384 | ```ts 385 | test('Can turn an object into another object', t => { 386 | type obj = { x: number, y: string }; 387 | type expected = obj; 388 | type got = ObjectType; 389 | 390 | assert(t); 391 | assert(t); 392 | }); 393 | ``` 394 | 395 | ### Omit 396 | Gives back an object with listed keys removed. 397 | This is the opposite of `Pick`. 398 | ```ts 399 | test('Can omit keys from an object', t => { 400 | type a = { x: number, y: string, z: boolean }; 401 | 402 | type got = Omit; 403 | type expected = { z: boolean }; 404 | 405 | assert(t); 406 | assert(t); 407 | }); 408 | ``` 409 | 410 | ### Optional 411 | Mark specific keys, `K`, of `T` as optional (think `Partial`). 412 | ```ts 413 | test('Can make properties optional', t => { 414 | type x = { x: number, y: string, z: 'hello there' }; 415 | 416 | type expected = { x?: number, y?: string, z: 'hello there' }; 417 | type got = Optional; 418 | 419 | assert(t); 420 | assert(t); 421 | }); 422 | ``` 423 | 424 | ### Overwrite 425 | Can change the types of properties on an object. 426 | This is similar to `Merge`, except that it will not add previously non-existent properties to the object. 427 | ```ts 428 | test('Can overwrite properties on an object', t => { 429 | type a = { x: number, y: string, z: 'hello there' }; 430 | 431 | type expected = { x: number, y: string, z: 'hello' | 'there' }; 432 | type got1 = Overwrite; 433 | type got2 = Overwrite; 434 | 435 | assert(t); 436 | assert(t); 437 | assert(t); 438 | assert(t); 439 | }); 440 | ``` 441 | 442 | ### PlainObject 443 | An object with string keys and values of type `any`. 444 | 445 | 446 | ### PureKeys 447 | When an object has optional or readonly keys, that information is contained within the key. 448 | When using optional/readonly keys in another object, they will retain optional/readonly status. 449 | `PureKeys` will remove the optional/readonly status modifiers from keys. 450 | 451 | 452 | ### Required 453 | Mark specific keys, `K`, of `T` as required. 454 | ```ts 455 | test('Can make certain fields of options object required', t => { 456 | type x = { a?: string, b: number | undefined }; 457 | type got1 = Required; 458 | type got2 = Required; 459 | type got3 = Required; 460 | 461 | type expected1 = { a: string, b: number | undefined }; 462 | type expected2 = { a?: string, b: number }; 463 | type expected3 = { a: string, b: number }; 464 | 465 | assert(t); 466 | assert(t); 467 | assert(t); 468 | }); 469 | ``` 470 | 471 | ### SharedKeys 472 | Gets all of the keys that are shared between two objects. 473 | ```ts 474 | test('Can get keys that are same between objects', t => { 475 | type a = { x: number, y: string }; 476 | type b = { x: string, y: string, z: boolean }; 477 | 478 | type got = SharedKeys; 479 | type expected = 'x' | 'y'; 480 | 481 | assert(t); 482 | assert(t); 483 | }); 484 | ``` 485 | 486 | ### StrictUnion 487 | Makes a union 'strict', such that members are disallowed from including the keys of other members 488 | For example, `{x: 1, y: 1}` is a valid member of `{x: number} | {y: number}`, 489 | but it's not a valid member of StrictUnion<{x: number} | {y: number}>. 490 | ```ts 491 | test('disallow union members with mixed properties', t => { 492 | type a = { a: number }; 493 | type b = { b: string }; 494 | 495 | type good1 = {a: 1}; 496 | type good2 = {b: "b"}; 497 | type bad = {a: 1, b: "foo"}; 498 | 499 | type isStrict = T extends Array> ? 'Yes' : 'No'; 500 | 501 | type strictUnion = [good1, good2]; 502 | type nonStrictUnion = [good1, good2, bad]; 503 | 504 | assert, 'Yes'>(t); 505 | assert, 'No'>(t); 506 | 507 | }); 508 | ``` 509 | 510 | ### StringKeys 511 | Typescript 2.9 introduced `number | symbol` as possible results from `keyof any`. 512 | For backwards compatibility with objects containing only `string` keys, this will 513 | exclude any `number | symbol` keys from `keyof`. 514 | 515 | 516 | ### TaggedObject 517 | For discriminated unions of objects, it is important to have a single "tag" property. 518 | Creates an object with each entry being tagged by the key defining that entry. 519 | 520 | 521 | ### TryKey 522 | Like `GetKey`, but returns `unknown` if the key is not present on the object. 523 | 524 | 525 | ### UnionizeProperties 526 | Get a union of the properties of an object. 527 | ```ts 528 | test('Can get a union of all values in an object', t => { 529 | type a = { x: 'hi', y: 'there', z: 'friend' }; 530 | 531 | type got = UnionizeProperties; 532 | type expected = 'hi' | 'there' | 'friend'; 533 | 534 | assert(t); 535 | assert(t); 536 | }); 537 | ``` 538 | 539 | ### UnionKeys 540 | 541 | ```ts 542 | test('Can get all keys between objects in a union', t => { 543 | type a = { w: number, x: string }; 544 | type b = { x: number, z: boolean }; 545 | type c = { y: boolean, z: string }; 546 | 547 | type got = UnionKeys; 548 | type expected = 'w' | 'x' | 'y' | 'z'; 549 | 550 | assert(t); 551 | assert(t); 552 | }); 553 | ``` 554 | 555 | ## Utils 556 | 557 | ### NoDistribute 558 | Prevent `T` from being distributed in a conditional type. 559 | A conditional is only distributed when the checked type is naked type param and T & {} is not a 560 | naked type param, but has the same contract as T. 561 | ```ts 562 | test("can create a conditional type that won't distribute over unions", t => { 563 | type IsString = T extends string ? "Yes" : "No"; 564 | type IsStringNoDistribute = NoDistribute extends string ? "Yes" : "No"; 565 | 566 | /** 567 | * Evaluates as: 568 | * ("foo" extends string ? "Yes" : "No") 569 | * | (42 extends string ? "Yes" : "No") 570 | */ 571 | type T1 = IsString<"foo" | 42>; 572 | assert(t); 573 | assert<"Yes" | "No", T1>(t); 574 | 575 | /** 576 | * Evaluates as: 577 | * ("foo" | 42) extends string ? "Yes" : "No" 578 | */ 579 | type T2 = IsStringNoDistribute<"foo" | 5>; 580 | assert(t); 581 | assert<"No", T2>(t); 582 | }); 583 | 584 | test("cannot be used to prevent a distributive conditional from distributing", t => { 585 | type IsString = T extends string ? "Yes" : "No"; 586 | // It's the defintion of the conditional type that matters, 587 | // not the type that's passed in, so this still distributes 588 | type Test = IsString>; 589 | assert(t); 590 | assert<"Yes" | "No", Test>(t); 591 | }); 592 | ``` 593 | 594 | ### NoInfer 595 | Prevent `T` from being inferred in generic function 596 | ```ts 597 | test('Will not infer based on second argument', t => { 598 | function doStuff(x: T, y: NoInfer): T { return x; } 599 | 600 | const hi = 'hi' as 'hi' | number; 601 | const there = 'there'; 602 | const x = doStuff(hi, there); 603 | 604 | assert(t); 605 | assert(t); 606 | }); 607 | ``` 608 | 609 | ### Nominal 610 | Constructs a nominal type of type `T`. 611 | Useful to prevent any value of type `T` from being used or modified in places it shouldn't (think `id`s). 612 | ```ts 613 | test('Can make a new nominal type', t => { 614 | type Id = Nominal; 615 | 616 | // TODO: improve once negative testing is in place 617 | assert>(t); 618 | }); 619 | ``` 620 | 621 | ### Nullable 622 | Mark a type as nullable (`null | undefined`). 623 | ```ts 624 | test('Will make a type nullable (null | undefined)', t => { 625 | type got = Nullable; 626 | type expected = string | null | undefined; 627 | 628 | assert(t); 629 | }); 630 | 631 | test('Will make a type not nullable', t => { 632 | type got = NonNullable>; 633 | 634 | assert(t); 635 | }); 636 | ``` 637 | 638 | ### PromiseOr 639 | Returns the given type or a Promise containing that type. 640 | ```ts 641 | test('Will give back a promise containing given type union the type itself', t => { 642 | type got = PromiseOr; 643 | type expected = Promise | string; 644 | 645 | assert(t); 646 | }); 647 | ``` 648 | 649 | ### UnionToIntersection 650 | Defines an intersection type of all union items. 651 | ```ts 652 | test('Union of Strings', t => { 653 | type got = UnionToIntersection<'hi' | 'there'>; 654 | type expected = 'hi' & 'there'; 655 | 656 | assert(t); 657 | }); 658 | 659 | test('Union of Objects', t => { 660 | type got = UnionToIntersection<{ a: 0 } | { b: 1 } | { c: 2 }>; 661 | 662 | type expected = { 663 | a: 0, 664 | b: 1, 665 | c: 2, 666 | }; 667 | 668 | assert(t); 669 | }); 670 | ``` 671 | 672 | ## Functions 673 | 674 | ### AnyFunc 675 | Concisely and cleanly define an arbitrary function. 676 | Useful when designing many api's that don't care what function they take in, they just need to know what it returns. 677 | ```ts 678 | test('Can define the type of a function that takes any arguments', t => { 679 | type got = AnyFunc; 680 | type got2 = AnyFunc; // takes anything, returns a number 681 | 682 | type expected = (...args: any[]) => any; 683 | type expected2 = (...args: any[]) => number; 684 | 685 | assert(t); 686 | assert(t); 687 | }); 688 | ``` 689 | 690 | ### ArgsAsTuple 691 | Returns a tuple type of a functions arguments up to 7. 692 | ```ts 693 | test("Can get a tuple of function's argument types", t => { 694 | type F0 = () => any; 695 | type F1 = (x: number) => any; 696 | type F2 = (x: number, y: string) => any; 697 | type F3 = (x: number, y: string, z: boolean) => any; 698 | 699 | type E0 = []; 700 | type E1 = [number]; 701 | type E2 = [number, string]; 702 | type E3 = [number, string, boolean]; 703 | 704 | assert, E0>(t); 705 | assert, E1>(t); 706 | assert, E2>(t); 707 | assert, E3>(t); 708 | }); 709 | ``` 710 | 711 | ### ConstructorFunction 712 | This represents the constructor for a particular object. 713 | ```ts 714 | test('Can build a constructor type for a type', t => { 715 | type Constructor = ConstructorFunction<{ x: string, y: number }>; 716 | class Thing { x: string = ''; y: number = 22; } 717 | 718 | assert(t); 719 | }); 720 | ``` 721 | 722 | ### OverwriteReturn 723 | Modifies the return value of a function of up to 7 parameters. 724 | ```ts 725 | test('Can change return type of a function', t => { 726 | type f = (x: 'hi', y: 'there', z: 22) => number; 727 | 728 | type got = OverwriteReturn; 729 | type expected = (x: 'hi', y: 'there', z: 22) => string; 730 | 731 | assert(t); 732 | assert(t); 733 | }); 734 | ``` 735 | 736 | ### Predicate 737 | This is a function that takes some args and returns a boolean 738 | ```ts 739 | test('Can build a predicate function with single known argument type', t => { 740 | type PredFunc = Predicate; 741 | type expected = (arg: string) => boolean; 742 | 743 | assert(t); 744 | }); 745 | ``` 746 | 747 | ## Strings 748 | 749 | ### DropString 750 | 751 | ```ts 752 | test('Can remove a string from a union of strings', t => { 753 | type a = 'hi' | 'there'; 754 | type b = 'hey' | 'there' | never; 755 | 756 | assert, 'there'>(t); 757 | assert, never>(t); 758 | assert, never>(t); 759 | }); 760 | ``` 761 | 762 | ### StringEqual 763 | 764 | ```ts 765 | test('Can check that two unions of strings are equal', t => { 766 | type a = 'hi' | 'there'; 767 | type b = 'there' | 'hi'; 768 | type c = 'hi' | 'there' | 'friend'; 769 | 770 | assert, True>(t); 771 | assert, True>(t); 772 | assert, False>(t); 773 | }); 774 | ``` 775 | 776 | ### UnionContains 777 | 778 | 779 | 780 | ## Tuples 781 | 782 | ### IntersectTuple 783 | Gives an intersection of all values contained in a tuple. 784 | ```ts 785 | test('Can get the intersection of tuple values', t => { 786 | type t = [{a: 'hi'}, {b: 'there'}, {c: 'friend'}]; 787 | 788 | type got = IntersectTuple; 789 | type expected = {a: 'hi'} & {b: 'there'} & {c: 'friend'}; 790 | 791 | assert(t); 792 | assert(t); 793 | }); 794 | ``` 795 | 796 | ### Length 797 | 798 | ```ts 799 | test('Can get the length of a tuple', t => { 800 | type t = [1, 2, 3, 4]; 801 | type x = ['hello', 'world']; 802 | 803 | type gotT = Length; 804 | type gotX = Length; 805 | 806 | assert(t); 807 | assert(t); 808 | }); 809 | ``` 810 | 811 | ### UnionizeTuple 812 | Gives a union of all values contained in a tuple. 813 | ```ts 814 | test('Can get a union of all values in tuple', t => { 815 | type t = ['hi', 'there', 'friend']; 816 | 817 | type got = UnionizeTuple; 818 | type expected = 'hi' | 'there' | 'friend'; 819 | 820 | assert(t); 821 | assert(t); 822 | }); 823 | ``` 824 | 825 | ## Numbers 826 | 827 | ### Add 828 | Adds two numbers together. 829 | ```ts 830 | test('Can add two numbers', t => { 831 | type fifty = Add<12, 38>; 832 | assert(t); 833 | }); 834 | ``` 835 | 836 | ### IsOne 837 | Returns true if the number is equal to one. 838 | ```ts 839 | test('Can check if a number is one', t => { 840 | type notOne = IsOne<0>; 841 | type one = IsOne<1>; 842 | assert(t); 843 | assert(t); 844 | }); 845 | ``` 846 | 847 | ### IsZero 848 | Returns true if the number is equal to zero. 849 | ```ts 850 | test('Can check if a number is zero', t => { 851 | type notZero = IsZero<1>; 852 | type zero = IsZero<0>; 853 | assert(t); 854 | assert(t); 855 | }); 856 | ``` 857 | 858 | ### Next 859 | Returns the number + 1. 860 | 861 | 862 | ### NumberEqual 863 | Returns `True` if the numbers are equivalent 864 | ```ts 865 | test('Can check if two numbers are equal', t => { 866 | type notEqual = NumberEqual<22, 23>; 867 | type equal = NumberEqual<12, 12>; 868 | assert(t); 869 | assert(t); 870 | }); 871 | ``` 872 | 873 | ### Numbers 874 | 875 | 876 | 877 | ### NumberToString 878 | Returns the string type for a given number 879 | ```ts 880 | test('Can get a number as a string', t => { 881 | type str = NumberToString<22>; 882 | assert(t); 883 | }); 884 | ``` 885 | 886 | ### Prev 887 | Returns the number - 1. 888 | 889 | 890 | ### Sub 891 | Subtracts the second from the first. 892 | ```ts 893 | test('Can subtract two numbers', t => { 894 | type ten = Sub<22, 12>; 895 | assert(t); 896 | }); 897 | ``` 898 | 899 | ## Conditionals 900 | 901 | ### And 902 | 903 | ```ts 904 | test('Conditions can be based on AND', t => { 905 | type conditional = If, number, string>; 906 | type gotFF = conditional; 907 | type gotFT = conditional; 908 | type gotTF = conditional; 909 | type gotTT = conditional; 910 | 911 | assert(t); 912 | assert(t); 913 | assert(t); 914 | assert(t); 915 | }); 916 | ``` 917 | 918 | ### If 919 | 920 | ```ts 921 | test('Can assign type conditionally', t => { 922 | type conditional = If; 923 | type gotF = conditional; 924 | type gotT = conditional; 925 | 926 | assert(t); 927 | assert(t); 928 | }); 929 | ``` 930 | 931 | ### Nand 932 | 933 | ```ts 934 | test('Conditions can be based on NAND', t => { 935 | assert, False>(t); 936 | assert, True>(t); 937 | assert, True>(t); 938 | assert, True>(t); 939 | }); 940 | ``` 941 | 942 | ### Not 943 | 944 | ```ts 945 | test('Conditional logic can be inversed with NOT', t => { 946 | type conditional = If, number, string>; 947 | type gotF = conditional; 948 | type gotT = conditional; 949 | 950 | assert(t); 951 | assert(t); 952 | }); 953 | ``` 954 | 955 | ### Or 956 | 957 | ```ts 958 | test('Conditions can be based on OR', t => { 959 | type conditional = If, number, string>; 960 | type gotFF = conditional; 961 | type gotFT = conditional; 962 | type gotTF = conditional; 963 | type gotTT = conditional; 964 | 965 | assert(t); 966 | assert(t); 967 | assert(t); 968 | assert(t); 969 | }); 970 | ``` 971 | 972 | ### Xor 973 | 974 | ```ts 975 | test('Conditions can be based on XOR', t => { 976 | assert, False>(t); 977 | assert, True>(t); 978 | assert, True>(t); 979 | assert, False>(t); 980 | }); 981 | ``` 982 | 983 | ## Predicates 984 | 985 | ### IsAny 986 | 987 | 988 | 989 | ### IsArray 990 | 991 | 992 | 993 | ### IsBoolean 994 | 995 | 996 | 997 | ### IsFunction 998 | 999 | 1000 | 1001 | ### IsNever 1002 | 1003 | 1004 | 1005 | ### IsNil 1006 | 1007 | 1008 | 1009 | ### IsNull 1010 | 1011 | 1012 | 1013 | ### IsNumber 1014 | 1015 | 1016 | 1017 | ### IsObject 1018 | 1019 | 1020 | 1021 | ### IsString 1022 | 1023 | 1024 | 1025 | ### IsStringFunction 1026 | 1027 | 1028 | 1029 | ### IsType 1030 | 1031 | 1032 | 1033 | ### IsUndefined 1034 | 1035 | 1036 | 1037 | ## Runtime 1038 | 1039 | ### isKeyOf 1040 | Type guard for any key, `k`. 1041 | Marks `k` as a key of `T` if `k` is in `obj`. 1042 | ```ts 1043 | test('Can check if an object contains a key', t => { 1044 | const o = { a: 'hi', b: 22 }; 1045 | const key1: string = 'a'; 1046 | 1047 | if (isKeyOf(o, key1)) { 1048 | assert(t); 1049 | t.pass(); 1050 | } else { 1051 | assert(t); 1052 | t.fail(); 1053 | } 1054 | }); 1055 | ``` 1056 | 1057 | ### objectKeys 1058 | Same as `Object.keys` except that the returned type is an array of keys of the object. 1059 | Note that for the same reason that `Object.keys` does not do this natively, this method _is not safe_ for objects on the perimeter of your code (user input, read in files, network requests etc.). 1060 | ```ts 1061 | test('Can get keys of an object', t => { 1062 | const o = { a: 'hi', b: 22 }; 1063 | const keys = objectKeys(o); 1064 | 1065 | type K = typeof keys; 1066 | type expected = Array<'a' | 'b'>; 1067 | assert(t); 1068 | assert(t); 1069 | 1070 | t.deepEqual(keys, ['a', 'b']); 1071 | }); 1072 | ``` 1073 | 1074 | ### Readonly 1075 | Useful for marking object literals as readonly while still keeping type inference: 1076 | `const obj = Readonly({ a: 22, b: 'yellow' });` 1077 | 1078 | 1079 | ### taggedObject 1080 | Useful for tagged unions of objects (imagine redux reducers) this tags every sub-object with the key pointing to that sub-object. 1081 | ```ts 1082 | test('Can generate a tagged object', t => { 1083 | const obj = { 1084 | a: { merp: 'hi' }, 1085 | b: { merp: 'there' }, 1086 | c: { merp: 'friend' }, 1087 | }; 1088 | 1089 | const expected = { 1090 | a: { name: 'a' as 'a', merp: 'hi' }, 1091 | b: { name: 'b' as 'b', merp: 'there' }, 1092 | c: { name: 'c' as 'c', merp: 'friend' }, 1093 | }; 1094 | 1095 | const got = taggedObject(obj, 'name'); 1096 | 1097 | t.deepEqual(got, expected); 1098 | assert(t); 1099 | assert(t); 1100 | 1101 | }); 1102 | ``` 1103 | 1104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplytyped", 3 | "version": "1.1.0", 4 | "description": "yet another Typescript type library for advanced types", 5 | "main": "dist/src/index", 6 | "types": "dist/src/index.d.ts", 7 | "deno": "edition-deno/index.ts", 8 | "browser": "edition-deno/index.ts", 9 | "private": true, 10 | "editions": [ 11 | { 12 | "description": "TypeScript source code with Import for modules", 13 | "directory": "src", 14 | "entry": "index.ts", 15 | "tags": [ 16 | "typescript", 17 | "import" 18 | ], 19 | "engines": false 20 | }, 21 | { 22 | "description": "TypeScript source code made to be compatible with Deno", 23 | "directory": "edition-deno", 24 | "entry": "index.ts", 25 | "tags": [ 26 | "typescript", 27 | "import", 28 | "deno" 29 | ], 30 | "engines": { 31 | "deno": true, 32 | "browsers": true 33 | } 34 | } 35 | ], 36 | "scripts": { 37 | "doc": "ts-node scripts/generateDocumentation.ts > README.md", 38 | "commitDocs": "sh scripts/commitDocsIfChanged.sh", 39 | "lint": "tslint --config tslint.json --project . --format stylish", 40 | "test": "npm run -s tsc && NODE_PATH=src/ ava", 41 | "test:all": "sh scripts/testTsVersions.sh", 42 | "tsc": "tsc", 43 | "prepub": "rm -rf dist && npm -s run tsc && ts-node scripts/preparePublish.ts && make-deno-edition", 44 | "release": "npm run -s prepub && cd dist/src && npx semantic-release" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/andnp/SimplyTyped.git" 49 | }, 50 | "keywords": [ 51 | "typescript", 52 | "types" 53 | ], 54 | "author": "Andy Patterson", 55 | "license": "MIT", 56 | "bugs": { 57 | "url": "https://github.com/andnp/SimplyTyped/issues" 58 | }, 59 | "files": [ 60 | "dist/src", 61 | "edition-deno" 62 | ], 63 | "homepage": "https://github.com/andnp/SimplyTyped#readme", 64 | "peerDependencies": { 65 | "typescript": ">=2.8.0" 66 | }, 67 | "devDependencies": { 68 | "@commitlint/config-conventional": "^8.0.0", 69 | "@types/node": "~13.9.0", 70 | "@ava/babel": "^1.0.1", 71 | "ava": "~3.7.1", 72 | "commitlint": "^8.0.0", 73 | "husky": "^4.0.2", 74 | "make-deno-edition": "^0.2.1", 75 | "ts-node": "^8.0.3", 76 | "tslint": "^5.13.0" 77 | }, 78 | "ava": { 79 | "files": [ 80 | "dist/test/**/*.test.js" 81 | ], 82 | "concurrency": 32, 83 | "babel": {} 84 | }, 85 | "commitlint": { 86 | "extends": [ 87 | "@commitlint/config-conventional" 88 | ] 89 | }, 90 | "husky": { 91 | "hooks": { 92 | "commit-msg": "commitlint -e $GIT_PARAMS", 93 | "pre-push": "npm run -s lint && npm run -s test && npm run -s commitDocs" 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripts/commitDocsIfChanged.sh: -------------------------------------------------------------------------------- 1 | npm run doc 2 | CHANGED=$(git status 'README.md' --porcelain) 3 | if [ -n "${CHANGED}" ]; then 4 | git add README.md 5 | git commit -m "docs: generate documentation" 6 | fi 7 | -------------------------------------------------------------------------------- /scripts/generateDocumentation.ts: -------------------------------------------------------------------------------- 1 | import * as tsc from 'typescript'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | interface TypeInfo { 6 | typeName: string; 7 | parameterDocs: Array<{ name: string, text: string }>; 8 | returnDocs: string; 9 | description: string; 10 | fileName: string; 11 | } 12 | 13 | const tsConfig = { 14 | baseUrl: "src/", 15 | }; 16 | 17 | const TEST_BASE = 'test'; 18 | const REGRESSION_SEPARATOR = '/* Regression Tests */'; 19 | 20 | // order dictates the order these appear in the generated markdown 21 | const files = [ 22 | { file: 'objects', test: `${TEST_BASE}/objects`, header: 'Objects' }, 23 | { file: 'utils', test: `${TEST_BASE}/utils`, header: 'Utils' }, 24 | { file: 'functions', test: `${TEST_BASE}/functions`, header: 'Functions' }, 25 | { file: 'strings', test: `${TEST_BASE}/strings`, header: 'Strings' }, 26 | { file: 'tuples', test: `${TEST_BASE}/tuples`, header: 'Tuples' }, 27 | { file: 'numbers', test: `${TEST_BASE}/numbers`, header: 'Numbers' }, 28 | { file: 'conditionals', test: `${TEST_BASE}/conditionals`, header: 'Conditionals' }, 29 | { file: 'predicates', test: `${TEST_BASE}/predicates`, header: 'Predicates' }, 30 | { file: 'src/impl/objects', test: `${TEST_BASE}/impl/objects`, header: 'Runtime' }, 31 | ]; 32 | 33 | const generateMarkdown = (fileName: string, testPath: string) => { 34 | const program = tsc.createProgram([fileName], tsConfig); 35 | const checker = program.getTypeChecker(); 36 | 37 | for (const sourceFile of program.getSourceFiles()) { 38 | if (sourceFile.fileName !== fileName) continue; // we don't care about imported source files, only the single file we are inspecting 39 | 40 | const sourceDocs = [] as TypeInfo[]; 41 | 42 | tsc.forEachChild(sourceFile, (node) => { 43 | if (!isNodeExported(node)) return; // we only need to document exported types 44 | if (!(tsc.isTypeAliasDeclaration(node) || tsc.isFunctionDeclaration(node))) return; // we only need to document types and functions 45 | 46 | const symbol = checker.getSymbolAtLocation(node.name!); 47 | if (!symbol) return; // we should never get into this state because we aren't dealing with .d.ts files 48 | 49 | // Get the JSDoc description 50 | const description = tsc.displayPartsToString(symbol.getDocumentationComment(checker)); 51 | 52 | // Don't document things with `no-doc` at start of description 53 | if (description.trim().startsWith('no-doc')) return; 54 | 55 | const typeName = symbol.name; 56 | 57 | const jsDocTags = symbol.getJsDocTags(); 58 | 59 | const parameterDocs = jsDocTags 60 | .filter(tag => tag.name === 'param') 61 | .map(tag => tag.text) 62 | .filter(text => !!text) 63 | .map(text => { 64 | const [ name, ...docWords] = text!.split(' '); 65 | return { 66 | name, 67 | text: docWords.join(' '), 68 | }; 69 | }); 70 | 71 | const returnDocs = jsDocTags 72 | .filter(tag => tag.name === 'returns') 73 | .map(tag => tag.text) 74 | .filter(text => !!text); 75 | 76 | const typeInfo: TypeInfo = { 77 | typeName, 78 | parameterDocs, 79 | returnDocs: returnDocs.length > 0 ? returnDocs[0]! : '', 80 | description, 81 | fileName, 82 | }; 83 | 84 | sourceDocs.push(typeInfo); 85 | }); 86 | 87 | const renderedSections = sourceDocs 88 | .sort((a, b) => a.typeName.localeCompare(b.typeName)) 89 | .map(typeInfo => { 90 | const header = `### ${typeInfo.typeName}`; 91 | const description = `${typeInfo.description}`; 92 | 93 | const codeExamplePath = path.join(testPath, `${typeInfo.typeName}.test.ts`); 94 | 95 | const testFile = fs.existsSync(codeExamplePath) && fs.readFileSync(codeExamplePath).toString(); 96 | const testCases = testFile && removeImports(removeRegressionTests(testFile)).trim(); 97 | 98 | const codeExample = testFile ? `\`\`\`ts\n${testCases}\n\`\`\`` : ''; 99 | 100 | return header + '\n' + description + '\n' + codeExample; 101 | }); 102 | 103 | const markdown = renderedSections.join('\n\n'); 104 | const toc = sourceDocs 105 | .sort((a, b) => a.typeName.localeCompare(b.typeName)) 106 | .map(typeInfo => ({ name: typeInfo.typeName, link: typeInfo.typeName.toLowerCase() })); 107 | 108 | return { 109 | toc, 110 | markdown, 111 | }; 112 | } 113 | }; 114 | 115 | let headerString = 116 | `# SimplyTyped 117 | 118 | [![Greenkeeper badge](https://badges.greenkeeper.io/andnp/SimplyTyped.svg)](https://greenkeeper.io/) 119 | [![Build Status](https://travis-ci.org/andnp/SimplyTyped.svg?branch=NumberPerformance)](https://travis-ci.org/andnp/SimplyTyped) 120 | 121 | Yet another typing library. 122 | This differs by aiming to be less experimental than others, driven by industry use cases. 123 | 124 | Many of the exposed types are a very thin layer above built in functionality. 125 | The goal is to provide all of the building blocks necessary to make concise, yet complex types. 126 | 127 | \`\`\` 128 | npm install --save-dev simplytyped 129 | \`\`\` 130 | 131 | ## Table of Contents 132 | 133 | `; 134 | 135 | const docString = files.map(file => { 136 | const fileName = file.file.includes(path.sep) 137 | ? `${file.file}.ts` 138 | : path.join('src', 'types', `${file.file}.ts`); 139 | 140 | const doc = generateMarkdown(fileName, file.test)!; 141 | 142 | let header = `**[${file.header}](#${file.header.toLowerCase()})**\n\n`; 143 | 144 | header += doc.toc.map(t => `[${t.name}](#${t.link})`).join(' - '); 145 | 146 | headerString += header + '\n\n'; 147 | 148 | return `## ${file.header}\n\n${doc.markdown}\n`; 149 | }).join('\n'); 150 | 151 | const markdown = headerString + docString; 152 | console.log(markdown); 153 | 154 | 155 | /** True if this is visible outside this file, false otherwise */ 156 | function isNodeExported(node: tsc.Node | tsc.Declaration): boolean { 157 | const declaration: tsc.Declaration = { 158 | _declarationBrand: 'branded', 159 | ...node, 160 | }; 161 | return (tsc.getCombinedModifierFlags(declaration) & tsc.ModifierFlags.Export) !== 0 || (!!declaration.parent && declaration.parent.kind === tsc.SyntaxKind.SourceFile); // tslint:disable-line no-bitwise 162 | } 163 | 164 | /** 165 | * Takes a unit (regression) test file and removes the regression tests. 166 | * This way we don't clutter up the documentation with regression test cases. 167 | */ 168 | function removeRegressionTests(testCode: string): string { 169 | if (!testCode.includes(REGRESSION_SEPARATOR)) return testCode; 170 | 171 | const [ before, ] = testCode.split(REGRESSION_SEPARATOR); 172 | return before; 173 | } 174 | 175 | // TODO: make this less atrocious 176 | function removeImports(fileString: string): string { 177 | const lines = fileString.split('\n'); 178 | 179 | return lines.reduce((final, line, i) => { 180 | const end = i === lines.length - 1 ? '' : '\n'; 181 | 182 | if (final !== '') return final + line + end; 183 | if (line.indexOf('import') === 0) return final; 184 | if (line.trim() === '') return final; 185 | 186 | return final + line + end; 187 | }, ''); 188 | } 189 | -------------------------------------------------------------------------------- /scripts/preparePublish.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | const pack = fs.readFileSync('package.json'); 4 | const json = JSON.parse(pack.toString()); 5 | 6 | json.main = 'index'; 7 | json.types = 'index.d.ts'; 8 | delete json.files; 9 | delete json.private; 10 | 11 | fs.writeFileSync('dist/src/package.json', JSON.stringify(json, undefined, 4)); 12 | 13 | fs.copyFileSync('README.md', 'dist/src/README.md'); 14 | -------------------------------------------------------------------------------- /scripts/testTsVersions.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # highest patch version of each minor version 4 | for v in 3.0.3 3.1.6 3.2.4 3.3.4000 3.4.5 3.5.3 3.6.5 3.7.5 3.8.3 next; do 5 | npm install --no-save typescript@$v 6 | npm test 7 | done 8 | -------------------------------------------------------------------------------- /src/impl.ts: -------------------------------------------------------------------------------- 1 | export * from './impl/objects'; 2 | -------------------------------------------------------------------------------- /src/impl/objects.ts: -------------------------------------------------------------------------------- 1 | import { DeepReadonly, TaggedObject } from '../types/objects'; 2 | 3 | /** 4 | * Type guard for any key, `k`. 5 | * Marks `k` as a key of `T` if `k` is in `obj`. 6 | * @param obj object to query for key `k` 7 | * @param k key to check existence in `obj` 8 | */ 9 | export function isKeyOf(obj: T, k: keyof any): k is keyof T { 10 | return k in obj; 11 | } 12 | 13 | /** 14 | * Same as `Object.keys` except that the returned type is an array of keys of the object. 15 | * Note that for the same reason that `Object.keys` does not do this natively, this method _is not safe_ for objects on the perimeter of your code (user input, read in files, network requests etc.). 16 | * @param obj object whose keys will be returned 17 | * @returns an array of keys from `obj` 18 | */ 19 | export function objectKeys(obj: T) { 20 | return Object.keys(obj) as Array; 21 | } 22 | 23 | /** 24 | * Useful for marking object literals as readonly while still keeping type inference: 25 | * `const obj = Readonly({ a: 22, b: 'yellow' });` 26 | * @param obj an object to be marked readonly 27 | * @returns `obj` marked as readonly at compile time 28 | */ 29 | export function Readonly(obj: T): DeepReadonly { return obj as any; } 30 | 31 | /** 32 | * Useful for tagged unions of objects (imagine redux reducers) this tags every sub-object with the key pointing to that sub-object. 33 | * @param obj an object of objects whose keys will be used as tags for the inner objects 34 | * @param key the name of the "tag" parameter 35 | * @returns `obj` with the inner objects tagged with parameter `key` and the key pointing to that inner object 36 | */ 37 | export function taggedObject, K extends string>(obj: T, key: K): TaggedObject { 38 | const keys = objectKeys(obj); 39 | return keys.reduce((collection: any, k) => { 40 | const inner: any = obj[k]; 41 | collection[k] = { 42 | [key]: k, 43 | ...inner, 44 | }; 45 | return collection; 46 | }, {} as TaggedObject); 47 | } 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './impl'; 3 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export * from './types/conditionals'; 2 | export * from './types/functions'; 3 | export * from './types/numbers'; 4 | export * from './types/objects'; 5 | export * from './types/predicates'; 6 | export * from './types/strings'; 7 | export * from './types/tuples'; 8 | export * from './types/utils'; 9 | -------------------------------------------------------------------------------- /src/types/conditionals.ts: -------------------------------------------------------------------------------- 1 | import { StringEqual } from './strings'; 2 | 3 | /** no-doc */ 4 | export type True = '1'; 5 | /** no-doc */ 6 | export type False = '0'; 7 | /** no-doc */ 8 | export type Bool = False | True; 9 | export type If = Cond extends True ? Then : Else; 10 | export type Not = A extends True ? False : True; 11 | export type And = If, False>; 12 | export type Or = If>; 13 | export type Xor = Or>, And, B>>; 14 | export type Nand = Not>; 15 | 16 | /** 17 | * no-doc - This shouldn't be exposed. 18 | * This is only useful for things that might return `True | False` which is falsy. 19 | */ 20 | export type ReallyTrue = StringEqual; 21 | -------------------------------------------------------------------------------- /src/types/functions.ts: -------------------------------------------------------------------------------- 1 | import { If } from './conditionals'; 2 | import { IsAny } from './predicates'; 3 | 4 | /** 5 | * This represents the constructor for a particular object. 6 | */ 7 | export type ConstructorFunction = new (...args: any[]) => T; 8 | /** 9 | * This is a function that takes some args and returns a boolean 10 | */ 11 | export type Predicate = (arg: A) => boolean; 12 | /** 13 | * Concisely and cleanly define an arbitrary function. 14 | * Useful when designing many api's that don't care what function they take in, they just need to know what it returns. 15 | */ 16 | export type AnyFunc = (...args: any[]) => R; 17 | 18 | /** 19 | * Modifies the return value of a function of up to 7 parameters. 20 | * @param F a function with up to 7 parameters 21 | * @param R the new return value of the function 22 | * @returns the function `F` with new return value `R` 23 | */ 24 | export type OverwriteReturn = 25 | F extends ((...x: infer T) => unknown) ? ((...x: T) => R) : never; 26 | 27 | /** 28 | * Returns a tuple type of a functions arguments up to 7. 29 | * @param F a function with up to 7 arguments 30 | * @returns a tuple containing `F`'s argument types 31 | */ 32 | export type ArgsAsTuple = 33 | F extends ((...x: infer T) => unknown) ? T : never; 34 | -------------------------------------------------------------------------------- /src/types/numbers.ts: -------------------------------------------------------------------------------- 1 | import { True, False } from './conditionals'; 2 | 3 | /** 4 | * Returns true if the number is equal to zero. 5 | */ 6 | export type IsZero = T extends 0 ? True : False; 7 | /** 8 | * Returns true if the number is equal to one. 9 | */ 10 | export type IsOne = T extends 1 ? True : False; 11 | /** 12 | * Returns the string type for a given number 13 | */ 14 | export type NumberToString = Numbers[N]; 15 | export type Numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63']; 16 | /** 17 | * Returns the number + 1. 18 | */ 19 | export type Next = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64][T]; 20 | /** 21 | * Returns the number - 1. 22 | */ 23 | export type Prev = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62][T]; 24 | /** 25 | * Adds two numbers together. 26 | */ 27 | export type Add = { '1': A, '0': Add, Prev> }[IsZero]; 28 | /** 29 | * Subtracts the second from the first. 30 | */ 31 | export type Sub = { '1': A, '0': Sub, Prev> }[IsZero]; 32 | /** 33 | * Returns `True` if the numbers are equivalent 34 | */ 35 | export type NumberEqual = A extends B ? B extends A ? True : False : False; 36 | -------------------------------------------------------------------------------- /src/types/objects.ts: -------------------------------------------------------------------------------- 1 | import { False, True } from './conditionals'; 2 | 3 | // ------- 4 | // Helpers 5 | // ------- 6 | /** 7 | * An object with string keys and values of type `any`. 8 | */ 9 | export type PlainObject = Record; 10 | /** 11 | * Takes any type and makes it an object type. 12 | * Useful when combined with `&` intersection types. 13 | * @param T A type who will be converted into an object 14 | * @returns An object formed by the key, value pairs of T 15 | */ 16 | export type ObjectType = { 17 | [k in keyof T]: T[k]; 18 | }; 19 | /** 20 | * Takes two objects and returns their intersection. 21 | * This combines all keys and uses `ObjectType` to "clean up" the resultant object. 22 | * Useful for making extremely complex types look nice in VSCode. 23 | * @param T First object to be intersected 24 | * @param U Second object to be intersected 25 | * @returns `T` & `U` cleaned up to look like flat object to VSCode 26 | */ 27 | export type CombineObjects = ObjectType; 28 | /** 29 | * Gets the value of specified property on any object without compile time error (`Property 'b' does not exist on type '{ a: string; }'.`) and the like. 30 | * Returns `never` if the key is not on the object. 31 | * It helps to use `If = K extends keyof T ? T[K] : never; 37 | /** 38 | * Like `GetKey`, but returns `unknown` if the key is not present on the object. 39 | * @param T Object to get values from 40 | * @param K Key to query object for value 41 | * @returns `T[K]` if the key exists, `unknown` otherwise 42 | */ 43 | export type TryKey = K extends keyof T ? T[K]: unknown; 44 | /** 45 | * Takes two objects and returns their element-wise intersection. 46 | * *Note*: this removes any key-level information, such as optional or readonly keys. 47 | * @param T First object to be intersected 48 | * @param U Second object to be intersected 49 | * @returns element-wise `T` & `U` cleaned up to look like flat object to VSCode 50 | */ 51 | export type ElementwiseIntersect = { 52 | [k in (keyof T | keyof U)]: TryKey & TryKey; 53 | }; 54 | 55 | // ---- 56 | // Keys 57 | // ---- 58 | /** 59 | * Objects can be indexed by multiple types: `string`, `number`, `symbol`. 60 | * For safe compatibility with typescript version, this type will always 61 | * have the correct set of object key types for the current version of TS. 62 | * 63 | * This is useful for functions that must take a key, instead of `K extends string`, 64 | * use `K extends ObjectKeys`. 65 | */ 66 | export type ObjectKeys = keyof any; 67 | /** 68 | * Typescript 2.9 introduced `number | symbol` as possible results from `keyof any`. 69 | * For backwards compatibility with objects containing only `string` keys, this will 70 | * exclude any `number | symbol` keys from `keyof`. 71 | * @param T type from which to get keys 72 | * @returns keys of `T` that extend `string` 73 | */ 74 | export type StringKeys = Exclude; 75 | /** 76 | * When an object has optional or readonly keys, that information is contained within the key. 77 | * When using optional/readonly keys in another object, they will retain optional/readonly status. 78 | * `PureKeys` will remove the optional/readonly status modifiers from keys. 79 | * @param T type from which to get keys 80 | * @returns keys of `T` without status modifiers (readonly/optional) 81 | */ 82 | export type PureKeys = Record[keyof T]; 83 | /** 84 | * Gets all of the keys that are shared between two objects. 85 | * @param T first type from which keys will be pulled 86 | * @param U second type from which keys will be pulled 87 | * @returns the keys that both `T` and `U` have in common. 88 | */ 89 | export type SharedKeys = keyof T & keyof U; 90 | /** 91 | * Gets all keys between two objects. 92 | * @param T first type from which keys will be pulled 93 | * @param U second type from which keys will be pulled 94 | * @returns the keys of `T` in addition to the keys of `U` 95 | */ 96 | export type AllKeys = keyof T | keyof U; 97 | /** 98 | * Gets all of the keys that are different between two objects. 99 | * This is a set difference between `keyof T` and `keyof U`. 100 | * Note that calling this with arguments reversed will have different results. 101 | * @param T first type from which keys will be pulled 102 | * @param U second type from which keys will be pulled 103 | * @returns keys of `T` minus the keys of `U` 104 | */ 105 | export type DiffKeys = Exclude; 106 | /** 107 | * Returns `True` if a key, `K`, is present in a type, `T`, else `False`. 108 | * @param T type to check for existence of key `K`. 109 | * @param K key to query `T` for 110 | * @returns `True` if `K` is a key of `T`. Else `False`. 111 | */ 112 | export type HasKey = K extends keyof T ? True : False; 113 | 114 | /** 115 | * @param T the union to get the keys of 116 | * @returns a union containing all the keys of members of `T` 117 | */ 118 | export type UnionKeys 119 | // Using a conditional here, so that it distributes over members of the union 120 | // See https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types 121 | = T extends unknown ? keyof T : never; 122 | 123 | // ------------- 124 | // Manipulations 125 | // ------------- 126 | /** 127 | * Get a union of the properties of an object. 128 | * @param T the object whose property values will be unionized 129 | * @returns a union of the right-side values of `T` 130 | */ 131 | export type UnionizeProperties = T[keyof T]; 132 | /** 133 | * Gives back an object with listed keys removed. 134 | * This is the opposite of `Pick`. 135 | * @param T the object whose properties will be removed 136 | * @param K the union of keys to remove from `T` 137 | * @returns `T` with the keys `K` removed 138 | */ 139 | export type Omit = Pick>; 140 | /** 141 | * Returns only the shared properties between two objects. 142 | * All shared properties must be the same type. 143 | * @param T the first object 144 | * @param U a second object whose shared properties (keys contained in both `T` and `U`) must have the same type as those in `T` 145 | * @returns the properties that are shared between `T` and `U` 146 | */ 147 | export type Intersect> = Omit>; 148 | /** 149 | * Can change the types of properties on an object. 150 | * This is similar to `Merge`, except that it will not add previously non-existent properties to the object. 151 | * @param T the object whose properties will be overwritten 152 | * @param U the object who will overwrite `T` 153 | * @returns `T` with properties overwritten with values in `U` 154 | */ 155 | export type Overwrite = { 156 | [k in keyof T]: k extends keyof U ? U[k] : T[k]; 157 | }; 158 | /** 159 | * Much like `_.merge` in javascript, this returns an object with all keys present between both objects, but conflicts resolved by rightmost object. 160 | * @param T the object whose properties will be overwritten 161 | * @param U the object whose properties will be present _as is_ in the resultant object 162 | * @returns an object containing all of `U` and properties of `T` not present in `U` 163 | */ 164 | export type Merge = Overwrite & U; 165 | /** 166 | * For discriminated unions of objects, it is important to have a single "tag" property. 167 | * Creates an object with each entry being tagged by the key defining that entry. 168 | * @param T a Record of objects 169 | * @param Key the key to add to each inner object as the tag property 170 | * @returns a record where each key of the record is now the `Key` property of the inner object 171 | */ 172 | export type TaggedObject, Key extends keyof any> = { 173 | [K in keyof T]: T[K] & Record; 174 | }; 175 | 176 | // --------- 177 | // Accessors 178 | // --------- 179 | /** 180 | * Uses `Partial` to make every parameter of an object optional (`| undefined`). 181 | * Iterates through arrays of objects and nested objects. 182 | * @param T the type to take a partial of. Can be deeply nested. 183 | * @returns `Partial` recursively through all properties of `T` 184 | */ 185 | export type DeepPartial = Partial<{ 186 | [k in keyof T]: 187 | T[k] extends unknown[] ? Array> : 188 | T[k] extends Function ? T[k] : 189 | T[k] extends object ? DeepPartial : 190 | T[k]; 191 | }>; 192 | /** 193 | * Marks all keys as required. 194 | * @param T object whose keys will be marked required 195 | * @returns `T` with all fields marked required 196 | */ 197 | export type AllRequired = { 198 | [K in keyof T]-?: NonNullable 199 | }; 200 | /** 201 | * Mark specific keys, `K`, of `T` as required. 202 | * @param T object whose keys will be marked required 203 | * @param K keys of `T` that will be marked required 204 | * @returns `T` with keys, `K`, marked as required 205 | */ 206 | export type Required = CombineObjects< 207 | {[k in K]-?: NonNullable }, 208 | Omit 209 | >; 210 | /** 211 | * Mark specific keys, `K`, of `T` as optional (think `Partial`). 212 | * @param T object whose keys will be marked optional 213 | * @param K keys of `T` that will be marked optional 214 | * @returns `T` with keys, `K`, marked as optional 215 | */ 216 | export type Optional = CombineObjects< 217 | {[k in K]?: T[k] | undefined }, 218 | Omit 219 | >; 220 | /** 221 | * Uses `Readonly` to make every parameter of an object - and its sub-objects recursively - readonly. 222 | * @param T type to be recursively traversed with keys marked as readonly 223 | * @returns `T` with all keys recursively marked as readonly 224 | */ 225 | export type DeepReadonly = Readonly<{ 226 | [k in keyof T]: 227 | T[k] extends unknown[] ? ReadonlyArray> : 228 | T[k] extends Function ? T[k] : 229 | T[k] extends object ? DeepReadonly : 230 | T[k]; 231 | }>; 232 | /** 233 | * Gets all keys that point to a given type. 234 | * @param O the object whose keys will be returned 235 | * @param T the type to filter by 236 | * @returns keys of `O` whose right-side value is `T` 237 | */ 238 | export type KeysByType = { 239 | [k in keyof O]-?: O[k] extends T ? k : never; 240 | }[keyof O]; 241 | 242 | 243 | // ------- 244 | // Classes 245 | // ------- 246 | /** 247 | * Builds the type of any constructor function for a particular object. 248 | * @param T the object to build a constructor for 249 | * @returns a constructor for `T` with the prototype set to `T` 250 | */ 251 | export interface ConstructorFor { 252 | new (...args: any[]): T; 253 | prototype: T; 254 | } 255 | 256 | // ------ 257 | // Unions 258 | // ------ 259 | 260 | /** 261 | * Makes a union 'strict', such that members are disallowed from including the keys of other members 262 | * For example, `{x: 1, y: 1}` is a valid member of `{x: number} | {y: number}`, 263 | * but it's not a valid member of StrictUnion<{x: number} | {y: number}>. 264 | * @param T a union type 265 | * @returns a the strict version of `T` 266 | */ 267 | export type StrictUnion = _StrictUnionHelper; 268 | 269 | // UnionMember is actually passed as the whole union, but it's used in a distributive conditional 270 | // to refer to each individual member of the union 271 | /** no-doc */ 272 | export type _StrictUnionHelper = 273 | UnionMember extends unknown ? 274 | UnionMember & Partial, keyof UnionMember>, never>> 275 | : never; 276 | -------------------------------------------------------------------------------- /src/types/predicates.ts: -------------------------------------------------------------------------------- 1 | import { False, True, And, Or, Not } from './conditionals'; 2 | 3 | /** no-doc */ 4 | export type KnownProblemPrototypeKeys = 'toString' | 'toLocaleString' | 'hasOwnProperty' | 'isPrototypeOf' | 'propertyIsEnumerable' | 'constructor' | 'valueOf'; 5 | /** no-doc */ 6 | export type ArrayPrototypeKeys = keyof unknown[]; 7 | /** no-doc */ 8 | export type NumberPrototypeKeys = keyof number; 9 | /** no-doc */ 10 | export type BooleanPrototypeKeys = keyof false; 11 | /** no-doc */ 12 | export type StringPrototypeKeys = keyof string; 13 | /** no-doc */ 14 | export type ObjectPrototypeKeys = keyof Object; 15 | /** no-doc */ 16 | export type FunctionPrototypeKeys = keyof Function; 17 | 18 | export type IsNever = Not<(Record & Record)[S]>; 19 | export type IsType = X extends T ? True : False; 20 | export type IsArray = T extends unknown[] ? True : False; 21 | export type IsNumber = T extends number ? True : False; 22 | export type IsString = T extends string ? True : False; 23 | export type IsFunction = 24 | Or< 25 | T extends Function ? True : False, 26 | T extends Function ? True : False>; 27 | 28 | export type IsStringFunction = And, IsNever>; 29 | export type IsBoolean = T extends boolean ? True : False; 30 | export type IsNull = T extends null ? True : False; 31 | export type IsUndefined = T extends undefined ? True : False; 32 | export type IsNil = Or, IsUndefined>; 33 | 34 | export type IsObject = And< 35 | T extends object ? True : False, 36 | And>, 37 | Not>> 38 | >; 39 | 40 | export type IsAny = 0 extends (1 & T) ? True : False; 41 | -------------------------------------------------------------------------------- /src/types/strings.ts: -------------------------------------------------------------------------------- 1 | import { True, False, And } from './conditionals'; 2 | import { IsNever } from './predicates'; 3 | 4 | export type DropString = Exclude; 5 | 6 | export type StringEqual = 7 | And< 8 | IsNever>, 9 | IsNever> 10 | >; 11 | 12 | export type UnionContains = U extends T ? True : False; 13 | -------------------------------------------------------------------------------- /src/types/tuples.ts: -------------------------------------------------------------------------------- 1 | import {UnionToIntersection} from './utils'; 2 | 3 | export interface Vector { readonly [x: number]: T; readonly length: number; } 4 | export type Length> = T['length']; 5 | 6 | /** 7 | * Gives a union of all values contained in a tuple. 8 | * @param T a tuple of items 9 | * @returns a union of all items in tuple 10 | */ 11 | export type UnionizeTuple> = T[number]; 12 | /** 13 | * Gives an intersection of all values contained in a tuple. 14 | * @param T a tuple of items 15 | * @returns an intersection of all items in the tuple 16 | */ 17 | export type IntersectTuple>= UnionToIntersection; 18 | -------------------------------------------------------------------------------- /src/types/utils.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType } from "./objects"; 2 | /** 3 | * Prevent `T` from being inferred in generic function 4 | * 5 | * @param T the type that will be un-inferrable 6 | * @returns type of `T` 7 | * @see https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-322267089 8 | */ 9 | export type NoInfer = T & ObjectType; 10 | 11 | /** 12 | * Prevent `T` from being distributed in a conditional type. 13 | * A conditional is only distributed when the checked type is naked type param and T & {} is not a 14 | * naked type param, but has the same contract as T. 15 | * 16 | * @note This must be used directly the condition itself: `NoDistribute extends U`, 17 | * it won't work wrapping a type argument passed to a conditional type. 18 | * 19 | * @see https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types 20 | */ 21 | export type NoDistribute = T & {}; 22 | 23 | /** 24 | * Mark a type as nullable (`null | undefined`). 25 | * @param T type that will become nullable 26 | * @returns `T | null | undefined` 27 | */ 28 | export type Nullable = T | null | undefined; 29 | 30 | /** 31 | * no-doc - This is a helper for `Nominal` and is not useful on its own 32 | */ 33 | export declare class Tagged { protected _nominal_: N; } 34 | /** 35 | * Constructs a nominal type of type `T`. 36 | * Useful to prevent any value of type `T` from being used or modified in places it shouldn't (think `id`s). 37 | * @param T the type of the `Nominal` type (string, number, etc.) 38 | * @param N the name of the `Nominal` type (id, username, etc.) 39 | * @returns a type that is equal only to itself, but can be used like its contained type `T` 40 | */ 41 | export type Nominal = T & Tagged; 42 | 43 | /** 44 | * Returns the given type or a Promise containing that type. 45 | * @param T the inner type for the promise 46 | * @returns a the type union with a promise containing the given type 47 | */ 48 | export type PromiseOr = Promise | T; 49 | 50 | 51 | /** 52 | * Defines an intersection type of all union items. 53 | * @param U Union of any types that will be intersected. 54 | * @returns U items intersected 55 | * @see https://stackoverflow.com/a/50375286/9259330 56 | */ 57 | export type UnionToIntersection = 58 | (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; 59 | -------------------------------------------------------------------------------- /test/conditionals/And.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If, And } from '../../src'; 5 | 6 | test('Conditions can be based on AND', t => { 7 | type conditional = If, number, string>; 8 | type gotFF = conditional; 9 | type gotFT = conditional; 10 | type gotTF = conditional; 11 | type gotTT = conditional; 12 | 13 | assert(t); 14 | assert(t); 15 | assert(t); 16 | assert(t); 17 | }); 18 | -------------------------------------------------------------------------------- /test/conditionals/If.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If } from '../../src'; 5 | 6 | test('Can assign type conditionally', t => { 7 | type conditional = If; 8 | type gotF = conditional; 9 | type gotT = conditional; 10 | 11 | assert(t); 12 | assert(t); 13 | }); 14 | -------------------------------------------------------------------------------- /test/conditionals/Nand.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If, Nand } from '../../src'; 5 | 6 | test('Conditions can be based on NAND', t => { 7 | assert, False>(t); 8 | assert, True>(t); 9 | assert, True>(t); 10 | assert, True>(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/conditionals/Not.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If, Not } from '../../src'; 5 | 6 | test('Conditional logic can be inversed with NOT', t => { 7 | type conditional = If, number, string>; 8 | type gotF = conditional; 9 | type gotT = conditional; 10 | 11 | assert(t); 12 | assert(t); 13 | }); 14 | -------------------------------------------------------------------------------- /test/conditionals/Or.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If, Or } from '../../src'; 5 | 6 | test('Conditions can be based on OR', t => { 7 | type conditional = If, number, string>; 8 | type gotFF = conditional; 9 | type gotFT = conditional; 10 | type gotTF = conditional; 11 | type gotTT = conditional; 12 | 13 | assert(t); 14 | assert(t); 15 | assert(t); 16 | assert(t); 17 | }); 18 | -------------------------------------------------------------------------------- /test/conditionals/Xor.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Bool, False, True, If, Xor } from '../../src'; 5 | 6 | test('Conditions can be based on XOR', t => { 7 | assert, False>(t); 8 | assert, True>(t); 9 | assert, True>(t); 10 | assert, False>(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/functions/AnyFunc.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { AnyFunc } from '../../src'; 5 | 6 | test('Can define the type of a function that takes any arguments', t => { 7 | type got = AnyFunc; 8 | type got2 = AnyFunc; // takes anything, returns a number 9 | 10 | type expected = (...args: any[]) => any; 11 | type expected2 = (...args: any[]) => number; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/functions/ArgsAsTuple.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { ArgsAsTuple } from '../../src'; 5 | 6 | test("Can get a tuple of function's argument types", t => { 7 | type F0 = () => any; 8 | type F1 = (x: number) => any; 9 | type F2 = (x: number, y: string) => any; 10 | type F3 = (x: number, y: string, z: boolean) => any; 11 | 12 | type E0 = []; 13 | type E1 = [number]; 14 | type E2 = [number, string]; 15 | type E3 = [number, string, boolean]; 16 | 17 | assert, E0>(t); 18 | assert, E1>(t); 19 | assert, E2>(t); 20 | assert, E3>(t); 21 | }); 22 | -------------------------------------------------------------------------------- /test/functions/ConstructorFunction.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { ConstructorFunction } from '../../src'; 5 | 6 | test('Can build a constructor type for a type', t => { 7 | type Constructor = ConstructorFunction<{ x: string, y: number }>; 8 | class Thing { x: string = ''; y: number = 22; } 9 | 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/functions/OverwriteReturn.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { OverwriteReturn } from '../../src'; 5 | 6 | test('Can change return type of a function', t => { 7 | type f = (x: 'hi', y: 'there', z: 22) => number; 8 | 9 | type got = OverwriteReturn; 10 | type expected = (x: 'hi', y: 'there', z: 22) => string; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /test/functions/Predicate.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Predicate } from '../../src'; 5 | 6 | test('Can build a predicate function with single known argument type', t => { 7 | type PredFunc = Predicate; 8 | type expected = (arg: string) => boolean; 9 | 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/helpers/assert.ts: -------------------------------------------------------------------------------- 1 | export function assert(t: { pass: any }) { t.pass(); } 2 | -------------------------------------------------------------------------------- /test/impl/objects/isKeyOf.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../../helpers/assert'; 3 | 4 | import { isKeyOf } from '../../../src'; 5 | 6 | test('Can check if an object contains a key', t => { 7 | const o = { a: 'hi', b: 22 }; 8 | const key1: string = 'a'; 9 | 10 | if (isKeyOf(o, key1)) { 11 | assert(t); 12 | t.pass(); 13 | } else { 14 | assert(t); 15 | t.fail(); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /test/impl/objects/objectKeys.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../../helpers/assert'; 3 | 4 | import { objectKeys } from '../../../src'; 5 | 6 | test('Can get keys of an object', t => { 7 | const o = { a: 'hi', b: 22 }; 8 | const keys = objectKeys(o); 9 | 10 | type K = typeof keys; 11 | type expected = Array<'a' | 'b'>; 12 | assert(t); 13 | assert(t); 14 | 15 | t.deepEqual(keys, ['a', 'b']); 16 | }); 17 | -------------------------------------------------------------------------------- /test/impl/objects/taggedObject.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../../helpers/assert'; 3 | 4 | import { taggedObject } from '../../../src'; 5 | 6 | test('Can generate a tagged object', t => { 7 | const obj = { 8 | a: { merp: 'hi' }, 9 | b: { merp: 'there' }, 10 | c: { merp: 'friend' }, 11 | }; 12 | 13 | const expected = { 14 | a: { name: 'a' as 'a', merp: 'hi' }, 15 | b: { name: 'b' as 'b', merp: 'there' }, 16 | c: { name: 'c' as 'c', merp: 'friend' }, 17 | }; 18 | 19 | const got = taggedObject(obj, 'name'); 20 | 21 | t.deepEqual(got, expected); 22 | assert(t); 23 | assert(t); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/numbers/Add.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Add } from '../../src'; 5 | 6 | test('Can add two numbers', t => { 7 | type fifty = Add<12, 38>; 8 | assert(t); 9 | }); 10 | -------------------------------------------------------------------------------- /test/numbers/IsOne.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { True, False, IsOne } from '../../src'; 5 | 6 | test('Can check if a number is one', t => { 7 | type notOne = IsOne<0>; 8 | type one = IsOne<1>; 9 | assert(t); 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/numbers/IsZero.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { True, False, IsZero } from '../../src'; 5 | 6 | test('Can check if a number is zero', t => { 7 | type notZero = IsZero<1>; 8 | type zero = IsZero<0>; 9 | assert(t); 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/numbers/NumberEqual.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { True, False, NumberEqual } from '../../src'; 5 | 6 | test('Can check if two numbers are equal', t => { 7 | type notEqual = NumberEqual<22, 23>; 8 | type equal = NumberEqual<12, 12>; 9 | assert(t); 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/numbers/NumberToString.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { NumberToString } from '../../src'; 5 | 6 | test('Can get a number as a string', t => { 7 | type str = NumberToString<22>; 8 | assert(t); 9 | }); 10 | -------------------------------------------------------------------------------- /test/numbers/Sub.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Sub } from '../../src'; 5 | 6 | test('Can subtract two numbers', t => { 7 | type ten = Sub<22, 12>; 8 | assert(t); 9 | }); 10 | -------------------------------------------------------------------------------- /test/objects/AllKeys.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { AllKeys } from '../../src'; 5 | 6 | test('Can get all keys between objects', t => { 7 | type a = { w: number, x: string }; 8 | type b = { x: number, z: boolean }; 9 | 10 | type got = AllKeys; 11 | type expected = 'w' | 'x' | 'z'; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/objects/AllRequired.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { AllRequired } from '../../src'; 5 | 6 | test('Can make all fields of options object required (not optional and not nullable)', t => { 7 | type x = { a?: string, b: number | undefined }; 8 | type got = AllRequired; 9 | type expected = { a: string, b: number }; 10 | 11 | assert(t); 12 | assert(t); 13 | }); 14 | -------------------------------------------------------------------------------- /test/objects/CombineObjects.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { CombineObjects } from '../../src'; 5 | 6 | test('Can combine two objects (without pesky & in vscode)', t => { 7 | type a = { x: number, y: 'hi' }; 8 | type b = { z: number }; 9 | 10 | type got = CombineObjects; 11 | type expected = { 12 | x: number, 13 | y: 'hi', 14 | z: number 15 | }; 16 | 17 | assert(t); 18 | assert(t); 19 | }); 20 | -------------------------------------------------------------------------------- /test/objects/ConstructorFor.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { ConstructorFor } from '../../src'; 5 | 6 | test('Can get a constructor type for any object', t => { 7 | type x = { a: number, b: string, c: boolean }; 8 | 9 | class Expected { 10 | a: number = null as any; 11 | b: string = null as any; 12 | c: boolean = null as any; 13 | } 14 | 15 | type got = ConstructorFor; 16 | assert(t); 17 | assert(t); 18 | }); 19 | -------------------------------------------------------------------------------- /test/objects/DeepPartial.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { DeepPartial } from '../../src'; 5 | 6 | test('Can get a deep partial object', t => { 7 | type a = { 8 | b: { 9 | c: number 10 | }, 11 | d: string 12 | }; 13 | 14 | type got = DeepPartial; 15 | type expected = { 16 | b?: { 17 | c?: number 18 | }, 19 | d?: string 20 | }; 21 | 22 | assert(t); 23 | assert(t); 24 | }); 25 | 26 | test('Can get a deep partial object with arrays', t => { 27 | type a = { 28 | b: Array<{ 29 | c: number, 30 | }>, 31 | }; 32 | 33 | type got = DeepPartial; 34 | type expected = { 35 | b?: Array<{ 36 | c?: number, 37 | }>, 38 | }; 39 | 40 | assert(t); 41 | assert(t); 42 | }); 43 | 44 | test('Can get a deep partial object with functions', t => { 45 | type x = { 46 | a: () => 22, 47 | b: string, 48 | c: { 49 | d: number, 50 | }, 51 | }; 52 | 53 | type expected = { 54 | a?: () => 22, 55 | b?: string, 56 | c?: { 57 | d?: number, 58 | }, 59 | }; 60 | 61 | type got = DeepPartial; 62 | 63 | assert(t); 64 | assert(t); 65 | }); 66 | -------------------------------------------------------------------------------- /test/objects/DeepReadonly.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { DeepReadonly } from '../../src'; 5 | 6 | test('Can make nested object readonly', t => { 7 | type x = { x: { a: 1, b: 'hi' }, y: 'hey' }; 8 | 9 | type expected = { readonly x: Readonly<{ a: 1, b: 'hi' }>, readonly y: 'hey' }; 10 | type got = DeepReadonly; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | 16 | test('Can make nested object with arrays readonly', t => { 17 | type x = { x: [{ a: 1, b: 'hi' }], y: 'hey' }; 18 | 19 | type expected = { readonly x: ReadonlyArray>, readonly y: 'hey' }; 20 | type got = DeepReadonly; 21 | 22 | assert(t); 23 | assert(t); 24 | }); 25 | 26 | test('Can make an object with functions readonly', t => { 27 | type x = { 28 | a: () => 22, 29 | b: string, 30 | c: { 31 | d: boolean, 32 | }, 33 | }; 34 | 35 | type expected = { 36 | readonly a: () => 22, 37 | readonly b: string, 38 | readonly c: { 39 | readonly d: boolean, 40 | }, 41 | }; 42 | type got = DeepReadonly; 43 | 44 | assert(t); 45 | assert(t); 46 | }); 47 | -------------------------------------------------------------------------------- /test/objects/DiffKeys.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { DiffKeys } from '../../src'; 5 | 6 | test('Can get all keys that are different between objects', t => { 7 | type a = { x: number, y: string }; 8 | type b = { y: string, z: number }; 9 | 10 | type gotA = DiffKeys; 11 | type gotB = DiffKeys; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/objects/ElementwiseIntersect.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { ElementwiseIntersect } from '../../src'; 5 | 6 | test('Can combine two objects elementwise', t => { 7 | type a = { x: number, y: 'hi' }; 8 | type b = { z: number, y: 'there' }; 9 | 10 | type got = ElementwiseIntersect; 11 | type expected = { 12 | x: number, 13 | y: 'hi' & 'there', 14 | z: number, 15 | }; 16 | 17 | assert(t); 18 | assert(t); 19 | }); 20 | 21 | test('Can combine two objects with private members elementwise', t => { 22 | class A { 23 | a: number = 1; 24 | private x: number = 2; 25 | y: 'hi' = 'hi'; 26 | private z: 'hey' = 'hey'; 27 | } 28 | 29 | class B { 30 | a: 22 = 22; 31 | private x: number = 2; 32 | y: 'there' = 'there'; 33 | private z: 'friend' = 'friend'; 34 | } 35 | 36 | type got = ElementwiseIntersect; 37 | type expected = { 38 | a: 22, 39 | y: 'hi' & 'there', 40 | }; 41 | 42 | assert(t); 43 | assert(t); 44 | }); 45 | -------------------------------------------------------------------------------- /test/objects/GetKey.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { GetKey } from '../../src'; 5 | 6 | test('Can safely get the value at a certain key if it exists', t => { 7 | type obj = { x: number, y: string }; 8 | type expected = number; 9 | type got = GetKey; 10 | 11 | assert(t); 12 | assert(t); 13 | }); 14 | 15 | test('Will get `never` if key does not exist', t => { 16 | type obj = { x: number, y: string }; 17 | type expected = never; 18 | type got = GetKey; 19 | 20 | assert(t); 21 | assert(t); 22 | }); 23 | -------------------------------------------------------------------------------- /test/objects/Intersect.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Intersect } from '../../src'; 5 | 6 | test('Can get an object with only shared properties', t => { 7 | type a = { x: number, y: string }; 8 | type b = { y: string, z: string }; 9 | 10 | type expected = { y: string }; 11 | type got = Intersect; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/objects/KeysByType.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { KeysByType } from '../../src'; 5 | 6 | test('Can filter object keys by right side type', t => { 7 | type obj = { 8 | a: 1, 9 | b: 2, 10 | c: 3, 11 | }; 12 | 13 | type expected = 'a' | 'b'; 14 | type got = KeysByType; 15 | 16 | assert(t); 17 | assert(t); 18 | }); 19 | 20 | /* Regression Tests */ 21 | 22 | test('Does not give `undefined` key', t => { 23 | type obj = { a: string, b?: number }; 24 | 25 | type got = KeysByType; 26 | type expected = 'a'; 27 | 28 | assert(t); 29 | assert(t); 30 | }); 31 | 32 | test('Filters optional keys by undefined type', t => { 33 | type obj = { a: string, b?: number }; 34 | 35 | type got = KeysByType; 36 | type expected = 'b'; 37 | 38 | // TODO: Known failure. Optional keys are not considered of type `undefined`. 39 | // assert(t); 40 | // assert(t); 41 | t.pass(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/objects/Merge.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Merge } from '../../src'; 5 | 6 | test('Can merge two objects, resolving matching keys by rightmost object', t => { 7 | type a = { x: number, y: string }; 8 | type b = { y: number, z: string }; 9 | 10 | type got = Merge; 11 | type expected = { x: number, y: number, z: string }; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | 17 | test('Can merge an object containing all strings as keys', t => { 18 | type a = { 19 | y: string; 20 | [s: string]: string; 21 | }; 22 | type b = { x: number, y: number }; 23 | 24 | type got = Merge; 25 | type expected = { x: number, y: number } & Record; 26 | 27 | assert(t); 28 | assert(t); 29 | }); 30 | -------------------------------------------------------------------------------- /test/objects/ObjectType.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { ObjectType } from '../../src'; 5 | 6 | test('Can turn an object into another object', t => { 7 | type obj = { x: number, y: string }; 8 | type expected = obj; 9 | type got = ObjectType; 10 | 11 | assert(t); 12 | assert(t); 13 | }); 14 | -------------------------------------------------------------------------------- /test/objects/Omit.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Omit } from '../../src'; 5 | 6 | test('Can omit keys from an object', t => { 7 | type a = { x: number, y: string, z: boolean }; 8 | 9 | type got = Omit; 10 | type expected = { z: boolean }; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/objects/Optional.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Optional } from '../../src'; 5 | 6 | test('Can make properties optional', t => { 7 | type x = { x: number, y: string, z: 'hello there' }; 8 | 9 | type expected = { x?: number, y?: string, z: 'hello there' }; 10 | type got = Optional; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/objects/Overwrite.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Overwrite } from '../../src'; 5 | 6 | test('Can overwrite properties on an object', t => { 7 | type a = { x: number, y: string, z: 'hello there' }; 8 | 9 | type expected = { x: number, y: string, z: 'hello' | 'there' }; 10 | type got1 = Overwrite; 11 | type got2 = Overwrite; 12 | 13 | assert(t); 14 | assert(t); 15 | assert(t); 16 | assert(t); 17 | }); 18 | -------------------------------------------------------------------------------- /test/objects/Required.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Required } from '../../src'; 5 | 6 | test('Can make certain fields of options object required', t => { 7 | type x = { a?: string, b: number | undefined }; 8 | type got1 = Required; 9 | type got2 = Required; 10 | type got3 = Required; 11 | 12 | type expected1 = { a: string, b: number | undefined }; 13 | type expected2 = { a?: string, b: number }; 14 | type expected3 = { a: string, b: number }; 15 | 16 | assert(t); 17 | assert(t); 18 | assert(t); 19 | }); 20 | -------------------------------------------------------------------------------- /test/objects/SharedKeys.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { SharedKeys } from '../../src'; 5 | 6 | test('Can get keys that are same between objects', t => { 7 | type a = { x: number, y: string }; 8 | type b = { x: string, y: string, z: boolean }; 9 | 10 | type got = SharedKeys; 11 | type expected = 'x' | 'y'; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/objects/StrictUnion.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { StrictUnion } from '../../src'; 5 | 6 | test('disallow union members with mixed properties', t => { 7 | type a = { a: number }; 8 | type b = { b: string }; 9 | 10 | type good1 = {a: 1}; 11 | type good2 = {b: "b"}; 12 | type bad = {a: 1, b: "foo"}; 13 | 14 | type isStrict = T extends Array> ? 'Yes' : 'No'; 15 | 16 | type strictUnion = [good1, good2]; 17 | type nonStrictUnion = [good1, good2, bad]; 18 | 19 | assert, 'Yes'>(t); 20 | assert, 'No'>(t); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /test/objects/UnionKeys.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { UnionKeys } from '../../src'; 5 | 6 | test('Can get all keys between objects in a union', t => { 7 | type a = { w: number, x: string }; 8 | type b = { x: number, z: boolean }; 9 | type c = { y: boolean, z: string }; 10 | 11 | type got = UnionKeys; 12 | type expected = 'w' | 'x' | 'y' | 'z'; 13 | 14 | assert(t); 15 | assert(t); 16 | }); 17 | -------------------------------------------------------------------------------- /test/objects/UnionizeProperties.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { UnionizeProperties } from '../../src'; 5 | 6 | test('Can get a union of all values in an object', t => { 7 | type a = { x: 'hi', y: 'there', z: 'friend' }; 8 | 9 | type got = UnionizeProperties; 10 | type expected = 'hi' | 'there' | 'friend'; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/prototypes.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { IsAny, IsArray, IsBoolean, IsFunction, IsNumber, IsObject, IsString, IsType, True, False } from '../src/index'; 3 | 4 | function assert(t: { pass: any }) { t.pass(); } 5 | 6 | class Class { x = 'hey'; } 7 | const inst = new Class(); 8 | 9 | type num = 0; 10 | type str = 'hi'; 11 | type boo = false; 12 | type obj = object; 13 | type func = () => string; 14 | type cls = typeof inst; 15 | 16 | test('Can check if a type is an array', t => { 17 | assert, True>(t); 18 | assert, True>(t); 19 | 20 | assert, False>(t); 21 | assert, False>(t); 22 | assert, False>(t); 23 | assert, False>(t); 24 | assert, False>(t); 25 | assert, False>(t); 26 | assert, False>(t); 27 | }); 28 | 29 | test('Can check if a type is a number', t => { 30 | assert, True>(t); 31 | assert, True>(t); 32 | 33 | assert, False>(t); 34 | assert, False>(t); 35 | assert, False>(t); 36 | assert, False>(t); 37 | assert, False>(t); 38 | assert, False>(t); 39 | assert, False>(t); 40 | 41 | }); 42 | 43 | test('Can check if a type is boolean', t => { 44 | assert, True>(t); 45 | assert, True>(t); 46 | 47 | assert, False>(t); 48 | assert, False>(t); 49 | assert, False>(t); 50 | assert, False>(t); 51 | assert, False>(t); 52 | assert, False>(t); 53 | assert, False>(t); 54 | }); 55 | 56 | test('Can check if a type is a string', t => { 57 | assert, True>(t); 58 | assert, True>(t); 59 | 60 | assert, False>(t); 61 | assert, False>(t); 62 | assert, False>(t); 63 | assert, False>(t); 64 | assert, False>(t); 65 | assert, False>(t); 66 | assert, False>(t); 67 | }); 68 | 69 | test('Can check if a type is a function', t => { 70 | assert, True>(t); 71 | assert, True>(t); 72 | 73 | assert, False>(t); 74 | assert, False>(t); 75 | assert, False>(t); 76 | assert, False>(t); 77 | assert, False>(t); 78 | assert, False>(t); 79 | assert, False>(t); 80 | }); 81 | 82 | test('Can check if a type is an object', t => { 83 | assert, True>(t); 84 | assert, True>(t); 85 | assert, True>(t); 86 | 87 | assert, True>(t); 88 | assert, False>(t); 89 | assert, False>(t); 90 | assert, False>(t); 91 | assert, False>(t); 92 | assert, False>(t); 93 | assert, False>(t); 94 | }); 95 | 96 | test('Can check if type is the same as another type', t => { 97 | assert, True>(t); 98 | assert, True>(t); 99 | 100 | assert, False>(t); 101 | }); 102 | 103 | test('Can check if a type is any', t => { 104 | assert, True>(t); 105 | 106 | assert, False>(t); 107 | assert, False>(t); 108 | assert, False>(t); 109 | assert, False>(t); 110 | assert, False>(t); 111 | assert, False>(t); 112 | assert, False>(t); 113 | }); 114 | -------------------------------------------------------------------------------- /test/strings/DropString.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { DropString } from '../../src'; 5 | 6 | test('Can remove a string from a union of strings', t => { 7 | type a = 'hi' | 'there'; 8 | type b = 'hey' | 'there' | never; 9 | 10 | assert, 'there'>(t); 11 | assert, never>(t); 12 | assert, never>(t); 13 | }); 14 | -------------------------------------------------------------------------------- /test/strings/IsNever.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { False, True, IsNever } from '../../src'; 5 | 6 | test('Can ask if a string is of type "never"', t => { 7 | type str = 'hi'; 8 | assert, False>(t); 9 | assert, True>(t); 10 | assert, True>(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/strings/StringEqual.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { True, False, StringEqual } from '../../src'; 5 | 6 | test('Can check that two unions of strings are equal', t => { 7 | type a = 'hi' | 'there'; 8 | type b = 'there' | 'hi'; 9 | type c = 'hi' | 'there' | 'friend'; 10 | 11 | assert, True>(t); 12 | assert, True>(t); 13 | assert, False>(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/tuples/IntersectTuple.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { IntersectTuple } from '../../src'; 5 | 6 | test('Can get the intersection of tuple values', t => { 7 | type t = [{a: 'hi'}, {b: 'there'}, {c: 'friend'}]; 8 | 9 | type got = IntersectTuple; 10 | type expected = {a: 'hi'} & {b: 'there'} & {c: 'friend'}; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/tuples/Length.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Length } from '../../src'; 5 | 6 | test('Can get the length of a tuple', t => { 7 | type t = [1, 2, 3, 4]; 8 | type x = ['hello', 'world']; 9 | 10 | type gotT = Length; 11 | type gotX = Length; 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/tuples/UnionizeTuple.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { UnionizeTuple } from '../../src'; 5 | 6 | test('Can get a union of all values in tuple', t => { 7 | type t = ['hi', 'there', 'friend']; 8 | 9 | type got = UnionizeTuple; 10 | type expected = 'hi' | 'there' | 'friend'; 11 | 12 | assert(t); 13 | assert(t); 14 | }); 15 | -------------------------------------------------------------------------------- /test/tuples/Vector.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Vector } from '../../src'; 5 | 6 | test('Can extend a vector with tuple', t => { 7 | function doStuff, E extends T, L extends number>(x: T, y: E, length: L) { 8 | assert(t); 9 | assert(t); 10 | } 11 | 12 | const x: ['hey', 22] = ['hey', 22]; 13 | const y: ['hey', 'there', 'friend'] = ['hey', 'there', 'friend']; 14 | doStuff(x, ['hey', 22], 2); 15 | doStuff(y, ['hey', 'there', 'friend'], 3); 16 | }); 17 | -------------------------------------------------------------------------------- /test/utils/NoDistribute.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { NoDistribute } from '../../src'; 5 | 6 | test("can create a conditional type that won't distribute over unions", t => { 7 | type IsString = T extends string ? "Yes" : "No"; 8 | type IsStringNoDistribute = NoDistribute extends string ? "Yes" : "No"; 9 | 10 | /** 11 | * Evaluates as: 12 | * ("foo" extends string ? "Yes" : "No") 13 | * | (42 extends string ? "Yes" : "No") 14 | */ 15 | type T1 = IsString<"foo" | 42>; 16 | assert(t); 17 | assert<"Yes" | "No", T1>(t); 18 | 19 | /** 20 | * Evaluates as: 21 | * ("foo" | 42) extends string ? "Yes" : "No" 22 | */ 23 | type T2 = IsStringNoDistribute<"foo" | 5>; 24 | assert(t); 25 | assert<"No", T2>(t); 26 | }); 27 | 28 | test("cannot be used to prevent a distributive conditional from distributing", t => { 29 | type IsString = T extends string ? "Yes" : "No"; 30 | // It's the defintion of the conditional type that matters, 31 | // not the type that's passed in, so this still distributes 32 | type Test = IsString>; 33 | assert(t); 34 | assert<"Yes" | "No", Test>(t); 35 | }); 36 | -------------------------------------------------------------------------------- /test/utils/NoInfer.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { NoInfer } from '../../src'; 5 | 6 | test('Will not infer based on second argument', t => { 7 | function doStuff(x: T, y: NoInfer): T { return x; } 8 | 9 | const hi = 'hi' as 'hi' | number; 10 | const there = 'there'; 11 | const x = doStuff(hi, there); 12 | 13 | assert(t); 14 | assert(t); 15 | }); 16 | -------------------------------------------------------------------------------- /test/utils/Nominal.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Nominal } from '../../src'; 5 | 6 | test('Can make a new nominal type', t => { 7 | type Id = Nominal; 8 | 9 | // TODO: improve once negative testing is in place 10 | assert>(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/utils/Nullable.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { Nullable } from '../../src'; 5 | 6 | test('Will make a type nullable (null | undefined)', t => { 7 | type got = Nullable; 8 | type expected = string | null | undefined; 9 | 10 | assert(t); 11 | }); 12 | 13 | test('Will make a type not nullable', t => { 14 | type got = NonNullable>; 15 | 16 | assert(t); 17 | }); 18 | -------------------------------------------------------------------------------- /test/utils/PromiseOr.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { PromiseOr } from '../../src'; 5 | 6 | test('Will give back a promise containing given type union the type itself', t => { 7 | type got = PromiseOr; 8 | type expected = Promise | string; 9 | 10 | assert(t); 11 | }); 12 | -------------------------------------------------------------------------------- /test/utils/UnionToIntersection.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { assert } from '../helpers/assert'; 3 | 4 | import { UnionToIntersection} from '../../src'; 5 | 6 | test('Union of Strings', t => { 7 | type got = UnionToIntersection<'hi' | 'there'>; 8 | type expected = 'hi' & 'there'; 9 | 10 | assert(t); 11 | }); 12 | 13 | test('Union of Objects', t => { 14 | type got = UnionToIntersection<{ a: 0 } | { b: 1 } | { c: 2 }>; 15 | 16 | type expected = { 17 | a: 0, 18 | b: 1, 19 | c: 2, 20 | }; 21 | 22 | assert(t); 23 | }); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "strict": true, 6 | "sourceMap": false, 7 | "outDir": "dist", 8 | "baseUrl": "src/", 9 | "noEmitOnError": true, 10 | "declaration": true, 11 | "skipLibCheck": true, 12 | "lib": [ 13 | "dom", 14 | "es2016" 15 | ] 16 | }, 17 | "include": [ 18 | "src/", 19 | "test/" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "rules": { 7 | "arrow-parens": false, 8 | "ban-types": false, 9 | "curly": false, 10 | "indent": [ 11 | true, 12 | "spaces", 4 13 | ], 14 | "interface-name": false, 15 | "interface-over-type-literal": false, 16 | "max-classes-per-file": false, 17 | "max-line-length": false, 18 | "member-access": [ 19 | true, 20 | "no-public" 21 | ], 22 | "member-ordering": false, 23 | "no-consecutive-blank-lines": false, 24 | "no-shadowed-variable": false, 25 | "object-literal-sort-keys": false, 26 | "only-arrow-functions": false, 27 | "ordered-imports": false, 28 | "prefer-object-spread": true, 29 | "quotemark": false, 30 | "radix": false, 31 | "space-before-function-paren": false, 32 | "trailing-comma": true, 33 | "triple-equals": true, 34 | "variable-name": [ 35 | true, 36 | "ban-keywords", 37 | "check-format", 38 | "allow-pascal-case", 39 | "allow-leading-underscore", 40 | "allow-trailing-underscore" 41 | ], 42 | "whitespace": [ 43 | true, 44 | "check-decl", 45 | "check-operator", 46 | "check-separator", 47 | "check-type", 48 | "check-typecast" 49 | ] 50 | } 51 | } 52 | --------------------------------------------------------------------------------