DEV Community

James Garbutt
James Garbutt

Posted on

String manipulation types in TypeScript 4.1

TypeScript recently introduced "template literal types", these basically allow us to use template string like semantics when creating literal types.

Along side this new functionality came some fairly useful new utility types: string manipulation types.

Template literal types

For example:

type Foo = 'foo';
type Bar = 'bar';
type FooBar = `${Foo | Bar}`; // "foo" | "bar"
Enter fullscreen mode Exit fullscreen mode

As you can see, the new thing here is that we can interpolate other types within another string literal type.

This is pretty powerful stuff as it means we could infer strings based on other string-like types.

A few possible use cases:

  • ${keyof T}-changed to infer "foo-changed"-style events
  • Enumerating combinations of string literal types as above
  • Inferring part of a string literal type

I won't go into this too much, but you can read more here.

String manipulation types

There are 4 new string manipulation types:

Uppercase<T>

Transforms a string literal type to uppercase:

type Foo = 'foo';
type UpperFoo = Uppercase<Foo>; // "FOO"
Enter fullscreen mode Exit fullscreen mode

Lowercase<T>

Transforms a string literal type to lowercase:

type FooBar = 'FOO, BAR';
type LowerFooBar = Lowercase<FooBar>; // "foo, bar"
Enter fullscreen mode Exit fullscreen mode

Capitalize<T>

Transforms a string literal type to have the first character capitalized:

type FooBar = 'foo bar';
type CapitalizedFooBar = Capitalize<FooBar>; // "Foo bar"
Enter fullscreen mode Exit fullscreen mode

Uncapitalize<T>

Transforms a string literal type to have the first character lowercased:

type FooBar = 'Foo Bar';
type CapitalizedFooBar = Uncapitalize<FooBar>; // "foo Bar"
Enter fullscreen mode Exit fullscreen mode

How?

If, like me, you wondered how these types can possibly work... the answer is compiler magic.

Usually TypeScript's utility types ultimately drill down to something you could have written yourself, for example:

// Record is defined as follows inside TypeScript
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

// Before it existed, people were writing things like:
type Record = {[key: string]: any};
Enter fullscreen mode Exit fullscreen mode

However, this time around, these new types are built into the compiler and cannot (easily) be written by us. As you can see:

// intrinsic is some special keyword the compiler
// understands, expected to never be used in userland code.
type Uppercase<S extends string> = intrinsic;

// under the hood, it uses this:
function applyStringMapping(symbol: Symbol, str: string) {
    switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
        case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
        case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
        case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
        case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
    }
    return str;
}
Enter fullscreen mode Exit fullscreen mode

Examples

A few possible uses of this:

/*
 * Technically you could use this to require UPPERCASE
 * or lowercase...
 */
declare function foo<T extends string>(str: T extends Uppercase<T> ? T : never): void;

foo('ABCDEF'); // Works
foo('abcdef'); // Error

/*
 * Or you might want a method to return a transformed
 * version of a string...
 */
declare function toUpper<T extends string>(val: T): Uppercase<T>;

toUpper('foo' as string); // string
toUpper('foo'); // "FOO"
Enter fullscreen mode Exit fullscreen mode

Wrap-up

If you want to know more about these types, see here:

https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html

They're very cool and this new functionality opens up many doors. What was previously weakly typed as a string can probably now be strongly typed in many cases.

Give them a go!

Top comments (1)

Collapse
 
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Very cool and thanks for the write up. I now hereby challenge you to find a use for intrinsic in userland code 😉

Thanks again