Skip to content

Commit

Permalink
feat: add includes (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
jly36963 authored Oct 5, 2023
1 parent 32a2d06 commit 48d40cd
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 53 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ It also only work with common ASCII characters characters. We don't plan to supp
- [charAt](#charat)
- [concat](#concat)
- [endsWith](#endsWith)
- [includes](#includes)
- [join](#join)
- [length](#length)
- [padEnd](#padend)
Expand Down Expand Up @@ -208,6 +209,17 @@ const result = endsWith('abc', 'c')
// ^ true
```

### includes

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

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

const result = includes('abcde', 'bcd')
// ^ true
```

### join

This function is a strongly-typed counterpart of `Array.prototype.join`.
Expand Down Expand Up @@ -712,6 +724,7 @@ Uppercase<'hello world'> // 'HELLO WORLD'
St.CharAt<'hello world', 6> // 'w'
St.Concat<['a', 'bc', 'def']> // 'abcdef'
St.EndsWith<'abc', 'c'> // true
St.Includes<'abcde', 'bcd'> // true
St.Join<['hello', 'world'], '-'> // 'hello-world'
St.Length<'hello'> // 5
St.PadEnd<'hello', 10, '='> // 'hello====='
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type {
CharAt,
Concat,
EndsWith,
Includes,
Join,
Length,
PadEnd,
Expand All @@ -21,6 +22,7 @@ export {
charAt,
concat,
endsWith,
includes,
join,
length,
padEnd,
Expand Down
157 changes: 105 additions & 52 deletions src/primitives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace TypeTests {

type test12 = Expect<Equal<Subject.StartsWith<'abc', 'a'>, true>>
type test13 = Expect<Equal<Subject.EndsWith<'abc', 'c'>, true>>
type test14 = Expect<Equal<Subject.Includes<'abcde', 'bcd'>, true>>
}

beforeEach(() => {
Expand All @@ -55,6 +56,102 @@ describe('primitives', () => {
})
})

describe('endsWith', () => {
const text = 'abc'

describe('without offset', () => {
test('should return true when text ends with search', () => {
const result = subject.endsWith(text, 'c')
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when text does not end with search', () => {
const result = subject.endsWith(text, 'b')
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with offset', () => {
test('should return true when offset text ends with search', () => {
const result = subject.endsWith(text, 'b', 2)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return true when offset text ends with search (multi-char)', () => {
const result = subject.endsWith(text, 'bc', 3)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when offset string does not end with search', () => {
const result = subject.endsWith(text, 'c', 1)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with bad offset', () => {
test('should return false when the offset is negative', () => {
const result = subject.endsWith(text, 'a', -1)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
test('should return true when the end matches and offset is greater than text length', () => {
const result = subject.endsWith(text, 'c', 10)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
})
})

describe('includes', () => {
const text = 'abcde'

describe('without offset', () => {
test('should return true when text contains search', () => {
const result = subject.includes(text, 'bcd')
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when text does not end with search', () => {
const result = subject.includes(text, 'hello')
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with offset', () => {
test('should return true when offset text does contain search', () => {
const result = subject.includes(text, 'c', 1)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return true when offset text does contain search (multi-char)', () => {
const result = subject.includes(text, 'bcd', 1)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when offset string does not contain search', () => {
const result = subject.includes(text, 'abc', 3)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with bad offset', () => {
test('should ignore offset when the offset is negative', () => {
const result = subject.includes(text, 'a', -100)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when text contains search but offset is greater than text length', () => {
const result = subject.includes(text, 'c', 10)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})
})

describe('join', () => {
test('should join words in both type level and runtime level', () => {
const result = subject.join(['a', 'b', 'c'], '-')
Expand Down Expand Up @@ -284,89 +381,45 @@ describe('primitives', () => {
})

describe('startsWith', () => {
const text = 'abc'

describe('without offset', () => {
test('should return true when text starts with search', () => {
const result = subject.startsWith('abc', 'a')
const result = subject.startsWith(text, 'a')
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when text does not start with search', () => {
const result = subject.startsWith('abc', 'b')
const result = subject.startsWith(text, 'b')
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with offset', () => {
test('should return true when offset text starts with search', () => {
const result = subject.startsWith('abc', 'b', 1)
const result = subject.startsWith(text, 'b', 1)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when offset string does not start with search', () => {
const result = subject.startsWith('abc', 'a', 1)
const result = subject.startsWith(text, 'a', 1)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with bad offset', () => {
test('should return true when text starts with search and offset is negative', () => {
const result = subject.startsWith('abc', 'a', -1)
const result = subject.startsWith(text, 'a', -1)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when offset is greater than text length', () => {
const result = subject.startsWith('abc', 'a', 10)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})
})

describe('endsWith', () => {
describe('without offset', () => {
test('should return true when text ends with search', () => {
const result = subject.endsWith('abc', 'c')
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when text does not end with search', () => {
const result = subject.endsWith('abc', 'b')
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with offset', () => {
test('should return true when offset text ends with search', () => {
const result = subject.endsWith('abc', 'b', 2)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return true when offset text ends with search (multi-char)', () => {
const result = subject.endsWith('abc', 'bc', 3)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
test('should return false when offset string does not end with search', () => {
const result = subject.endsWith('abc', 'c', 1)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
})

describe('with bad offset', () => {
test('should return false when the offset is negative', () => {
const result = subject.endsWith('abc', 'a', -1)
const result = subject.startsWith(text, 'a', 10)
expect(result).toEqual(false)
type test = Expect<Equal<typeof result, false>>
})
test('should return true when the end matches and offset is greater than text length', () => {
const result = subject.endsWith('abc', 'c', 10)
expect(result).toEqual(true)
type test = Expect<Equal<typeof result, true>>
})
})
})

Expand Down
38 changes: 37 additions & 1 deletion src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Math } from './math'
import { TupleOf } from './utils'
import type { TupleOf } from './utils'

/**
* Gets the character at the given index.
Expand Down Expand Up @@ -372,6 +372,40 @@ function startsWith<T extends string, S extends string, P extends number = 0>(
return text.startsWith(search, position) as StartsWith<T, S, P>
}

/**
* Checks if a string includes another string.
* T: The string to check.
* S: The string to check against.
* P: The position to start the search.
*/
type Includes<
T extends string,
S extends string,
P extends number = 0,
> = Math.IsNegative<P> extends false
? P extends 0
? T extends `${string}${S}${string}`
? true
: false
: Includes<Slice<T, P>, S, 0> // P is >0, slice
: Includes<T, S, 0> // P is negative, ignore it

/**
* A strongly-typed version of `String.prototype.includes`.
* @param text the string to search
* @param search the string to search with
* @param position the index to start search at
* @returns boolean, whether or not the text contains the search string.
* @example includes('abcde', 'bcd') // true
*/
function includes<T extends string, S extends string, P extends number = 0>(
text: T,
search: S,
position = 0 as P,
) {
return text.includes(search, position) as Includes<T, S, P>
}

/**
* Trims all whitespaces at the start of a string.
* T: The string to trim.
Expand Down Expand Up @@ -424,6 +458,7 @@ export type {
CharAt,
Concat,
EndsWith,
Includes,
Join,
Length,
PadEnd,
Expand All @@ -442,6 +477,7 @@ export {
charAt,
concat,
endsWith,
includes,
join,
length,
padEnd,
Expand Down

0 comments on commit 48d40cd

Please sign in to comment.