diff --git a/README.md b/README.md index da6eff8..4bc522b 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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`. @@ -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=====' diff --git a/src/index.ts b/src/index.ts index cc56e8e..91f41bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export type { CharAt, Concat, EndsWith, + Includes, Join, Length, PadEnd, @@ -21,6 +22,7 @@ export { charAt, concat, endsWith, + includes, join, length, padEnd, diff --git a/src/primitives.test.ts b/src/primitives.test.ts index 32c367b..a0a82d8 100644 --- a/src/primitives.test.ts +++ b/src/primitives.test.ts @@ -39,6 +39,7 @@ namespace TypeTests { type test12 = Expect, true>> type test13 = Expect, true>> + type test14 = Expect, true>> } beforeEach(() => { @@ -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> + }) + test('should return false when text does not end with search', () => { + const result = subject.endsWith(text, 'b') + expect(result).toEqual(false) + type test = Expect> + }) + }) + + 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> + }) + 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> + }) + 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> + }) + }) + + 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> + }) + 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> + }) + }) + }) + + 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> + }) + test('should return false when text does not end with search', () => { + const result = subject.includes(text, 'hello') + expect(result).toEqual(false) + type test = Expect> + }) + }) + + 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> + }) + 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> + }) + 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> + }) + }) + + 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> + }) + 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> + }) + }) + }) + describe('join', () => { test('should join words in both type level and runtime level', () => { const result = subject.join(['a', 'b', 'c'], '-') @@ -284,14 +381,16 @@ 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> }) 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> }) @@ -299,12 +398,12 @@ describe('primitives', () => { 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> }) 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> }) @@ -312,61 +411,15 @@ describe('primitives', () => { 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> }) 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> - }) - }) - }) - - 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> - }) - test('should return false when text does not end with search', () => { - const result = subject.endsWith('abc', 'b') - expect(result).toEqual(false) - type test = Expect> - }) - }) - - 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> - }) - 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> - }) - 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> - }) - }) - - 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> }) - 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> - }) }) }) diff --git a/src/primitives.ts b/src/primitives.ts index d8fbf75..1dc0c99 100644 --- a/src/primitives.ts +++ b/src/primitives.ts @@ -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. @@ -372,6 +372,40 @@ function startsWith( return text.startsWith(search, position) as StartsWith } +/** + * 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

extends false + ? P extends 0 + ? T extends `${string}${S}${string}` + ? true + : false + : Includes, S, 0> // P is >0, slice + : Includes // 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( + text: T, + search: S, + position = 0 as P, +) { + return text.includes(search, position) as Includes +} + /** * Trims all whitespaces at the start of a string. * T: The string to trim. @@ -424,6 +458,7 @@ export type { CharAt, Concat, EndsWith, + Includes, Join, Length, PadEnd, @@ -442,6 +477,7 @@ export { charAt, concat, endsWith, + includes, join, length, padEnd,