Home > Blockchain >  Modified Template Literal Types
Modified Template Literal Types

Time:03-29

const lines = ['one', 'two', 'three'] as const;
const linesWithA = lines.map(line => `${line}-A` as const);
const linesWithB = lines.map(line => `${line.toUpperCase()}-B` as const);

will give types:

declare const lines: readonly ["one", "two", "three"];
declare const linesWithA: ("one-A" | "two-A" | "three-A")[];
declare const linesWithB: `${string}-B`[];

Is it somehow possible to get a type for linesWithB as ("ONE-B" | "TWO-B" | "THREE-B")[]? I'm getting ​`${string}-A`[] instead, becaue of the toUpperCase call.

TS Playground

CodePudding user response:

The problem is that the toUpperCase just returns a string:

const test1 = 'no uppercase'.toUpperCase();
// const test1: string

I think the safest way to handle this issue is to create a new function toUpperCase with an assertion to get the proper type:

const toUpperCase = <S extends string>(line: S) =>
  line.toUpperCase() as Uppercase<S>

const test2 = toUpperCase('uppercase');
// const test2: "UPPERCASE"

If you use this function in linesWithB, it will have the desired return type:

const linesWithB = lines.map(line => `${toUpperCase(line)}-B` as const);
// const linesWithB: ("ONE-B" | "TWO-B" | "THREE-B")[]

Playground Link

Update: There's a TypeScript issue about this, but it will (probably?) require making String generic, which is a rather extensive change.

CodePudding user response:

I can only do it with a (fairly innocuous) type assertion:

const lines = ['one', 'two', 'three'] as const;
type Lines = typeof lines;
type UpperLines = Uppercase<Lines[number]>[];
const linesWithA = lines.map(line => `${line}-A` as const);
const linesWithB = (lines.map(line => line.toUpperCase()) as UpperLines).map(line => `${line}-B` as const);

Playground link

It's also possible without the intermediary type aliases, but...I wouldn't:

const lines = ['one', 'two', 'three'] as const;
const linesWithA = lines.map(line => `${line}-A` as const);
const linesWithB = (lines.map(line => line.toUpperCase()) as [Uppercase<(typeof lines)[number]>]).map(line => `${line}-B` as const);

Playground link

Hopefully someone better at TypeScript than I can do it without the type assertion, though again, it's fairly innocuous.

CodePudding user response:

You could augment the global namespace and add an overload for the toUpperCase function:

declare global {
    interface String {
        toUpperCase<T extends string>(this: T): Uppercase<T>
    }
}

No type assertion is required:

// ("ONE-B" | "TWO-B" | "THREE-B")[]
const linesWithB = lines.map(line => `${line.toUpperCase()}-B` as const)

Playground link

  • Related