Hi kind of new to typescript and been trying to transform an enum/literal to another enum/literal using a function.
For example, a capitalize function that takes a string and returns the same string with the first letter as a capital. (i.e map type name = "ryuma"
to "Ryuma"
)
function capitalize<T>(str:T & string){
return str.charAt(0).toUpperCase() str.slice(1)
}
const myName = 'ryuma' as const
const result = capitalize(myName)
Here, result gets a type of string, but I want it to have a type of "Ryuma". Is there a way to:
- ensure the function preserves the information about the literal and thus returns the type "Ryuma"
- or to transform the type of the input
str: T & string
such that it returns the capitalized version of the input.
Here is a code sandbox which contains the type hints for the return value of capitalize.
I tried testing a bunch of variations but couldn't get something to preserve the information, any help would be greatly appreciated ty :D
CodePudding user response:
TypeScript has some built-in intrinsic string manipulation utility types including the Capitalize<T>
type which turns a string literal type input into a capitalized version where the first character is uppercased and the rest of the string literal is unchanged.
This is exactly what your capitalize()
function does, so it makes sense for you to give it a generic call signature <T extends string>(str: T) => Capitalize<T>
(where you constrain T
to string
, which is preferable to having T
unconstrained and making str
of type T & string
).
Unfortunately, the compiler doesn't understand what the charAt()
, the toUpperCase()
, or the slice()
string methods or the string concatenation operator (
) actually do to string literal types. It knows that they produce string
s in general, but it doesn't know that, for example, "foo".toUpperCase()
has literal type "FOO"
specifically. That means the compiler can't easily verify that capitalize(str)
returns Capitalize<typeof str>
.
So, for now at least, you'd have to just assert that you are returning a value of the right type, via return (...) as Capitalize<T>
. Like this:
function capitalize<T extends string>(str: T){
return (str.charAt(0).toUpperCase() str.slice(1)) as Capitalize<T>;
}
Now we can test it:
const myName = 'ryuma' as const
const result = capitalize(myName);
// const result: "Ryuma"
Looks good. The compiler knows that result
is of literal type "Ryuma"
, as desired.
Note: you could go through a lot of effort, as shown in this Playground link, to teach the compiler how to figure out what charAt()
, toUpperCase()
, concat()
m and slice()
do at the string literal type level, but it's very complicated and fragile and probably scales poorly. Unless you have some compelling reason not to, I'd stick with the type assertion as Capitalize<T>
.