Skip to content

Commit

Permalink
Support template literals for nested calls (#392)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
toonijn and sindresorhus committed Jun 9, 2020
1 parent 55816cd commit 09ddbad
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 2 deletions.
12 changes: 12 additions & 0 deletions benchmark.js
Expand Up @@ -47,4 +47,16 @@ suite('chalk', () => {
bench('cached: 1 style nested non-intersecting', () => {
chalkBgRed(blueStyledString);
});

set('iterations', 10000);

bench('cached: 1 style template literal', () => {
// eslint-disable-next-line no-unused-expressions
chalkRed`the fox jumps over the lazy dog`;
});

bench('cached: nested styles template literal', () => {
// eslint-disable-next-line no-unused-expressions
chalkRed`the fox {bold jumps} over the {underline lazy} dog`;
});
});
7 changes: 7 additions & 0 deletions index.d.ts
Expand Up @@ -137,6 +137,13 @@ declare namespace chalk {
DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%}
`);
```
@example
```
import chalk = require('chalk');
log(chalk.red.bgBlack`2 + 3 = {bold ${2 + 3}}`)
```
*/
(text: TemplateStringsArray, ...placeholders: unknown[]): string;

Expand Down
5 changes: 5 additions & 0 deletions index.test-d.ts
Expand Up @@ -152,6 +152,11 @@ expectType<string>(chalk.bgWhiteBright`foo`);
expectType<string>(chalk.red.bgGreen.underline('foo'));
expectType<string>(chalk.underline.red.bgGreen('foo'));

// -- Complex template literal --
expectType<string>(chalk.underline``);
expectType<string>(chalk.red.bgGreen.bold`Hello {italic.blue ${name}}`);
expectType<string>(chalk.strikethrough.cyanBright.bgBlack`Works with {reset {bold numbers}} {bold.red ${1}}`);

// -- Color types ==
expectType<typeof chalk.Color>('red');
expectError<typeof chalk.Color>('hotpink');
3 changes: 2 additions & 1 deletion readme.md
Expand Up @@ -215,10 +215,11 @@ console.log(chalk`

Blocks are delimited by an opening curly brace (`{`), a style, some content, and a closing curly brace (`}`).

Template styles are chained exactly like normal Chalk styles. The following two statements are equivalent:
Template styles are chained exactly like normal Chalk styles. The following three statements are equivalent:

```js
console.log(chalk.bold.rgb(10, 100, 200)('Hello!'));
console.log(chalk.bold.rgb(10, 100, 200)`Hello!`);
console.log(chalk`{bold.rgb(10,100,200) Hello!}`);
```

Expand Down
9 changes: 8 additions & 1 deletion source/index.js
Expand Up @@ -6,6 +6,8 @@ const {
stringEncaseCRLFWithFirstIndex
} = require('./util');

const {isArray} = Array;

// `supportsColor.level` → `ansiStyles.color[name]` mapping
const levelMapping = [
'ansi',
Expand Down Expand Up @@ -135,6 +137,11 @@ const createStyler = (open, close, parent) => {

const createBuilder = (self, _styler, _isEmpty) => {
const builder = (...arguments_) => {
if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) {
// Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}`
return applyStyle(builder, chalkTag(builder, ...arguments_));
}

// Single argument is hot path, implicit coercion is faster than anything
// eslint-disable-next-line no-implicit-coercion
return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' '));
Expand Down Expand Up @@ -189,7 +196,7 @@ let template;
const chalkTag = (chalk, ...strings) => {
const [firstString] = strings;

if (!Array.isArray(firstString)) {
if (!isArray(firstString) || !isArray(firstString.raw)) {
// If chalk() was called by itself or with a string,
// return the string itself as a string.
return strings.join(' ');
Expand Down
8 changes: 8 additions & 0 deletions test/chalk.js
Expand Up @@ -16,6 +16,14 @@ test('support multiple arguments in base function', t => {
t.is(chalk('hello', 'there'), 'hello there');
});

test('support automatic casting to string', t => {
t.is(chalk(['hello', 'there']), 'hello,there');
t.is(chalk(123), '123');

t.is(chalk.bold(['foo', 'bar']), '\u001B[1mfoo,bar\u001B[22m');
t.is(chalk.green(98765), '\u001B[32m98765\u001B[39m');
});

test('style string', t => {
t.is(chalk.underline('foo'), '\u001B[4mfoo\u001B[24m');
t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m');
Expand Down
19 changes: 19 additions & 0 deletions test/template-literal.js
Expand Up @@ -30,6 +30,25 @@ test('correctly perform template substitutions', t => {
instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!'));
});

test('correctly perform nested template substitutions', t => {
const instance = new chalk.Instance({level: 0});
const name = 'Sindre';
const exclamation = 'Neat';
t.is(instance.bold`Hello, {cyan.inverse ${name}!} This is a` + ' test. ' + instance.green`${exclamation}!`,
instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!'));

t.is(instance.red.bgGreen.bold`Hello {italic.blue ${name}}`,
instance.red.bgGreen.bold('Hello ' + instance.italic.blue(name)));

t.is(instance.strikethrough.cyanBright.bgBlack`Works with {reset {bold numbers}} {bold.red ${1}}`,
instance.strikethrough.cyanBright.bgBlack('Works with ' + instance.reset.bold('numbers') + ' ' + instance.bold.red(1)));

t.is(chalk.bold`Also works on the shared {bgBlue chalk} object`,
'\u001B[1mAlso works on the shared \u001B[1m' +
'\u001B[44mchalk\u001B[49m\u001B[22m' +
'\u001B[1m object\u001B[22m');
});

test('correctly parse and evaluate color-convert functions', t => {
const instance = new chalk.Instance({level: 3});
t.is(instance`{bold.rgb(144,10,178).inverse Hello, {~inverse there!}}`,
Expand Down

0 comments on commit 09ddbad

Please sign in to comment.