Skip to content

Commit

Permalink
Merge pull request #17 from piotrwitek/next
Browse files Browse the repository at this point in the history
v2.0.0
  • Loading branch information
piotrwitek authored Apr 2, 2018
2 parents 90495fd + 3103951 commit 7331c85
Show file tree
Hide file tree
Showing 11 changed files with 876 additions and 477 deletions.
447 changes: 350 additions & 97 deletions README.md

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "utility-types",
"version": "1.1.0",
"version": "2.0.0",
"description": "Utility Types Library for TypeScript",
"author": "Piotr Witek <[email protected]> (http://piotrwitek.github.io)",
"repository": "https://github.com/piotrwitek/utility-types",
Expand Down Expand Up @@ -28,25 +28,24 @@
"build:jsnext": "rm -rf jsnext/ && tsc -p . --outDir jsnext/ -t 'ES2015'",
"precommit": "npm run lint",
"prepush": "npm run test",
"prepublishOnly": "npm run clean && npm run reinstall && npm run lint && npm run tsc && npm run test && npm run build"
"prepublishOnly":
"npm run clean && npm run reinstall && npm run lint && npm run tsc && npm run test && npm run build"
},
"dependencies": {},
"devDependencies": {
"@types/jest": "22.0.0",
"@types/node": "6.0.95",
"@types/jest": "22.2.2",
"husky": "0.14.3",
"jest-cli": "22.0.4",
"redux": "3.7.2",
"ts-jest": "22.0.0",
"ts-node": "4.1.0",
"tslib": "1.8.1",
"tslint": "5.8.0",
"typescript": "2.7.2"
"jest": "22.4.3",
"ts-jest": "22.4.2",
"ts-node": "5.0.1",
"tslib": "1.9.0",
"tslint": "5.9.1",
"typescript": "2.8.1"
},
"keywords": [
"utility-types",
"typescript",
"utilities",
"utility",
"types",
"static-typing",
"mapped-types",
"flow",
Expand Down
6 changes: 6 additions & 0 deletions src/experimental.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// type Unionize<T> = { [P in keyof T]: { [Q in P]: T[P] } }[keyof T];

// type GetComponentProps<T> =
// T extends new (props: infer P) => any ? P :
// T extends (props: infer P & { children?: React.ReactNode }) => any ? P :
// any;
9 changes: 3 additions & 6 deletions src/functional-helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { testType } from './test-utils';
import { $call, getReturnOfExpression } from '.';
import { getReturnOfExpression } from '.';

//#region Docs Example
const increment = () => ({ type: 'INCREMENT' as 'INCREMENT' });

const returnOfIncrement = $call(increment);
const returnOfIncrement = getReturnOfExpression(increment);
type IncrementAction = typeof returnOfIncrement; // { type: "INCREMENT"; }
//#endregion

describe('Type Utils', () => {
describe('$call', () => {
it('should return null value', () => {
expect(returnOfIncrement).toBe(undefined);
testType<{ type: 'INCREMENT'; }>(returnOfIncrement);
});
it('should be equal with alias returntypeof', () => {
expect($call).toBe(getReturnOfExpression);
testType<{ type: 'INCREMENT' }>(returnOfIncrement);
});
});
});
16 changes: 5 additions & 11 deletions src/functional-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
// Copyright (c) 2016 Piotr Witek <[email protected]> (http://piotrwitek.github.io)
// (tracking issue: https://github.com/Microsoft/TypeScript/issues/6606)

/**
* @function $call
* @alias getReturnOfExpression
* @function getReturnOfExpression
* @deprecated from TS v2.8 use built-in ReturnType<T> or $Call API
* @description infer the return type from a given "expression" (at runtime it's equivalent of "noop")
* @template RT - Return Type
* @template RT - ReturnType
* @param expression: (...params: any[]) => RT
* @returns undefined as RT
*/
export function $call<RT>(
expression: (...params: any[]) => RT,
): RT {
return undefined as any as RT;
export function getReturnOfExpression<RT>(expression: (...params: any[]) => RT): RT {
return (undefined as any) as RT;
}

// ALIAS
export const getReturnOfExpression = $call;
82 changes: 74 additions & 8 deletions src/mapped-types.spec.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
import { testType } from './test-utils';
import {
SetIntersection,
SetDifference,
SetComplement,
SymmetricDifference,
FunctionKeys,
NonFunctionKeys,
Omit,
Subtract,
Intersection,
Diff,
Subtract,
Overwrite,
Assign,
UnboxPromise,
DeepReadonly,
} from '.';

/**
* Fixtures
*/
type Props = { name: string, age: number, visible: boolean };
type Props = { name: string; age: number; visible: boolean };
type DefaultProps = { age: number };
type UpdatedProps = { age: string };
type OtherProps = { other: string };
type NewProps = { age: string; other: string };
type MixedProps = { name: string; setName: (name: string) => void };

/**
* Tests
*/
describe('mapped types', () => {
it('SetIntersection', () => {
type ResultSet = SetIntersection<'1' | '2' | '3', '2' | '3' | '4'>;
// Expect: "2" | "3"
testType<ResultSet>('2');
testType<ResultSet>('3');

type ResultSetMixed = SetIntersection<string | number | (() => void), Function>;
// Expect: () => void
testType<ResultSetMixed>(() => undefined);
});

it('SetDifference', () => {
type ResultSet = SetDifference<'1' | '2' | '3', '2' | '3' | '4'>;
// Expect: "1"
testType<ResultSet>('1');

type ResultSetMixed = SetDifference<string | number | (() => void), Function>;
// Expect: string | number
testType<ResultSetMixed>('foo');
testType<ResultSetMixed>(2);
});

it('SetComplement', () => {
Expand All @@ -42,14 +63,32 @@ describe('mapped types', () => {
testType<ResultSet>('4');
});

it('FunctionKeys', () => {
type FunctionKeysProps = FunctionKeys<MixedProps>;
// Expect: "setName"
testType<FunctionKeysProps>('setName');
});

it('NonFunctionKeys', () => {
type NonFunctionKeysProps = NonFunctionKeys<MixedProps>;
// Expect: "name"
testType<NonFunctionKeysProps>('name');
});

it('Omit', () => {
type RequiredProps = Omit<Props, keyof DefaultProps>;
type RequiredProps = Omit<Props, 'age'>;
// Expect: { name: string; visible: boolean; }
testType<RequiredProps>({ name: 'foo', visible: true });
});

it('Intersection', () => {
type DuplicatedProps = Intersection<Props, DefaultProps>;
// Expect: { age: number; }
testType<DuplicatedProps>({ age: 2 });
});

it('Diff', () => {
type RequiredProps = Diff<Props, UpdatedProps & OtherProps>;
type RequiredProps = Diff<Props, NewProps>;
// Expect: { name: string; visible: boolean; }
testType<RequiredProps>({ name: 'foo', visible: true });
});
Expand All @@ -61,15 +100,42 @@ describe('mapped types', () => {
});

it('Overwrite', () => {
type ReplacedProps = Overwrite<Props, UpdatedProps>;
type ReplacedProps = Overwrite<Props, NewProps>;
// Expect: { name: string; age: string; visible: boolean; }
testType<ReplacedProps>({ name: 'foo', age: '2', visible: true });
});

it('Assign', () => {
type ExtendedProps = Assign<Props, UpdatedProps & OtherProps>;
type ExtendedProps = Assign<Props, NewProps>;
// Expect: { name: string; age: string; visible: boolean; other: string; }
testType<ExtendedProps>({ name: 'foo', age: '2', visible: true, other: 'baz' });
});

it('UnboxPromise', () => {
type PromiseType = UnboxPromise<Promise<string>>;
// Expect: string
testType<PromiseType>('foo');
});

it('DeepReadonly', () => {
type NestedProps = {
first: {
second: {
name: string;
};
};
};
let a: { readonly name: string };

type ReadonlyNestedProps = DeepReadonly<NestedProps>;
a = {} as ReadonlyNestedProps['first']['second'];

type NestedArrayProps = {
first: {
second: Array<{ name: string }>;
};
};
type ReadonlyNestedArrayProps = DeepReadonly<NestedArrayProps>;
a = {} as ReadonlyNestedArrayProps['first']['second'][number];
});
});
102 changes: 75 additions & 27 deletions src/mapped-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,111 @@
* in the following github issue: https://github.com/Microsoft/TypeScript/issues/12215
*/

/**
* SetIntersection
* @desc Set intersection of given literal union types `A` and `B`
*/
export type SetIntersection<A, B> = A extends B ? A : never;

/**
* SetDifference
* @desc Set difference of given literal union types `A` and `B`
*/
export type SetDifference<A extends string, B extends string> = (
{[P in A]: P } & {[P in B]: never } & { [k: string]: never }
)[A];
export type SetDifference<A, B> = A extends B ? never : A;

/**
* SetComplement
* @desc Set complement of given literal union types `A` and it's subset `A2`
* @desc Set complement of given literal union types `A` and (it's subset) `A1`
*/
export type SetComplement<A extends string, A2 extends A> =
SetDifference<A, A2>;
export type SetComplement<A, A1 extends A> = SetDifference<A, A1>;

/**
* SymmetricDifference
* @desc Set difference of the union and the intersection of given literal union types `A` and `B`
*/
export type SymmetricDifference<A extends string, B extends string> =
SetDifference<A | B, A & B>;
export type SymmetricDifference<A, B> = SetDifference<A | B, A & B>;

/**
* FunctionKeys
* @desc get union type of keys that are functions in object type `T`
*/
export type FunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];

/**
* Omit
* NonFunctionKeys
* @desc get union type of keys that are non-functions in object type `T`
*/
export type NonFunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

/**
* Omit (complements Pick)
* @desc From `T` remove a set of properties `K`
*/
export type Omit<T extends object, K extends keyof T> = (
Pick<T, SetComplement<keyof T, K>>
);
export type Omit<T, K extends keyof T> = Pick<T, SetComplement<keyof T, K>>;

/**
* Intersection
* @desc From `T` pick properties that exist in `U`
*/
export type Intersection<T extends object, U extends object> = Pick<
T,
SetIntersection<keyof T, keyof U>
>;

/**
* Diff
* @desc From `T` pick properties that doesn't exist in `U`
* @desc From `T` remove properties that exist in `U`
*/
export type Diff<T extends object, U extends object> = (
Pick<T, SetDifference<keyof T, keyof U>>
);
export type Diff<T extends object, U extends object> = Pick<T, SetDifference<keyof T, keyof U>>;

/**
* Subtract
* @desc From `T` pick properties that doesn't exist in `U`, when `U` is a subtype of `T`
* @desc From `T` remove properties that exist in `T1` (`T1` is a subtype of `T`)
*/
export type Subtract<T extends U, U extends object> = (
Pick<T, SetComplement<keyof T, keyof U>>
);
export type Subtract<T extends T1, T1 extends object> = Pick<T, SetComplement<keyof T, keyof T1>>;

/**
* Overwrite
* @desc Overwrite intersecting properties in `T` with `U`.
* @desc From `U` overwrite properties to `T`
*/
export type Overwrite<T extends object, U extends object, N = Diff<T, U> & Omit<U, SetDifference<keyof U, keyof T>>> = (
Pick<N, keyof N>
);
export type Overwrite<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>;

/**
* Assign
* @desc Assign `U` to `T` just like object assign
* @desc From `U` assign properties to `T` (just like object assign)
*/
export type Assign<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T> & Diff<U, T>
> = Pick<I, keyof I>;

/**
* UnboxPromise
* @desc Obtain Promise resolve type
*/
export type UnboxPromise<T> = T extends Promise<infer U> ? U : T;

/**
* DeepReadonly
* @desc DeepReadonly - recursive readonly that works for deeply nested structure
*/
export type DeepReadonly<T> = T extends any[]
? DeepReadonlyArray<T[number]>
: T extends object ? DeepReadonlyObject<T> : T;

/**
* DeepReadonlyArray
* @desc DeepReadonlyArray - nested array condition handler
*/
export interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

/**
* DeepReadonlyObject
* @desc DeepReadonlyObject - nested object condition handler
*/
export type Assign<T extends object, U extends object, N = (Diff<T, U> & U) & Omit<U, SetDifference<keyof U, keyof T>>> =
Pick<N, keyof N>;
export type DeepReadonlyObject<T> = { readonly [P in NonFunctionKeys<T>]: DeepReadonly<T[P]> };
Loading

0 comments on commit 7331c85

Please sign in to comment.