Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle non-literal strings #105

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export { trimStart } from './native/trim-start.js'
export type { TrimEnd } from './native/trim-end.js'
export { trimEnd } from './native/trim-end.js'
export type { Trim } from './native/trim.js'

export { trim } from './native/trim.js'

export { toLowerCase } from './native/to-lower-case.js'
export { toUpperCase } from './native/to-upper-case.js'

Expand Down
19 changes: 15 additions & 4 deletions src/internal/internals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@ import * as subject from './internals'
import type * as Subject from './internals'

namespace Internals {
type test = Expect<
type testPascalCaseAll1 = Expect<
Equal<
Subject.PascalCaseAll<['one', 'two', 'three']>,
['One', 'Two', 'Three']
>
>
type testPascalCaseAll2 = Expect<
Equal<Subject.PascalCaseAll<string[]>, string[]>
>

type test1 = Expect<
type testReject1 = Expect<
Equal<
Subject.Reject<['one', '', 'two', '', 'three'], ''>,
['one', 'two', 'three']
>
>

type test2 = Expect<Equal<Subject.DropSuffix<'helloWorld', 'World'>, 'hello'>>
type testDropSuffix1 = Expect<
Equal<Subject.DropSuffix<'helloWorld', 'World'>, 'hello'>
>
type testDropSuffix2 = Expect<
Equal<Subject.DropSuffix<string, 'World'>, string>
>
type testDropSuffix3 = Expect<
Equal<Subject.DropSuffix<'helloWorld', string>, string>
>

type test3 = Expect<Equal<Subject.TupleOf<3, ' '>, [' ', ' ', ' ']>>
type testTupleOf1 = Expect<Equal<Subject.TupleOf<3, ' '>, [' ', ' ', ' ']>>
}

describe('typeOf', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/internal/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ type Reject<tuple, cond, output extends any[] = []> = tuple extends [
type DropSuffix<
sentence extends string,
suffix extends string,
> = sentence extends `${infer rest}${suffix}` ? rest : sentence
> = string extends sentence | suffix
? string
: sentence extends `${infer rest}${suffix}`
? rest
: sentence

/**
* Returns a tuple of the given length with the given type.
Expand Down
6 changes: 6 additions & 0 deletions src/internal/math.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace MathTest {
// NOTE: `Subtract` only supports non-negative integers
type testSubtract1 = Expect<Equal<Math.Subtract<2, 1>, 1>>
type testSubtract2 = Expect<Equal<Math.Subtract<2, 2>, 0>>
type testSubtract3 = Expect<Equal<Math.Subtract<number, 2>, number>>
type testSubtract4 = Expect<Equal<Math.Subtract<2, number>, number>>

type testIsNegative1 = Expect<Equal<Math.IsNegative<2>, false>>
type testIsNegative2 = Expect<Equal<Math.IsNegative<0>, false>>
Expand All @@ -13,10 +15,14 @@ namespace MathTest {
type testAbs2 = Expect<Equal<Math.Abs<1>, 1>>
type testAbs3 = Expect<Equal<Math.Abs<0>, 0>>
type testAbs4 = Expect<Equal<Math.Abs<-0>, 0>>
type testAbs5 = Expect<Equal<Math.Abs<number>, number>>

type testGetPositiveIndex1 = Expect<
Equal<Math.GetPositiveIndex<'abc', -1>, 2>
>
type testGetPositiveIndex2 = Expect<
Equal<Math.GetPositiveIndex<string, -1>, number>
>
}

test('dummy test', () => expect(true).toBe(true))
21 changes: 14 additions & 7 deletions src/internal/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ import type { Length } from '../native/length.js'
import type { TupleOf } from './internals.js'

namespace Math {
export type Subtract<
A extends number,
B extends number,
> = TupleOf<A> extends [...infer U, ...TupleOf<B>] ? U['length'] : 0
export type Subtract<A extends number, B extends number> = number extends
| A
| B
Comment on lines +6 to +7
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever

? number
: TupleOf<A> extends [...infer U, ...TupleOf<B>]
? U['length']
: 0

export type IsNegative<T extends number> = `${T}` extends `-${number}`
export type IsNegative<T extends number> = number extends T
? boolean
: `${T}` extends `-${number}`
? true
: false

export type Abs<T extends number> =
`${T}` extends `-${infer U extends number}` ? U : T
export type Abs<T extends number> = `${T}` extends `-${infer U extends
number}`
? U
: T

export type GetPositiveIndex<
T extends string,
Expand Down
7 changes: 6 additions & 1 deletion src/native/char-at.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { type CharAt, charAt } from './char-at.js'

namespace TypeTests {
type test = Expect<Equal<CharAt<'some nice string', 5>, 'n'>>
type test1 = Expect<Equal<CharAt<'some nice string', 5>, 'n'>>
type test2 = Expect<Equal<CharAt<string, 5>, string>>
type test3 = Expect<Equal<CharAt<'some nice string', number>, string>>

// TODO: index greater than Length<T>
// type test4 = Expect<Equal<CharAt<'some nice string', 100>, ''>>
Comment on lines +8 to +9
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the plan?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope, will fix later

}

describe('charAt', () => {
Expand Down
8 changes: 6 additions & 2 deletions src/native/char-at.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import type { Split } from './split.js'
* T: The string to get the character from.
* index: The index of the character.
*/
export type CharAt<T extends string, index extends number> = Split<T>[index]
export type CharAt<T extends string, index extends number> = string extends T
? string
: number extends index
? string
: Split<T>[index]
/**
* A strongly-typed version of `String.prototype.charAt`.
* @param str the string to get the character from.
Expand All @@ -17,5 +21,5 @@ export function charAt<T extends string, I extends number>(
str: T,
index: I,
): CharAt<T, I> {
return str.charAt(index)
return str.charAt(index) as CharAt<T, I>
}
4 changes: 3 additions & 1 deletion src/native/concat.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { type Concat, concat } from './concat.js'

namespace TypeTests {
type test = Expect<
type test1 = Expect<Equal<Concat<['a', 'bc', 'def']>, 'abcdef'>>
type test2 = Expect<
Equal<Concat<['a', 'bc', 'def'] | ['1', '23', '456']>, 'abcdef' | '123456'>
>
type test3 = Expect<Equal<Concat<string[]>, string>>
}

describe('concat', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/ends-with.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { type EndsWith, endsWith } from './ends-with.js'

namespace TypeTests {
type test = Expect<Equal<EndsWith<'abc', 'c'>, true>>
type test1 = Expect<Equal<EndsWith<'abc', 'c'>, true>>
type test2 = Expect<Equal<EndsWith<string, 'c'>, boolean>>
type test3 = Expect<Equal<EndsWith<'abc', string>, boolean>>
}

describe('endsWith', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/ends-with.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export type EndsWith<
T extends string,
S extends string,
P extends number = Length<T>,
> = Math.IsNegative<P> extends false
> = string extends T | S
? boolean
: Math.IsNegative<P> extends false
? P extends Length<T>
? S extends Slice<T, Math.Subtract<Length<T>, Length<S>>, Length<T>>
? true
Expand Down
4 changes: 3 additions & 1 deletion src/native/includes.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { type Includes, includes } from './includes.js'

namespace TypeTests {
type test = Expect<Equal<Includes<'abcde', 'bcd'>, true>>
type test1 = Expect<Equal<Includes<'abcde', 'bcd'>, true>>
type test2 = Expect<Equal<Includes<string, 'bcd'>, boolean>>
type test3 = Expect<Equal<Includes<'abcde', string>, boolean>>
}

describe('includes', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/includes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export type Includes<
T extends string,
S extends string,
P extends number = 0,
> = Math.IsNegative<P> extends false
> = string extends T | S
? boolean
: Math.IsNegative<P> extends false
? P extends 0
? T extends `${string}${S}${string}`
? true
Expand Down
4 changes: 3 additions & 1 deletion src/native/join.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { type Join, join } from './join.js'

namespace TypeTests {
type test = Expect<
type test1 = Expect<
Equal<Join<['some', 'nice', 'string'], ' '>, 'some nice string'>
>
type test2 = Expect<Equal<Join<string[], ' '>, string>>
type test3 = Expect<Equal<Join<['some', 'nice', 'string'], string>, string>>
}

describe('join', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export type Join<
T extends readonly string[],
delimiter extends string = '',
> = string[] extends T
? string // Avoid spending resources on a wide type
? string
: string extends delimiter
? string
: T extends readonly [
infer first extends string,
...infer rest extends string[],
Expand Down
3 changes: 2 additions & 1 deletion src/native/length.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type Length, length } from './length.js'

namespace TypeTests {
type test = Expect<Equal<Length<'some nice string'>, 16>>
type test1 = Expect<Equal<Length<'some nice string'>, 16>>
type test2 = Expect<Equal<Length<string>, number>>
}

describe('length', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/length.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import type { Split } from './split.js'
/**
* Gets the length of a string.
*/
export type Length<T extends string> = Split<T>['length']
export type Length<T extends string> = string extends T
? number
: Split<T>['length']
/**
* A strongly-typed version of `String.prototype.length`.
* @param str the string to get the length from.
Expand Down
9 changes: 8 additions & 1 deletion src/native/pad-end.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { padEnd } from './pad-end.js'
import { type PadEnd, padEnd } from './pad-end.js'

namespace TypeTests {
type test1 = Expect<Equal<PadEnd<'hello', 10, ' '>, 'hello '>>
type test2 = Expect<Equal<PadEnd<string, 10, ' '>, string>>
type test3 = Expect<Equal<PadEnd<'hello', number, ' '>, string>>
type test4 = Expect<Equal<PadEnd<'hello', 10, string>, string>>
}

describe('padEnd', () => {
test('should pad a string at the end', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/native/pad-end.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export type PadEnd<
T extends string,
times extends number = 0,
pad extends string = ' ',
> = Math.IsNegative<times> extends false
> = string extends T | pad
? string
: number extends times
? string
: Math.IsNegative<times> extends false
? Math.Subtract<times, Length<T>> extends infer missing extends number
? `${T}${Slice<Repeat<pad, missing>, 0, missing>}`
: never
Expand Down
9 changes: 8 additions & 1 deletion src/native/pad-start.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { padStart } from './pad-start.js'
import { type PadStart, padStart } from './pad-start.js'

namespace TypeTests {
type test1 = Expect<Equal<PadStart<'hello', 10, ' '>, ' hello'>>
type test2 = Expect<Equal<PadStart<string, 10, ' '>, string>>
type test3 = Expect<Equal<PadStart<'hello', number, ' '>, string>>
type test4 = Expect<Equal<PadStart<'hello', 10, string>, string>>
}

describe('padStart', () => {
test('should pad a string at the start', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/native/pad-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export type PadStart<
T extends string,
times extends number = 0,
pad extends string = ' ',
> = Math.IsNegative<times> extends false
> = string extends T | pad
? string
: number extends times
? string
: Math.IsNegative<times> extends false
? Math.Subtract<times, Length<T>> extends infer missing extends number
? `${Slice<Repeat<pad, missing>, 0, missing>}${T}`
: never
Expand Down
9 changes: 7 additions & 2 deletions src/native/repeat.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Repeat } from './repeat.js'
import { repeat } from './repeat.js'
import { type Repeat, repeat } from './repeat.js'

namespace TypeTests {
type test1 = Expect<Equal<Repeat<' ', 3>, ' '>>
type test2 = Expect<Equal<Repeat<string, 3>, string>>
type test3 = Expect<Equal<Repeat<' ', number>, string>>
}

describe('repeat', () => {
test('should repeat the string by a given number of times', () => {
Expand Down
9 changes: 8 additions & 1 deletion src/native/repeat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import type { TupleOf } from '../internal/internals.js'
* T: The string to repeat.
* N: The number of times to repeat.
*/
export type Repeat<T extends string, times extends number = 0> = times extends 0
export type Repeat<
T extends string,
times extends number = 0,
> = string extends T
? string
: number extends times
? string
: times extends 0
? ''
: Math.IsNegative<times> extends false
? Join<TupleOf<times, T>>
Expand Down
12 changes: 11 additions & 1 deletion src/native/replace-all.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@
import { type ReplaceAll, replaceAll } from './replace-all.js'

namespace TypeTests {
type test = Expect<
type test1 = Expect<
Equal<ReplaceAll<'some nice string', ' ', '-'>, 'some-nice-string'>
>
type test2 = Expect<
Equal<ReplaceAll<'some nice string', RegExp, '-'>, string>
>
type test3 = Expect<Equal<ReplaceAll<string, ' ', '-'>, string>>
type test4 = Expect<
Equal<ReplaceAll<'some nice string', string, '-'>, string>
>
type test5 = Expect<
Equal<ReplaceAll<'some nice string', ' ', string>, string>
>
}

beforeEach(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/native/replace-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export type ReplaceAll<
lookup extends string | RegExp,
replacement extends string = '',
> = lookup extends string
? sentence extends `${infer rest}${lookup}${infer rest2}`
? string extends lookup | sentence | replacement
? string
: sentence extends `${infer rest}${lookup}${infer rest2}`
? `${rest}${replacement}${ReplaceAll<rest2, lookup, replacement>}`
: sentence
: string
Expand Down
6 changes: 5 additions & 1 deletion src/native/replace.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { type Replace, replace } from './replace.js'

namespace TypeTests {
type test = Expect<
type test1 = Expect<
Equal<Replace<'some nice string', ' ', '-'>, 'some-nice string'>
>
type test2 = Expect<Equal<Replace<'some nice string', RegExp, '-'>, string>>
type test3 = Expect<Equal<Replace<string, ' ', '-'>, string>>
type test4 = Expect<Equal<Replace<'some nice string', string, '-'>, string>>
type test5 = Expect<Equal<Replace<'some nice string', ' ', string>, string>>
}

describe('replace', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/native/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export type Replace<
lookup extends string | RegExp,
replacement extends string = '',
> = lookup extends string
? sentence extends `${infer rest}${lookup}${infer rest2}`
? string extends lookup | sentence | replacement
? string
: sentence extends `${infer rest}${lookup}${infer rest2}`
? `${rest}${replacement}${rest2}`
: sentence
: string
: string // Regex used, can't preserve literal
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one

/**
* A strongly-typed version of `String.prototype.replace`.
* @param sentence the sentence to replace.
Expand Down
Loading