diff --git a/type-plus/ts/index.ts b/type-plus/ts/index.ts index f8a9fcf480..f6f269da80 100644 --- a/type-plus/ts/index.ts +++ b/type-plus/ts/index.ts @@ -83,8 +83,11 @@ export * from './predicates/index.js' export type * from './predicates/not_assignable.js' export type { PrimitiveTypes } from './primitive.js' export * from './promise/index.js' +export type * from './string/$extract_processed_string.js' export type * from './string/is_not_string.js' export type * from './string/is_string.js' +export type * from './string/is_string_literal.js' +export type * from './string/is_template_literal.js' export type { StringIncludes, StringSplit } from './string/string.js' export type { StringPlus } from './string/string_plus.js' export type * from './symbol/is_not_symbol.js' diff --git a/type-plus/ts/mix_types/merge.spec.ts b/type-plus/ts/mix_types/merge.spec.ts index b3dd91c801..14414b262b 100644 --- a/type-plus/ts/mix_types/merge.spec.ts +++ b/type-plus/ts/mix_types/merge.spec.ts @@ -171,3 +171,4 @@ describe(`${merge.name}()`, () => { }) // TODO: array merge check +testType.equal, string>(true) diff --git a/type-plus/ts/number/is_number.spec.ts b/type-plus/ts/number/is_number.spec.ts index 668fa04e69..a97887d5ea 100644 --- a/type-plus/ts/number/is_number.spec.ts +++ b/type-plus/ts/number/is_number.spec.ts @@ -142,6 +142,7 @@ describe('exact', () => { it('can disable union distribution', () => { testType.equal, false>(true) + testType.equal, true>(true) }) it('returns true for intersection type', () => { diff --git a/type-plus/ts/object/merge.spec.ts b/type-plus/ts/object/merge.spec.ts index 51a34fce9c..fa28d55ba6 100644 --- a/type-plus/ts/object/merge.spec.ts +++ b/type-plus/ts/object/merge.spec.ts @@ -129,4 +129,3 @@ it('spread across unions', () => { // { a: number } | { a: number, b: 2 } // >(true) }) - diff --git a/type-plus/ts/string/$extract_processed_string.spec.ts b/type-plus/ts/string/$extract_processed_string.spec.ts new file mode 100644 index 0000000000..9d5c3bc83b --- /dev/null +++ b/type-plus/ts/string/$extract_processed_string.spec.ts @@ -0,0 +1,234 @@ +import { it } from '@jest/globals' +import { testType } from '../index.js' +import { type $ExtractProcessedString } from './$extract_processed_string.js' + +it('returns T if T is a string', () => { + testType.equal<$ExtractProcessedString, string>(true) +}) + +it(`returns T if T is a string literal`, () => { + testType.equal<$ExtractProcessedString<''>, ''>(true) + testType.equal<$ExtractProcessedString<'a'>, 'a'>(true) +}) + +it('returns T if T can be reduced to a string literal', () => { + testType.equal<$ExtractProcessedString<`${''}`>, ''>(true) + testType.equal<$ExtractProcessedString<`${'b'}`>, 'b'>(true) + testType.equal<$ExtractProcessedString<`${boolean}`>, `true` | `false`>(true) + testType.equal<$ExtractProcessedString<`${true}`>, `true`>(true) + testType.equal<$ExtractProcessedString<`${false}`>, `false`>(true) + testType.equal<$ExtractProcessedString<`${1}`>, `1`>(true) + testType.equal<$ExtractProcessedString<`${1.1}`>, `1.1`>(true) + testType.equal<$ExtractProcessedString<`${1e99}`>, `1e+99`>(true) + testType.equal<$ExtractProcessedString<`${-1}`>, `-1`>(true) + testType.equal<$ExtractProcessedString<`${1n}`>, `1`>(true) + testType.equal<$ExtractProcessedString<`${null}`>, `null`>(true) + testType.equal<$ExtractProcessedString<`${undefined}`>, `undefined`>(true) +}) + + +it('returns T for template literal', () => { + testType.equal<$ExtractProcessedString<`${number}`>, `${number}`>(true) + testType.equal<$ExtractProcessedString<`${string}`>, string>(true) + testType.equal<$ExtractProcessedString<`${bigint}`>, `${bigint}`>(true) + + testType.equal<$ExtractProcessedString<`a${number}`>, `a${number}`>(true) + testType.equal<$ExtractProcessedString<`a${string}`>, `a${string}`>(true) + testType.equal<$ExtractProcessedString<`a${bigint}`>, `a${bigint}`>(true) + + testType.equal<$ExtractProcessedString<`${number}c`>, `${number}c`>(true) + testType.equal<$ExtractProcessedString<`${string}c`>, `${string}c`>(true) + testType.equal<$ExtractProcessedString<`${bigint}c`>, `${bigint}c`>(true) + + testType.equal<$ExtractProcessedString<`a${number}c`>, `a${number}c`>(true) + testType.equal<$ExtractProcessedString<`a${string}c`>, `a${string}c`>(true) + testType.equal<$ExtractProcessedString<`a${bigint}c`>, `a${bigint}c`>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.equal<$ExtractProcessedString<`${number | string | bigint}`>, string>(true) + testType.equal<$ExtractProcessedString<`${number | bigint}`>, `${number | bigint}`>(true) + testType.equal<$ExtractProcessedString<`a${number | string | bigint}`>, `a${number | string | bigint}`>(true) + testType.equal<$ExtractProcessedString<`${number | string | bigint}c`>, `${number | string | bigint}c`>(true) + testType.equal<$ExtractProcessedString<`a${number | string | bigint}c`>, `a${number | string | bigint}c`>(true) +}) + +it('unwraps T in Uppercase', () => { + testType.equal<$ExtractProcessedString>, string>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'A'>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'B'>(true) + testType.equal<$ExtractProcessedString>, `TRUE` | `FALSE`>(true) + testType.equal<$ExtractProcessedString>, `TRUE`>(true) + testType.equal<$ExtractProcessedString>, `FALSE`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `1.1`>(true) + testType.equal<$ExtractProcessedString>, `1E+99`>(true) + testType.equal<$ExtractProcessedString>, `-1`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `NULL`>(true) + testType.equal<$ExtractProcessedString>, `UNDEFINED`>(true) + + testType.equal<$ExtractProcessedString>, `${number}`>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${number}`>}`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${string}`>}`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${bigint}`>}`>(true) + + testType.equal<$ExtractProcessedString>, `${Uppercase<`${number}`>}C`>(true) + testType.equal<$ExtractProcessedString>, `${Uppercase<`${string}`>}C`>(true) + testType.equal<$ExtractProcessedString>, `${Uppercase<`${bigint}`>}C`>(true) + + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${number}`>}C`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${string}`>}C`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${bigint}`>}C`>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${number | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${number | string | bigint}`>}`>(true) + testType.equal<$ExtractProcessedString>, `${Uppercase<`${number | string | bigint}`>}C`>(true) + testType.equal<$ExtractProcessedString>, `A${Uppercase<`${number | string | bigint}`>}C`>(true) +}) + +it('unwraps T in Lowercase', () => { + testType.equal<$ExtractProcessedString>, string>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'a'>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'b'>(true) + testType.equal<$ExtractProcessedString>, `true` | `false`>(true) + testType.equal<$ExtractProcessedString>, `true`>(true) + testType.equal<$ExtractProcessedString>, `false`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `1.1`>(true) + testType.equal<$ExtractProcessedString>, `1e+99`>(true) + testType.equal<$ExtractProcessedString>, `-1`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `null`>(true) + testType.equal<$ExtractProcessedString>, `undefined`>(true) + + testType.equal<$ExtractProcessedString>, `${number}`>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${number}`>}`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${string}`>}`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${bigint}`>}`>(true) + + testType.equal<$ExtractProcessedString>, `${Lowercase<`${number}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Lowercase<`${string}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Lowercase<`${bigint}`>}c`>(true) + + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${number}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${string}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${bigint}`>}c`>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${number | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${number | string | bigint}`>}`>(true) + testType.equal<$ExtractProcessedString>, `${Lowercase<`${number | string | bigint}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `a${Lowercase<`${number | string | bigint}`>}c`>(true) +}) + +it('unwraps T in Capitalize', () => { + testType.equal<$ExtractProcessedString>, string>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'A'>(true) + testType.equal<$ExtractProcessedString>, 'Hello world'>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'B'>(true) + testType.equal<$ExtractProcessedString>, `True` | `False`>(true) + testType.equal<$ExtractProcessedString>, `True`>(true) + testType.equal<$ExtractProcessedString>, `False`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `1.1`>(true) + testType.equal<$ExtractProcessedString>, `1e+99`>(true) + testType.equal<$ExtractProcessedString>, `-1`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `Null`>(true) + testType.equal<$ExtractProcessedString>, `Undefined`>(true) + + testType.equal<$ExtractProcessedString>, `${number}`>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `A${number}`>(true) + testType.equal<$ExtractProcessedString>, `A${string}`>(true) + testType.equal<$ExtractProcessedString>, `A${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `${Capitalize<`${number}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Capitalize<`${string}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Capitalize<`${bigint}`>}c`>(true) + + testType.equal<$ExtractProcessedString>, `A${number}c`>(true) + testType.equal<$ExtractProcessedString>, `A${string}c`>(true) + testType.equal<$ExtractProcessedString>, `A${bigint}c`>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${number | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `A${number | string | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `${Capitalize<`${number | string | bigint}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `A${number | string | bigint}c`>(true) +}) + +it('unwraps T in Uncapitalize', () => { + testType.equal<$ExtractProcessedString>, string>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'aBC'>(true) + testType.equal<$ExtractProcessedString>, 'hELLO WORLD'>(true) + + testType.equal<$ExtractProcessedString>, ''>(true) + testType.equal<$ExtractProcessedString>, 'b'>(true) + testType.equal<$ExtractProcessedString>, `true` | `false`>(true) + testType.equal<$ExtractProcessedString>, `true`>(true) + testType.equal<$ExtractProcessedString>, `false`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `1.1`>(true) + testType.equal<$ExtractProcessedString>, `1e+99`>(true) + testType.equal<$ExtractProcessedString>, `-1`>(true) + testType.equal<$ExtractProcessedString>, `1`>(true) + testType.equal<$ExtractProcessedString>, `null`>(true) + testType.equal<$ExtractProcessedString>, `undefined`>(true) + + testType.equal<$ExtractProcessedString>, `${number}`>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `a${number}`>(true) + testType.equal<$ExtractProcessedString>, `a${string}`>(true) + testType.equal<$ExtractProcessedString>, `a${bigint}`>(true) + + testType.equal<$ExtractProcessedString>, `${Uncapitalize<`${number}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Uncapitalize<`${string}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `${Uncapitalize<`${bigint}`>}c`>(true) + + testType.equal<$ExtractProcessedString>, `a${number}c`>(true) + testType.equal<$ExtractProcessedString>, `a${string}c`>(true) + testType.equal<$ExtractProcessedString>, `a${bigint}c`>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.equal<$ExtractProcessedString>, string>(true) + testType.equal<$ExtractProcessedString>, `${number | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `a${number | string | bigint}`>(true) + testType.equal<$ExtractProcessedString>, `${Uncapitalize<`${number | string | bigint}`>}c`>(true) + testType.equal<$ExtractProcessedString>, `a${number | string | bigint}c`>(true) +}) + +it('unwraps nested T', () => { + testType.equal<$ExtractProcessedString>>>>, 'ABC'>(true) + + testType.equal<$ExtractProcessedString>>>>, `${number}`>(true) + testType.equal<$ExtractProcessedString>>>>, `A${Uppercase>}`>(true) +}) diff --git a/type-plus/ts/string/$extract_processed_string.ts b/type-plus/ts/string/$extract_processed_string.ts new file mode 100644 index 0000000000..1fe7f0c4f3 --- /dev/null +++ b/type-plus/ts/string/$extract_processed_string.ts @@ -0,0 +1,45 @@ +/** + * 🧰 *type util* + * + * Extract the processed string from any of the intrinsic string manipulation types: + * + * - `Uppercase` + * - `Lowercase` + * - `Capitalize` + * - `Uncapitalize` + */ +export type $ExtractProcessedString = + ([T, unknown] extends [unknown, T] + ? T + : $ExtractProcessedString._UpperOrElse>>> + ) + +export namespace $ExtractProcessedString { + export type _UpperOrElse = N extends Uppercase + ? (string extends Y + ? Uppercase extends N ? Y : N + : $ExtractProcessedString + ) + : Else + export type _LowerOrElse = N extends Lowercase + ? (string extends Y + ? Lowercase extends N ? Y : N + : $ExtractProcessedString + ) + : Else + export type _CapOrElse = N extends Capitalize + ? (string extends Y + ? Capitalize extends N ? Y : N + : $ExtractProcessedString + ) + : Else + export type _UncapOrElse = N extends Uncapitalize + ? (string extends Y + ? Uncapitalize extends N ? Y : N + : $ExtractProcessedString + ) + : Else +} diff --git a/type-plus/ts/string/is_string.ts b/type-plus/ts/string/is_string.ts index c424ae06ab..ae679cce06 100644 --- a/type-plus/ts/string/is_string.ts +++ b/type-plus/ts/string/is_string.ts @@ -7,7 +7,6 @@ import type { $Exact } from '../type_plus/branch/$exact.js' import type { $IsDistributive } from '../type_plus/branch/$is_distributive.js' import type { $ResolveBranch } from '../type_plus/branch/$resolve_branch.js' import type { $Else, $Then } from '../type_plus/branch/$selection.js' -import type { $SelectionOptions } from '../type_plus/branch/$selection_options.js' /** * 🎭 *predicate* @@ -86,7 +85,7 @@ export namespace IsString { export type $UtilOptions = Assignable.$UtilOptions & $Exact.$Options - export type _D = + export type _D = T extends string & infer U ? ( U extends string @@ -94,7 +93,7 @@ export namespace IsString { : $ResolveBranch ) : $ResolveBranch - export type _N = + export type _N = [T] extends [string & infer U] ? U extends string ? $ResolveBranch : $ResolveBranch diff --git a/type-plus/ts/string/is_string_literal.spec.ts b/type-plus/ts/string/is_string_literal.spec.ts new file mode 100644 index 0000000000..dd0d6cfdb5 --- /dev/null +++ b/type-plus/ts/string/is_string_literal.spec.ts @@ -0,0 +1,896 @@ +import { describe, it } from '@jest/globals' + +import { testType, type $Else, type $Then, type IsStringLiteral } from '../index.js' + +it('returns false for string', () => { + testType.false>(true) + testType.equal<`${string}`, string>(true) + testType.false>(true) +}) + +it('returns true if T is a string literal', () => { + testType.true>(true) + testType.true>(true) +}) + +it('returns true if T is a template string literal', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) +}) + +it('returns true for Uppercase string literal', () => { + testType.true>>(true) + testType.true>>(true) +}) + +it('returns true for Uppercase template string literal', () => { + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.false>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) +}) + +it('returns false for Uppercase string', () => { + testType.false>>(true) + testType.false>>>(true) + testType.false>>>(true) + testType.false>>>>(true) + testType.false>>>>(true) +}) + +it('returns true for Lowercase string literal', () => { + testType.true>>(true) + testType.true>>(true) +}) + +it('returns true for Lowercase template string literal', () => { + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.false>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) +}) + +it('returns false for Lowercase string', () => { + testType.false>>(true) + testType.false>>>(true) + testType.false>>>(true) + testType.false>>>>(true) + testType.false>>>>(true) +}) + +it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) +}) + +it('returns false for all other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void>>(true) +}) + +it('distributes over union type', () => { + testType.equal, false>(true) + testType.equal, boolean>(true) +}) + +it('works with intersection type', () => { + testType.equal, false>(true) + testType.equal, false>(true) + testType.equal, true>(true) + testType.equal, true>(true) +}) + +describe('disable distribution', () => { + it('returns false for string', () => { + testType.false>(true) + }) + + it('returns true if T is a string literal', () => { + testType.true>(true) + testType.true>(true) + }) + + it('returns true if T is a template string literal', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.false>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + }) + + it('returns true for Uppercase string literal', () => { + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + }) + + it('returns true for Uppercase template string literal', () => { + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.false, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + }) + + it('returns false for Uppercase string', () => { + testType.false, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + }) + + it('returns true for Lowercase string literal', () => { + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + }) + + it('returns true for Lowercase template string literal', () => { + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.false, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + }) + + it('returns false for Lowercase string', () => { + testType.false, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + }) + + it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + }) + + it('returns false for all other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void, { distributive: false }>>(true) + }) + + it('over union', () => { + testType.equal, false>(true) + }) +}) + +it('works as filter', () => { + testType.equal, never>(true) + testType.equal, ''>(true) + + testType.equal, never>(true) + testType.equal, never>(true) + testType.equal, never>(true) + + testType.equal, ''>(true) +}) + +it('works with unique branches', () => { + testType.equal, $Else>(true) + testType.equal, $Then>(true) + testType.equal, String>(true) + + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) +}) + +it('can override $any branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) + +it('can override $unknown branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) + +it('can override $never branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) + +describe('exact', () => { + it('returns false for string', () => { + testType.false>(true) + }) + + it('returns true if T is a string literal', () => { + testType.true>(true) + testType.true>(true) + }) + + it('returns true if T is a template string literal reducible to simple string literal', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + }) + + it('returns false if T is a non-reducible template string literal', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + }) + + it('returns true for Uppercase string literal', () => { + testType.true, { exact: true }>>(true) + testType.true, { exact: true }>>(true) + }) + + it('returns false for Uppercase or Lowercase string', () => { + testType.false, { exact: true }>>(true) + testType.false>, { exact: true }>>(true) + testType.false>, { exact: true }>>(true) + testType.false>>, { exact: true }>>(true) + testType.false>>, { exact: true }>>(true) + + testType.false, { exact: true }>>(true) + testType.false>, { exact: true }>>(true) + testType.false>, { exact: true }>>(true) + testType.false>>, { exact: true }>>(true) + testType.false>>, { exact: true }>>(true) + }) + + it('returns true for intrinsic manipulative types with template string literal reducible to simple string literal', () => { + testType.true, { exact: true }>>(true) + testType.true, { exact: true }>>(true) + testType.true, { exact: true }>>(true) + testType.true, { exact: true }>>(true) + }) + + it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + }) + + it('returns false for all other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void>>(true) + }) + + it('distributes over union type', () => { + testType.equal, false>(true) + testType.equal, boolean>(true) + }) + + it('works with intersection type', () => { + testType.equal, false>(true) + testType.equal, false>(true) + testType.equal, true>(true) + testType.equal, true>(true) + }) + + describe('disable distribution', () => { + it('returns false for string', () => { + testType.false>(true) + }) + + it('returns true if T is a string literal', () => { + testType.true>(true) + testType.true>(true) + }) + + it('returns true if T is a template string literal', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.false>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + }) + + it('returns true for Uppercase string literal', () => { + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + }) + + it('returns true for Uppercase template string literal', () => { + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.false, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + }) + + it('returns false for Uppercase string', () => { + testType.false, { distributive: false, exact: true }>>(true) + testType.false>, { distributive: false, exact: true }>>(true) + testType.false>, { distributive: false, exact: true }>>(true) + testType.false>>, { distributive: false, exact: true }>>(true) + testType.false>>, { distributive: false, exact: true }>>(true) + }) + + it('returns true for Lowercase string literal', () => { + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + }) + + it('returns true for Lowercase template string literal', () => { + testType.true, { distributive: false, exact: true }>>(true) + testType.true, { distributive: false, exact: true }>>(true) + }) + + it('returns false for Lowercase string', () => { + testType.false, { distributive: false, exact: true }>>(true) + testType.false>, { distributive: false, exact: true }>>(true) + testType.false>, { distributive: false, exact: true }>>(true) + testType.false>>, { distributive: false, exact: true }>>(true) + testType.false>>, { distributive: false, exact: true }>>(true) + }) + + it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + }) + + it('returns false for all other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void, { distributive: false, exact: true }>>(true) + }) + + it('over union', () => { + testType.equal, false>(true) + }) + }) + + it('works as filter', () => { + testType.equal, never>(true) + testType.equal, ''>(true) + + testType.equal, never>(true) + testType.equal, never>(true) + testType.equal, never>(true) + + testType.equal, ''>(true) + }) + + it('works with unique branches', () => { + testType.equal, $Else>(true) + testType.equal, $Then>(true) + testType.equal, String>(true) + + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) + }) + + it('can override $any branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) + }) + + it('can override $unknown branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) + }) + + it('can override $never branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) + }) +}) diff --git a/type-plus/ts/string/is_string_literal.ts b/type-plus/ts/string/is_string_literal.ts new file mode 100644 index 0000000000..715a5b80fb --- /dev/null +++ b/type-plus/ts/string/is_string_literal.ts @@ -0,0 +1,128 @@ +import type { Assignable } from '../predicates/assignable.js' +import type { $Equality } from '../type_plus/$equality.js' +import type { $MergeOptions } from '../type_plus/$merge_options.js' +import type { $ResolveOptions } from '../type_plus/$resolve_options.js' +import type { $SpecialType } from '../type_plus/$special_type.js' +import type { $Exact } from '../type_plus/branch/$exact.js' +import type { $IsDistributive } from '../type_plus/branch/$is_distributive.js' +import type { $ResolveBranch } from '../type_plus/branch/$resolve_branch.js' +import type { $Else, $Then } from '../type_plus/branch/$selection.js' +import type { $SelectionOptions } from '../type_plus/branch/$selection_options.js' +import type { $ExtractProcessedString } from './$extract_processed_string.js' + +/** + * 🎭 *predicate* + * + * Validate if `T` is string literals. + * + * @example + * ```ts + * type R = IsStringLiteral // false + * type R = IsStringLiteral<'a'> // true + * + * type R = IsStringLiteral // false + * type R = IsStringLiteral // false + * type R = IsStringLiteral // boolean + * ``` + * + * 🔢 *customize* + * + * Filter to ensure `T` is string literals, otherwise returns `never`. + * + * @example + * ```ts + * type R = IsStringLiteral // never + * type R = IsStringLiteral<'a', { selection: 'filter' }> // 'a' + * + * type R = IsStringLiteral // never + * type R = IsStringLiteral // never + * type R = IsStringLiteral // string + * ``` + * + * 🔢 *customize*: + * + * Disable distribution of union types. + * + * ```ts + * type R = IsStringLiteral // boolean + * type R = IsStringLiteral // false + * ``` + * + * 🔢 *customize* + * + * Use unique branch identifiers to allow precise processing of the result. + * + * @example + * ```ts + * type R = IsStringLiteral // $Then + * type R = IsStringLiteral // $Else + * ``` + */ +export type IsStringLiteral = + $SpecialType, + $else: IsStringLiteral.$ + } + > + > + +export namespace IsStringLiteral { + export type $Options = $Equality.$Options & $Exact.$Options + export type $Branch<$O extends $Options = {}> = $Equality.$Branch<$O> + + /** + * 🧰 *type util* + * + * Validate if `T` is string literals. + * + * This is a type util for building custom types. + * It does not check against special types. + */ + export type $ = + $ResolveOptions<[$O['exact'], $Exact.$Default]> extends true + ? $IsDistributive<$O, { $then: _ED, $else: _EN }> + : $IsDistributive<$O, { $then: _D, $else: _N }> + + export type $UtilOptions = Assignable.$UtilOptions & $Exact.$Options + + export type _ED = + T extends string + ? ($ExtractProcessedString<`${T}`> extends infer K + ? (string extends K + ? $ResolveBranch + : (K extends string + ? (Uppercase extends Uppercase> + ? (Lowercase extends Lowercase> + ? $ResolveBranch + : $ResolveBranch) + : $ResolveBranch) + : $ResolveBranch)) + : never) + : $ResolveBranch + export type _EN = + _D extends infer R + ? $Then | $Else extends R + ? $ResolveBranch + : $ResolveBranch + : never + + export type _D = + T extends string & infer U ? _U : $ResolveBranch + + export type _N = + [T] extends [string & infer U] ? _U : $ResolveBranch + + export type _U = + U extends `${any}` + ? $ResolveBranch + : ( + U extends Uppercase + ? _D + : U extends Lowercase + ? _D + : $ResolveBranch + ) + +} diff --git a/type-plus/ts/string/is_template_literal.spec.ts b/type-plus/ts/string/is_template_literal.spec.ts new file mode 100644 index 0000000000..1dce84a75c --- /dev/null +++ b/type-plus/ts/string/is_template_literal.spec.ts @@ -0,0 +1,467 @@ +import { describe, it } from '@jest/globals' +import { testType, type $Else, type IsTemplateLiteral } from '../index.js' + +it('returns false for string', () => { + testType.false>(true) +}) + +it('returns false if T is a string literal', () => { + testType.false>(true) + testType.false>(true) +}) + +it('returns true for template literal', () => { + testType.true>(true) + testType.false>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.false>(true) + testType.true>(true) + testType.true>(true) +}) + +it('returns false if T can be reduced to a string literal', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) +}) + +it('returns false for Uppercase or Lowercase string', () => { + testType.false>>(true) + testType.false>>>(true) + testType.false>>>(true) + testType.false>>>>(true) + testType.false>>>>(true) + + testType.false>>(true) + testType.false>>>(true) + testType.false>>>(true) + testType.false>>>>(true) + testType.false>>>>(true) +}) + +it('returns false for Uppercase or Lowercase string literal', () => { + testType.false>>(true) + testType.false>>(true) + + testType.false>>(true) + testType.false>>(true) +}) + +it('returns true for intrinsic manipulative types with template literal', () => { + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + testType.true>>(true) + + + testType.true>>>(true) + testType.true>>>(true) + testType.true>>>>(true) + testType.true>>>>(true) +}) + +it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) +}) + +it('returns false for other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void>>(true) +}) + +it('distributes over union type', () => { + testType.equal<`${number}` | string, string>(true) + testType.equal, false>(true) + testType.equal, boolean>(true) + testType.equal, boolean>(true) + testType.equal, true>(true) + testType.equal, false>(true) + + testType.equal | number>, boolean>(true) + testType.equal | 'abc'>, boolean>(true) + testType.equal>, true>(true) + testType.equal>, false>(true) +}) + +it('works with intersection type', () => { + testType.equal, false>(true) + + testType.equal, false>(true) + testType.equal, false>(true) + + testType.equal, true>(true) + testType.equal, false>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + + testType.equal & { a: 1 }>, false>(true) + testType.equal & { a: 1 }>, true>(true) + + testType.equal>, false>(true) + testType.equal>, true>(true) +}) + +describe('disable distribution', () => { + it('returns false for string', () => { + testType.false>(true) + }) + + it('returns false if T is a string literal', () => { + testType.false>(true) + testType.false>(true) + }) + + it('returns true for template literal', () => { + testType.true>(true) + testType.false>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.equal<`${number | string | bigint}`, string>(true) + testType.false>(true) + testType.true>(true) + testType.true>(true) + }) + + it('returns false if T can be reduced to a string literal', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + }) + + it('returns false for Uppercase or Lowercase string', () => { + testType.false, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + + testType.false, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + testType.false>>, { distributive: false }>>(true) + }) + + it('returns false for Uppercase or Lowercase string literal', () => { + testType.false, { distributive: false }>>(true) + testType.false, { distributive: false }>>(true) + + testType.false, { distributive: false }>>(true) + testType.false, { distributive: false }>>(true) + }) + + it('returns true for Uppercase or Lowercase template literal', () => { + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + testType.true, { distributive: false }>>(true) + + testType.true>, { distributive: false }>>(true) + testType.true>, { distributive: false }>>(true) + testType.true>>, { distributive: false }>>(true) + testType.true>>, { distributive: false }>>(true) + }) + + it('returns false for special types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + }) + + it('returns false for other types', () => { + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false void, { distributive: false }>>(true) + }) + + it('distribute is disabled', () => { + testType.equal<`${number}` | string, string>(true) + testType.equal, false>(true) + testType.equal, false>(true) + testType.equal, false>(true) + testType.equal, true>(true) + testType.equal, false>(true) + + testType.equal | number, { distributive: false }>, false>(true) + testType.equal | 'abc', { distributive: false }>, false>(true) + testType.equal, { distributive: false }>, true>(true) + testType.equal, { distributive: false }>, false>(true) + }) + + it('works with intersection type', () => { + testType.equal, false>(true) + + testType.equal, false>(true) + testType.equal, false>(true) + + testType.equal, true>(true) + testType.equal, false>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + + testType.equal & { a: 1 }, { distributive: false }>, false>(true) + testType.equal & { a: 1 }, { distributive: false }>, true>(true) + + testType.equal, { distributive: false }>, false>(true) + testType.equal, { distributive: false }>, true>(true) + }) +}) + +it('works as filter', () => { + testType.equal, never>(true) + testType.equal, never>(true) + testType.equal, `${number}`>(true) + + testType.equal, never>(true) + testType.equal, never>(true) + testType.equal, never>(true) + testType.equal, `${number}`>(true) +}) + +it('works with unique branches', () => { + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, String>(true) + + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) + testType.equal, $Else>(true) +}) + +it('can override $any branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) + +it('can override $unknown branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) + +it('can override $never branch', () => { + testType.equal, false>(true) + testType.equal, unknown>(true) +}) diff --git a/type-plus/ts/string/is_template_literal.ts b/type-plus/ts/string/is_template_literal.ts new file mode 100644 index 0000000000..4631e4050b --- /dev/null +++ b/type-plus/ts/string/is_template_literal.ts @@ -0,0 +1,67 @@ +import type { Assignable } from '../predicates/assignable.js' +import type { $Equality } from '../type_plus/$equality.js' +import type { $MergeOptions } from '../type_plus/$merge_options.js' +import type { $SpecialType } from '../type_plus/$special_type.js' +import type { $IsDistributive } from '../type_plus/branch/$is_distributive.js' +import type { $ResolveBranch } from '../type_plus/branch/$resolve_branch.js' +import type { $Else, $Then } from '../type_plus/branch/$selection.js' +import type { $ExtractProcessedString } from './$extract_processed_string.js' + +export type IsTemplateLiteral = + $SpecialType, + $else: IsTemplateLiteral.$ + } + > + > + +export namespace IsTemplateLiteral { + export type $Options = $Equality.$Options + export type $Branch<$O extends $Options = {}> = $Equality.$Branch<$O> + + export type $UtilOptions = Assignable.$UtilOptions + + export type $ = + $IsDistributive<$O, { $then: _D, $else: _N }> + + type _D = + T extends string + ? ($ExtractProcessedString<`${T}`> extends infer K + ? (string extends K + ? $ResolveBranch + : (K extends string + ? (Uppercase extends Uppercase> + ? (Lowercase extends Lowercase> + ? $ResolveBranch + : $ResolveBranch) + : $ResolveBranch) + : $ResolveBranch)) + : never) + : $ResolveBranch + + type _N = + _D extends infer R + ? $Then | $Else extends R + ? $ResolveBranch + : $ResolveBranch + : never + // [T] extends [string] + // ? ($ExtractProcessedString<`${T}`> extends infer K + // ? ([string] extends [K] + // ? $ResolveBranch + // : ([K] extends [string] //? Uppercase> + // ? (Uppercase extends Uppercase> + // ? (Lowercase extends Lowercase> + // ? $ResolveBranch + // : $ResolveBranch + // ) + // : $ResolveBranch + // ) + // : $ResolveBranch + // ) + // ) + // : never) + // : $ResolveBranch +} diff --git a/type-plus/ts/string/string_plus.ts b/type-plus/ts/string/string_plus.ts index ed3e4bd9ef..d40334ca5b 100644 --- a/type-plus/ts/string/string_plus.ts +++ b/type-plus/ts/string/string_plus.ts @@ -30,4 +30,5 @@ export namespace StringPlus { * ``` */ export type Split = StringSplit + }