diff --git a/script/test/source-files-extension.js b/script/test/source-files-extension.js index b74c6c8b4..aab7435f6 100755 --- a/script/test/source-files-extension.js +++ b/script/test/source-files-extension.js @@ -4,10 +4,14 @@ const process = require('node:process'); const checkSourceFilesExtension = async () => { try { - const files = await fs.promises.readdir('source'); + const files = await fs.promises.readdir('source', {recursive: true}); let hasIncorrectFileExtension = false; for (const file of files) { + if (!file.includes('.')) { + continue; + } + if (!file.endsWith('.d.ts')) { hasIncorrectFileExtension = true; console.error(`source/${file} extension should be \`.d.ts\`.`); diff --git a/source/array-slice.d.ts b/source/array-slice.d.ts index c7c5848dc..b339fd6ba 100644 --- a/source/array-slice.d.ts +++ b/source/array-slice.d.ts @@ -3,7 +3,7 @@ import type {LessThanOrEqual} from './less-than-or-equal'; import type {GreaterThanOrEqual} from './greater-than-or-equal'; import type {GreaterThan} from './greater-than'; import type {IsNegative} from './numeric'; -import type {Not, ArrayMin} from './internal'; +import type {Not, TupleMin} from './internal'; import type {IsEqual} from './is-equal'; import type {And} from './and'; import type {ArraySplice} from './array-splice'; @@ -95,7 +95,7 @@ type ArraySliceHelper< PositiveE extends number = IsNegative extends true ? Sum : End, > = true extends [IsNegative, LessThanOrEqual, GreaterThanOrEqual][number] ? [] - : ArraySliceByPositiveIndex, ArrayMin<[PositiveE, ArrayLength]>>; + : ArraySliceByPositiveIndex, TupleMin<[PositiveE, ArrayLength]>>; type ArraySliceByPositiveIndex< Array_ extends readonly unknown[], diff --git a/source/delimiter-case.d.ts b/source/delimiter-case.d.ts index 9a237b770..15bceaa32 100644 --- a/source/delimiter-case.d.ts +++ b/source/delimiter-case.d.ts @@ -1,4 +1,4 @@ -import type {UpperCaseCharacters, WordSeparators} from '../source/internal'; +import type {UpperCaseCharacters, WordSeparators} from './internal'; // Transforms a string that is fully uppercase into a fully lowercase version. Needed to add support for SCREAMING_SNAKE_CASE, see https://github.com/sindresorhus/type-fest/issues/385 type UpperCaseToLowerCase = T extends Uppercase ? Lowercase : T; diff --git a/source/get.d.ts b/source/get.d.ts index 06e5726aa..bf54213a9 100644 --- a/source/get.d.ts +++ b/source/get.d.ts @@ -1,4 +1,4 @@ -import type {StringDigit} from '../source/internal'; +import type {StringDigit} from './internal'; import type {Split} from './split'; import type {StringKeyOf} from './string-key-of'; diff --git a/source/internal.d.ts b/source/internal.d.ts deleted file mode 100644 index 43b6cb733..000000000 --- a/source/internal.d.ts +++ /dev/null @@ -1,773 +0,0 @@ -import type {Primitive} from './primitive'; -import type {Simplify} from './simplify'; -import type {Trim} from './trim'; -import type {IsAny} from './is-any'; -import type {NegativeInfinity, PositiveInfinity} from './numeric'; -import type {GreaterThan} from './greater-than'; -import type {LessThan} from './less-than'; -import type {IsLiteral} from './is-literal'; -import type {UnknownRecord} from './unknown-record'; -import type {IsNever} from './is-never'; -import type {UnknownArray} from './unknown-array'; -import type {IsEqual} from './is-equal'; - -// TODO: Remove for v5. -export type {UnknownRecord} from './unknown-record'; - -/** -Infer the length of the given array ``. - -@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f -*/ -type ArrayLength = T extends {readonly length: infer L} ? L : never; - -/** -Infer the length of the given tuple ``. - -Returns `never` if the given type is an non-fixed-length array like `Array`. - -@example -``` -type Tuple = TupleLength<[string, number, boolean]>; -//=> 3 - -type Array = TupleLength; -//=> never - -// Supports union types. -type Union = TupleLength<[] | [1, 2, 3] | Array>; -//=> 1 | 3 -``` -*/ -export type TupleLength = - // `extends unknown` is used to convert `T` (if `T` is a union type) to - // a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types)) - T extends unknown - ? number extends T['length'] - ? never // Return never if the given type is an non-flexed-length array like `Array` - : T['length'] - : never; // Should never happen - -/** -Create a tuple type of the given length `` and fill it with the given type ``. - -If `` is not provided, it will default to `unknown`. - -@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f -*/ -export type BuildTuple = T['length'] extends L - ? T - : BuildTuple; - -/** -Create an object type with the given key `` and value ``. - -It will copy the prefix and optional status of the same key from the given object `CopiedFrom` into the result. - -@example -``` -type A = BuildObject<'a', string>; -//=> {a: string} - -// Copy `readonly` and `?` from the key `a` of `{readonly a?: any}` -type B = BuildObject<'a', string, {readonly a?: any}>; -//=> {readonly a?: string} -``` -*/ -export type BuildObject = - Key extends keyof CopiedFrom - ? Pick<{[_ in keyof CopiedFrom]: Value}, Key> - : Key extends `${infer NumberKey extends number}` - ? NumberKey extends keyof CopiedFrom - ? Pick<{[_ in keyof CopiedFrom]: Value}, NumberKey> - : {[_ in Key]: Value} - : {[_ in Key]: Value}; - -/** -Return a string representation of the given string or number. - -Note: This type is not the return type of the `.toString()` function. -*/ -export type ToString = T extends string | number ? `${T}` : never; - -/** -Matches any primitive, `void`, `Date`, or `RegExp` value. -*/ -export type BuiltIns = Primitive | void | Date | RegExp; - -/** -Matches non-recursive types. -*/ -export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown); - -/** -Returns a boolean for whether the given type is a plain key-value object. -*/ -export type IsPlainObject = - T extends NonRecursiveType | UnknownArray | ReadonlyMap | ReadonlySet - ? false - : T extends object - ? true - : false; - -/** -Converts a numeric string to a number. - -@example -``` -type PositiveInt = StringToNumber<'1234'>; -//=> 1234 - -type NegativeInt = StringToNumber<'-1234'>; -//=> -1234 - -type PositiveFloat = StringToNumber<'1234.56'>; -//=> 1234.56 - -type NegativeFloat = StringToNumber<'-1234.56'>; -//=> -1234.56 - -type PositiveInfinity = StringToNumber<'Infinity'>; -//=> Infinity - -type NegativeInfinity = StringToNumber<'-Infinity'>; -//=> -Infinity -``` - -@category String -@category Numeric -@category Template literal -*/ -export type StringToNumber = S extends `${infer N extends number}` - ? N - : S extends 'Infinity' - ? PositiveInfinity - : S extends '-Infinity' - ? NegativeInfinity - : never; - -/** -Returns a boolean for whether the given string `S` starts with the given string `SearchString`. - -@example -``` -StartsWith<'abcde', 'abc'>; -//=> true - -StartsWith<'abcde', 'bc'>; -//=> false - -StartsWith; -//=> never - -StartsWith<'abcde', string>; -//=> never -``` - -@category String -@category Template literal -*/ -export type StartsWith = string extends S | SearchString - ? never - : S extends `${SearchString}${infer T}` - ? true - : false; - -/** -Returns the length of the given string. - -@example -``` -StringLength<'abcde'>; -//=> 5 - -StringLength; -//=> never -``` - -@category String -@category Template literal -*/ -export type StringLength = string extends S - ? never - : StringToArray['length']; - -/** -Returns an array of the characters of the string. - -@example -``` -StringToArray<'abcde'>; -//=> ['a', 'b', 'c', 'd', 'e'] - -StringToArray; -//=> never -``` - -@category String -*/ -export type StringToArray = string extends S - ? never - : S extends `${infer F}${infer R}` - ? StringToArray - : Result; - -export type UpperCaseCharacters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'; - -export type StringDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; - -export type Whitespace = - | '\u{9}' // '\t' - | '\u{A}' // '\n' - | '\u{B}' // '\v' - | '\u{C}' // '\f' - | '\u{D}' // '\r' - | '\u{20}' // ' ' - | '\u{85}' - | '\u{A0}' - | '\u{1680}' - | '\u{2000}' - | '\u{2001}' - | '\u{2002}' - | '\u{2003}' - | '\u{2004}' - | '\u{2005}' - | '\u{2006}' - | '\u{2007}' - | '\u{2008}' - | '\u{2009}' - | '\u{200A}' - | '\u{2028}' - | '\u{2029}' - | '\u{202F}' - | '\u{205F}' - | '\u{3000}' - | '\u{FEFF}'; - -export type WordSeparators = '-' | '_' | Whitespace; - -/** -Matches any unknown array or tuple. -*/ -export type UnknownArrayOrTuple = readonly [...unknown[]]; - -/** -Returns a boolean for whether the two given types extends the base type. -*/ -export type IsBothExtends = FirstType extends BaseType - ? SecondType extends BaseType - ? true - : false - : false; - -/** -Extracts the type of the first element of an array or tuple. -*/ -export type FirstArrayElement = TArray extends readonly [infer THead, ...unknown[]] - ? THead - : never; - -/** -Extract the element of an array that also works for array union. - -Returns `never` if T is not an array. - -It creates a type-safe way to access the element type of `unknown` type. -*/ -export type ArrayElement = T extends readonly unknown[] ? T[0] : never; - -/** -Extract the object field type if T is an object and K is a key of T, return `never` otherwise. - -It creates a type-safe way to access the member type of `unknown` type. -*/ -export type ObjectValue = - K extends keyof T - ? T[K] - : ToString extends keyof T - ? T[ToString] - : K extends `${infer NumberK extends number}` - ? NumberK extends keyof T - ? T[NumberK] - : never - : never; - -/** -Returns a boolean for whether the string is lowercased. -*/ -export type IsLowerCase = T extends Lowercase ? true : false; - -/** -Returns a boolean for whether the string is uppercased. -*/ -export type IsUpperCase = T extends Uppercase ? true : false; - -/** -Returns a boolean for whether a string is whitespace. -*/ -export type IsWhitespace = T extends Whitespace - ? true - : T extends `${Whitespace}${infer Rest}` - ? IsWhitespace - : false; - -/** -Returns a boolean for whether the string is numeric. - -This type is a workaround for [Microsoft/TypeScript#46109](https://github.com/microsoft/TypeScript/issues/46109#issuecomment-930307987). -*/ -export type IsNumeric = T extends `${number}` - ? Trim extends T - ? true - : false - : false; - -/** -For an object T, if it has any properties that are a union with `undefined`, make those into optional properties instead. - -@example -``` -type User = { - firstName: string; - lastName: string | undefined; -}; - -type OptionalizedUser = UndefinedToOptional; -//=> { -// firstName: string; -// lastName?: string; -// } -``` -*/ -export type UndefinedToOptional = Simplify< -{ - // Property is not a union with `undefined`, keep it as-is. - [Key in keyof Pick>]: T[Key]; -} & { - // Property _is_ a union with defined value. Set as optional (via `?`) and remove `undefined` from the union. - [Key in keyof Pick>]?: Exclude; -} ->; - -// Returns `never` if the key or property is not jsonable without testing whether the property is required or optional otherwise return the key. -type BaseKeyFilter = Key extends symbol - ? never - : Type[Key] extends symbol - ? never - /* - To prevent a problem where an object with only a `name` property is incorrectly treated as assignable to a function, we first check if the property is a record. - This check is necessary, because without it, if we don't verify whether the property is a record, an object with a type of `{name: any}` would return `never` due to its potential assignability to a function. - See: https://github.com/sindresorhus/type-fest/issues/657 - */ - : Type[Key] extends Record - ? Key - : [(...arguments_: any[]) => any] extends [Type[Key]] - ? never - : Key; - -/** -Returns the required keys. -*/ -type FilterDefinedKeys = Exclude< -{ - [Key in keyof T]: IsAny extends true - ? Key - : undefined extends T[Key] - ? never - : T[Key] extends undefined - ? never - : BaseKeyFilter; -}[keyof T], -undefined ->; - -/** -Returns the optional keys. -*/ -type FilterOptionalKeys = Exclude< -{ - [Key in keyof T]: IsAny extends true - ? never - : undefined extends T[Key] - ? T[Key] extends undefined - ? never - : BaseKeyFilter - : never; -}[keyof T], -undefined ->; - -/** -Test if the given function has multiple call signatures. - -Needed to handle the case of a single call signature with properties. - -Multiple call signatures cannot currently be supported due to a TypeScript limitation. -@see https://github.com/microsoft/TypeScript/issues/29732 -*/ -export type HasMultipleCallSignatures unknown> = - T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown} - ? B extends A - ? A extends B - ? false - : true - : true - : false; - -/** -Returns a boolean for whether the given `boolean` is not `false`. -*/ -export type IsNotFalse = [T] extends [false] ? false : true; - -/** -Disallows any of the given keys. -*/ -export type RequireNone = Partial>; - -/** -Returns a boolean for whether the given type is primitive value or primitive type. - -@example -``` -IsPrimitive<'string'> -//=> true - -IsPrimitive -//=> true - -IsPrimitive -//=> false -``` -*/ -export type IsPrimitive = [T] extends [Primitive] ? true : false; - -/** -Returns a boolean for whether A is false. - -@example -``` -Not; -//=> false - -Not; -//=> true -``` -*/ -export type Not = A extends true - ? false - : A extends false - ? true - : never; - -/** -Returns the maximum value from a tuple of integers. - -Note: -- Float numbers are not supported. - -@example -``` -ArrayMax<[1, 2, 5, 3]>; -//=> 5 - -ArrayMax<[1, 2, 5, 3, 99, -1]>; -//=> 99 -``` -*/ -export type ArrayMax = number extends A[number] - ? never : - A extends [infer F extends number, ...infer R extends number[]] - ? GreaterThan extends true - ? ArrayMax - : ArrayMax - : Result; - -/** -Returns the minimum value from a tuple of integers. - -Note: -- Float numbers are not supported. - -@example -``` -ArrayMin<[1, 2, 5, 3]>; -//=> 1 - -ArrayMin<[1, 2, 5, 3, -5]>; -//=> -5 -``` -*/ -export type ArrayMin = number extends A[number] - ? never - : A extends [infer F extends number, ...infer R extends number[]] - ? LessThan extends true - ? ArrayMin - : ArrayMin - : Result; - -/** -Returns the absolute value of a given value. - -@example -``` -NumberAbsolute<-1>; -//=> 1 - -NumberAbsolute<1>; -//=> 1 - -NumberAbsolute -//=> PositiveInfinity -``` -*/ -export type NumberAbsolute = `${N}` extends `-${infer StringPositiveN}` ? StringToNumber : N; - -/** -Returns a boolean for whether `A` represents a number greater than `B`, where `A` and `B` are both numeric strings and have the same length. - -@example -``` -SameLengthPositiveNumericStringGt<'50', '10'>; -//=> true - -SameLengthPositiveNumericStringGt<'10', '10'>; -//=> false -``` -*/ -type SameLengthPositiveNumericStringGt = A extends `${infer FirstA}${infer RestA}` - ? B extends `${infer FirstB}${infer RestB}` - ? FirstA extends FirstB - ? SameLengthPositiveNumericStringGt - : PositiveNumericCharacterGt - : never - : false; - -type NumericString = '0123456789'; - -/** -Returns a boolean for whether `A` is greater than `B`, where `A` and `B` are both positive numeric strings. - -@example -``` -PositiveNumericStringGt<'500', '1'>; -//=> true - -PositiveNumericStringGt<'1', '1'>; -//=> false - -PositiveNumericStringGt<'1', '500'>; -//=> false -``` -*/ -export type PositiveNumericStringGt = A extends B - ? false - : [BuildTuple, 0>, BuildTuple, 0>] extends infer R extends [readonly unknown[], readonly unknown[]] - ? R[0] extends [...R[1], ...infer Remain extends readonly unknown[]] - ? 0 extends Remain['length'] - ? SameLengthPositiveNumericStringGt - : true - : false - : never; - -/** -Returns a boolean for whether `A` represents a number greater than `B`, where `A` and `B` are both positive numeric characters. - -@example -``` -PositiveNumericCharacterGt<'5', '1'>; -//=> true - -PositiveNumericCharacterGt<'1', '1'>; -//=> false -``` -*/ -type PositiveNumericCharacterGt = NumericString extends `${infer HeadA}${A}${infer TailA}` - ? NumericString extends `${infer HeadB}${B}${infer TailB}` - ? HeadA extends `${HeadB}${infer _}${infer __}` - ? true - : false - : never - : never; - -/** -Utility type to retrieve only literal keys from type. -*/ -export type LiteralKeyOf = keyof {[K in keyof T as IsLiteral extends true ? K : never]-?: never}; - -/** -Returns the static, fixed-length portion of the given array, excluding variable-length parts. - -@example -``` -type A = [string, number, boolean, ...string[]]; -type B = StaticPartOfArray; -//=> [string, number, boolean] -``` -*/ -export type StaticPartOfArray = - T extends unknown - ? number extends T['length'] ? - T extends readonly [infer U, ...infer V] - ? StaticPartOfArray - : Result - : T - : never; // Should never happen - -/** -Returns the variable, non-fixed-length portion of the given array, excluding static-length parts. - -@example -``` -type A = [string, number, boolean, ...string[]]; -type B = VariablePartOfArray; -//=> string[] -``` -*/ -export type VariablePartOfArray = - T extends unknown - ? T extends readonly [...StaticPartOfArray, ...infer U] - ? U - : [] - : never; // Should never happen - -/** -Returns the minimum number in the given union of numbers. - -Note: Just supports numbers from 0 to 999. - -@example -``` -type A = UnionMin<3 | 1 | 2>; -//=> 1 -``` -*/ -export type UnionMin = InternalUnionMin; - -/** -The actual implementation of `UnionMin`. It's private because it has some arguments that don't need to be exposed. -*/ -type InternalUnionMin = - T['length'] extends N - ? T['length'] - : InternalUnionMin; - -/** -Returns the maximum number in the given union of numbers. - -Note: Just supports numbers from 0 to 999. - -@example -``` -type A = UnionMax<1 | 3 | 2>; -//=> 3 -``` -*/ -export type UnionMax = InternalUnionMax; - -/** -The actual implementation of `UnionMax`. It's private because it has some arguments that don't need to be exposed. -*/ -type InternalUnionMax = - IsNever extends true - ? T['length'] - : T['length'] extends N - ? InternalUnionMax, T> - : InternalUnionMax; - -/** -Returns a boolean for whether the given type is a union type. - -@example -``` -type A = IsUnion; -//=> true - -type B = IsUnion; -//=> false -``` -*/ -export type IsUnion = InternalIsUnion; - -/** -The actual implementation of `IsUnion`. -*/ -type InternalIsUnion = -( - // @link https://ghaiklor.github.io/type-challenges-solutions/en/medium-isunion.html - IsNever extends true - ? false - : T extends any - ? [U] extends [T] - ? false - : true - : never -) extends infer Result - // In some cases `Result` will return `false | true` which is `boolean`, - // that means `T` has at least two types and it's a union type, - // so we will return `true` instead of `boolean`. - ? boolean extends Result ? true - : Result - : never; // Should never happen - -/** -Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result. - -@example -``` -type ReadonlyArray = readonly string[]; -type NormalArray = string[]; - -type ReadonlyResult = SetArrayAccess; -//=> readonly string[] - -type NormalResult = SetArrayAccess; -//=> string[] -``` -*/ -export type SetArrayAccess = -T extends readonly [...infer U] ? - IsReadonly extends true - ? readonly [...U] - : [...U] - : T; - -/** -Returns whether the given array `T` is readonly. -*/ -export type IsArrayReadonly = T extends unknown[] ? false : true; - -/** -Get the exact version of the given `Key` in the given object `T`. - -Use-case: You known that a number key (e.g. 10) is in an object, but you don't know how it is defined in the object, as a string or as a number (e.g. 10 or '10'). You can use this type to get the exact version of the key. See the example. - -@example -``` -type Object = { - 0: number; - '1': string; -}; - -type Key1 = ExactKey; -//=> 0 -type Key2 = ExactKey; -//=> 0 - -type Key3 = ExactKey; -//=> '1' -type Key4 = ExactKey; -//=> '1' -``` - -@category Object -*/ -export type ExactKey = -Key extends keyof T - ? Key - : ToString extends keyof T - ? ToString - : Key extends `${infer NumberKey extends number}` - ? NumberKey extends keyof T - ? NumberKey - : never - : never; diff --git a/source/internal/array.d.ts b/source/internal/array.d.ts new file mode 100644 index 000000000..566f40592 --- /dev/null +++ b/source/internal/array.d.ts @@ -0,0 +1,93 @@ +import type {UnknownArray} from '../unknown-array'; + +/** +Infer the length of the given array ``. + +@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f +*/ +type ArrayLength = T extends {readonly length: infer L} ? L : never; + +/** +Matches any unknown array or tuple. +*/ +export type UnknownArrayOrTuple = readonly [...unknown[]]; +// TODO: should unknown-array be updated? + +/** +Extracts the type of the first element of an array or tuple. +*/ +export type FirstArrayElement = TArray extends readonly [infer THead, ...unknown[]] + ? THead + : never; + +/** +Extract the element of an array that also works for array union. + +Returns `never` if T is not an array. + +It creates a type-safe way to access the element type of `unknown` type. +*/ +export type ArrayElement = T extends readonly unknown[] ? T[0] : never; + +/** +Returns the static, fixed-length portion of the given array, excluding variable-length parts. + +@example +``` +type A = [string, number, boolean, ...string[]]; +type B = StaticPartOfArray; +//=> [string, number, boolean] +``` +*/ +export type StaticPartOfArray = + T extends unknown + ? number extends T['length'] ? + T extends readonly [infer U, ...infer V] + ? StaticPartOfArray + : Result + : T + : never; // Should never happen + +/** +Returns the variable, non-fixed-length portion of the given array, excluding static-length parts. + +@example +``` +type A = [string, number, boolean, ...string[]]; +type B = VariablePartOfArray; +//=> string[] +``` +*/ +export type VariablePartOfArray = + T extends unknown + ? T extends readonly [...StaticPartOfArray, ...infer U] + ? U + : [] + : never; // Should never happen + +/** +Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result. + +@example +``` +type ReadonlyArray = readonly string[]; +type NormalArray = string[]; + +type ReadonlyResult = SetArrayAccess; +//=> readonly string[] + +type NormalResult = SetArrayAccess; +//=> string[] +``` +*/ +export type SetArrayAccess = +T extends readonly [...infer U] ? + IsReadonly extends true + ? readonly [...U] + : [...U] + : T; + +/** +Returns whether the given array `T` is readonly. +*/ +export type IsArrayReadonly = T extends unknown[] ? false : true; diff --git a/source/internal/characters.d.ts b/source/internal/characters.d.ts new file mode 100644 index 000000000..30c0bf59f --- /dev/null +++ b/source/internal/characters.d.ts @@ -0,0 +1,33 @@ +export type UpperCaseCharacters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'; + +export type StringDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; + +export type Whitespace = + | '\u{9}' // '\t' + | '\u{A}' // '\n' + | '\u{B}' // '\v' + | '\u{C}' // '\f' + | '\u{D}' // '\r' + | '\u{20}' // ' ' + | '\u{85}' + | '\u{A0}' + | '\u{1680}' + | '\u{2000}' + | '\u{2001}' + | '\u{2002}' + | '\u{2003}' + | '\u{2004}' + | '\u{2005}' + | '\u{2006}' + | '\u{2007}' + | '\u{2008}' + | '\u{2009}' + | '\u{200A}' + | '\u{2028}' + | '\u{2029}' + | '\u{202F}' + | '\u{205F}' + | '\u{3000}' + | '\u{FEFF}'; + +export type WordSeparators = '-' | '_' | Whitespace; diff --git a/source/internal/index.d.ts b/source/internal/index.d.ts new file mode 100644 index 000000000..31c0711ab --- /dev/null +++ b/source/internal/index.d.ts @@ -0,0 +1,8 @@ +export type * from './array'; +export type * from './characters'; +export type * from './keys'; +export type * from './numeric'; +export type * from './object'; +export type * from './string'; +export type * from './tuple'; +export type * from './type'; diff --git a/source/internal/keys.d.ts b/source/internal/keys.d.ts new file mode 100644 index 000000000..43f53c795 --- /dev/null +++ b/source/internal/keys.d.ts @@ -0,0 +1,97 @@ +import type {IsAny} from '../is-any'; +import type {IsLiteral} from '../is-literal'; +import type {ToString} from './string'; + +// Returns `never` if the key or property is not jsonable without testing whether the property is required or optional otherwise return the key. +type BaseKeyFilter = Key extends symbol + ? never + : Type[Key] extends symbol + ? never + /* + To prevent a problem where an object with only a `name` property is incorrectly treated as assignable to a function, we first check if the property is a record. + This check is necessary, because without it, if we don't verify whether the property is a record, an object with a type of `{name: any}` would return `never` due to its potential assignability to a function. + See: https://github.com/sindresorhus/type-fest/issues/657 + */ + : Type[Key] extends Record + ? Key + : [(...arguments_: any[]) => any] extends [Type[Key]] + ? never + : Key; + +/** +Returns the required keys. +*/ +type FilterDefinedKeys = Exclude< +{ + [Key in keyof T]: IsAny extends true + ? Key + : undefined extends T[Key] + ? never + : T[Key] extends undefined + ? never + : BaseKeyFilter; +}[keyof T], +undefined +>; + +/** +Returns the optional keys. +*/ +type FilterOptionalKeys = Exclude< +{ + [Key in keyof T]: IsAny extends true + ? never + : undefined extends T[Key] + ? T[Key] extends undefined + ? never + : BaseKeyFilter + : never; +}[keyof T], +undefined +>; + +/** +Disallows any of the given keys. +*/ +export type RequireNone = Partial>; + +/** +Utility type to retrieve only literal keys from type. +*/ +export type LiteralKeyOf = keyof {[K in keyof T as IsLiteral extends true ? K : never]-?: never}; + +/** +Get the exact version of the given `Key` in the given object `T`. + +Use-case: You known that a number key (e.g. 10) is in an object, but you don't know how it is defined in the object, as a string or as a number (e.g. 10 or '10'). You can use this type to get the exact version of the key. See the example. + +@example +``` +type Object = { + 0: number; + '1': string; +}; + +type Key1 = ExactKey; +//=> 0 +type Key2 = ExactKey; +//=> 0 + +type Key3 = ExactKey; +//=> '1' +type Key4 = ExactKey; +//=> '1' +``` + +@category Object +*/ +export type ExactKey = +Key extends keyof T + ? Key + : ToString extends keyof T + ? ToString + : Key extends `${infer NumberKey extends number}` + ? NumberKey extends keyof T + ? NumberKey + : never + : never; diff --git a/source/internal/numeric.d.ts b/source/internal/numeric.d.ts new file mode 100644 index 000000000..089120fb5 --- /dev/null +++ b/source/internal/numeric.d.ts @@ -0,0 +1,64 @@ +import type {IsNever} from '../is-never'; +import type {UnknownArray} from '../unknown-array'; +import type {StringToNumber} from './string'; + +/** +Returns the absolute value of a given value. + +@example +``` +NumberAbsolute<-1>; +//=> 1 + +NumberAbsolute<1>; +//=> 1 + +NumberAbsolute +//=> PositiveInfinity +``` +*/ +export type NumberAbsolute = `${N}` extends `-${infer StringPositiveN}` ? StringToNumber : N; + +/** +Returns the minimum number in the given union of numbers. + +Note: Just supports numbers from 0 to 999. + +@example +``` +type A = UnionMin<3 | 1 | 2>; +//=> 1 +``` +*/ +export type UnionMin = InternalUnionMin; + +/** +The actual implementation of `UnionMin`. It's private because it has some arguments that don't need to be exposed. +*/ +type InternalUnionMin = + T['length'] extends N + ? T['length'] + : InternalUnionMin; + +/** +Returns the maximum number in the given union of numbers. + +Note: Just supports numbers from 0 to 999. + +@example +``` +type A = UnionMax<1 | 3 | 2>; +//=> 3 +``` +*/ +export type UnionMax = InternalUnionMax; + +/** +The actual implementation of `UnionMax`. It's private because it has some arguments that don't need to be exposed. +*/ +type InternalUnionMax = + IsNever extends true + ? T['length'] + : T['length'] extends N + ? InternalUnionMax, T> + : InternalUnionMax; diff --git a/source/internal/object.d.ts b/source/internal/object.d.ts new file mode 100644 index 000000000..5a6edda22 --- /dev/null +++ b/source/internal/object.d.ts @@ -0,0 +1,82 @@ +import type {Simplify} from '../simplify'; +import type {UnknownArray} from '../unknown-array'; +import type {FilterDefinedKeys, FilterOptionalKeys} from './keys'; +import type {NonRecursiveType} from './type'; +import type {ToString} from './string'; + +/** +Create an object type with the given key `` and value ``. + +It will copy the prefix and optional status of the same key from the given object `CopiedFrom` into the result. + +@example +``` +type A = BuildObject<'a', string>; +//=> {a: string} + +// Copy `readonly` and `?` from the key `a` of `{readonly a?: any}` +type B = BuildObject<'a', string, {readonly a?: any}>; +//=> {readonly a?: string} +``` +*/ +export type BuildObject = + Key extends keyof CopiedFrom + ? Pick<{[_ in keyof CopiedFrom]: Value}, Key> + : Key extends `${infer NumberKey extends number}` + ? NumberKey extends keyof CopiedFrom + ? Pick<{[_ in keyof CopiedFrom]: Value}, NumberKey> + : {[_ in Key]: Value} + : {[_ in Key]: Value}; + +/** +Returns a boolean for whether the given type is a plain key-value object. +*/ +export type IsPlainObject = + T extends NonRecursiveType | UnknownArray | ReadonlyMap | ReadonlySet + ? false + : T extends object + ? true + : false; + +/** +Extract the object field type if T is an object and K is a key of T, return `never` otherwise. + +It creates a type-safe way to access the member type of `unknown` type. +*/ +export type ObjectValue = + K extends keyof T + ? T[K] + : ToString extends keyof T + ? T[ToString] + : K extends `${infer NumberK extends number}` + ? NumberK extends keyof T + ? T[NumberK] + : never + : never; + +/** +For an object T, if it has any properties that are a union with `undefined`, make those into optional properties instead. + +@example +``` +type User = { + firstName: string; + lastName: string | undefined; +}; + +type OptionalizedUser = UndefinedToOptional; +//=> { +// firstName: string; +// lastName?: string; +// } +``` +*/ +export type UndefinedToOptional = Simplify< +{ + // Property is not a union with `undefined`, keep it as-is. + [Key in keyof Pick>]: T[Key]; +} & { + // Property _is_ a union with defined value. Set as optional (via `?`) and remove `undefined` from the union. + [Key in keyof Pick>]?: Exclude; +} +>; diff --git a/source/internal/string.d.ts b/source/internal/string.d.ts new file mode 100644 index 000000000..5c3f8ef75 --- /dev/null +++ b/source/internal/string.d.ts @@ -0,0 +1,210 @@ +import type {NegativeInfinity, PositiveInfinity} from '../numeric'; +import type {Trim} from '../trim'; +import type {Whitespace} from './characters'; +import type {BuildTuple} from './tuple'; + +/** +Return a string representation of the given string or number. + +Note: This type is not the return type of the `.toString()` function. +*/ +export type ToString = T extends string | number ? `${T}` : never; + +/** +Converts a numeric string to a number. + +@example +``` +type PositiveInt = StringToNumber<'1234'>; +//=> 1234 + +type NegativeInt = StringToNumber<'-1234'>; +//=> -1234 + +type PositiveFloat = StringToNumber<'1234.56'>; +//=> 1234.56 + +type NegativeFloat = StringToNumber<'-1234.56'>; +//=> -1234.56 + +type PositiveInfinity = StringToNumber<'Infinity'>; +//=> Infinity + +type NegativeInfinity = StringToNumber<'-Infinity'>; +//=> -Infinity +``` + +@category String +@category Numeric +@category Template literal +*/ +export type StringToNumber = S extends `${infer N extends number}` + ? N + : S extends 'Infinity' + ? PositiveInfinity + : S extends '-Infinity' + ? NegativeInfinity + : never; + +/** +Returns a boolean for whether the given string `S` starts with the given string `SearchString`. + +@example +``` +StartsWith<'abcde', 'abc'>; +//=> true + +StartsWith<'abcde', 'bc'>; +//=> false + +StartsWith; +//=> never + +StartsWith<'abcde', string>; +//=> never +``` + +@category String +@category Template literal +*/ +export type StartsWith = string extends S | SearchString + ? never + : S extends `${SearchString}${infer T}` + ? true + : false; + +/** +Returns an array of the characters of the string. + +@example +``` +StringToArray<'abcde'>; +//=> ['a', 'b', 'c', 'd', 'e'] + +StringToArray; +//=> never +``` + +@category String +*/ +export type StringToArray = string extends S + ? never + : S extends `${infer F}${infer R}` + ? StringToArray + : Result; + +/** +Returns the length of the given string. + +@example +``` +StringLength<'abcde'>; +//=> 5 + +StringLength; +//=> never +``` + +@category String +@category Template literal +*/ +export type StringLength = string extends S + ? never + : StringToArray['length']; + +/** +Returns a boolean for whether the string is lowercased. +*/ +export type IsLowerCase = T extends Lowercase ? true : false; + +/** +Returns a boolean for whether the string is uppercased. +*/ +export type IsUpperCase = T extends Uppercase ? true : false; + +/** +Returns a boolean for whether a string is whitespace. +*/ +export type IsWhitespace = T extends Whitespace + ? true + : T extends `${Whitespace}${infer Rest}` + ? IsWhitespace + : false; + +/** +Returns a boolean for whether the string is numeric. + +This type is a workaround for [Microsoft/TypeScript#46109](https://github.com/microsoft/TypeScript/issues/46109#issuecomment-930307987). +*/ +export type IsNumeric = T extends `${number}` + ? Trim extends T + ? true + : false + : false; + +/** +Returns a boolean for whether `A` represents a number greater than `B`, where `A` and `B` are both numeric strings and have the same length. + +@example +``` +SameLengthPositiveNumericStringGt<'50', '10'>; +//=> true + +SameLengthPositiveNumericStringGt<'10', '10'>; +//=> false +``` +*/ +type SameLengthPositiveNumericStringGt = A extends `${infer FirstA}${infer RestA}` + ? B extends `${infer FirstB}${infer RestB}` + ? FirstA extends FirstB + ? SameLengthPositiveNumericStringGt + : PositiveNumericCharacterGt + : never + : false; + +type NumericString = '0123456789'; + +/** +Returns a boolean for whether `A` is greater than `B`, where `A` and `B` are both positive numeric strings. + +@example +``` +PositiveNumericStringGt<'500', '1'>; +//=> true + +PositiveNumericStringGt<'1', '1'>; +//=> false + +PositiveNumericStringGt<'1', '500'>; +//=> false +``` +*/ +export type PositiveNumericStringGt = A extends B + ? false + : [BuildTuple, 0>, BuildTuple, 0>] extends infer R extends [readonly unknown[], readonly unknown[]] + ? R[0] extends [...R[1], ...infer Remain extends readonly unknown[]] + ? 0 extends Remain['length'] + ? SameLengthPositiveNumericStringGt + : true + : false + : never; + +/** +Returns a boolean for whether `A` represents a number greater than `B`, where `A` and `B` are both positive numeric characters. + +@example +``` +PositiveNumericCharacterGt<'5', '1'>; +//=> true + +PositiveNumericCharacterGt<'1', '1'>; +//=> false +``` +*/ +type PositiveNumericCharacterGt = NumericString extends `${infer HeadA}${A}${infer TailA}` + ? NumericString extends `${infer HeadB}${B}${infer TailB}` + ? HeadA extends `${HeadB}${infer _}${infer __}` + ? true + : false + : never + : never; diff --git a/source/internal/tuple.d.ts b/source/internal/tuple.d.ts new file mode 100644 index 000000000..6d173e70b --- /dev/null +++ b/source/internal/tuple.d.ts @@ -0,0 +1,88 @@ +import type {GreaterThan} from '../greater-than'; +import type {LessThan} from '../less-than'; +import type {NegativeInfinity, PositiveInfinity} from '../numeric'; +import type {UnknownArray} from '../unknown-array'; + +/** +Infer the length of the given tuple ``. + +Returns `never` if the given type is an non-fixed-length array like `Array`. + +@example +``` +type Tuple = TupleLength<[string, number, boolean]>; +//=> 3 + +type Array = TupleLength; +//=> never + +// Supports union types. +type Union = TupleLength<[] | [1, 2, 3] | Array>; +//=> 1 | 3 +``` +*/ +export type TupleLength = + // `extends unknown` is used to convert `T` (if `T` is a union type) to + // a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types)) + T extends unknown + ? number extends T['length'] + ? never // Return never if the given type is an non-flexed-length array like `Array` + : T['length'] + : never; // Should never happen + +/** +Create a tuple type of the given length `` and fill it with the given type ``. + +If `` is not provided, it will default to `unknown`. + +@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f +*/ +export type BuildTuple = T['length'] extends L + ? T + : BuildTuple; + +/** +Returns the maximum value from a tuple of integers. + +Note: +- Float numbers are not supported. + +@example +``` +ArrayMax<[1, 2, 5, 3]>; +//=> 5 + +ArrayMax<[1, 2, 5, 3, 99, -1]>; +//=> 99 +``` +*/ +export type TupleMax = number extends A[number] + ? never : + A extends [infer F extends number, ...infer R extends number[]] + ? GreaterThan extends true + ? TupleMax + : TupleMax + : Result; + +/** +Returns the minimum value from a tuple of integers. + +Note: +- Float numbers are not supported. + +@example +``` +ArrayMin<[1, 2, 5, 3]>; +//=> 1 + +ArrayMin<[1, 2, 5, 3, -5]>; +//=> -5 +``` +*/ +export type TupleMin = number extends A[number] + ? never + : A extends [infer F extends number, ...infer R extends number[]] + ? LessThan extends true + ? TupleMin + : TupleMin + : Result; diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts new file mode 100644 index 000000000..46c0343b4 --- /dev/null +++ b/source/internal/type.d.ts @@ -0,0 +1,113 @@ +import type {IsNever} from '../is-never'; +import type {Primitive} from '../primitive'; + +/** +Matches any primitive, `void`, `Date`, or `RegExp` value. +*/ +export type BuiltIns = Primitive | void | Date | RegExp; + +/** +Matches non-recursive types. +*/ +export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown); + +/** +Returns a boolean for whether the two given types extends the base type. +*/ +export type IsBothExtends = FirstType extends BaseType + ? SecondType extends BaseType + ? true + : false + : false; + +/** +Test if the given function has multiple call signatures. + +Needed to handle the case of a single call signature with properties. + +Multiple call signatures cannot currently be supported due to a TypeScript limitation. +@see https://github.com/microsoft/TypeScript/issues/29732 +*/ +export type HasMultipleCallSignatures unknown> = + T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown} + ? B extends A + ? A extends B + ? false + : true + : true + : false; + +/** +Returns a boolean for whether the given `boolean` is not `false`. +*/ +export type IsNotFalse = [T] extends [false] ? false : true; + +/** +Returns a boolean for whether the given type is primitive value or primitive type. + +@example +``` +IsPrimitive<'string'> +//=> true + +IsPrimitive +//=> true + +IsPrimitive +//=> false +``` +*/ +export type IsPrimitive = [T] extends [Primitive] ? true : false; + +/** +Returns a boolean for whether A is false. + +@example +``` +Not; +//=> false + +Not; +//=> true +``` +*/ +export type Not = A extends true + ? false + : A extends false + ? true + : never; + +/** +Returns a boolean for whether the given type is a union type. + +@example +``` +type A = IsUnion; +//=> true + +type B = IsUnion; +//=> false +``` +*/ +export type IsUnion = InternalIsUnion; + +/** +The actual implementation of `IsUnion`. +*/ +type InternalIsUnion = +( + // @link https://ghaiklor.github.io/type-challenges-solutions/en/medium-isunion.html + IsNever extends true + ? false + : T extends any + ? [U] extends [T] + ? false + : true + : never +) extends infer Result + // In some cases `Result` will return `false | true` which is `boolean`, + // that means `T` has at least two types and it's a union type, + // so we will return `true` instead of `boolean`. + ? boolean extends Result ? true + : Result + : never; // Should never happen diff --git a/source/invariant-of.d.ts b/source/invariant-of.d.ts index 2fa821e46..bb913ae77 100644 --- a/source/invariant-of.d.ts +++ b/source/invariant-of.d.ts @@ -1,5 +1,3 @@ -import type {Opaque} from './opaque'; - declare const invariantBrand: unique symbol; /** diff --git a/source/spread.d.ts b/source/spread.d.ts index b182611e6..8d2c353a8 100644 --- a/source/spread.d.ts +++ b/source/spread.d.ts @@ -1,4 +1,3 @@ -import type {Except} from './except'; import type {RequiredKeysOf} from './required-keys-of'; import type {Simplify} from './simplify'; diff --git a/source/sum.d.ts b/source/sum.d.ts index 14ecd8f54..29d07dc30 100644 --- a/source/sum.d.ts +++ b/source/sum.d.ts @@ -1,4 +1,4 @@ -import type {NumberAbsolute, BuildTuple, ArrayMax, ArrayMin} from './internal'; +import type {NumberAbsolute, BuildTuple, TupleMax, TupleMin} from './internal'; import type {IsEqual} from './is-equal'; import type {PositiveInfinity, NegativeInfinity, IsNegative} from './numeric'; import type {Subtract} from './subtract'; @@ -59,8 +59,8 @@ export type Sum = number extends A | B ? [...BuildTuple, ...BuildTuple]['length'] : [true, true] extends R ? number - : ArrayMax<[NumberAbsolute, NumberAbsolute]> extends infer Max_ - ? ArrayMin<[NumberAbsolute, NumberAbsolute]> extends infer Min_ extends number + : TupleMax<[NumberAbsolute, NumberAbsolute]> extends infer Max_ + ? TupleMin<[NumberAbsolute, NumberAbsolute]> extends infer Min_ extends number ? Max_ extends A | B ? Subtract : number diff --git a/source/undefined-on-partial-deep.d.ts b/source/undefined-on-partial-deep.d.ts index 324e098af..ed7eef9f6 100644 --- a/source/undefined-on-partial-deep.d.ts +++ b/source/undefined-on-partial-deep.d.ts @@ -1,5 +1,4 @@ import type {BuiltIns} from './internal'; -import type {Merge} from './merge'; /** Create a deep version of another type where all optional keys are set to also accept `undefined`. diff --git a/test-d/internal/array-max.ts b/test-d/internal/array-max.ts deleted file mode 100644 index 449d5e071..000000000 --- a/test-d/internal/array-max.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {expectType} from 'tsd'; -import type {ArrayMax} from '../../source/internal'; -import type {PositiveInfinity} from '../../source/numeric'; - -expectType>(7); -expectType>(null! as PositiveInfinity); -expectType>(1); -expectType>(-1); -expectType>(10); diff --git a/test-d/internal/array-min.ts b/test-d/internal/array-min.ts deleted file mode 100644 index e1cc540d8..000000000 --- a/test-d/internal/array-min.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {expectType} from 'tsd'; -import type {ArrayMin} from '../../source/internal'; -import type {NegativeInfinity, PositiveInfinity} from '../../source/numeric'; - -declare const never: never; - -expectType>(-9); -expectType>(null! as NegativeInfinity); -expectType>(1); -expectType>(-5); -expectType>(never); diff --git a/test-d/internal/object-value.ts b/test-d/internal/object-value.ts index a5985157a..1e14db99d 100644 --- a/test-d/internal/object-value.ts +++ b/test-d/internal/object-value.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import {type ObjectValue} from '../../source/internal'; +import type {ObjectValue} from '../../source/internal'; type ObjectT = { string: string; diff --git a/test-d/internal/require-none.ts b/test-d/internal/require-none.ts index 45382741c..0f37ee645 100644 --- a/test-d/internal/require-none.ts +++ b/test-d/internal/require-none.ts @@ -1,5 +1,5 @@ import {expectAssignable, expectNotAssignable} from 'tsd'; -import {type RequireNone} from '../../source/internal'; +import type {RequireNone} from '../../source/internal'; type NoneAllowed = RequireNone<'foo' | 'bar'>; diff --git a/test-d/internal/tuple-max.ts b/test-d/internal/tuple-max.ts new file mode 100644 index 000000000..76104f8a2 --- /dev/null +++ b/test-d/internal/tuple-max.ts @@ -0,0 +1,9 @@ +import {expectType} from 'tsd'; +import type {TupleMax} from '../../source/internal'; +import type {PositiveInfinity} from '../../source/numeric'; + +expectType>(7); +expectType>(null! as PositiveInfinity); +expectType>(1); +expectType>(-1); +expectType>(10); diff --git a/test-d/internal/tuple-min.ts b/test-d/internal/tuple-min.ts new file mode 100644 index 000000000..034f150e6 --- /dev/null +++ b/test-d/internal/tuple-min.ts @@ -0,0 +1,11 @@ +import {expectType} from 'tsd'; +import type {TupleMin} from '../../source/internal'; +import type {NegativeInfinity, PositiveInfinity} from '../../source/numeric'; + +declare const never: never; + +expectType>(-9); +expectType>(null! as NegativeInfinity); +expectType>(1); +expectType>(-5); +expectType>(never);