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 NonDiscriminatedUnion = 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 DiscriminatedUnion = DFoo | DFooBar | DYay;
type MappedAdd<T, K extends string | number | symbol, V> = T & { [P in K]: V };
type MappedOmit<T, K extends string | number | symbol> = T extends { [P in K]: infer U }
? SimpleOmit<T, K> : T;
// sanity checks on the NonDiscriminatedUnion type
const sanityCheckWorks1: NonDiscriminatedUnion = { foo: 'foo'};
const sanityCheckWorks2: NonDiscriminatedUnion = { foo: 'foo', bar: 'bar'};
const sanityCheckWorks3: NonDiscriminatedUnion = { yay: true};
const sanityCheckFail1: NonDiscriminatedUnion = { };
const sanityCheckFail2: NonDiscriminatedUnion = { foo: 'foo', yay: true }; // FIXME: False positive!
const sanityCheckFail3: NonDiscriminatedUnion = { bar: 'bar', yay: true }; // FIXME: False positive!
const sanityCheckFail4: NonDiscriminatedUnion = { foo: 'foo', bar: 'bar', yay: true }; // FIXME: False positive!
// sanity checks on the Discriminated NonDiscriminatedUnion type
const dsanityCheckWorks1: DiscriminatedUnion = { type: 'foo', foo: 'foo' };
const dsanityCheckWorks2: DiscriminatedUnion = { type: 'foobar', foo: 'foo', bar: 'bar' };
const dsanityCheckWorks3: DiscriminatedUnion = { type: 'yay', yay: true };
const dsanityCheckFail1: DiscriminatedUnion = { type: 'foo' };
const dsanityCheckFail2: DiscriminatedUnion = { type: 'foobar' };
const dsanityCheckFail3: DiscriminatedUnion = { type: 'yay' };
const dsanityCheckFail4: DiscriminatedUnion = { type: 'foo', foo: 'foo', bar: 'bar', yay: true };
const dsanityCheckFail5: DiscriminatedUnion = { type: 'foobar', foo: 'foo', bar: 'bar', yay: true };
const dsanityCheckFail6: DiscriminatedUnion = { type: 'yay', foo: 'foo', bar: 'bar', yay: true };
// positive and negative checks on MappedAdd<NonDiscriminatedUnion
const addKeyWorks1: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', beep: 'beep' };
const addKeyWorks2: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
const addKeyWorks3: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { yay: true, beep: 'beep' };
const addKeyWorks4: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo' };
const addKeyWorks5: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo', bar: 'bar' };
const addKeyWorks6: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo', yay: true };
const addKeyFails1: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo' };
const addKeyFails2: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar' };
const addKeyFails3: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { yay: true };
const addKeyFails4: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { yay: true };
const addKeyFails5: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep', yay: true }; // FIXME: False positive!
// positive and negative checks on MappedAdd<DiscriminatedUnion
const daddKeyWorks1: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
const daddKeyWorks2: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar', beep: 'beep' };
const daddKeyWorks3: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', yay: true, beep: 'beep' };
const daddKeyWorks4: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'foo', foo: 'foo' };
const daddKeyWorks5: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const daddKeyWorks6: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'yay', foo: 'foo', yay: true };
const daddKeyFails1: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foo', foo: 'foo' };
const daddKeyFails2: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const daddKeyFails3: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', yay: true };
const daddKeyFails4: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'yay', yay: true };
const daddKeyFails5: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', foo: 'foo', bar: 'bar', beep: 'beep', yay: true };
// positive and negative checks on MappedOmit<NonDiscriminatedUnion
const removeKeyWorks1: MappedOmit<NonDiscriminatedUnion, 'foo'> = { };
const removeKeyWorks2: MappedOmit<NonDiscriminatedUnion, 'foo'> = { bar: 'bar' };
const removeKeyWorks3: MappedOmit<NonDiscriminatedUnion, 'foo'> = { yay: true };
const removeKeyFails1: MappedOmit<NonDiscriminatedUnion, 'foo'> = { foo: 'foo' }; // FIXME: False positive!
const removeKeyFails2: MappedOmit<NonDiscriminatedUnion, 'foo'> = { foo: 'foo', bar: 'bar' }; // FIXME: False positive!
// positive and negative checks on MappedOmit<DiscriminatedUnion
const dremoveKeyWorks1: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foo' };
const dremoveKeyWorks2: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foobar', bar: 'bar' };
const dremoveKeyWorks3: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'yay', yay: true };
const dremoveKeyFails1: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foo', foo: 'foo' };
const dremoveKeyFails2: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foobar', foo: 'foo', bar: 'bar' };
const dremoveKeyFails3: MappedOmit<DiscriminatedUnion, '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