Skip to content

Commit

Permalink
feat: uncapitalize, lengh, and slice methods (#24)
Browse files Browse the repository at this point in the history
* feat: Adds uncapitalize runtime counterpart to native Uncapitalize type

* feat: Add strongly-typed string.prototype.length alternative

* feat: Add strongly-typed string.prototype.slice alternative

* chore: Re-order key-casing file

* fix: Simplify casing function implementations to avoid type casting
  • Loading branch information
gustavoguichard authored Oct 4, 2023
1 parent 2cf8123 commit 5409e8e
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 175 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,14 @@ npm install string-ts

- [Runtime counterparts of native type utilities](#runtime-counterparts-of-native-type-utilities)
- [capitalize](#capitalize)
- [uncapitalize](#uncapitalize)
- [Strongly-typed alternatives to native runtime utilities](#strongly-typed-alternatives-to-native-runtime-utilities)
- [chartAt](#charat)
- [join](#join)
- [length](#length)
- [replace](#replace)
- [replaceAll](#replaceall)
- [slice](#slice)
- [split](#split)
- [toLowerCase](#tolowercase)
- [toUpperCase](#touppercase)
Expand Down Expand Up @@ -146,6 +149,18 @@ const result = capitalize(str)
// ^ 'Hello world'
```

### uncapitalize

Uncapitalizes the first letter of a string. This is a runtime counterpart of `Uncapitalize<T>` from `src/types.d.ts`.

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

const str = 'Hello world'
const result = uncapitalize(str)
// ^ 'hello world'
```

## Strongly-typed alternatives to native runtime utilities

### charAt
Expand All @@ -172,6 +187,18 @@ const result = join(str, ' ')
// ^ 'hello world'
```

### length

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

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

const str = 'hello'
const result = length(str)
// ^ 5
```

### replace

This function is a strongly-typed counterpart of `String.prototype.replace`.
Expand All @@ -196,6 +223,24 @@ const result = replaceAll(str, '-', ' ')
// ^ 'hello world '
```

### slice

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

_Warning: due to TS limitations - for now - we ignore the second argument (endIndex) if the first (startIndex) is negative and we also don't support a negative endIndex._

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

const str = 'hello-world'
const result = slice(str, 6)
// ^ 'world'
const result2 = slice(str, 1, 5)
// ^ 'ello'
const result3 = slice(str, -5)
// ^ 'world'
```

### split

This function is a strongly-typed counterpart of `String.prototype.split`.
Expand Down Expand Up @@ -583,8 +628,10 @@ Uppercase<'hello world'> // 'HELLO WORLD'
```ts
St.CharAt<'hello world', 6> // 'w'
St.Join<['hello', 'world'], '-'> // 'hello-world'
St.Length<'hello'> // 5
St.Replace<'hello-world', 'l', '1'> // 'he1lo-world'
St.ReplaceAll<'hello-world', 'l', '1'> // 'he11o-wor1d'
St.Slice<'hello-world', -5> // 'world'
St.Split<'hello-world', '-'> // ['hello', 'world']
St.Trim<' hello world '> // 'hello world'
St.TrimEnd<' hello world '> // ' hello world'
Expand Down
16 changes: 16 additions & 0 deletions src/casing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ describe('capitalize', () => {
})
})

describe('uncapitalize', () => {
test('it does nothing with a string that has no char at the beginning', () => {
const expected = weirdString
const result = subject.uncapitalize(weirdString)
expect(result).toEqual(expected)
type test = Expect<Equal<typeof result, typeof expected>>
})

test('it uncapitalizes the first char of a string', () => {
const expected = 'someWeird-casedString' as const
const result = subject.uncapitalize('SomeWeird-casedString')
expect(result).toEqual(expected)
type test = Expect<Equal<typeof result, typeof expected>>
})
})

describe('casing functions', () => {
test('toUpperCase', () => {
const expected = ' SOMEWEIRD-CASED$*STRING1986FOO BAR W_FOR_WUMBO' as const
Expand Down
50 changes: 28 additions & 22 deletions src/casing.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import type { PascalCaseAll } from './internals'
import { pascalCaseAll, type PascalCaseAll } from './internals'
import type { Join } from './primitives'
import { join } from './primitives'
import type { Is, Words } from './utils'
import { charAt, join, slice } from './primitives'
import type { Words } from './utils'
import { words } from './utils'

// CASING UTILITIES
// CASING UTILITIES THAT ALREADY HAVE NATIVE TS TYPES

/**
* Capitalizes the first letter of a string. This is a runtime counterpart of `Capitalize<T>` from `src/types.d.ts`.
* @param str the string to capitalize.
* @returns the capitalized string.
* @example capitalize('hello world') // 'Hello world'
*/
function capitalize<T extends string>(str: T): Capitalize<T> {
return join([toUpperCase(charAt(str, 0)), slice(str, 1)])
}

/**
* This function is a strongly-typed counterpart of String.prototype.toLowerCase.
* @param str the string to make lowercase.
Expand All @@ -26,15 +37,16 @@ function toUpperCase<T extends string>(str: T) {
}

/**
* Capitalizes the first letter of a string. This is a runtime counterpart of `Capitalize<T>` from `src/types.d.ts`.
* @param str the string to capitalize.
* @returns the capitalized string.
* @example capitalize('hello world') // 'Hello world'
* Uncapitalizes the first letter of a string. This is a runtime counterpart of `Uncapitalize<T>` from `src/types.d.ts`.
* @param str the string to uncapitalize.
* @returns the uncapitalized string.
* @example uncapitalize('Hello world') // 'hello world'
*/
function capitalize<T extends string>(str: T) {
return (toUpperCase(str.charAt(0)) + str.slice(1)) as Capitalize<T>
function uncapitalize<T extends string>(str: T): Uncapitalize<T> {
return join([toLowerCase(charAt(str, 0)), slice(str, 1)])
}

// CASING UTILITIES
/**
* Transforms a string with the specified separator (delimiter).
*/
Expand All @@ -56,37 +68,30 @@ function toDelimiterCase<T extends string, D extends string>(
/**
* Transforms a string to camelCase.
*/
type CamelCase<T extends string> = T extends unknown
? PascalCase<T> extends `${infer first}${infer rest}`
? `${Lowercase<first>}${rest}`
: T
: never
type CamelCase<T extends string> = Uncapitalize<PascalCase<T>>

/**
* A strongly typed version of `toCamelCase` that works in both runtime and type level.
* @param str the string to convert to camel case.
* @returns the camel cased string.
* @example toCamelCase('hello world') // 'helloWorld'
*/
function toCamelCase<T extends string>(str: T) {
const res = toPascalCase(str)
return (res.slice(0, 1).toLowerCase() + res.slice(1)) as CamelCase<T>
function toCamelCase<T extends string>(str: T): CamelCase<T> {
return uncapitalize(toPascalCase(str))
}

/**
* Transforms a string to PascalCase.
*/
type PascalCase<T extends string> = Join<PascalCaseAll<Is<Words<T>, string[]>>>
type PascalCase<T extends string> = Join<PascalCaseAll<Words<T>>>
/**
* A strongly typed version of `toPascalCase` that works in both runtime and type level.
* @param str the string to convert to pascal case.
* @returns the pascal cased string.
* @example toPascalCase('hello world') // 'HelloWorld'
*/
function toPascalCase<T extends string>(str: T): PascalCase<T> {
return words(str)
.map((v) => capitalize(toLowerCase(v)))
.join('') as PascalCase<T>
return join(pascalCaseAll(words(str)))
}

/**
Expand Down Expand Up @@ -165,4 +170,5 @@ export {
toSnakeCase,
toTitleCase,
toUpperCase,
uncapitalize,
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
export type {
CharAt,
Join,
Length,
Replace,
ReplaceAll,
Slice,
Split,
TrimStart,
TrimEnd,
Expand All @@ -12,8 +14,10 @@ export type {
export {
charAt,
join,
length,
replace,
replaceAll,
slice,
split,
trim,
trimStart,
Expand All @@ -23,7 +27,6 @@ export {
// UTILS
export type {
Digit,
Is,
IsDigit,
IsLetter,
IsLower,
Expand Down Expand Up @@ -56,6 +59,7 @@ export {
toSnakeCase,
toTitleCase,
toUpperCase,
uncapitalize,
} from './casing'

// KEY CASING
Expand Down
20 changes: 12 additions & 8 deletions src/internals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Is } from './utils'
import { capitalize, toLowerCase } from './casing'

/**
* This is an enhanced version of the typeof operator to check the type of more complex values.
Expand Down Expand Up @@ -28,6 +28,10 @@ function typeOf(t: unknown) {
| 'urlsearchparams'
}

function pascalCaseAll<T extends string[]>(words: T) {
return words.map((v) => capitalize(toLowerCase(v))) as PascalCaseAll<T>
}

/**
* Removes all the elements matching the given condition from a tuple.
*/
Expand All @@ -49,12 +53,12 @@ type DropSuffix<
/**
* PascalCases all the words in a tuple of strings
*/
type PascalCaseAll<T extends string[]> = T extends [infer First, ...infer Rest]
? [
Capitalize<Lowercase<Is<First, string>>>,
...PascalCaseAll<Is<Rest, string[]>>,
]
type PascalCaseAll<T extends string[]> = T extends [
infer head extends string,
...infer rest extends string[],
]
? [Capitalize<Lowercase<head>>, ...PascalCaseAll<rest>]
: T

export type { PascalCaseAll, Drop, DropSuffix }
export { typeOf }
export type { Drop, DropSuffix, PascalCaseAll }
export { pascalCaseAll, typeOf }
Loading

0 comments on commit 5409e8e

Please sign in to comment.