Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate chalk.constructor() in favor of new chalk.Instance() #322

Merged
merged 10 commits into from Mar 12, 2019
4 changes: 2 additions & 2 deletions index.d.ts
Expand Up @@ -35,7 +35,7 @@ export interface Options {
level?: Level;
}

export interface Constructor {
export interface Instance {
/**
* Return a new Chalk instance.
*/
Expand Down Expand Up @@ -75,7 +75,7 @@ export interface Chalk {
/**
* Return a new Chalk instance.
*/
constructor: Constructor;
Instance: Instance;

/**
* Enable or disable Chalk.
Expand Down
33 changes: 20 additions & 13 deletions index.js
Expand Up @@ -25,24 +25,31 @@ function applyOptions(object, options = {}) {
object.enabled = 'enabled' in options ? options.enabled : object.level > 0;
}

function Chalk(options) {
// We check for this.template here since calling `chalk.constructor()`
// by itself will have a `this` of a previously constructed chalk object
if (!this || !(this instanceof Chalk) || this.template) {
const chalk = {};
applyOptions(chalk, options);
class ChalkClass {
constructor(options) {
return chalkFactory(options);
}
}

chalk.template = (...args) => chalkTag(chalk.template, ...args);
function chalkFactory(options) {
const chalk = {};
applyOptions(chalk, options);

Object.setPrototypeOf(chalk, Chalk.prototype);
Object.setPrototypeOf(chalk.template, chalk);
chalk.template = (...args) => chalkTag(chalk.template, ...args);

chalk.template.constructor = Chalk;
Object.setPrototypeOf(chalk, Chalk.prototype);
Object.setPrototypeOf(chalk.template, chalk);

return chalk.template;
}
chalk.template.constructor = () => {
throw new Error('Chalk.constructor() is deprecated. Use new Chalk.Instance() instead.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error('Chalk.constructor() is deprecated. Use new Chalk.Instance() instead.');
throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.');

};
chalk.template.Instance = ChalkClass;

return chalk.template;
}

applyOptions(this, options);
function Chalk(options) {
return chalkFactory(options);
}

// Use bright blue on Windows as the normal blue color is illegible
Expand Down
2 changes: 1 addition & 1 deletion index.js.flow
Expand Up @@ -24,7 +24,7 @@ export type ColorSupport = {|
export interface Chalk {
(...text: string[]): string,
(text: TemplateStringsArray, ...placeholders: mixed[]): string,
constructor(options?: Options): Chalk,
Instance(options?: Options): Chalk,
enabled: boolean,
level: Level,
rgb(red: number, green: number, blue: number): Chalk,
Expand Down
4 changes: 2 additions & 2 deletions index.test-d.ts
Expand Up @@ -16,8 +16,8 @@ expectType<boolean>(chalk.supportsColor.has256);
expectType<boolean>(chalk.supportsColor.has16m);

// - Chalk -
// -- Constructor --
expectType<Chalk>(new chalk.constructor({level: 1}));
// -- Instance --
expectType<Chalk>(new chalk.Instance({level: 1}));

// -- Properties --
expectType<boolean>(chalk.enabled);
Expand Down
6 changes: 3 additions & 3 deletions readme.md
Expand Up @@ -140,12 +140,12 @@ Multiple arguments will be separated by space.

Color support is automatically detected, as is the level (see `chalk.level`). However, if you'd like to simply enable/disable Chalk, you can do so via the `.enabled` property. When `chalk.enabled` is `true`, `chalk.level` must *also* be greater than `0` for colored output to be produced.

Chalk is enabled by default unless explicitly disabled via the constructor or `chalk.level` is `0`.
Chalk is enabled by default unless explicitly disabled via `Chalk.Instance()` or `chalk.level` is `0`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Chalk is enabled by default unless explicitly disabled via `Chalk.Instance()` or `chalk.level` is `0`.
Chalk is enabled by default unless explicitly disabled via `new chalk.Instance()` or `chalk.level` is `0`.


If you need to change this in a reusable module, create a new instance:

```js
const ctx = new chalk.constructor({enabled: false});
const ctx = new chalk.Instance({enabled: false});
```

### chalk.level
Expand All @@ -155,7 +155,7 @@ Color support is automatically detected, but you can override it by setting the
If you need to change this in a reusable module, create a new instance:

```js
const ctx = new chalk.constructor({level: 0});
const ctx = new chalk.Instance({level: 0});
```

Levels are as follows:
Expand Down
14 changes: 7 additions & 7 deletions test/_flow.js
Expand Up @@ -2,12 +2,12 @@
import chalk from '..';

// $ExpectError (Can't have typo in option name)
chalk.constructor({levl: 1});
chalk.constructor({level: 1});
new chalk.Instance({levl: 1});
new chalk.Instance({level: 1});

// $ExpectError (Option must have proper type)
new chalk.constructor({enabled: 'true'});
new chalk.constructor({enabled: true});
new chalk.Instance({enabled: 'true'});
new chalk.Instance({enabled: true});

// $ExpectError (Can't have typo in chalk method)
chalk.rd('foo');
Expand All @@ -22,8 +22,8 @@ chalk.red.bgBlu.underline('foo');
chalk.red.bgBlue.underline('foo');

// $ExpectError (Level must be 0, 1, 2, or 3)
const badCtx = chalk.constructor({level: 4});
const ctx = chalk.constructor({level: 3});
const badCtx = chalk.Instance({level: 4});
const ctx = chalk.Instance({level: 3});

// $ExpectError (Can't have typo in method name)
ctx.gry('foo');
Expand All @@ -41,7 +41,7 @@ chalk.enabled = true;
chalk.level = 10;
chalk.level = 1;

const chalkInstance = new chalk.constructor();
const chalkInstance = new chalk.Instance();

// $ExpectError (Can't have typo in method name)
chalkInstance.blu('foo');
Expand Down
14 changes: 7 additions & 7 deletions test/chalk.js
Expand Up @@ -83,17 +83,17 @@ test('line breaks should open and close colors', t => {
});

test('properly convert RGB to 16 colors on basic color terminals', t => {
t.is(new chalk.constructor({level: 1}).hex('#FF0000')('hello'), '\u001B[91mhello\u001B[39m');
t.is(new chalk.constructor({level: 1}).bgHex('#FF0000')('hello'), '\u001B[101mhello\u001B[49m');
t.is(new chalk.Instance({level: 1}).hex('#FF0000')('hello'), '\u001B[91mhello\u001B[39m');
t.is(new chalk.Instance({level: 1}).bgHex('#FF0000')('hello'), '\u001B[101mhello\u001B[49m');
});

test('properly convert RGB to 256 colors on basic color terminals', t => {
t.is(new chalk.constructor({level: 2}).hex('#FF0000')('hello'), '\u001B[38;5;196mhello\u001B[39m');
t.is(new chalk.constructor({level: 2}).bgHex('#FF0000')('hello'), '\u001B[48;5;196mhello\u001B[49m');
t.is(new chalk.constructor({level: 3}).bgHex('#FF0000')('hello'), '\u001B[48;2;255;0;0mhello\u001B[49m');
t.is(new chalk.Instance({level: 2}).hex('#FF0000')('hello'), '\u001B[38;5;196mhello\u001B[39m');
t.is(new chalk.Instance({level: 2}).bgHex('#FF0000')('hello'), '\u001B[48;5;196mhello\u001B[49m');
t.is(new chalk.Instance({level: 3}).bgHex('#FF0000')('hello'), '\u001B[48;2;255;0;0mhello\u001B[49m');
});

test('don\'t emit RGB codes if level is 0', t => {
t.is(new chalk.constructor({level: 0}).hex('#FF0000')('hello'), 'hello');
t.is(new chalk.constructor({level: 0}).bgHex('#FF0000')('hello'), 'hello');
t.is(new chalk.Instance({level: 0}).hex('#FF0000')('hello'), 'hello');
t.is(new chalk.Instance({level: 0}).bgHex('#FF0000')('hello'), 'hello');
});
33 changes: 7 additions & 26 deletions test/constructor.js
@@ -1,34 +1,15 @@
import test from 'ava';

// Spoof supports-color
require('./_supports-color')(__dirname);

const chalk = require('..');

test('create an isolated context where colors can be disabled (by level)', t => {
const instance = new chalk.constructor({level: 0, enabled: true});
t.is(instance.red('foo'), 'foo');
t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m');
instance.level = 2;
t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m');
});
test('Chalk.constructor should throw an expected error', t => {
const expectedError = t.throws(() => {
chalk.constructor();
});

test('create an isolated context where colors can be disabled (by enabled flag)', t => {
const instance = new chalk.constructor({enabled: false});
t.is(instance.red('foo'), 'foo');
t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m');
instance.enabled = true;
t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m');
});

test('the `level` option should be a number from 0 to 3', t => {
/* eslint-disable no-new */
t.throws(() => {
new chalk.constructor({level: 10});
}, /should be an integer from 0 to 3/);
t.is(expectedError.message, 'Chalk.constructor() is deprecated. Use new Chalk.Instance() instead.');

t.throws(() => {
new chalk.constructor({level: -1});
}, /should be an integer from 0 to 3/);
/* eslint-enable no-new */
new chalk.constructor(); // eslint-disable-line no-new
});
});
34 changes: 34 additions & 0 deletions test/instance.js
@@ -0,0 +1,34 @@
import test from 'ava';

// Spoof supports-color
require('./_supports-color')(__dirname);

const chalk = require('..');

test('create an isolated context where colors can be disabled (by level)', t => {
const instance = new chalk.Instance({level: 0, enabled: true});
t.is(instance.red('foo'), 'foo');
t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m');
instance.level = 2;
t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m');
});

test('create an isolated context where colors can be disabled (by enabled flag)', t => {
const instance = new chalk.Instance({enabled: false});
t.is(instance.red('foo'), 'foo');
t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m');
instance.enabled = true;
t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m');
});

test('the `level` option should be a number from 0 to 3', t => {
/* eslint-disable no-new */
t.throws(() => {
new chalk.Instance({level: 10});
}, /should be an integer from 0 to 3/);

t.throws(() => {
new chalk.Instance({level: -1});
}, /should be an integer from 0 to 3/);
/* eslint-enable no-new */
});
40 changes: 20 additions & 20 deletions test/template-literal.js
Expand Up @@ -7,31 +7,31 @@ require('./_supports-color')(__dirname);
const chalk = require('..');

test('return an empty string for an empty literal', t => {
const instance = chalk.constructor();
const instance = new chalk.Instance();
t.is(instance``, '');
});

test('return a regular string for a literal with no templates', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`hello`, 'hello');
});

test('correctly perform template parsing', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`{bold Hello, {cyan World!} This is a} test. {green Woo!}`,
instance.bold('Hello,', instance.cyan('World!'), 'This is a') + ' test. ' + instance.green('Woo!'));
});

test('correctly perform template substitutions', t => {
const instance = chalk.constructor({level: 0});
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. {green ${exclamation}!}`,
instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!'));
});

test('correctly parse and evaluate color-convert functions', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
t.is(instance`{bold.rgb(144,10,178).inverse Hello, {~inverse there!}}`,
'\u001B[1m\u001B[38;2;144;10;178m\u001B[7mHello, ' +
'\u001B[27m\u001B[39m\u001B[22m\u001B[1m' +
Expand All @@ -44,13 +44,13 @@ test('correctly parse and evaluate color-convert functions', t => {
});

test('properly handle escapes', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
t.is(instance`{bold hello \{in brackets\}}`,
'\u001B[1mhello {in brackets}\u001B[22m');
});

test('throw if there is an unclosed block', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
try {
console.log(instance`{bold this shouldn't appear ever\}`);
t.fail();
Expand All @@ -67,7 +67,7 @@ test('throw if there is an unclosed block', t => {
});

test('throw if there is an invalid style', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
try {
console.log(instance`{abadstylethatdoesntexist this shouldn't appear ever}`);
t.fail();
Expand All @@ -77,7 +77,7 @@ test('throw if there is an invalid style', t => {
});

test('properly style multiline color blocks', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
t.is(
instance`{bold
Hello! This is a
Expand All @@ -97,49 +97,49 @@ test('properly style multiline color blocks', t => {
});

test('escape interpolated values', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`Hello {bold hi}`, 'Hello hi');
t.is(instance`Hello ${'{bold hi}'}`, 'Hello {bold hi}');
});

test('allow custom colors (themes) on custom contexts', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
instance.rose = instance.hex('#F6D9D9');
t.is(instance`Hello, {rose Rose}.`, 'Hello, \u001B[38;2;246;217;217mRose\u001B[39m.');
});

test('correctly parse newline literals (bug #184)', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`Hello
{red there}`, 'Hello\nthere');
});

test('correctly parse newline escapes (bug #177)', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`Hello\nthere!`, 'Hello\nthere!');
});

test('correctly parse escape in parameters (bug #177 comment 318622809)', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
const str = '\\';
t.is(instance`{blue ${str}}`, '\\');
});

test('correctly parses unicode/hex escapes', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`\u0078ylophones are fo\x78y! {magenta.inverse \u0078ylophones are fo\x78y!}`,
'xylophones are foxy! xylophones are foxy!');
});

test('correctly parses string arguments', t => {
const instance = chalk.constructor({level: 3});
const instance = new chalk.Instance({level: 3});
t.is(instance`{keyword('black').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m');
t.is(instance`{keyword('blac\x6B').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m');
t.is(instance`{keyword('blac\u006B').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m');
});

test('throws if a bad argument is encountered', t => {
const instance = chalk.constructor({level: 3}); // Keep level at least 1 in case we optimize for disabled chalk instances
const instance = new chalk.Instance({level: 3}); // Keep level at least 1 in case we optimize for disabled chalk instances
try {
console.log(instance`{keyword(????) hi}`);
t.fail();
Expand All @@ -149,7 +149,7 @@ test('throws if a bad argument is encountered', t => {
});

test('throws if an extra unescaped } is found', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
try {
console.log(instance`{red hi!}}`);
t.fail();
Expand All @@ -159,12 +159,12 @@ test('throws if an extra unescaped } is found', t => {
});

test('should not parse upper-case escapes', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`\N\n\T\t\X07\x07\U000A\u000A\U000a\u000a`, 'N\nT\tX07\x07U000A\u000AU000a\u000A');
});

test('should properly handle undefined template interpolated values', t => {
const instance = chalk.constructor({level: 0});
const instance = new chalk.Instance({level: 0});
t.is(instance`hello ${undefined}`, 'hello undefined');
t.is(instance`hello ${null}`, 'hello null');
});