Add IsLowercase and IsUppercase types (#1141)

Co-authored-by: Som Shekhar Mukherjee <iamssmkhrj@gmail.com>
This commit is contained in:
Sindre Sorhus 2025-05-18 11:19:56 +02:00 committed by GitHub
parent 484e030ab9
commit afe132c4b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 251 additions and 14 deletions

2
index.d.ts vendored
View File

@ -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';

View File

@ -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

View File

@ -93,3 +93,31 @@ T extends readonly [...infer U] ?
Returns whether the given array `T` is readonly.
*/
export type IsArrayReadonly<T extends UnknownArray> = If<IsNever<T>, 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 UnknownArray, Type> = TArray extends readonly [infer First, ...infer Rest]
? First extends Type
? Every<Rest, Type>
: false
: true;

View File

@ -112,16 +112,6 @@ export type StringLength<S extends string> = string extends S
? never
: StringToArray<S>['length'];
/**
Returns a boolean for whether the string is lowercased.
*/
export type IsLowerCase<T extends string> = T extends Lowercase<T> ? true : false;
/**
Returns a boolean for whether the string is uppercased.
*/
export type IsUpperCase<T extends string> = T extends Uppercase<T> ? true : false;
/**
Returns a boolean for whether a string is whitespace.
*/

36
source/is-lowercase.d.ts vendored Normal file
View File

@ -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<string>;
//=> boolean
```
*/
export type IsLowercase<S extends string> = Every<_IsLowercase<S>, true>;
/**
Loops through each part in the string and returns a boolean array indicating whether each part is lowercase.
*/
type _IsLowercase<S extends string, Accumulator extends boolean[] = []> = S extends `${infer First}${infer Rest}`
? _IsLowercase<Rest, [...Accumulator, IsLowercaseHelper<First>]>
: [...Accumulator, IsLowercaseHelper<S>];
/**
Returns a boolean for whether an individual part of the string is lowercase.
*/
type IsLowercaseHelper<S extends string> = S extends Lowercase<string>
? true
: S extends Uppercase<string> | Capitalize<string> | `${string}${Uppercase<string>}${string}`
? false
: boolean;

36
source/is-uppercase.d.ts vendored Normal file
View File

@ -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<string>;
//=> boolean
```
*/
export type IsUppercase<S extends string> = Every<_IsUppercase<S>, true>;
/**
Loops through each part in the string and returns a boolean array indicating whether each part is uppercase.
*/
type _IsUppercase<S extends string, Accumulator extends boolean[] = []> = S extends `${infer First}${infer Rest}`
? _IsUppercase<Rest, [...Accumulator, IsUppercaseHelper<First>]>
: [...Accumulator, IsUppercaseHelper<S>];
/**
Returns a boolean for whether an individual part of the string is uppercase.
*/
type IsUppercaseHelper<S extends string> = S extends Uppercase<string>
? true
: S extends Lowercase<string> | Uncapitalize<string> | `${string}${Lowercase<string>}${string}`
? false
: boolean;

8
source/words.d.ts vendored
View File

@ -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 string> = Word extends '' ? [] : [Word];
@ -108,10 +108,10 @@ type WordsImplementation<
: [true, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
? WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
// Case change: lower to upper, push word
: [true, true] extends [IsLowerCase<LastCharacter>, IsUpperCase<FirstCharacter>]
: [true, true] extends [IsLowercase<LastCharacter>, IsUppercase<FirstCharacter>]
? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>]
// Case change: upper to lower, brings back the last character, push word
: [true, true] extends [IsUpperCase<LastCharacter>, IsLowerCase<FirstCharacter>]
: [true, true] extends [IsUppercase<LastCharacter>, IsLowercase<FirstCharacter>]
? [...RemoveLastCharacter<CurrentWord, LastCharacter>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${LastCharacter}${FirstCharacter}`>]
// No case change: concat word
: WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>

31
test-d/internal/every.ts Normal file
View File

@ -0,0 +1,31 @@
import {expectType} from 'tsd';
import type {Every} from '../../source/internal/array.js';
expectType<Every<[], number>>(true);
expectType<Every<[1, 2, 3], number>>(true);
expectType<Every<[1, 2, 3], number | bigint>>(true);
expectType<Every<[true, false, true, false, boolean], boolean>>(true);
expectType<Every<[string, '1', '2', `${number}`], string>>(true);
expectType<Every<[1, 2, '3'], number>>(false);
expectType<Every<['1', 2, 3], number>>(false);
expectType<Every<['1', '2', '3', 4, '5'], string>>(false);
// Union type elements
expectType<Every<[string, string | number], string>>({} as boolean);
expectType<Every<[true, true, true, boolean], true>>({} as boolean);
expectType<Every<[false, false, boolean, false], false>>({} as boolean);
expectType<Every<['1', '2', number | bigint, '3'], string>>(false);
expectType<Every<[1, 2, number | bigint, 3], number | bigint>>(true);
// Readonly arrays
expectType<Every<readonly [], number>>(true);
expectType<Every<readonly [1, 2, 3], number>>(true);
expectType<Every<readonly [1, 2, '3'], number>>(false);
// Unions
expectType<Every<[1, 2, 3] | [4, 5, 6], number>>(true); // Both `true`
expectType<Every<[1, 2, '3'] | [4, 5, '6'], number>>(false); // Both `false`
expectType<Every<[1, 2, 3] | ['1', '2', 3], number>>({} as boolean); // One `true`, one `false`
expectType<Every<[true, true] | [true, boolean], true>>({} as boolean); // One `true`, one `boolean`
expectType<Every<[true, false] | [true, boolean], true>>({} as boolean); // One `false`, one `boolean`

56
test-d/is-lowercase.ts Normal file
View File

@ -0,0 +1,56 @@
import {expectType} from 'tsd';
import type {IsLowercase} from '../index.d.ts';
// Literals
expectType<IsLowercase<'abc'>>(true);
expectType<IsLowercase<''>>(true);
expectType<IsLowercase<'123'>>(true);
expectType<IsLowercase<'!@#'>>(true);
expectType<IsLowercase<'Abc'>>(false);
expectType<IsLowercase<'ABC'>>(false);
// Non-literals
expectType<IsLowercase<Lowercase<string>>>(true);
expectType<IsLowercase<Lowercase<`on${string}`>>>(true);
expectType<IsLowercase<Uppercase<string>>>(false);
expectType<IsLowercase<Capitalize<`on${string}`>>>(false);
expectType<IsLowercase<`on${Capitalize<string>}`>>(false);
expectType<IsLowercase<Capitalize<`${string}end`>>>(false);
expectType<IsLowercase<string>>({} as boolean);
expectType<IsLowercase<`on${string}`>>({} as boolean);
expectType<IsLowercase<Uncapitalize<string>>>({} as boolean);
expectType<IsLowercase<Uncapitalize<`on${string}`>>>({} as boolean);
expectType<IsLowercase<`on${Uncapitalize<string>}`>>({} as boolean);
expectType<IsLowercase<`${number}`>>({} as boolean);
expectType<IsLowercase<`Start${string}`>>(false);
expectType<IsLowercase<`${string}End`>>(false);
expectType<IsLowercase<`${string}Mid${string}`>>(false);
// Complex non-literals
expectType<IsLowercase<`${Lowercase<string>}${Lowercase<string>}`>>(true);
expectType<IsLowercase<`${Uppercase<string>}${Lowercase<string>}`>>(false);
expectType<IsLowercase<`${Lowercase<string>}${Uppercase<string>}`>>(false);
expectType<IsLowercase<`${Lowercase<string>}${Uppercase<string>}${Lowercase<string>}`>>(false);
expectType<IsLowercase<`${Capitalize<string>}${Lowercase<string>}`>>(false);
expectType<IsLowercase<`${Lowercase<string>}${Capitalize<string>}`>>(false);
expectType<IsLowercase<`${string}${Capitalize<string>}`>>(false);
expectType<IsLowercase<`${number}${Capitalize<string>}`>>(false);
expectType<IsLowercase<`${string}${Lowercase<string>}`>>({} as boolean);
expectType<IsLowercase<`${string}${string}`>>({} as boolean);
expectType<IsLowercase<`${Lowercase<string>}${Lowercase<string>}${string}`>>({} as boolean);
expectType<IsLowercase<`${string}${Uncapitalize<string>}`>>({} as boolean);
expectType<IsLowercase<`${number}/${number}`>>({} as boolean);
expectType<IsLowercase<`${string}${number}`>>({} as boolean);
// Unions
expectType<IsLowercase<'abc' | 'xyz'>>(true); // Both `true`
expectType<IsLowercase<'abC' | 'xYz'>>(false); // Both `false`
expectType<IsLowercase<'abc' | 'Abc'>>({} as boolean); // One `true`, one `false`
expectType<IsLowercase<'abc' | `${Uncapitalize<string>}end`>>({} as boolean); // One `true`, one `boolean`
expectType<IsLowercase<'xYz' | `abc${string}`>>({} as boolean); // One `false`, one `boolean`

56
test-d/is-uppercase.ts Normal file
View File

@ -0,0 +1,56 @@
import {expectType} from 'tsd';
import type {IsUppercase} from '../index.d.ts';
// Literals
expectType<IsUppercase<'ABC'>>(true);
expectType<IsUppercase<''>>(true);
expectType<IsUppercase<'123'>>(true);
expectType<IsUppercase<'!@#'>>(true);
expectType<IsUppercase<'Abc'>>(false);
expectType<IsUppercase<'abc'>>(false);
// Non-literals
expectType<IsUppercase<Uppercase<string>>>(true);
expectType<IsUppercase<Uppercase<`on${string}`>>>(true);
expectType<IsUppercase<Lowercase<string>>>(false);
expectType<IsUppercase<Uncapitalize<`ON${string}`>>>(false);
expectType<IsUppercase<`ON${Uncapitalize<string>}`>>(false);
expectType<IsUppercase<Uncapitalize<`${string}END`>>>(false);
expectType<IsUppercase<string>>({} as boolean);
expectType<IsUppercase<`ON${string}`>>({} as boolean);
expectType<IsUppercase<Capitalize<string>>>({} as boolean);
expectType<IsUppercase<Capitalize<`ON${string}`>>>({} as boolean);
expectType<IsUppercase<`ON${Capitalize<string>}`>>({} as boolean);
expectType<IsUppercase<`${number}`>>({} as boolean);
expectType<IsUppercase<`sTART${string}`>>(false);
expectType<IsUppercase<`${string}eND`>>(false);
expectType<IsUppercase<`${string}mID${string}`>>(false);
// Complex non-literals
expectType<IsUppercase<`${Uppercase<string>}${Uppercase<string>}`>>(true);
expectType<IsUppercase<`${Lowercase<string>}${Uppercase<string>}`>>(false);
expectType<IsUppercase<`${Uppercase<string>}${Lowercase<string>}`>>(false);
expectType<IsUppercase<`${Uppercase<string>}${Lowercase<string>}${Uppercase<string>}`>>(false);
expectType<IsUppercase<`${Uncapitalize<string>}${Uppercase<string>}`>>(false);
expectType<IsUppercase<`${Uppercase<string>}${Uncapitalize<string>}`>>(false);
expectType<IsUppercase<`${string}${Uncapitalize<string>}`>>(false);
expectType<IsUppercase<`${number}${Uncapitalize<string>}`>>(false);
expectType<IsUppercase<`${string}${Uppercase<string>}`>>({} as boolean);
expectType<IsUppercase<`${string}${string}`>>({} as boolean);
expectType<IsUppercase<`${Uppercase<string>}${Uppercase<string>}${string}`>>({} as boolean);
expectType<IsUppercase<`${string}${Capitalize<string>}`>>({} as boolean);
expectType<IsUppercase<`${number}/${number}`>>({} as boolean);
expectType<IsUppercase<`${string}${number}`>>({} as boolean);
// Unions
expectType<IsUppercase<'ABC' | 'XYZ'>>(true); // Both `true`
expectType<IsUppercase<'ABc' | 'XyZ'>>(false); // Both `false`
expectType<IsUppercase<'ABC' | 'aBC'>>({} as boolean); // One `true`, one `false`
expectType<IsUppercase<'ABC' | `${Capitalize<string>}END`>>({} as boolean); // One `true`, one `boolean`
expectType<IsUppercase<'XyZ' | `ABC${string}`>>({} as boolean); // One `false`, one `boolean`