Skip to content

Instantly share code, notes, and snippets.

@donabrams
Last active November 8, 2018 15:29
Show Gist options
  • Save donabrams/b849927f5a0160081db913e3d52cc7b3 to your computer and use it in GitHub Desktop.
Save donabrams/b849927f5a0160081db913e3d52cc7b3 to your computer and use it in GitHub Desktop.
Typesafe omit typescript (TS 3.1.6)
// This doesn't support map over union types individually
type SimpleOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// The below type, MappedRemoveKey, only works with discrimated unions!
// Otherwise the intersection of the types ALSO passes (submiting a TS bug now-- in typescript-3.1.6)
// example union type WITHOUT a discriminator
type Foo = { foo: string };
type FooBar = { foo: string, bar: string };
type Yay = { yay: true };
type Before = Foo | FooBar | Yay;
// example union type WITH a discriminator
type DFoo = { type: 'foo', foo: string };
type DFooBar = { type: 'foobar', foo: string, bar: string };
type DYay = { type: 'yay', yay: true };
type DBefore = DFoo | DFooBar | DYay;
type MappedAddKey<T, K extends string | number | symbol, V> = T & { [P in K]: V };
type MappedRemoveKey<T, K extends string | number | symbol> = T extends { [P in K]: infer U }
? SimpleOmit<T, K> : T;
// sanity checks on the Before type
const sanityCheckWorks1: Before = { foo: 'foo'};
const sanityCheckWorks2: Before = { foo: 'foo', bar: 'bar'};
const sanityCheckWorks3: Before = { yay: true};
const sanityCheckFail1: Before = { };
const sanityCheckFail2: Before = { foo: 'foo', yay: true }; // FIXME: False positive!
const sanityCheckFail3: Before = { bar: 'bar', yay: true }; // FIXME: False positive!
const sanityCheckFail4: Before = { foo: 'foo', bar: 'bar', yay: true }; // FIXME: False positive!
// sanity checks on the Discriminated Before type
const dsanityCheckWorks1: DBefore = { type: 'foo', foo: 'foo' };
const dsanityCheckWorks2: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar' };
const dsanityCheckWorks3: DBefore = { type: 'yay', yay: true };
const dsanityCheckFail1: DBefore = { type: 'foo' };
const dsanityCheckFail2: DBefore = { type: 'foobar' };
const dsanityCheckFail3: DBefore = { type: 'yay' };
const dsanityCheckFail4: DBefore = { type: 'foo', foo: 'foo', bar: 'bar', yay: true };
const dsanityCheckFail5: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar', yay: true };
const dsanityCheckFail6: DBefore = { type: 'yay', foo: 'foo', bar: 'bar', yay: true };
// positive and negative checks on MappedAddKey<Before
const addKeyWorks1: MappedAddKey<Before, 'beep', string> = { foo: 'foo', beep: 'beep' };
const addKeyWorks2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
const addKeyWorks3: MappedAddKey<Before, 'beep', string> = { yay: true, beep: 'beep' };
const addKeyWorks4: MappedAddKey<Before, 'foo', string> = { foo: 'foo' };
const addKeyWorks5: MappedAddKey<Before, 'foo', string> = { foo: 'foo', bar: 'bar' };
const addKeyWorks6: MappedAddKey<Before, 'foo', string> = { foo: 'foo', yay: true };
const addKeyFails1: MappedAddKey<Before, 'beep', string> = { foo: 'foo' };
const addKeyFails2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
const addKeyFails3: MappedAddKey<Before, 'beep', string> = { yay: true };
const addKeyFails4: MappedAddKey<Before, 'foo', string> = { yay: true };
const addKeyFails5: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep', yay: true }; // FIXME: False positive!
// positive and negative checks on MappedAddKey<DBefore
const daddKeyWorks1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
const daddKeyWorks2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar', beep: 'beep' };
const daddKeyWorks3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true, beep: 'beep' };
const daddKeyWorks4: MappedAddKey<DBefore, 'foo', string> = { type: 'foo', foo: 'foo' };
const daddKeyWorks5: MappedAddKey<DBefore, 'foo', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const daddKeyWorks6: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', foo: 'foo', yay: true };
const daddKeyFails1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo' };
const daddKeyFails2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const daddKeyFails3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true };
const daddKeyFails4: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', yay: true };
const daddKeyFails5: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', foo: 'foo', bar: 'bar', beep: 'beep', yay: true };
// positive and negative checks on MappedRemoveKey<Before
const removeKeyWorks1: MappedRemoveKey<Before, 'foo'> = { };
const removeKeyWorks2: MappedRemoveKey<Before, 'foo'> = { bar: 'bar' };
const removeKeyWorks3: MappedRemoveKey<Before, 'foo'> = { yay: true };
const removeKeyFails1: MappedRemoveKey<Before, 'foo'> = { foo: 'foo' }; // FIXME: False positive!
const removeKeyFails2: MappedRemoveKey<Before, 'foo'> = { foo: 'foo', bar: 'bar' }; // FIXME: False positive!
// positive and negative checks on MappedRemoveKey<DBefore
const dremoveKeyWorks1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo' };
const dremoveKeyWorks2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', bar: 'bar' };
const dremoveKeyWorks3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true };
const dremoveKeyFails1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo', foo: 'foo' };
const dremoveKeyFails2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const dremoveKeyFails3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true, foo: 'foo'};
{
"compilerOptions": {
"target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"strict": true, /* Enable all strict type-checking options. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment