From 9c1b2a82f2b7a06f82af18e3fb00df321af58f5a Mon Sep 17 00:00:00 2001 From: Landon Yarrington Date: Fri, 3 Nov 2023 11:01:43 -0700 Subject: [PATCH] fix: better literal differentiation --- src/internal/internals.test.ts | 40 +++++--------- src/internal/internals.ts | 1 + src/internal/literals.test.ts | 69 ++++++++++++++++++++++++ src/internal/literals.ts | 64 ++++++++++++++++++++++ src/native/char-at.test.ts | 3 +- src/native/char-at.ts | 16 ++++-- src/native/ends-with.test.ts | 3 +- src/native/ends-with.ts | 19 +++---- src/native/join.test.ts | 3 +- src/native/join.ts | 22 ++++---- src/native/length.test.ts | 3 +- src/native/length.ts | 7 +-- src/native/pad-end.test.ts | 5 +- src/native/pad-end.ts | 23 ++++---- src/native/pad-start.test.ts | 5 +- src/native/pad-start.ts | 23 ++++---- src/native/repeat.test.ts | 3 +- src/native/repeat.ts | 28 +++++----- src/native/replace-all.test.ts | 5 +- src/native/replace-all.ts | 20 ++++--- src/native/replace.test.ts | 5 +- src/native/replace.ts | 18 +++++-- src/native/slice.test.ts | 5 +- src/native/slice.ts | 35 ++++++------ src/native/split.test.ts | 3 +- src/native/split.ts | 21 ++++---- src/native/starts-with.test.ts | 7 +-- src/native/starts-with.ts | 27 ++++++---- src/native/trim-end.test.ts | 1 + src/native/trim-end.ts | 12 +++-- src/native/trim-start.test.ts | 1 + src/native/trim-start.ts | 12 +++-- src/native/trim.test.ts | 1 + src/utils/characters/letters.ts | 34 ++++++------ src/utils/characters/numbers.ts | 12 +++-- src/utils/characters/separators.test.ts | 1 + src/utils/characters/separators.ts | 12 +++-- src/utils/characters/special.test.ts | 1 + src/utils/characters/special.ts | 19 +++---- src/utils/reverse.test.ts | 1 + src/utils/truncate.test.ts | 5 +- src/utils/truncate.ts | 23 ++++---- src/utils/words.ts | 71 +++++++++++++------------ 43 files changed, 450 insertions(+), 239 deletions(-) create mode 100644 src/internal/literals.test.ts create mode 100644 src/internal/literals.ts diff --git a/src/internal/internals.test.ts b/src/internal/internals.test.ts index fd9e6f5..480e8a5 100644 --- a/src/internal/internals.test.ts +++ b/src/internal/internals.test.ts @@ -1,55 +1,43 @@ -import * as subject from './internals' -import type * as Subject from './internals' +import type { Reject, PascalCaseAll, DropSuffix, TupleOf } from './internals.js' +import { typeOf, pascalCaseAll } from './internals.js' namespace Internals { type testPascalCaseAll1 = Expect< - Equal< - Subject.PascalCaseAll<['one', 'two', 'three']>, - ['One', 'Two', 'Three'] - > - > - type testPascalCaseAll2 = Expect< - Equal, string[]> + Equal, ['One', 'Two', 'Three']> > + type testPascalCaseAll2 = Expect, string[]>> type testReject1 = Expect< - Equal< - Subject.Reject<['one', '', 'two', '', 'three'], ''>, - ['one', 'two', 'three'] - > + Equal, ['one', 'two', 'three']> > type testDropSuffix1 = Expect< - Equal, 'hello'> - > - type testDropSuffix2 = Expect< - Equal, string> - > - type testDropSuffix3 = Expect< - Equal, string> + Equal, 'hello'> > + type testDropSuffix2 = Expect, string>> + type testDropSuffix3 = Expect, string>> - type testTupleOf1 = Expect, [' ', ' ', ' ']>> + type testTupleOf1 = Expect, [' ', ' ', ' ']>> } describe('typeOf', () => { test('null', () => { - expect(subject.typeOf(null)).toEqual('null') + expect(typeOf(null)).toEqual('null') }) test('object', () => { - expect(subject.typeOf({})).toEqual('object') + expect(typeOf({})).toEqual('object') }) test('object', () => { - expect(subject.typeOf(['a', 'b', 'c'])).toEqual('array') + expect(typeOf(['a', 'b', 'c'])).toEqual('array') }) test('string', () => { - expect(subject.typeOf('hello')).toEqual('string') + expect(typeOf('hello')).toEqual('string') }) }) describe('pascalCaseAll', () => { test('simple', () => { - const result = subject.pascalCaseAll(['one', 'two', 'three']) + const result = pascalCaseAll(['one', 'two', 'three']) const expected = ['One', 'Two', 'Three'] expect(result).toEqual(expected) }) diff --git a/src/internal/internals.ts b/src/internal/internals.ts index d65625b..1280155 100644 --- a/src/internal/internals.ts +++ b/src/internal/internals.ts @@ -15,6 +15,7 @@ function typeOf(t: unknown) { } // MAP TYPES + /** * PascalCases all the words in a tuple of strings */ diff --git a/src/internal/literals.test.ts b/src/internal/literals.test.ts new file mode 100644 index 0000000..3d03904 --- /dev/null +++ b/src/internal/literals.test.ts @@ -0,0 +1,69 @@ +import type { + IsNumberLiteral, + IsBooleanLiteral, + Any, + All, + IsStringLiteral, + IsStringLiteralArray, +} from './literals.js' + +namespace LiteralsTests { + // IsNumberLiteral + type testINL1 = Expect>> + type testINL2 = Expect>> + + // IsBooleanLiteral + type testIBL1 = Expect, true>> + type testIBL2 = Expect, true>> + type testIBL3 = Expect, false>> + + // Any + type testAny1 = Expect, true>> + type testAny2 = Expect, true>> + type testAny3 = Expect, false>> + type testAny4 = Expect, false>> + type testAny5 = Expect, false>> + type testAny6 = Expect, false>> + + // All + type testAll1 = Expect, true>> + type testAll2 = Expect, false>> + type testAll3 = Expect, false>> + type testAll4 = Expect, false>> + type testAll5 = Expect, false>> + type testAll6 = Expect, true>> + type testAll7 = Expect, false>> + + // IsStringLiteral + type testISL1 = Expect>> + type testISL2 = Expect>>> + type testISL3 = Expect< + Equal>> + > + type testISL4 = Expect>> + type testISL5 = Expect>> + type testISL6 = Expect>> + type testISL7 = Expect>>> + type testISL8 = Expect< + Equal>>> + > + type testISL9 = Expect>> + type testISL10 = Expect>>> + type testISL11 = Expect>>> + type testISL12 = Expect>> + type testISL13 = Expect< + Equal>> + > + type testISL14 = Expect>> + type testISL15 = Expect>> + type testISL16 = Expect>> + + // IsStringLiteralArray + type testISLA1 = Expect, true>> + type testISLA2 = Expect, false>> + type testISLA3 = Expect< + Equal, false> + > +} + +test('dummy test', () => expect(true).toBe(true)) diff --git a/src/internal/literals.ts b/src/internal/literals.ts new file mode 100644 index 0000000..6340a54 --- /dev/null +++ b/src/internal/literals.ts @@ -0,0 +1,64 @@ +/** + * Returns true if input number type is a literal + */ +type IsNumberLiteral = [T] extends [number] + ? [number] extends [T] + ? false + : true + : false + +type IsBooleanLiteral = [T] extends [boolean] + ? [boolean] extends [T] + ? false + : true + : false + +/** + * Returns true if any elements in boolean array are the literal true (not false or boolean) + */ +type Any = Arr extends [ + infer Head extends boolean, + ...infer Rest extends boolean[], +] + ? IsBooleanLiteral extends true + ? Head extends true + ? true + : Any + : Any + : false + +/** + * Returns true if every element in boolean array is the literal true (not false or boolean) + */ +type All = IsBooleanLiteral extends true + ? Arr extends [infer Head extends boolean, ...infer Rest extends boolean[]] + ? Head extends true + ? Any + : false // Found `false` in array + : true // Empty array (or all elements have already passed test) + : false // Array/Tuple contains `boolean` type + +/** + * Returns true if string input type is a literal + */ +type IsStringLiteral = [T] extends [string] + ? [string] extends [T] + ? false + : Uppercase extends Uppercase> + ? Lowercase extends Lowercase> + ? true + : false + : false + : false + +type IsStringLiteralArray = + IsStringLiteral extends true ? true : false + +export type { + IsNumberLiteral, + IsBooleanLiteral, + Any, + All, + IsStringLiteral, + IsStringLiteralArray, +} diff --git a/src/native/char-at.test.ts b/src/native/char-at.test.ts index 02cb77b..7035063 100644 --- a/src/native/char-at.test.ts +++ b/src/native/char-at.test.ts @@ -3,7 +3,8 @@ import { type CharAt, charAt } from './char-at.js' namespace TypeTests { type test1 = Expect, 'n'>> type test2 = Expect, string>> - type test3 = Expect, string>> + type test3 = Expect, 5>, string>> + type test4 = Expect, string>> // TODO: index greater than Length // type test4 = Expect, ''>> diff --git a/src/native/char-at.ts b/src/native/char-at.ts index 36aab9b..38f3e33 100644 --- a/src/native/char-at.ts +++ b/src/native/char-at.ts @@ -1,15 +1,21 @@ import type { Split } from './split.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' /** * Gets the character at the given index. * T: The string to get the character from. * index: The index of the character. */ -export type CharAt = string extends T - ? string - : number extends index - ? string - : Split[index] +export type CharAt = All< + [IsStringLiteral, IsNumberLiteral] +> extends true + ? Split[index] + : string + /** * A strongly-typed version of `String.prototype.charAt`. * @param str the string to get the character from. diff --git a/src/native/ends-with.test.ts b/src/native/ends-with.test.ts index 753c29a..57aa114 100644 --- a/src/native/ends-with.test.ts +++ b/src/native/ends-with.test.ts @@ -3,7 +3,8 @@ import { type EndsWith, endsWith } from './ends-with.js' namespace TypeTests { type test1 = Expect, true>> type test2 = Expect, boolean>> - type test3 = Expect, boolean>> + type test3 = Expect, 'c'>, boolean>> + type test4 = Expect, boolean>> } describe('endsWith', () => { diff --git a/src/native/ends-with.ts b/src/native/ends-with.ts index be04fcd..e57d849 100644 --- a/src/native/ends-with.ts +++ b/src/native/ends-with.ts @@ -1,6 +1,7 @@ import type { Math } from '../internal/math.js' import type { Length } from './length.js' import type { Slice } from './slice.js' +import type { All, IsStringLiteral } from '../internal/literals.js' /** * Checks if a string ends with another string. @@ -12,15 +13,15 @@ export type EndsWith< T extends string, S extends string, P extends number = Length, -> = string extends T | S - ? boolean - : Math.IsNegative

extends false - ? P extends Length - ? S extends Slice, Length>, Length> - ? true - : false - : EndsWith, S, Length> // P !== T.length, slice - : false // P is negative, false +> = All<[IsStringLiteral, IsStringLiteral]> extends true + ? Math.IsNegative

extends false + ? P extends Length + ? S extends Slice, Length>, Length> + ? true + : false + : EndsWith, S, Length> // P !== T.length, slice + : false // P is negative, false + : boolean /** * A strongly-typed version of `String.prototype.endsWith`. diff --git a/src/native/join.test.ts b/src/native/join.test.ts index b82312a..70e0e7c 100644 --- a/src/native/join.test.ts +++ b/src/native/join.test.ts @@ -5,7 +5,8 @@ namespace TypeTests { Equal, 'some nice string'> > type test2 = Expect, string>> - type test3 = Expect, string>> + type test3 = Expect[], ' '>, string>> + type test4 = Expect, string>> } describe('join', () => { diff --git a/src/native/join.ts b/src/native/join.ts index 04cdba1..4edc344 100644 --- a/src/native/join.ts +++ b/src/native/join.ts @@ -1,3 +1,9 @@ +import type { + IsStringLiteral, + IsStringLiteralArray, + All, +} from '../internal/literals.js' + /** * Joins a tuple of strings with the given delimiter. * T: The tuple of strings to join. @@ -6,18 +12,16 @@ export type Join< T extends readonly string[], delimiter extends string = '', -> = string[] extends T - ? string - : string extends delimiter - ? string - : T extends readonly [ +> = All<[IsStringLiteralArray, IsStringLiteral]> extends true + ? T extends readonly [ infer first extends string, ...infer rest extends string[], ] - ? rest extends [] - ? first - : `${first}${delimiter}${Join}` - : '' + ? rest extends [] + ? first + : `${first}${delimiter}${Join}` + : '' + : string /** * A strongly-typed version of `Array.prototype.join`. diff --git a/src/native/length.test.ts b/src/native/length.test.ts index 3d217d0..3f909f8 100644 --- a/src/native/length.test.ts +++ b/src/native/length.test.ts @@ -2,7 +2,8 @@ import { type Length, length } from './length.js' namespace TypeTests { type test1 = Expect, 16>> - type test2 = Expect, number>> + type test2 = Expect>, number>> + type test3 = Expect, number>> } describe('length', () => { diff --git a/src/native/length.ts b/src/native/length.ts index 210c05a..a1d0252 100644 --- a/src/native/length.ts +++ b/src/native/length.ts @@ -1,11 +1,12 @@ import type { Split } from './split.js' +import type { IsStringLiteral } from '../internal/literals.js' /** * Gets the length of a string. */ -export type Length = string extends T - ? number - : Split['length'] +export type Length = IsStringLiteral extends true + ? Split['length'] + : number /** * A strongly-typed version of `String.prototype.length`. * @param str the string to get the length from. diff --git a/src/native/pad-end.test.ts b/src/native/pad-end.test.ts index c6b5683..06d0c19 100644 --- a/src/native/pad-end.test.ts +++ b/src/native/pad-end.test.ts @@ -3,8 +3,9 @@ import { type PadEnd, padEnd } from './pad-end.js' namespace TypeTests { type test1 = Expect, 'hello '>> type test2 = Expect, string>> - type test3 = Expect, string>> - type test4 = Expect, string>> + type test3 = Expect, 10, ' '>, string>> + type test4 = Expect, string>> + type test5 = Expect, string>> } describe('padEnd', () => { diff --git a/src/native/pad-end.ts b/src/native/pad-end.ts index 1c5721b..3d53493 100644 --- a/src/native/pad-end.ts +++ b/src/native/pad-end.ts @@ -2,6 +2,11 @@ import type { Math } from '../internal/math.js' import type { Slice } from './slice.js' import type { Repeat } from './repeat.js' import type { Length } from './length.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' /** * Pads a string at the end with another string. @@ -13,15 +18,15 @@ export type PadEnd< T extends string, times extends number = 0, pad extends string = ' ', -> = string extends T | pad - ? string - : number extends times - ? string - : Math.IsNegative extends false - ? Math.Subtract> extends infer missing extends number - ? `${T}${Slice, 0, missing>}` - : never - : T +> = All< + [IsStringLiteral, IsNumberLiteral, IsStringLiteral] +> extends true + ? Math.IsNegative extends false + ? Math.Subtract> extends infer missing extends number + ? `${T}${Slice, 0, missing>}` + : never + : T + : string /** * A strongly-typed version of `String.prototype.padEnd`. * @param str the string to pad. diff --git a/src/native/pad-start.test.ts b/src/native/pad-start.test.ts index a40091b..cbf3186 100644 --- a/src/native/pad-start.test.ts +++ b/src/native/pad-start.test.ts @@ -3,8 +3,9 @@ import { type PadStart, padStart } from './pad-start.js' namespace TypeTests { type test1 = Expect, ' hello'>> type test2 = Expect, string>> - type test3 = Expect, string>> - type test4 = Expect, string>> + type test3 = Expect, 10, ' '>, string>> + type test4 = Expect, string>> + type test5 = Expect, string>> } describe('padStart', () => { diff --git a/src/native/pad-start.ts b/src/native/pad-start.ts index cd95621..3d49122 100644 --- a/src/native/pad-start.ts +++ b/src/native/pad-start.ts @@ -2,6 +2,11 @@ import type { Math } from '../internal/math.js' import type { Slice } from './slice.js' import type { Repeat } from './repeat.js' import type { Length } from './length.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' /** * Pads a string at the start with another string. @@ -13,15 +18,15 @@ export type PadStart< T extends string, times extends number = 0, pad extends string = ' ', -> = string extends T | pad - ? string - : number extends times - ? string - : Math.IsNegative extends false - ? Math.Subtract> extends infer missing extends number - ? `${Slice, 0, missing>}${T}` - : never - : T +> = All< + [IsStringLiteral, IsNumberLiteral, IsStringLiteral] +> extends true + ? Math.IsNegative extends false + ? Math.Subtract> extends infer missing extends number + ? `${Slice, 0, missing>}${T}` + : never + : T + : string /** * A strongly-typed version of `String.prototype.padStart`. * @param str the string to pad. diff --git a/src/native/repeat.test.ts b/src/native/repeat.test.ts index 570bb93..0f43996 100644 --- a/src/native/repeat.test.ts +++ b/src/native/repeat.test.ts @@ -3,7 +3,8 @@ import { type Repeat, repeat } from './repeat.js' namespace TypeTests { type test1 = Expect, ' '>> type test2 = Expect, string>> - type test3 = Expect, string>> + type test3 = Expect, 3>, string>> + type test4 = Expect, string>> } describe('repeat', () => { diff --git a/src/native/repeat.ts b/src/native/repeat.ts index fe98e48..4e9e7e3 100644 --- a/src/native/repeat.ts +++ b/src/native/repeat.ts @@ -2,23 +2,27 @@ import type { Math } from '../internal/math.js' import type { Join } from './join.js' import type { TupleOf } from '../internal/internals.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' + /** * Repeats a string N times. * T: The string to repeat. * N: The number of times to repeat. */ -export type Repeat< - T extends string, - times extends number = 0, -> = string extends T - ? string - : number extends times - ? string - : times extends 0 - ? '' - : Math.IsNegative extends false - ? Join> - : never +export type Repeat = All< + [IsStringLiteral, IsNumberLiteral] +> extends true + ? times extends 0 + ? '' + : Math.IsNegative extends false + ? Join> + : never + : string + /** * A strongly-typed version of `String.prototype.repeat`. * @param str the string to repeat. diff --git a/src/native/replace-all.test.ts b/src/native/replace-all.test.ts index 197832c..fdc0e87 100644 --- a/src/native/replace-all.test.ts +++ b/src/native/replace-all.test.ts @@ -9,10 +9,11 @@ namespace TypeTests { Equal, string> > type test3 = Expect, string>> - type test4 = Expect< + type test4 = Expect, ' ', '-'>, string>> + type test5 = Expect< Equal, string> > - type test5 = Expect< + type test6 = Expect< Equal, string> > } diff --git a/src/native/replace-all.ts b/src/native/replace-all.ts index d2f5b2f..3f22ae8 100644 --- a/src/native/replace-all.ts +++ b/src/native/replace-all.ts @@ -1,3 +1,5 @@ +import type { All, IsStringLiteral } from '../internal/literals.js' + /** * Replaces all the occurrences of a string with another string. * sentence: The sentence to replace. @@ -9,12 +11,18 @@ export type ReplaceAll< lookup extends string | RegExp, replacement extends string = '', > = lookup extends string - ? string extends lookup | sentence | replacement - ? string - : sentence extends `${infer rest}${lookup}${infer rest2}` - ? `${rest}${replacement}${ReplaceAll}` - : sentence - : string + ? All< + [ + IsStringLiteral, + IsStringLiteral, + IsStringLiteral, + ] + > extends true + ? sentence extends `${infer rest}${lookup}${infer rest2}` + ? `${rest}${replacement}${ReplaceAll}` + : sentence + : string + : string // Regex used, can't preserve literal /** * A strongly-typed version of `String.prototype.replaceAll`. diff --git a/src/native/replace.test.ts b/src/native/replace.test.ts index b442a75..ef01ead 100644 --- a/src/native/replace.test.ts +++ b/src/native/replace.test.ts @@ -6,8 +6,9 @@ namespace TypeTests { > type test2 = Expect, string>> type test3 = Expect, string>> - type test4 = Expect, string>> - type test5 = Expect, string>> + type test4 = Expect, ' ', '-'>, string>> + type test5 = Expect, string>> + type test6 = Expect, string>> } describe('replace', () => { diff --git a/src/native/replace.ts b/src/native/replace.ts index 13cd9c4..6159de8 100644 --- a/src/native/replace.ts +++ b/src/native/replace.ts @@ -1,3 +1,5 @@ +import type { All, IsStringLiteral } from '../internal/literals.js' + /** * Replaces the first occurrence of a string with another string. * sentence: The sentence to replace. @@ -9,11 +11,17 @@ export type Replace< lookup extends string | RegExp, replacement extends string = '', > = lookup extends string - ? string extends lookup | sentence | replacement - ? string - : sentence extends `${infer rest}${lookup}${infer rest2}` - ? `${rest}${replacement}${rest2}` - : sentence + ? All< + [ + IsStringLiteral, + IsStringLiteral, + IsStringLiteral, + ] + > extends true + ? sentence extends `${infer rest}${lookup}${infer rest2}` + ? `${rest}${replacement}${rest2}` + : sentence + : string : string // Regex used, can't preserve literal /** * A strongly-typed version of `String.prototype.replace`. diff --git a/src/native/slice.test.ts b/src/native/slice.test.ts index b7b59a7..813f19f 100644 --- a/src/native/slice.test.ts +++ b/src/native/slice.test.ts @@ -4,8 +4,9 @@ namespace TypeTests { type test1 = Expect, 'nice string'>> type test2 = Expect, 'nice'>> type test3 = Expect, string>> - type test4 = Expect, string>> - type test5 = Expect, string>> + type test4 = Expect, 5, 9>, string>> + type test5 = Expect, string>> + type test6 = Expect, string>> } describe('slice', () => { diff --git a/src/native/slice.ts b/src/native/slice.ts index c276b78..ec8baf3 100644 --- a/src/native/slice.ts +++ b/src/native/slice.ts @@ -1,5 +1,10 @@ import type { Math } from '../internal/math.js' import type { Length } from './length.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' /** * Slices a string from a startIndex to an endIndex. @@ -11,25 +16,25 @@ export type Slice< T extends string, startIndex extends number = 0, endIndex extends number = Length, -> = string extends T - ? string - : number extends startIndex | endIndex - ? string - : T extends `${infer head}${infer rest}` - ? startIndex extends 0 - ? endIndex extends 0 - ? '' - : `${head}${Slice< +> = All< + [IsStringLiteral, IsNumberLiteral, IsNumberLiteral] +> extends true + ? T extends `${infer head}${infer rest}` + ? startIndex extends 0 + ? endIndex extends 0 + ? '' + : `${head}${Slice< + rest, + Math.Subtract, 1>, + Math.Subtract, 1> + >}` + : `${Slice< rest, Math.Subtract, 1>, Math.Subtract, 1> >}` - : `${Slice< - rest, - Math.Subtract, 1>, - Math.Subtract, 1> - >}` - : '' + : '' + : string /** * A strongly-typed version of `String.prototype.slice`. * @param str the string to slice. diff --git a/src/native/split.test.ts b/src/native/split.test.ts index 1cd0102..af099d4 100644 --- a/src/native/split.test.ts +++ b/src/native/split.test.ts @@ -5,7 +5,8 @@ namespace TypeTests { Equal, ['some', 'nice', 'string']> > type test2 = Expect, string[]>> - type test3 = Expect, string[]>> + type test3 = Expect, ' '>, string[]>> + type test4 = Expect, string[]>> } describe('split', () => { diff --git a/src/native/split.ts b/src/native/split.ts index 0126617..ba9098f 100644 --- a/src/native/split.ts +++ b/src/native/split.ts @@ -1,18 +1,19 @@ +import type { All, IsStringLiteral } from '../internal/literals.js' + /** * Splits a string into an array of substrings. * T: The string to split. * delimiter: The delimiter. */ -export type Split< - T extends string, - delimiter extends string = '', -> = string extends T | delimiter - ? string[] - : T extends `${infer first}${delimiter}${infer rest}` - ? [first, ...Split] - : T extends '' - ? [] - : [T] +export type Split = All< + [IsStringLiteral, IsStringLiteral] +> extends true + ? T extends `${infer first}${delimiter}${infer rest}` + ? [first, ...Split] + : T extends '' + ? [] + : [T] + : string[] /** * A strongly-typed version of `String.prototype.split`. * @param str the string to split. diff --git a/src/native/starts-with.test.ts b/src/native/starts-with.test.ts index 0aaa051..0bad327 100644 --- a/src/native/starts-with.test.ts +++ b/src/native/starts-with.test.ts @@ -3,9 +3,10 @@ import { type StartsWith, startsWith } from './starts-with.js' namespace TypeTests { type test1 = Expect, true>> type test2 = Expect, true>> - type test3 = Expect, boolean>> - type test4 = Expect, boolean>> - type test5 = Expect, boolean>> + type test3 = Expect, 'a'>, boolean>> + type test4 = Expect, boolean>> + type test5 = Expect, boolean>> + type test6 = Expect, boolean>> } describe('startsWith', () => { diff --git a/src/native/starts-with.ts b/src/native/starts-with.ts index 70a46e6..1328ffa 100644 --- a/src/native/starts-with.ts +++ b/src/native/starts-with.ts @@ -1,5 +1,10 @@ import type { Math } from '../internal/math.js' import type { Slice } from './slice.js' +import type { + All, + IsStringLiteral, + IsNumberLiteral, +} from '../internal/literals.js' /** * Checks if a string starts with another string. @@ -11,17 +16,17 @@ export type StartsWith< T extends string, S extends string, P extends number = 0, -> = string extends T | S - ? boolean - : number extends P - ? boolean - : Math.IsNegative

extends false - ? P extends 0 - ? T extends `${S}${string}` - ? true - : false - : StartsWith, S, 0> // P is >0, slice - : StartsWith // P is negative, ignore it +> = All< + [IsStringLiteral, IsStringLiteral, IsNumberLiteral

] +> extends true + ? Math.IsNegative

extends false + ? P extends 0 + ? T extends `${S}${string}` + ? true + : false + : StartsWith, S, 0> // P is >0, slice + : StartsWith // P is negative, ignore it + : boolean /** * A strongly-typed version of `String.prototype.startsWith`. diff --git a/src/native/trim-end.test.ts b/src/native/trim-end.test.ts index bb7f481..9bb5475 100644 --- a/src/native/trim-end.test.ts +++ b/src/native/trim-end.test.ts @@ -3,6 +3,7 @@ import { type TrimEnd, trimEnd } from './trim-end.js' namespace TypeTests { type test1 = Expect, ' some nice string'>> type test2 = Expect, string>> + type test3 = Expect>, string>> } describe('trimEnd', () => { diff --git a/src/native/trim-end.ts b/src/native/trim-end.ts index c472544..f8559b6 100644 --- a/src/native/trim-end.ts +++ b/src/native/trim-end.ts @@ -1,12 +1,14 @@ +import type { IsStringLiteral } from '../internal/literals.js' + /** * Trims all whitespaces at the end of a string. * T: The string to trim. */ -export type TrimEnd = string extends T - ? string - : T extends `${infer rest} ` - ? TrimEnd - : T +export type TrimEnd = IsStringLiteral extends true + ? T extends `${infer rest} ` + ? TrimEnd + : T + : string /** * A strongly-typed version of `String.prototype.trimEnd`. * @param str the string to trim. diff --git a/src/native/trim-start.test.ts b/src/native/trim-start.test.ts index 0b34503..42fed92 100644 --- a/src/native/trim-start.test.ts +++ b/src/native/trim-start.test.ts @@ -5,6 +5,7 @@ namespace TypeTests { Equal, 'some nice string '> > type test2 = Expect, string>> + type test3 = Expect>, string>> } describe('trimStart', () => { diff --git a/src/native/trim-start.ts b/src/native/trim-start.ts index 4bc8025..0b14d66 100644 --- a/src/native/trim-start.ts +++ b/src/native/trim-start.ts @@ -1,12 +1,14 @@ +import type { IsStringLiteral } from '../internal/literals.js' + /** * Trims all whitespaces at the start of a string. * T: The string to trim. */ -export type TrimStart = string extends T - ? string - : T extends ` ${infer rest}` - ? TrimStart - : T +export type TrimStart = IsStringLiteral extends true + ? T extends ` ${infer rest}` + ? TrimStart + : T + : string /** * A strongly-typed version of `String.prototype.trimStart`. * @param str the string to trim. diff --git a/src/native/trim.test.ts b/src/native/trim.test.ts index 861fde2..1e139dd 100644 --- a/src/native/trim.test.ts +++ b/src/native/trim.test.ts @@ -3,6 +3,7 @@ import { type Trim, trim } from './trim.js' namespace TypeTests { type test1 = Expect, 'some nice string'>> type test2 = Expect, string>> + type test3 = Expect>, string>> } describe('trim', () => { diff --git a/src/utils/characters/letters.ts b/src/utils/characters/letters.ts index 007d636..6af2c67 100644 --- a/src/utils/characters/letters.ts +++ b/src/utils/characters/letters.ts @@ -1,3 +1,5 @@ +import type { IsStringLiteral } from '../../internal/literals.js' + // prettier-ignore type UpperChars = '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' type LowerChars = Lowercase @@ -6,28 +8,26 @@ type LowerChars = Lowercase /** * Checks if the given character is an upper case letter. */ -export type IsUpper = string extends T - ? boolean - : T extends UpperChars - ? true - : false +export type IsUpper = IsStringLiteral extends true + ? T extends UpperChars + ? true + : false + : boolean /** * Checks if the given character is a lower case letter. */ -export type IsLower = string extends T - ? boolean - : T extends LowerChars - ? true - : false +export type IsLower = IsStringLiteral extends true + ? T extends LowerChars + ? true + : false + : boolean /** * Checks if the given character is a letter. */ -export type IsLetter = string extends T - ? boolean - : IsUpper extends true - ? true - : IsLower extends true - ? true - : false +export type IsLetter = IsStringLiteral extends true + ? T extends LowerChars | UpperChars + ? true + : false + : boolean diff --git a/src/utils/characters/numbers.ts b/src/utils/characters/numbers.ts index f9ce7fb..509bb35 100644 --- a/src/utils/characters/numbers.ts +++ b/src/utils/characters/numbers.ts @@ -1,10 +1,12 @@ +import type { IsStringLiteral } from '../../internal/literals.js' + export type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' /** * Checks if the given character is a number. */ -export type IsDigit = string extends T - ? boolean - : T extends Digit - ? true - : false +export type IsDigit = IsStringLiteral extends true + ? T extends Digit + ? true + : false + : boolean diff --git a/src/utils/characters/separators.test.ts b/src/utils/characters/separators.test.ts index bac3672..6123e8d 100644 --- a/src/utils/characters/separators.test.ts +++ b/src/utils/characters/separators.test.ts @@ -12,6 +12,7 @@ namespace TypeChecks { type test8 = Expect, true>> type test9 = Expect, true>> type test10 = Expect, boolean>> + type test11 = Expect>, boolean>> } describe('SEPARATOR_REGEX', () => { diff --git a/src/utils/characters/separators.ts b/src/utils/characters/separators.ts index 49a4dcd..95ae3e9 100644 --- a/src/utils/characters/separators.ts +++ b/src/utils/characters/separators.ts @@ -1,3 +1,5 @@ +import type { IsStringLiteral } from '../../internal/literals.js' + const UNESCAPED_SEPARATORS = [ '[', ']', @@ -30,8 +32,8 @@ export type Separator = (typeof SEPARATORS)[number] * Checks if the given character is a separator. * E.g. space, underscore, dash, dot, slash. */ -export type IsSeparator = string extends T - ? boolean - : T extends Separator - ? true - : false +export type IsSeparator = IsStringLiteral extends true + ? T extends Separator + ? true + : false + : boolean diff --git a/src/utils/characters/special.test.ts b/src/utils/characters/special.test.ts index dbf2964..3a52f4b 100644 --- a/src/utils/characters/special.test.ts +++ b/src/utils/characters/special.test.ts @@ -9,5 +9,6 @@ namespace TypeChecks { type test6 = Expect, true>> type test7 = Expect, false>> type test8 = Expect, boolean>> + type test9 = Expect>, boolean>> } test('dummy test', () => expect(true).toBe(true)) diff --git a/src/utils/characters/special.ts b/src/utils/characters/special.ts index 6ad32bc..ec03766 100644 --- a/src/utils/characters/special.ts +++ b/src/utils/characters/special.ts @@ -1,17 +1,18 @@ import type { IsSeparator } from './separators.js' import type { IsLetter } from './letters.js' import type { IsDigit } from './numbers.js' +import type { IsStringLiteral } from '../../internal/literals.js' /** * Checks if the given character is a special character. * E.g. not a letter, number, or separator. */ -export type IsSpecial = string extends T - ? boolean - : IsLetter extends true - ? false - : IsDigit extends true - ? false - : IsSeparator extends true - ? false - : true +export type IsSpecial = IsStringLiteral extends true + ? IsLetter extends true + ? false + : IsDigit extends true + ? false + : IsSeparator extends true + ? false + : true + : boolean diff --git a/src/utils/reverse.test.ts b/src/utils/reverse.test.ts index d425bcb..791fd60 100644 --- a/src/utils/reverse.test.ts +++ b/src/utils/reverse.test.ts @@ -7,6 +7,7 @@ namespace ReverseTests { Equal, '!tpircSepyT evol I'> > type test4 = Expect, string>> + type test5 = Expect>, Uppercase>> } describe('reverse', () => { diff --git a/src/utils/truncate.test.ts b/src/utils/truncate.test.ts index 1bc9b40..4950169 100644 --- a/src/utils/truncate.test.ts +++ b/src/utils/truncate.test.ts @@ -8,8 +8,9 @@ namespace TruncateTests { type test5 = Expect, '...'>> type test6 = Expect, '[...]'>> type test7 = Expect, string>> - type test8 = Expect, string>> - type test9 = Expect, string>> + type test8 = Expect, 0, '[...]'>, string>> + type test9 = Expect, string>> + type test10 = Expect, string>> } describe('truncate', () => { diff --git a/src/utils/truncate.ts b/src/utils/truncate.ts index 49716b9..64249f5 100644 --- a/src/utils/truncate.ts +++ b/src/utils/truncate.ts @@ -2,6 +2,11 @@ import type { Math } from '../internal/math.js' import { join, type Join } from '../native/join.js' import { type Length } from '../native/length.js' import { type Slice } from '../native/slice.js' +import type { + IsStringLiteral, + IsNumberLiteral, + All, +} from '../internal/literals.js' // STRING FUNCTIONS @@ -13,15 +18,15 @@ export type Truncate< T extends string, Size extends number, Omission extends string = '...', -> = string extends T | Omission - ? string - : number extends Size - ? string - : Math.IsNegative extends true - ? Omission - : Math.Subtract, Size> extends 0 - ? T - : Join<[Slice>>, Omission]> +> = All< + [IsStringLiteral, IsNumberLiteral, IsStringLiteral] +> extends true + ? Math.IsNegative extends true + ? Omission + : Math.Subtract, Size> extends 0 + ? T + : Join<[Slice>>, Omission]> + : string /** * A strongly typed function to truncate a string if it's longer than the given maximum string length. diff --git a/src/utils/words.ts b/src/utils/words.ts index df4a975..a672b7e 100644 --- a/src/utils/words.ts +++ b/src/utils/words.ts @@ -4,6 +4,7 @@ import { SEPARATOR_REGEX } from './characters/separators.js' import type { IsLower, IsUpper } from './characters/letters.js' import type { IsDigit } from './characters/numbers.js' import type { IsSpecial } from './characters/special.js' +import type { IsStringLiteral, All } from '../internal/literals.js' /** * Splits a string into words. @@ -15,40 +16,42 @@ export type Words< sentence extends string, word extends string = '', prev extends string = '', -> = string extends sentence | word | prev - ? string[] // Avoid spending resources on a wide type - : sentence extends `${infer curr}${infer rest}` - ? IsSeparator extends true - ? // Step 1: Remove separators - Reject<[word, ...Words], ''> - : prev extends '' - ? // Start of sentence, start a new word - Reject, ''> - : [false, true] extends [IsDigit, IsDigit] - ? // Step 2: From non-digit to digit - [word, ...Words] - : [true, false] extends [IsDigit, IsDigit] - ? // Step 3: From digit to non-digit - [word, ...Words] - : [false, true] extends [IsSpecial, IsSpecial] - ? // Step 4: From non-special to special - [word, ...Words] - : [true, false] extends [IsSpecial, IsSpecial] - ? // Step 5: From special to non-special - [word, ...Words] - : [true, true] extends [IsDigit, IsDigit] - ? // If both are digit, continue with the sentence - Reject, ''> - : [true, true] extends [IsLower, IsUpper] - ? // Step 6: From lower to upper - [word, ...Words] - : [true, true] extends [IsUpper, IsLower] - ? // Step 7: From upper to upper and lower - // Remove the last character from the current word and start a new word with it - [DropSuffix, ...Words] - : Reject, ''> // Otherwise continue with the sentence - : // Step 8: Trim the last word - Reject<[word], ''> +> = All< + [IsStringLiteral, IsStringLiteral, IsStringLiteral] +> extends true + ? sentence extends `${infer curr}${infer rest}` + ? IsSeparator extends true + ? // Step 1: Remove separators + Reject<[word, ...Words], ''> + : prev extends '' + ? // Start of sentence, start a new word + Reject, ''> + : [false, true] extends [IsDigit, IsDigit] + ? // Step 2: From non-digit to digit + [word, ...Words] + : [true, false] extends [IsDigit, IsDigit] + ? // Step 3: From digit to non-digit + [word, ...Words] + : [false, true] extends [IsSpecial, IsSpecial] + ? // Step 4: From non-special to special + [word, ...Words] + : [true, false] extends [IsSpecial, IsSpecial] + ? // Step 5: From special to non-special + [word, ...Words] + : [true, true] extends [IsDigit, IsDigit] + ? // If both are digit, continue with the sentence + Reject, ''> + : [true, true] extends [IsLower, IsUpper] + ? // Step 6: From lower to upper + [word, ...Words] + : [true, true] extends [IsUpper, IsLower] + ? // Step 7: From upper to upper and lower + // Remove the last character from the current word and start a new word with it + [DropSuffix, ...Words] + : Reject, ''> // Otherwise continue with the sentence + : // Step 8: Trim the last word + Reject<[word], ''> + : string[] // Avoid spending resources on a wide type /** * A strongly typed function to extract the words from a sentence.