Skip to content

Commit

Permalink
feat: Add strongly-typed string.prototype.repeat alternative (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavoguichard committed Oct 5, 2023
1 parent d76bc8e commit bb0a7c2
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 6 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ npm install string-ts
- [endsWith](#endsWith)
- [join](#join)
- [length](#length)
- [repeat](#repeat)
- [replace](#replace)
- [replaceAll](#replaceall)
- [slice](#slice)
Expand Down Expand Up @@ -224,6 +225,18 @@ const result = length(str)
// ^ 5
```

### repeat

This function is a strongly-typed counterpart of `String.prototype.repeat`.

```ts
import { repeat } from 'string-ts'

const str = 'abc'
const result = repeat(str, 3)
// ^ 'abcabcabc'
```

### replace

This function is a strongly-typed counterpart of `String.prototype.replace`.
Expand Down Expand Up @@ -670,6 +683,7 @@ St.Concat<['a', 'bc', 'def']> // 'abcdef'
St.EndsWith<'abc', 'c'> // true
St.Join<['hello', 'world'], '-'> // 'hello-world'
St.Length<'hello'> // 5
St.Repeat<'abc', 3> // 'abcabcabc'
St.Replace<'hello-world', 'l', '1'> // 'he1lo-world'
St.ReplaceAll<'hello-world', 'l', '1'> // 'he11o-wor1d'
St.Slice<'hello-world', -5> // 'world'
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type {
EndsWith,
Join,
Length,
Repeat,
Replace,
ReplaceAll,
Slice,
Expand All @@ -20,6 +21,7 @@ export {
endsWith,
join,
length,
repeat,
replace,
replaceAll,
slice,
Expand Down
8 changes: 2 additions & 6 deletions src/math.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type { Length } from './primitives'

type GetTuple<
L extends number,
result extends any[] = [],
> = result['length'] extends L ? result : GetTuple<L, [...result, any]>
import { TupleOf } from './utils'

namespace Math {
export type Subtract<
A extends number,
B extends number,
> = GetTuple<A> extends [...infer U, ...GetTuple<B>] ? U['length'] : 0
> = TupleOf<A> extends [...infer U, ...TupleOf<B>] ? U['length'] : 0

export type IsPositive<T extends number> = `${T}` extends `-${number}`
? false
Expand Down
22 changes: 22 additions & 0 deletions src/primitives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ describe('primitives', () => {
})
})

test('repeat', () => {
test('should repeat the string by a given number of times', () => {
const data = 'abc'
const result = subject.repeat(data, 3)
expect(result).toEqual('abcabcabc')
type test = Expect<Equal<typeof result, 'abcabcabc'>>
})

test('should be empty when repeating 0 times', () => {
const data = 'abc'
const result = subject.repeat(data)
expect(result).toEqual('')
type test = Expect<Equal<typeof result, ''>>
})

test('should throw when trying to repeat with negative number', () => {
const data = 'abc'
expect(() => subject.repeat(data, -1)).toThrow()
type test = Expect<Equal<subject.Repeat<'a', -1>, never>>
})
})

test('replace', () => {
test('should replace chars in a string at both type level and runtime level once', () => {
const data = 'some nice string'
Expand Down
27 changes: 27 additions & 0 deletions src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Math } from './math'
import { TupleOf } from './utils'

/**
* Gets the character at the given index.
Expand Down Expand Up @@ -117,6 +118,30 @@ function length<T extends string>(str: T) {
return str.length as Length<T>
}

/**
* Repeats a string N times.
* T: The string to repeat.
* N: The number of times to repeat.
*/
type Repeat<T extends string, N extends number = 0> = N extends 0
? ''
: Math.IsPositive<N> extends true
? Join<TupleOf<N, T>>
: never
/**
* A strongly-typed version of `String.prototype.repeat`.
* @param str the string to repeat.
* @param times the number of times to repeat.
* @returns the repeated string in both type level and runtime.
* @example repeat('hello', 3) // 'hellohellohello'
*/
function repeat<T extends string, N extends number = 0>(
str: T,
times: N = 0 as N,
) {
return str.repeat(times) as Repeat<T, N>
}

/**
* Replaces the first occurrence of a string with another string.
* sentence: The sentence to replace.
Expand Down Expand Up @@ -339,6 +364,7 @@ export type {
EndsWith,
Join,
Length,
Repeat,
Replace,
ReplaceAll,
Slice,
Expand All @@ -354,6 +380,7 @@ export {
endsWith,
join,
length,
repeat,
replace,
replaceAll,
slice,
Expand Down
10 changes: 10 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ type IsSpecial<T extends string> = IsLetter<T> extends true
? false
: true

/**
* Returns a tuple of the given length with the given type.
*/
type TupleOf<
L extends number,
T = unknown,
result extends any[] = [],
> = result['length'] extends L ? result : TupleOf<L, T, [...result, T]>

// STRING FUNCTIONS
/**
* Splits a string into words.
Expand Down Expand Up @@ -136,6 +145,7 @@ export type {
IsSpecial,
IsUpper,
Separator,
TupleOf,
Words,
}
export { words }

0 comments on commit bb0a7c2

Please sign in to comment.