Skip to content

Instantly share code, notes, and snippets.

@aaronbeall
Last active February 11, 2026 14:20
Show Gist options
  • Select an option

  • Save aaronbeall/50e64f1db20913335aec9b9e2a702ed0 to your computer and use it in GitHub Desktop.

Select an option

Save aaronbeall/50e64f1db20913335aec9b9e2a702ed0 to your computer and use it in GitHub Desktop.
Type-safe string transforms, complements intrinsic string conversion literal types
/**
* Convert first character of string to uppercase
*/
const capitalize = <T extends string>(s: T): Capitalize<T> =>
`${ s.charAt(0).toUpperCase() }${ s.slice(1) }` as Capitalize<T>;
/**
* Convert first character of string to lowercase
*/
const uncapitalize = <T extends string>(s: T): Uncapitalize<T> =>
`${ s.charAt(0).toLowerCase() }${ s.slice(1) }` as Uncapitalize<T>;
/**
* Convert string to uppercase
*/
const uppercase = <T extends string>(s: T): Uppercase<T> =>
s.toUpperCase() as Uppercase<T>;
/**
* Convert string to lowercase
*/
const lowercase = <T extends string>(s: T): Lowercase<T> =>
s.toLowerCase() as Lowercase<T>;
@aaronbeall
Copy link
Author

aaronbeall commented Feb 11, 2026

Bonus:

/**
 * Capitalize words after the first word in a string literal type, and lowercase the rest of each word, ex `Camel case` -> `camelCase`
 */
type CamelCase<T extends string> = 
  T extends `${infer First}${ '_' | ' ' | '-' }${infer Rest}` 
    ? `${Lowercase<First>}${Capitalize<CamelCase<Rest>>}` 
    : Lowercase<T>;

/**
 * Convert string to camelCase
 */
const camelCase = <T extends string>(s: T): CamelCase<T> =>
  s.toLowerCase().replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) as CamelCase<T>;

// Examples:
camelCase("Hello world"); // -> `helloWorld`
camelCase("Hello_world"); // -> `helloWorld`
camelCase("Hello-world"); // -> `helloWorld`

/**
 * Capitalize words in a string literal, and lowercase the rest of each word, ex `pascal case` -> `PascalCase`
 */
type PascalCase<T extends string> =
  T extends `${infer First}${'_' | ' ' | '-'}${infer Rest}`
    ? `${Capitalize<Lowercase<First>>}${Capitalize<PascalCase<Rest>>}`
    : Capitalize<Lowercase<T>>;

/**
 * Convert string to PascalCase
 */
const pascalCase = <T extends string>(s: T): PascalCase<T> =>
  s.toLowerCase().replace(/(^|[-_\s]+)(.)?/g, (_, __, c) => (c ? c.toUpperCase() : "")) as PascalCase<T>;

// Examples:
pascalCase("hello world"); // -> `HelloWorld`
pascalCase("hello-world"); // -> `HelloWorld`
pascalCase("hello_world"); // -> `HelloWorld`

/**
 * Lowercase words in a string literal type and join by underscores, ex `Snake Case` -> `snake_case`
 */
type SnakeCase<T extends string> =
  T extends `${infer First}${'_' | ' ' | '-'}${infer Rest}`
    ? `${Lowercase<First>}_${SnakeCase<Rest>}`
    : Lowercase<T>;

/**
 * Convert string to snake_case
 */
const snakeCase = <T extends string>(s: T): SnakeCase<T> =>
  s.toLowerCase().replace(/(^|[-_\s]+)/g, "_") as SnakeCase<T>;

// Examples:
snakeCase("Hello World"); // -> `hello_world`
snakeCase("Hello-World"); // -> `hello_world`

/**
 * Lowercase words in a string literal type and join by dashes, ex `Kebab Case` -> `kebab-case`
 */
type KebabCase<T extends string> =
  T extends `${infer First}${'_' | ' ' | '-'}${infer Rest}`
    ? `${Lowercase<First>}-${KebabCase<Rest>}`
    : Lowercase<T>;

/**
 * Convert string to snake_case
 */
const kebabCase = <T extends string>(s: T): KebabCase<T> =>
  s.toLowerCase().replace(/(^|[-_\s]+)/g, "-") as KebabCase<T>;

// Examples:
kebabCase("Hello World"); // -> `hello-world`
kebabCase("Hello_World"); // -> `hello-world`

/**
 * Upperase words in a string literal type and join by underscores, ex `Screaming Snake Case` -> `SCREAMING_SNAKE_CASE`
 */
type ScreamingSnakeCase<T extends string> =
  T extends `${infer First}${'_' | ' ' | '-'}${infer Rest}`
    ? `${Uppercase<First>}_${ScreamingSnakeCase<Rest>}`
    : Capitalize<Uppercase<T>>;

/**
 * Convert string to SCREAMIN_SNAKE_CASE (aka CONST_CASE)
 */
const screamingSnakeCase = <T extends string>(s: T): ScreamingSnakeCase<T> =>
  s.toUpperCase().replace(/(^|[-_\s]+)/g, "_") as ScreamingSnakeCase<T>;

// Example:
screamingSnakeCase("hello world"); // -> `HELLO_WORLD`

/**
 * Adds -s, -es, or -ies to string literal type following basic English pluralization rules:
 */
type Pluralize<T extends string> =
  T extends `${infer Stem}y`
    ? Stem extends `${string}${"a" | "e" | "i" | "o" | "u"}`
      ? `${T}s`
      : `${Stem}ies`
    : T extends `${string}${"s" | "x" | "z" | "sh" | "ch"}`
      ? `${T}es`
      : `${T}s`

/**
 * Pluralizes a string using basic English pluralization rules appropriate for programmatic mapping
 * - `consonant + y` -> `-ies` (e.g. `baby` -> `babies`)
 * - `vowel + y`-> `-s` (e.g. `boy` -> `boys`)
 * - `-s, -x, -z, -sh, -ch` -> `-es` (e.g. `box` -> `boxes`)
 * - All others add `-s` (e.g. `dog` -> `dogs`)
 */
const pluralize = <T extends string>(s: T): Pluralize<T> =>
  (/[^aeiou]y$/i.test(s)
    ? s.replace(/y$/i, "ies")
    : /(s|sh|ch|x|z)$/i.test(s)
      ? `${s}es`
      : `${s}s`) as Pluralize<T>;

// Examples:
pluralize("dog") // -> `dogs`
pluralize("box") // -> `boxes`
pluralize("baby") // -> `babys`
pluralize("boy") // -> `boys`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment