diff --git a/index.d.ts b/index.d.ts index 78ecc807..ee07d85a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -147,6 +147,8 @@ export type {NonEmptyTuple} from './source/non-empty-tuple.d.ts'; export type {FindGlobalInstanceType, FindGlobalType} from './source/find-global-type.d.ts'; export type {If} from './source/if.d.ts'; export type {IsUnion} from './source/is-union.d.ts'; +export type {IsLowercase} from './source/is-lowercase.d.ts'; +export type {IsUppercase} from './source/is-uppercase.d.ts'; // Template literal types export type {CamelCase} from './source/camel-case.d.ts'; diff --git a/readme.md b/readme.md index 13fb122b..0756ff4e 100644 --- a/readme.md +++ b/readme.md @@ -228,6 +228,8 @@ Click the type names for complete docs. - [`IsNull`](source/is-null.d.ts) - Returns a boolean for whether the given type is `null`. - [`IsTuple`](source/is-tuple.d.ts) - Returns a boolean for whether the given array is a tuple. - [`IsUnion`](source/is-union.d.ts) - Returns a boolean for whether the given type is a union. +- [`IsLowercase`](source/is-lowercase.d.ts) - Returns a boolean for whether the given string literal is lowercase. +- [`IsUppercase`](source/is-uppercase.d.ts) - Returns a boolean for whether the given string literal is uppercase. ### JSON diff --git a/source/internal/array.d.ts b/source/internal/array.d.ts index fb8b0f61..782e1979 100644 --- a/source/internal/array.d.ts +++ b/source/internal/array.d.ts @@ -93,3 +93,31 @@ T extends readonly [...infer U] ? Returns whether the given array `T` is readonly. */ export type IsArrayReadonly = If, false, T extends unknown[] ? false : true>; + +/** +Returns a boolean for whether every element in an array type extends another type. + +Note: This type is not designed to be used with non-tuple arrays (like `number[]`), tuples with optional elements (like `[1?, 2?, 3?]`), or tuples that contain a rest element (like `[1, 2, ...number[]]`). + +@example +``` +import type {Every} from 'type-fest'; + +type A = Every<[1, 2, 3], number>; +//=> true + +type B = Every<[1, 2, '3'], number>; +//=> false + +type C = Every<[number, number | string], number>; +//=> boolean + +type D = Every<[true, boolean, true], true>; +//=> boolean +``` +*/ +export type Every = TArray extends readonly [infer First, ...infer Rest] + ? First extends Type + ? Every + : false + : true; diff --git a/source/internal/string.d.ts b/source/internal/string.d.ts index b82832e1..b7b2e790 100644 --- a/source/internal/string.d.ts +++ b/source/internal/string.d.ts @@ -112,16 +112,6 @@ 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. */ diff --git a/source/is-lowercase.d.ts b/source/is-lowercase.d.ts new file mode 100644 index 00000000..01aa9113 --- /dev/null +++ b/source/is-lowercase.d.ts @@ -0,0 +1,36 @@ +import type {Every} from './internal/array.js'; + +/** +Returns a boolean for whether the given string literal is lowercase. + +@example +``` +import type {IsLowercase} from 'type-fest'; + +IsLowercase<'abc'>; +//=> true + +IsLowercase<'Abc'>; +//=> false + +IsLowercase; +//=> boolean +``` +*/ +export type IsLowercase = Every<_IsLowercase, true>; + +/** +Loops through each part in the string and returns a boolean array indicating whether each part is lowercase. +*/ +type _IsLowercase = S extends `${infer First}${infer Rest}` + ? _IsLowercase]> + : [...Accumulator, IsLowercaseHelper]; + +/** +Returns a boolean for whether an individual part of the string is lowercase. +*/ +type IsLowercaseHelper = S extends Lowercase + ? true + : S extends Uppercase | Capitalize | `${string}${Uppercase}${string}` + ? false + : boolean; diff --git a/source/is-uppercase.d.ts b/source/is-uppercase.d.ts new file mode 100644 index 00000000..218a5f0d --- /dev/null +++ b/source/is-uppercase.d.ts @@ -0,0 +1,36 @@ +import type {Every} from './internal/array.js'; + +/** +Returns a boolean for whether the given string literal is uppercase. + +@example +``` +import type {IsUppercase} from 'type-fest'; + +IsUppercase<'ABC'>; +//=> true + +IsUppercase<'Abc'>; +//=> false + +IsUppercase; +//=> boolean +``` +*/ +export type IsUppercase = Every<_IsUppercase, true>; + +/** +Loops through each part in the string and returns a boolean array indicating whether each part is uppercase. +*/ +type _IsUppercase = S extends `${infer First}${infer Rest}` + ? _IsUppercase]> + : [...Accumulator, IsUppercaseHelper]; + +/** +Returns a boolean for whether an individual part of the string is uppercase. +*/ +type IsUppercaseHelper = S extends Uppercase + ? true + : S extends Lowercase | Uncapitalize | `${string}${Lowercase}${string}` + ? false + : boolean; diff --git a/source/words.d.ts b/source/words.d.ts index 0355d89a..fc15e14f 100644 --- a/source/words.d.ts +++ b/source/words.d.ts @@ -1,10 +1,10 @@ import type { ApplyDefaultOptions, - IsLowerCase, IsNumeric, - IsUpperCase, WordSeparators, } from './internal/index.d.ts'; +import type {IsLowercase} from './is-lowercase.d.ts'; +import type {IsUppercase} from './is-uppercase.d.ts'; type SkipEmptyWord = Word extends '' ? [] : [Word]; @@ -108,10 +108,10 @@ type WordsImplementation< : [true, true] extends [IsNumeric, IsNumeric] ? WordsImplementation // Case change: lower to upper, push word - : [true, true] extends [IsLowerCase, IsUpperCase] + : [true, true] extends [IsLowercase, IsUppercase] ? [...SkipEmptyWord, ...WordsImplementation] // Case change: upper to lower, brings back the last character, push word - : [true, true] extends [IsUpperCase, IsLowerCase] + : [true, true] extends [IsUppercase, IsLowercase] ? [...RemoveLastCharacter, ...WordsImplementation] // No case change: concat word : WordsImplementation diff --git a/test-d/internal/every.ts b/test-d/internal/every.ts new file mode 100644 index 00000000..b8fa7102 --- /dev/null +++ b/test-d/internal/every.ts @@ -0,0 +1,31 @@ +import {expectType} from 'tsd'; +import type {Every} from '../../source/internal/array.js'; + +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); +expectType>(false); + +// Union type elements +expectType>({} as boolean); +expectType>({} as boolean); +expectType>({} as boolean); +expectType>(false); +expectType>(true); + +// Readonly arrays +expectType>(true); +expectType>(true); +expectType>(false); + +// Unions +expectType>(true); // Both `true` +expectType>(false); // Both `false` +expectType>({} as boolean); // One `true`, one `false` +expectType>({} as boolean); // One `true`, one `boolean` +expectType>({} as boolean); // One `false`, one `boolean` diff --git a/test-d/is-lowercase.ts b/test-d/is-lowercase.ts new file mode 100644 index 00000000..ef355d51 --- /dev/null +++ b/test-d/is-lowercase.ts @@ -0,0 +1,56 @@ +import {expectType} from 'tsd'; +import type {IsLowercase} from '../index.d.ts'; + +// Literals +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); + +// Non-literals +expectType>>(true); +expectType>>(true); + +expectType>>(false); +expectType>>(false); +expectType}`>>(false); +expectType>>(false); + +expectType>({} as boolean); +expectType>({} as boolean); +expectType>>({} as boolean); +expectType>>({} as boolean); +expectType}`>>({} as boolean); +expectType>({} as boolean); + +expectType>(false); +expectType>(false); +expectType>(false); + +// Complex non-literals +expectType}${Lowercase}`>>(true); + +expectType}${Lowercase}`>>(false); +expectType}${Uppercase}`>>(false); +expectType}${Uppercase}${Lowercase}`>>(false); +expectType}${Lowercase}`>>(false); +expectType}${Capitalize}`>>(false); +expectType}`>>(false); +expectType}`>>(false); + +expectType}`>>({} as boolean); +expectType>({} as boolean); +expectType}${Lowercase}${string}`>>({} as boolean); +expectType}`>>({} as boolean); +expectType>({} as boolean); +expectType>({} as boolean); + +// Unions +expectType>(true); // Both `true` +expectType>(false); // Both `false` +expectType>({} as boolean); // One `true`, one `false` +expectType}end`>>({} as boolean); // One `true`, one `boolean` +expectType>({} as boolean); // One `false`, one `boolean` diff --git a/test-d/is-uppercase.ts b/test-d/is-uppercase.ts new file mode 100644 index 00000000..2ce91a71 --- /dev/null +++ b/test-d/is-uppercase.ts @@ -0,0 +1,56 @@ +import {expectType} from 'tsd'; +import type {IsUppercase} from '../index.d.ts'; + +// Literals +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); + +// Non-literals +expectType>>(true); +expectType>>(true); + +expectType>>(false); +expectType>>(false); +expectType}`>>(false); +expectType>>(false); + +expectType>({} as boolean); +expectType>({} as boolean); +expectType>>({} as boolean); +expectType>>({} as boolean); +expectType}`>>({} as boolean); +expectType>({} as boolean); + +expectType>(false); +expectType>(false); +expectType>(false); + +// Complex non-literals +expectType}${Uppercase}`>>(true); + +expectType}${Uppercase}`>>(false); +expectType}${Lowercase}`>>(false); +expectType}${Lowercase}${Uppercase}`>>(false); +expectType}${Uppercase}`>>(false); +expectType}${Uncapitalize}`>>(false); +expectType}`>>(false); +expectType}`>>(false); + +expectType}`>>({} as boolean); +expectType>({} as boolean); +expectType}${Uppercase}${string}`>>({} as boolean); +expectType}`>>({} as boolean); +expectType>({} as boolean); +expectType>({} as boolean); + +// Unions +expectType>(true); // Both `true` +expectType>(false); // Both `false` +expectType>({} as boolean); // One `true`, one `false` +expectType}END`>>({} as boolean); // One `true`, one `boolean` +expectType>({} as boolean); // One `false`, one `boolean`