Skip to content

Commit

Permalink
chore: enhance utility type
Browse files Browse the repository at this point in the history
- Reduce code lines
- extend basic types to advanced types
- Offer custom types
- Add a new type called `Discard`
  • Loading branch information
halvaradop committed Nov 29, 2024
1 parent 0585436 commit 59d6659
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 95 deletions.
67 changes: 49 additions & 18 deletions src/array-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ToUnion } from "./object-types.js"
import type { Equals } from "./test.js"

/**
Expand Down Expand Up @@ -229,18 +228,21 @@ export type AllEquals<Array extends unknown[], Comparator> = Array extends [infe

type ChunkImplementation<
Array extends unknown[],
Size extends number,
Length extends number,
Build extends unknown[] = [],
Partition extends unknown[] = [],
> = Array extends [infer Item, ...infer Spread]
? [...Partition, Item]["length"] extends Size
? ChunkImplementation<Spread, Size, [...Build, [...Partition, Item]], []>
: ChunkImplementation<Spread, Size, Build, [...Partition, Item]>
: Partition["length"] extends 0
? [...Partition, Item]["length"] extends Length
? ChunkImplementation<Spread, Length, [...Build, [...Partition, Item]], []>
: ChunkImplementation<Spread, Length, Build, [...Partition, Item]>
: Size<Partition> extends 0
? Build
: [...Build, Partition]

export type Chunk<Array extends unknown[], Size extends number> = ChunkImplementation<Array, Size, [], []>
/**
* TODO: add examples
*/
export type Chunk<Array extends unknown[], Length extends number> = ChunkImplementation<Array, Length, [], []>

type ZipImplementation<T, U, Build extends unknown[] = []> = T extends [infer ItemT, ...infer SpreadT]
? U extends [infer ItemU, ...infer SpreadU]
Expand Down Expand Up @@ -290,25 +292,18 @@ export type CompareArrayLength<T extends any[], U extends any[]> = T extends [an
? U extends [any, ...infer SpreadU]
? CompareArrayLength<SpreadT, SpreadU>
: 1
: U extends [infer Item, ...infer Spread]
? -1
: 0
: Size<U> extends 0
? 0
: -1

type UniqueImplementation<Array extends unknown[], Uniques extends unknown = never, Set extends unknown[] = []> = Array extends [
infer Item,
...infer Spread,
]
? HasImplementation<Set, Item> extends true
? Includes<Set, Item> extends true
? UniqueImplementation<Spread, Uniques, Set>
: UniqueImplementation<Spread, Uniques | Item, [...Set, Item]>
: Set

type HasImplementation<Array extends unknown[], Compare extends unknown> = Array extends [infer Item, ...infer Spread]
? Equals<Item, Compare> extends true
? true
: HasImplementation<Spread, Compare>
: false

/**
* Returns the uniques values of an array
*
Expand All @@ -320,3 +315,39 @@ type HasImplementation<Array extends unknown[], Compare extends unknown> = Array
* type Uniques2 = Unique<["a", "b", "c", "a", "b"]>;
*/
export type Unique<Array extends unknown[]> = UniqueImplementation<Array>

/**
* Create an union type based in the literal values of the tuple provided.
* This utility type is similar to `TupleToUnion` but this utility type
* receive whatever type
*
* @example
* // Expected: 1
* type TupleNumber = ToUnion<1>;
*
* // Expected: "foo"
* type TupleString = ToUnion<"foo">;
*
* // Expected: 1 | ["foo" | "bar"]
* type TupleMultiple = ToUnion<1 | ["foo" | "bar"]>;
*/
export type ToUnion<T> = T extends [infer Item, ...infer Spread] ? Item | ToUnion<Spread> : T

/**
* Check if a value exists within a tuple and is equal to a specific value
*
* @example
* // Expected: true
* type IncludesNumber = Includes<[1, 2, 3], 3>;
*
* // Expected: true
* type IncludesString = Includes<["foo", "bar", "foobar"], "bar">;
*
* // Expected: false
* type NoIncludes = Includes<["foo", "bar", "foofoo"], "foobar">;
*/
export type Includes<Array extends unknown[], Match> = Array extends [infer Compare, ...infer Spread]
? Equals<Compare, Match> extends true
? true
: Includes<Spread, Match>
: false
103 changes: 47 additions & 56 deletions src/object-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Equals } from "./test.js"
import type { IsNever } from "./type-guards.js"
import type { ArgsFunction, ReturnTypeOf } from "./types.js"

/**
Expand Down Expand Up @@ -35,11 +36,35 @@ export type DeepReadonly<Obj extends object> = {
}

/**
* TODO: add examples
*
* Exclude properties of type `Match` from type `T`
*/
export type Exclude<T, Match> = T extends Match ? never : T

/**
* Compare two types and return `never` if the type `T` extends the type `Match`,
* otherwise return the type `T`.
*
* @example
* // Expected: number
* type A = Discard<string | number, string>;
* // Expected: string | number
* type B = Discard<string | number, boolean>;
*/
export type Discard<Compare, Match, Value = never, Reverse extends boolean = false> = Reverse extends true
? Compare extends Match
? Value
: never
: Compare extends Match
? never
: IsNever<Value> extends true
? Compare
: Value

/**
* TODO: add examples
*
* Get the type of the resolved value of a PromiseLike object.
*/
export type Awaited<T extends PromiseLike<unknown>> =
Expand Down Expand Up @@ -79,25 +104,6 @@ export type Pick<Obj extends object, Keys extends keyof Obj> = {
[Property in Keys]: Obj[Property]
}

/**
* Check if a value exists within a tuple and is equal to a specific value
*
* @example
* // Expected: true
* type IncludesNumber = Includes<[1, 2, 3], 3>;
*
* // Expected: true
* type IncludesString = Includes<["foo", "bar", "foobar"], "bar">;
*
* // Expected: false
* type NoIncludes = Includes<["foo", "bar", "foofoo"], "foobar">;
*/
export type Includes<Array extends unknown[], Match> = Array extends [infer Compare, ...infer Spread]
? Equals<Compare, Match> extends true
? true
: Includes<Spread, Match>
: false

/**
* Creates a new type that omits properties from an object type based on another type
*
Expand All @@ -106,7 +112,7 @@ export type Includes<Array extends unknown[], Match> = Array extends [infer Comp
* type NoEmailPerson = Omit<{ name: string; age: number; email: string }, "email">;
*/
export type Omit<Obj extends object, Keys extends keyof Obj> = {
[Property in keyof Obj as Property extends Keys ? never : Property]: Obj[Property]
[Property in keyof Obj as Discard<Property, Keys>]: Obj[Property]
}

/**
Expand All @@ -124,7 +130,9 @@ export type Omit<Obj extends object, Keys extends keyof Obj> = {
* // Expected: "foo" | "bar"
* type PropsFooBar = Properties<Foo, Bar>;
*/
export type Properties<Obj1 extends object, Obj2 extends object> = keyof Obj1 | keyof Obj2
export type Properties<Obj1 extends object, Obj2 extends object, Extends extends boolean = false> = Extends extends true
? keyof Obj1 & keyof Obj2
: keyof Obj1 | keyof Obj2

/**
* Creates a new object by merging two objects. Properties from `Obj1` override properties
Expand All @@ -148,6 +156,10 @@ export type Merge<Obj1 extends object, Obj2 extends object> = {
[Property in Properties<Obj1, Obj2>]: RetrieveKeyValue<Obj2, Obj1, Property>
}

type IntersectionImplementation<Obj1 extends object, Obj2 extends object, Keys = Properties<Obj1, Obj2, true>> = {
[Property in Properties<Obj1, Obj2> as Discard<Property, Keys>]: RetrieveKeyValue<Obj1, Obj2, Property>
}

/**
* Create a new object based in the difference keys between the objects.
*
Expand All @@ -166,13 +178,7 @@ export type Merge<Obj1 extends object, Obj2 extends object> = {
* // Expected: { gender: number }
* type DiffFoo = Intersection<Foo, Bar>;
*/
export type Intersection<Obj1 extends object, Obj2 extends object> = {
[Property in Properties<Obj1, Obj2> as Property extends keyof Obj1 & keyof Obj2 ? never : Property]: RetrieveKeyValue<
Obj1,
Obj2,
Property
>
}
export type Intersection<Obj1 extends object, Obj2 extends object> = IntersectionImplementation<Obj1, Obj2>

/**
* Create a new object based in the type of its keys
Expand All @@ -188,10 +194,12 @@ export type Intersection<Obj1 extends object, Obj2 extends object> = {
* type UserStr = PickByType<User, string>;
*/
export type PickByType<Obj extends object, Type> = {
[Property in keyof Obj as Obj[Property] extends Type ? Property : never]: Obj[Property]
[Property in keyof Obj as Discard<Obj[Property], Type, Property, true>]: Obj[Property]
}

/**
* TODO: add examples
*
* Converts the specified keys of an object into optional ones
*
* @example
Expand All @@ -205,9 +213,7 @@ export type PickByType<Obj extends object, Type> = {
* type UserPartialName = PartialByKeys<User, "name">;
*/
export type PartialByKeys<Obj extends object, Keys extends keyof Obj = keyof Obj> = Prettify<
{
[Property in keyof Obj as Property extends Keys ? never : Property]: Obj[Property]
} & { [Property in Keys]?: Obj[Property] }
Partial<Pick<Obj, Keys>> & Omit<Obj, Keys>
>

/**
Expand All @@ -228,23 +234,29 @@ export type OmitByType<Obj extends object, Type> = {
}

/**
* TODO: add examples
*
* Extracts the value of a key from an object and returns a new object with that value,
* while keeping the other values unchanged.
*/
export type FlattenProperties<Obj extends object, Keys extends keyof Obj> = Prettify<
{
[Property in keyof Obj as Property extends Keys ? never : Property]: Obj[Property]
[Property in keyof Obj as Discard<Property, Keys>]: Obj[Property]
} & Obj[Keys]
>

/**
* TODO: add examples
*
* Removes the properties whose keys start with an underscore (_).
*/
export type PublicOnly<Obj extends object> = {
[Property in keyof Obj as Property extends `_${string}` ? never : Property]: Obj[Property]
[Property in keyof Obj as Discard<Property, `_${string}`>]: Obj[Property]
}

/**
* TODO: add examples
*
* Checks if a key exists in either of the two objects and returns its value.
* If the key does not exist in either object, it returns `never`.
*/
Expand All @@ -269,11 +281,7 @@ export type RetrieveKeyValue<Obj1 extends object, Obj2 extends object, Key> = Ke
* type UserRequiredName = RequiredByKeys<User, "name">;
*/
export type RequiredByKeys<Obj extends object, Keys extends keyof Obj = keyof Obj> = Prettify<
{
[Property in keyof Obj as Property extends Keys ? never : Property]: Obj[Property]
} & {
[Property in keyof Obj as Property extends Keys ? Property : never]-?: Obj[Property]
}
Required<Pick<Obj, Keys>> & Omit<Obj, Keys>
>

/**
Expand Down Expand Up @@ -372,23 +380,6 @@ type MergeAllImplementation<Array extends readonly object[], Merge extends objec
*/
export type MergeAll<Array extends readonly object[]> = MergeAllImplementation<Array, {}>

/**
* Create an union type based in the literal values of the tuple provided.
* This utility type is similar to `TupleToUnion` but this utility type
* receive whatever type
*
* @example
* // Expected: 1
* type TupleNumber = ToUnion<1>;
*
* // Expected: "foo"
* type TupleString = ToUnion<"foo">;
*
* // Expected: 1 | ["foo" | "bar"]
* type TupleMultiple = ToUnion<1 | ["foo" | "bar"]>;
*/
export type ToUnion<T> = T extends [infer Item, ...infer Spread] ? Item | ToUnion<Spread> : T

/**
* Create a new object type appending a new property with its value
*
Expand Down
5 changes: 4 additions & 1 deletion src/string-mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export type Trim<Str extends string> = Str extends `${WhiteSpaces}${infer Charac
: Str

/**
* Converts a string to uppercase
* TODO: add example
* Converts a string to uppercase
*/
export type Uppercase<Str extends string> = Str extends `${infer Char}${infer Characters}`
? Char extends keyof LetterToUppercase
Expand All @@ -39,6 +40,7 @@ export type Uppercase<Str extends string> = Str extends `${infer Char}${infer Ch
: Str

/**
* TODO: add example
* Converts a string to lowercase
*/
export type Lowercase<Str extends string> = Str extends `${infer Char}${infer Characters}`
Expand All @@ -48,6 +50,7 @@ export type Lowercase<Str extends string> = Str extends `${infer Char}${infer Ch
: Str

/**
* TODO: add example and fix the type
* Capitalizes the first letter of a word and converts the rest to lowercase
*/
export type Capitalize<Str extends string, FirstWord extends boolean = true> = Str extends `${infer Char}${infer Characters}`
Expand Down
20 changes: 20 additions & 0 deletions test/array-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,23 @@ describe("FlattenArrayType", () => {
expectTypeOf<utilities.FlattenArrayType<unknown[][][][]>>().toEqualTypeOf<unknown>()
})
})

describe("ToUnion", () => {
test("Create an union type", () => {
expectTypeOf<utilities.ToUnion<12>>().toEqualTypeOf<12>()
expectTypeOf<utilities.ToUnion<"foo">>().toEqualTypeOf<"foo">()
expectTypeOf<utilities.ToUnion<12 | 21>>().toEqualTypeOf<12 | 21>()
expectTypeOf<utilities.ToUnion<[12, 21, "foo"]>>().toEqualTypeOf<12 | 21 | "foo" | []>()
})
})

describe("Includes", () => {
test("Check if an element exist withins a tuple", () => {
expectTypeOf<utilities.Includes<[], any>>().toEqualTypeOf<false>()
expectTypeOf<utilities.Includes<[1, 2, "foo", "bar"], 2>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<["foo", "bar", () => void, {}], () => void>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[string, 1, () => void, {}], string>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[string, number, () => void, {}], number>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[true, false, true], number>>().toEqualTypeOf<false>()
})
})
20 changes: 0 additions & 20 deletions test/object-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,6 @@ describe("MergeAll", () => {
})
})

describe("ToUnion", () => {
test("Create an union type", () => {
expectTypeOf<utilities.ToUnion<12>>().toEqualTypeOf<12>()
expectTypeOf<utilities.ToUnion<"foo">>().toEqualTypeOf<"foo">()
expectTypeOf<utilities.ToUnion<12 | 21>>().toEqualTypeOf<12 | 21>()
expectTypeOf<utilities.ToUnion<[12, 21, "foo"]>>().toEqualTypeOf<12 | 21 | "foo" | []>()
})
})

describe("AddPropertyToObject", () => {
test("Append a new property of an exist object type", () => {
expectTypeOf<utilities.AddPropertyToObject<{ foo: string }, "bar", number>>().toEqualTypeOf<{
Expand Down Expand Up @@ -252,17 +243,6 @@ describe("Parameters", () => {
})
})

describe("Includes", () => {
test("Check if an element exist withins a tuple", () => {
expectTypeOf<utilities.Includes<[], any>>().toEqualTypeOf<false>()
expectTypeOf<utilities.Includes<[1, 2, "foo", "bar"], 2>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<["foo", "bar", () => void, {}], () => void>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[string, 1, () => void, {}], string>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[string, number, () => void, {}], number>>().toEqualTypeOf<true>()
expectTypeOf<utilities.Includes<[true, false, true], number>>().toEqualTypeOf<false>()
})
})

describe("ObjectEntries", () => {
test("Returns the entries from an object", () => {
expectTypeOf<utilities.ObjectEntries<{ foo: string }>>().toEqualTypeOf<["foo", string]>()
Expand Down

0 comments on commit 59d6659

Please sign in to comment.