There are a few things you need to do.
- Remove the redundant explicit types from your variables
color
, message
, and light
. Doing so is not only unnecessary, it actually interferes with TypeScript properly inferring the literal value of those types. In other words, if you define const color = "red";
TypeScript will know, because it is a const
, that it will never change and it can always be treated as the string literal "red"
instead of the more generic string
. (As a general rule, you should never explicitly define the type of a const
variable.)
const color = "red";
const message = "My Title";
const light = false;
- Make sure your
capitalize()
function properly defines its return type. In this case, there is actually an awesome built-in utility type Capitalize<>
which you can use here. In combination with a TypeScript generic, you can define the function in such a way that TypeScript knows that e.g. if "red"
is what goes in, "Red"
is what comes out.
function capitalize<S extends string>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
- Use the
as const
assertion when you define colorName
(and any other similar variables you may need to define). If you don't do this, the type of colorName
will be inferred as string
, which is no good for indexing chalk
. With as const
, you are basically telling TypeScript to treat the resulting expression as a literal string value. In this case, the type of colorName
becomes "bgRedBright" | "bgRed"
, both of which are valid indices of chalk
.
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;
^^^^^^^^
When you put it all together:
import chalk from 'chalk';
const color = "red";
const message = "My Title";
const light = false;
function capitalize<S extends string>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;
console.log(chalk[colorName](message));
Edit: It's possible in the future that you would need to define your color
variable in such a way that it is dynamic and not just a hard coded literal "red"
value. In that case you need a way to satisfy TypeScript that color
will always be something that is valid given all the other inferences that we just set up.
Here, we actually do want to explicitly define a type for certain variables, particularly let
and function arguments. Fortunately, chalk
provides a very useful type for this case, ForegroundColor
which is essentially all of the valid "base" colors and happen to be compatible with BackgroundColor
when put into the form `bg${ForegroundColor`
, which is exactly what we need here.
import chalk, { ForegroundColor } from 'chalk';
let color: ForegroundColor = "red";
color = "blue";
We could even improve our capitalize()
function by more strictly controlling what the type of the argument can be:
function capitalize<S extends ForegroundColor>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
Another playground example that puts those further improvements into practice.