From 8f1adf8713b19e3ac0fce932657f1dbd52d6385d Mon Sep 17 00:00:00 2001 From: Piotr Witek Date: Tue, 7 May 2019 16:23:09 +0200 Subject: [PATCH] Improved Optional type to better handle few edge cases --- package-lock.json | 118 +++++++++++++------- package.json | 2 +- src/__snapshots__/mapped-types.spec.ts.snap | 8 +- src/mapped-types.spec.snap.ts | 14 ++- src/mapped-types.spec.ts | 8 +- src/mapped-types.ts | 20 ++-- utils/test-utils.ts | 2 +- 7 files changed, 112 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index d70baf9..09ab025 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,12 @@ "integrity": "sha512-qDyqzbcyNgW2RgWbl606xCYQ+5fK9khOW5+Hl3wH7RggVES0dB6GcZvpmPs/XIty5qpu1xYCwpiK+iRkJ3xFBw==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -1128,17 +1134,27 @@ "dev": true }, "cosmiconfig": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", - "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.0.tgz", + "integrity": "sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g==", "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", + "js-yaml": "^3.13.0", "parse-json": "^4.0.0" }, "dependencies": { + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -2312,9 +2328,9 @@ "dev": true }, "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", "dev": true }, "get-stream": { @@ -2550,19 +2566,19 @@ } }, "husky": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", - "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-2.2.0.tgz", + "integrity": "sha512-lG33E7zq6v//H/DQIojPEi1ZL9ebPFt3MxUMD8MR0lrS2ljEPiuUUxlziKIs/o9EafF0chL7bAtLQkcPvXmdnA==", "dev": true, "requires": { - "cosmiconfig": "^5.0.7", + "cosmiconfig": "^5.2.0", "execa": "^1.0.0", "find-up": "^3.0.0", - "get-stdin": "^6.0.0", + "get-stdin": "^7.0.0", "is-ci": "^2.0.0", - "pkg-dir": "^3.0.0", + "pkg-dir": "^4.1.0", "please-upgrade-node": "^3.1.1", - "read-pkg": "^4.0.1", + "read-pkg": "^5.0.0", "run-node": "^1.0.0", "slash": "^2.0.0" }, @@ -2638,10 +2654,22 @@ "path-exists": "^3.0.0" } }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2657,9 +2685,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "parse-json": { @@ -2672,21 +2700,25 @@ "json-parse-better-errors": "^1.0.1" } }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, "read-pkg": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", - "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.1.1.tgz", + "integrity": "sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==", "dev": true, "requires": { - "normalize-package-data": "^2.3.2", + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", "parse-json": "^4.0.0", - "pify": "^3.0.0" + "type-fest": "^0.4.1" + } + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" } }, "slash": { @@ -4156,9 +4188,9 @@ } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.1.0.tgz", + "integrity": "sha512-55k9QN4saZ8q518lE6EFgYiu95u3BWkSajCifhdQjvLvmr8IpnRbhI+UGpWJQfa0KzDguHeeWT1ccO1PmkOi3A==", "dev": true, "requires": { "find-up": "^3.0.0" @@ -4184,9 +4216,9 @@ } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4202,9 +4234,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true } } @@ -5458,6 +5490,12 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", + "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", + "dev": true + }, "typescript": { "version": "3.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", diff --git a/package.json b/package.json index 09fcac3..a707b9a 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@types/jest": "21.1.10", "dts-jest": "22.0.4", - "husky": "1.3.1", + "husky": "2.2.0", "jest": "21.2.1", "prettier": "1.17.0", "ts-jest": "23.10.5", diff --git a/src/__snapshots__/mapped-types.spec.ts.snap b/src/__snapshots__/mapped-types.spec.ts.snap index 2380a68..b5dd18c 100644 --- a/src/__snapshots__/mapped-types.spec.ts.snap +++ b/src/__snapshots__/mapped-types.spec.ts.snap @@ -126,9 +126,13 @@ exports[`OmitByValueExact testType>() 1`] = `"Pick"`; -exports[`Optional testType>() 1`] = `"(Pick & { age?: number | undefined; }) | (Pick & { visible?: boolean | undefined; })"`; +exports[`Optional testType>({ name: 'Yolo' }) 1`] = `"Optional"`; -exports[`Optional testType>() 1`] = `"Partial"`; +exports[`Optional testType>({ name: 'Yolo', age: 99 }) 1`] = `"Optional"`; + +exports[`Optional testType>({ age: 99 }) 1`] = `"Optional"`; + +exports[`Optional testType>({}) 1`] = `"Optional"`; exports[`OptionalKeys testType>() 1`] = `"\\"opt\\" | \\"optUndef\\""`; diff --git a/src/mapped-types.spec.snap.ts b/src/mapped-types.spec.snap.ts index caf747f..00d1fde 100644 --- a/src/mapped-types.spec.snap.ts +++ b/src/mapped-types.spec.snap.ts @@ -486,9 +486,13 @@ type RequiredOptionalProps = { // @dts-jest:group Optional { - // @dts-jest:pass:snap -> Partial - testType>(); - - // @dts-jest:pass:snap -> (Pick & { age?: number | undefined; }) | (Pick & { visible?: boolean | undefined; }) - testType>(); + // @dts-jest:pass:snap -> Optional + testType>({}); + // @dts-jest:pass:snap -> Optional + testType>({ age: 99 }); + + // @dts-jest:pass:snap -> Optional + testType>({ name: 'Yolo' }); + // @dts-jest:pass:snap -> Optional + testType>({ name: 'Yolo', age: 99 }); } diff --git a/src/mapped-types.spec.ts b/src/mapped-types.spec.ts index 0859e27..0c2e0e9 100644 --- a/src/mapped-types.spec.ts +++ b/src/mapped-types.spec.ts @@ -487,8 +487,12 @@ type RequiredOptionalProps = { // @dts-jest:group Optional { // @dts-jest:pass:snap - testType>(); + testType>({}); + // @dts-jest:pass:snap + testType>({ age: 99 }); // @dts-jest:pass:snap - testType>(); + testType>({ name: 'Yolo' }); + // @dts-jest:pass:snap + testType>({ name: 'Yolo', age: 99 }); } diff --git a/src/mapped-types.ts b/src/mapped-types.ts index d9a288a..e9d5736 100644 --- a/src/mapped-types.ts +++ b/src/mapped-types.ts @@ -150,6 +150,12 @@ export type ReadonlyKeys = { > }[keyof T]; +type IfEquals = (() => T extends X + ? 1 + : 2) extends (() => T extends Y ? 1 : 2) + ? A + : B; + /** * RequiredKeys * @desc get union type of keys that are required in object type `T` @@ -527,12 +533,6 @@ export interface _DeepPartialArray extends Array> {} /** @private */ export type _DeepPartialObject = { [P in keyof T]?: DeepPartial }; -type IfEquals = (() => T extends X - ? 1 - : 2) extends (() => T extends Y ? 1 : 2) - ? A - : B; - /** * Brand * @desc Define nominal type of U based on type of T. @@ -571,6 +571,8 @@ export type Brand = T & { __brand: U }; * // Expect: { name: string; age?: number; visible?: boolean; } * type Props = Optional; */ -export type Optional = K extends (keyof T) - ? (Omit & { [key in K]?: T[key] }) - : Partial; +export type Optional = Omit< + T, + K +> & + Partial>; diff --git a/utils/test-utils.ts b/utils/test-utils.ts index 55c9f1e..e3fddbf 100644 --- a/utils/test-utils.ts +++ b/utils/test-utils.ts @@ -1,5 +1,5 @@ /** @internal */ -export function testType(): T { +export function testType(a?: T): T { return undefined as any; }