diff --git a/src/native/ends-with.test.ts b/src/native/ends-with.test.ts index 57aa114..9345455 100644 --- a/src/native/ends-with.test.ts +++ b/src/native/ends-with.test.ts @@ -5,6 +5,18 @@ namespace TypeTests { type test2 = Expect, boolean>> type test3 = Expect, 'c'>, boolean>> type test4 = Expect, boolean>> + type test6 = Expect, true>> + type test7 = Expect, false>> + type test8 = Expect, true>> + type test9 = Expect, false>> + + // Template strings + type testTS1 = Expect, true>> + type testTS2 = Expect, false>> + type testTS3 = Expect, true>> + type testTS4 = Expect, false>> + type testTS5 = Expect, true>> + type testTS6 = Expect, boolean>> } describe('endsWith', () => { diff --git a/src/native/ends-with.ts b/src/native/ends-with.ts index 0652e6f..f1d8cea 100644 --- a/src/native/ends-with.ts +++ b/src/native/ends-with.ts @@ -1,6 +1,8 @@ import type { Math } from '../internal/math.js' import type { Length } from './length.js' import type { Slice } from './slice.js' +import type { StartsWith } from './starts-with.js' +import type { Reverse } from '../utils/reverse.js' import type { All, IsNumberLiteral, @@ -16,17 +18,29 @@ import type { export type EndsWith< T extends string, S extends string, - P extends number = Length, -> = All<[IsStringLiteral, IsNumberLiteral

]> extends true + P extends number | undefined = undefined, +> = P extends number ? _EndsWith : _EndsWithNoPosition + +type _EndsWith = All< + [IsStringLiteral, IsNumberLiteral

] +> extends true ? Math.IsNegative

extends false ? P extends Length - ? S extends Slice, Length>, Length> - ? true - : false - : EndsWith, S, Length> // P !== T.length, slice + ? IsStringLiteral extends true + ? S extends Slice, Length>, Length> + ? true + : false + : _EndsWithNoPosition, S> // Eg: EndsWith<`abc${string}xyz`, 'c', 3> + : _EndsWithNoPosition, S> // P !== T.length, slice : false // P is negative, false : boolean +/** Overload of EndsWith without P */ +type _EndsWithNoPosition = StartsWith< + Reverse, + Reverse +> + /** * A strongly-typed version of `String.prototype.endsWith`. * @param text the string to search. diff --git a/src/native/slice.test.ts b/src/native/slice.test.ts index 813f19f..951d59e 100644 --- a/src/native/slice.test.ts +++ b/src/native/slice.test.ts @@ -7,6 +7,16 @@ namespace TypeTests { type test4 = Expect, 5, 9>, string>> type test5 = Expect, string>> type test6 = Expect, string>> + type test7 = Expect, 'cde'>> + type test8 = Expect, ''>> + type test9 = Expect, ''>> + + // Template literals + type testTS1 = Expect, 'bc'>> + type testTS2 = Expect, 'bc'>> + type testTS3 = Expect, 'abc'>> + type testTS4 = Expect, string>> + type testTS5 = Expect, `bc${string}`>> } describe('slice', () => { diff --git a/src/native/slice.ts b/src/native/slice.ts index e587b50..5ad4313 100644 --- a/src/native/slice.ts +++ b/src/native/slice.ts @@ -1,10 +1,5 @@ import type { Math } from '../internal/math.js' -import type { Length } from './length.js' -import type { - All, - IsStringLiteral, - IsNumberLiteral, -} from '../internal/literals.js' +import type { IsStringLiteral, IsNumberLiteral } from '../internal/literals.js' /** * Slices a string from a startIndex to an endIndex. @@ -15,26 +10,68 @@ import type { export type Slice< T extends string, startIndex extends number = 0, - endIndex extends number = Length, -> = All< - [IsStringLiteral, IsNumberLiteral] -> extends true + endIndex extends number | undefined = undefined, +> = endIndex extends number + ? _Slice + : _SliceStart + +/** Slice with startIndex and endIndex */ +type _Slice< + T extends string, + startIndex extends number, + endIndex extends number, + _result extends string = '', +> = IsNumberLiteral extends true ? T extends `${infer head}${infer rest}` - ? startIndex extends 0 - ? endIndex extends 0 - ? '' - : `${head}${Slice< + ? IsStringLiteral extends true + ? startIndex extends 0 + ? endIndex extends 0 + ? _result + : _Slice< + rest, + 0, + Math.Subtract, 1>, + `${_result}${head}` + > + : _Slice< rest, Math.Subtract, 1>, - Math.Subtract, 1> - >}` - : `${Slice< - rest, - Math.Subtract, 1>, - Math.Subtract, 1> - >}` - : '' + Math.Subtract, 1>, + _result + > + : startIndex | endIndex extends 0 + ? _result + : string // Head is non-literal + : IsStringLiteral extends true // Couldn't be split into head/tail + ? _result // T ran out + : startIndex | endIndex extends 0 + ? _result // Eg: Slice<`abc${string}`, 1, 3> -> 'bc' + : string // Head is non-literal : string + +/** Slice with startIndex only */ +type _SliceStart< + T extends string, + startIndex extends number, + _result extends string = '', +> = IsNumberLiteral extends true + ? T extends `${infer head}${infer rest}` + ? IsStringLiteral extends true + ? startIndex extends 0 + ? T + : _SliceStart< + rest, + Math.Subtract, 1>, + _result + > + : string + : IsStringLiteral extends true + ? _result + : startIndex extends 0 + ? _result + : string + : string + /** * A strongly-typed version of `String.prototype.slice`. * @param str the string to slice. @@ -46,7 +83,7 @@ export type Slice< export function slice< T extends string, S extends number = 0, - E extends number = Length, ->(str: T, start: S = 0 as S, end: E = str.length as E) { + E extends number | undefined = undefined, +>(str: T, start: S = 0 as S, end: E = undefined as E) { return str.slice(start, end) as Slice } diff --git a/src/native/starts-with.test.ts b/src/native/starts-with.test.ts index 0bad327..bcbf5d7 100644 --- a/src/native/starts-with.test.ts +++ b/src/native/starts-with.test.ts @@ -7,6 +7,10 @@ namespace TypeTests { type test4 = Expect, boolean>> type test5 = Expect, boolean>> type test6 = Expect, boolean>> + type test7 = Expect, true>> + type test8 = Expect, false>> + type test9 = Expect, true>> + type test10 = Expect, true>> } describe('startsWith', () => { diff --git a/src/native/starts-with.ts b/src/native/starts-with.ts index 5004313..90b092f 100644 --- a/src/native/starts-with.ts +++ b/src/native/starts-with.ts @@ -16,12 +16,20 @@ export type StartsWith< T extends string, S extends string, P extends number = 0, -> = All<[IsStringLiteral, IsNumberLiteral

]> extends true +> = All<[IsStringLiteral, IsNumberLiteral

]> extends true ? Math.IsNegative

extends false ? P extends 0 - ? T extends `${S}${string}` - ? true - : false + ? S extends `${infer SHead}${infer SRest}` + ? T extends `${infer THead}${infer TRest}` + ? IsStringLiteral extends true + ? THead extends SHead + ? StartsWith + : false // Heads weren't equal + : boolean // THead is non-literal + : IsStringLiteral extends true // Couldn't split T + ? false // T ran out, but we still have S + : boolean // T (or TRest) is not a literal + : true // Couldn't split S, we've already ruled out non-literal : StartsWith, S, 0> // P is >0, slice : StartsWith // P is negative, ignore it : boolean diff --git a/src/utils/reverse.test.ts b/src/utils/reverse.test.ts index ce228ad..8535f2e 100644 --- a/src/utils/reverse.test.ts +++ b/src/utils/reverse.test.ts @@ -8,6 +8,11 @@ namespace ReverseTests { > type test4 = Expect, string>> type test5 = Expect>, Uppercase>> + + // Template strings + type testTS1 = Expect, `${string}cba`>> + type testTS2 = Expect, `zyx${string}cba`>> + type testTS3 = Expect, `zyx${string}`>> } describe('reverse', () => { @@ -20,8 +25,10 @@ describe('reverse', () => { }) test('should reverse a long string', () => { - const expected = 'murobal tse di mina tillom tnuresed aiciffo iuq apluc ni tnus ,tnediorp non tatadipuc taceacco tnis ruetpecxE .rutairap allun taiguf ue erolod mullic esse tilev etatpulov ni tiredneherper ni rolod eruri etua siuD .tauqesnoc odommoc ae xe piuqila tu isin sirobal ocmallu noitaticrexe durtson siuq ,mainev minim da mine tU .auqila angam erolod te erobal tu tnudidicni ropmet domsuie od des ,tile gnicsipida rutetcesnoc ,tema tis rolod muspi meroL' - const data = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum' + const expected = + 'murobal tse di mina tillom tnuresed aiciffo iuq apluc ni tnus ,tnediorp non tatadipuc taceacco tnis ruetpecxE .rutairap allun taiguf ue erolod mullic esse tilev etatpulov ni tiredneherper ni rolod eruri etua siuD .tauqesnoc odommoc ae xe piuqila tu isin sirobal ocmallu noitaticrexe durtson siuq ,mainev minim da mine tU .auqila angam erolod te erobal tu tnudidicni ropmet domsuie od des ,tile gnicsipida rutetcesnoc ,tema tis rolod muspi meroL' + const data = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum' const result = reverse(data) expect(result).toEqual(expected) type test = Expect> diff --git a/src/utils/reverse.ts b/src/utils/reverse.ts index cf6ebd4..dac6a70 100644 --- a/src/utils/reverse.ts +++ b/src/utils/reverse.ts @@ -2,9 +2,14 @@ * Reverses a string. * - `T` The string to reverse. */ -type Reverse = T extends `${infer Head}${infer Tail}` +type Reverse< + T extends string, + _acc extends string = '', +> = T extends `${infer Head}${infer Tail}` ? Reverse - : _acc extends '' ? T : _acc + : _acc extends '' + ? T + : `${T}${_acc}` /** * A strongly-typed function to reverse a string.