diff --git a/.flowconfig b/.flowconfig index 2318f0d..48ee960 100644 --- a/.flowconfig +++ b/.flowconfig @@ -3,3 +3,4 @@ [options] suppress_comment= \\(.\\|\n\\)*\\$ExpectError +include_warnings=true diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.travis.yml b/.travis.yml index ea5900d..c89aee6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js node_js: + - '10' - '8' - '6' - - '4' -after_success: npm run coveralls +after_success: + - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' diff --git a/benchmark.js b/benchmark.js index af90c06..f4e9cf3 100644 --- a/benchmark.js +++ b/benchmark.js @@ -1,4 +1,4 @@ -/* globals set bench */ +/* globals suite, set, bench */ 'use strict'; const chalk = require('.'); diff --git a/examples/rainbow.js b/examples/rainbow.js index a4a2e0a..fc086fa 100644 --- a/examples/rainbow.js +++ b/examples/rainbow.js @@ -1,7 +1,7 @@ 'use strict'; const chalk = require('..'); -const ignoreChars = /[^!-~]/; +const ignoreChars = /[^!-~]/g; function rainbow(str, offset) { if (!str || str.length === 0) { diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..b2c7213 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,276 @@ +export const enum Level { + /** + * All colors disabled. + */ + None = 0, + + /** + * Basic 16 colors support. + */ + Basic = 1, + + /** + * ANSI 256 colors support. + */ + Ansi256 = 2, + + /** + * Truecolor 16 million colors support. + */ + TrueColor = 3 +} + +export interface Options { + /** + * Enable or disable Chalk. + * + * @default true + */ + enabled?: boolean; + + /** + * Specify the color support for Chalk. + * By default, color support is automatically detected based on the environment. + */ + level?: Level; +} + +export interface Constructor { + /** + * Return a new Chalk instance. + */ + new (options?: Options): Chalk; +} + +/** + * Detect whether the terminal supports color. + */ +export interface ColorSupport { + /** + * The color level used by Chalk. + */ + level: Level; + + /** + * Return whether Chalk supports basic 16 colors. + */ + hasBasic: boolean; + + /** + * Return whether Chalk supports ANSI 256 colors. + */ + has256: boolean; + + /** + * Return whether Chalk supports Truecolor 16 million colors. + */ + has16m: boolean; +} + +export interface Chalk { + (...text: unknown[]): string; + + (text: TemplateStringsArray, ...placeholders: unknown[]): string; + + /** + * Return a new Chalk instance. + */ + constructor: Constructor; + + /** + * Enable or disable Chalk. + * + * @default true + */ + enabled: boolean; + + /** + * The color support for Chalk. + * By default, color support is automatically detected based on the environment. + */ + level: Level; + + /** + * Use HEX value to set text color. + * + * @param color - Hexadecimal value representing the desired color. + * + * @example + * + * import chalk from 'chalk'; + * + * chalk.hex('#DEADED'); + */ + hex(color: string): this; + + /** + * Use keyword color value to set text color. + * + * @param color - Keyword value representing the desired color. + * + * @example + * + * import chalk from 'chalk'; + * + * chalk.keyword('orange'); + */ + keyword(color: string): this; + + /** + * Use RGB values to set text color. + */ + rgb(red: number, green: number, blue: number): this; + + /** + * Use HSL values to set text color. + */ + hsl(hue: number, saturation: number, lightness: number): this; + + /** + * Use HSV values to set text color. + */ + hsv(hue: number, saturation: number, value: number): this; + + /** + * Use HWB values to set text color. + */ + hwb(hue: number, whiteness: number, blackness: number): this; + + /** + * Use HEX value to set background color. + * + * @param color - Hexadecimal value representing the desired color. + * + * @example + * + * import chalk from 'chalk'; + * + * chalk.bgHex('#DEADED'); + */ + bgHex(color: string): this; + + /** + * Use keyword color value to set background color. + * + * @param color - Keyword value representing the desired color. + * + * @example + * + * import chalk from 'chalk'; + * + * chalk.bgKeyword('orange'); + */ + bgKeyword(color: string): this; + + /** + * Use RGB values to set background color. + */ + bgRgb(red: number, green: number, blue: number): this; + + /** + * Use HSL values to set background color. + */ + bgHsl(hue: number, saturation: number, lightness: number): this; + + /** + * Use HSV values to set background color. + */ + bgHsv(hue: number, saturation: number, value: number): this; + + /** + * Use HWB values to set background color. + */ + bgHwb(hue: number, whiteness: number, blackness: number): this; + + /** + * Modifier: Resets the current color chain. + */ + readonly reset: this; + + /** + * Modifier: Make text bold. + */ + readonly bold: this; + + /** + * Modifier: Emitting only a small amount of light. + */ + readonly dim: this; + + /** + * Modifier: Make text italic. (Not widely supported) + */ + readonly italic: this; + + /** + * Modifier: Make text underline. (Not widely supported) + */ + readonly underline: this; + + /** + * Modifier: Inverse background and foreground colors. + */ + readonly inverse: this; + + /** + * Modifier: Prints the text, but makes it invisible. + */ + readonly hidden: this; + + /** + * Modifier: Puts a horizontal line through the center of the text. (Not widely supported) + */ + readonly strikethrough: this; + + /** + * Modifier: Prints the text only when Chalk is enabled. + * Can be useful for things that are purely cosmetic. + */ + readonly visible: this; + + readonly black: this; + readonly red: this; + readonly green: this; + readonly yellow: this; + readonly blue: this; + readonly magenta: this; + readonly cyan: this; + readonly white: this; + readonly gray: this; + readonly grey: this; + readonly blackBright: this; + readonly redBright: this; + readonly greenBright: this; + readonly yellowBright: this; + readonly blueBright: this; + readonly magentaBright: this; + readonly cyanBright: this; + readonly whiteBright: this; + + readonly bgBlack: this; + readonly bgRed: this; + readonly bgGreen: this; + readonly bgYellow: this; + readonly bgBlue: this; + readonly bgMagenta: this; + readonly bgCyan: this; + readonly bgWhite: this; + readonly bgBlackBright: this; + readonly bgRedBright: this; + readonly bgGreenBright: this; + readonly bgYellowBright: this; + readonly bgBlueBright: this; + readonly bgMagentaBright: this; + readonly bgCyanBright: this; + readonly bgWhiteBright: this; +} + +/** + * Main Chalk object that allows to chain styles together. + * Call the last one as a method with a string argument. + * Order doesn't matter, and later styles take precedent in case of a conflict. + * This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`. + */ +declare const chalk: Chalk & { supportsColor: ColorSupport }; + +export default chalk; diff --git a/index.js b/index.js index 1cc5fa8..73598be 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,7 @@ 'use strict'; const escapeStringRegexp = require('escape-string-regexp'); const ansiStyles = require('ansi-styles'); -const stdoutColor = require('supports-color').stdout; - +const {stdout: stdoutColor} = require('supports-color'); const template = require('./templates.js'); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -15,13 +14,15 @@ const skipModels = new Set(['gray']); const styles = Object.create(null); -function applyOptions(obj, options) { - options = options || {}; +function applyOptions(object, options = {}) { + if (options.level > 3 || options.level < 0) { + throw new Error('The `level` option should be an integer from 0 to 3'); + } // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === undefined ? colorLevel : options.level; + object.enabled = 'enabled' in options ? options.enabled : object.level > 0; } function Chalk(options) { @@ -31,10 +32,7 @@ function Chalk(options) { const chalk = {}; applyOptions(chalk, options); - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; + chalk.template = (...args) => chalkTag(chalk.template, ...args); Object.setPrototypeOf(chalk, Chalk.prototype); Object.setPrototypeOf(chalk.template, chalk); @@ -58,7 +56,7 @@ for (const key of Object.keys(ansiStyles)) { styles[key] = { get() { const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + return build.call(this, [...(this._styles || []), codes], this._empty, key); } }; } @@ -77,15 +75,15 @@ for (const model of Object.keys(ansiStyles.color.ansi)) { styles[model] = { get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const {level} = this; + return function (...args) { + const open = ansiStyles.color[levelMapping[level]][model](...args); const codes = { open, close: ansiStyles.color.close, closeRe: ansiStyles.color.closeRe }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + return build.call(this, [...(this._styles || []), codes], this._empty, model); }; } }; @@ -100,15 +98,15 @@ for (const model of Object.keys(ansiStyles.bgColor.ansi)) { const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); styles[bgModel] = { get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const {level} = this; + return function (...args) { + const open = ansiStyles.bgColor[levelMapping[level]][model](...args); const codes = { open, close: ansiStyles.bgColor.close, closeRe: ansiStyles.bgColor.closeRe }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + return build.call(this, [...(this._styles || []), codes], this._empty, model); }; } }; @@ -117,10 +115,7 @@ for (const model of Object.keys(ansiStyles.bgColor.ansi)) { const proto = Object.defineProperties(() => {}, styles); function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; - + const builder = (...args) => applyStyle.call(builder, ...args); builder._styles = _styles; builder._empty = _empty; @@ -156,25 +151,11 @@ function build(_styles, _empty, key) { return builder; } -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); +function applyStyle(...args) { + let string = args.join(' '); - if (argsLen === 0) { - return ''; - } - - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } - - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; + if (!this.enabled || this.level <= 0 || !string) { + return this._empty ? '' : string; } // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, @@ -189,33 +170,37 @@ function applyStyle() { // Replace any instances already present with a re-opening code // otherwise only the part of the string until said closing code // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; + string = code.open + string.replace(code.closeRe, code.open) + code.close; // Close the styling before a linebreak and reopen // after next line to fix a bleed issue on macOS // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + string = string.replace(/\r?\n/g, `${code.close}$&${code.open}`); } // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue ansiStyles.dim.open = originalDim; - return str; + return string; } -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { +function chalkTag(chalk, ...strings) { + const [firstString] = strings; + + if (!Array.isArray(firstString)) { // If chalk() was called by itself or with a string, // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); + return strings.join(' '); } - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; + const args = strings.slice(1); + const parts = [firstString.raw[0]]; - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); + for (let i = 1; i < firstString.length; i++) { + parts.push( + String(args[i - 1]).replace(/[{}\\]/g, '\\$&'), + String(firstString.raw[i]) + ); } return template(chalk, parts.join('')); diff --git a/index.js.flow b/index.js.flow index 622caaa..1a3099d 100644 --- a/index.js.flow +++ b/index.js.flow @@ -9,7 +9,7 @@ export type Level = $Values<{ TrueColor: 3 }>; -export type ChalkOptions = {| +export type Options = {| enabled?: boolean, level?: Level |}; @@ -23,20 +23,20 @@ export type ColorSupport = {| export interface Chalk { (...text: string[]): string, - (text: TemplateStringsArray, ...placeholders: string[]): string, - constructor(options?: ChalkOptions): Chalk, + (text: TemplateStringsArray, ...placeholders: mixed[]): string, + constructor(options?: Options): Chalk, enabled: boolean, level: Level, - rgb(r: number, g: number, b: number): Chalk, - hsl(h: number, s: number, l: number): Chalk, - hsv(h: number, s: number, v: number): Chalk, - hwb(h: number, w: number, b: number): Chalk, + rgb(red: number, green: number, blue: number): Chalk, + hsl(hue: number, saturation: number, lightness: number): Chalk, + hsv(hue: number, saturation: number, value: number): Chalk, + hwb(hue: number, whiteness: number, blackness: number): Chalk, bgHex(color: string): Chalk, bgKeyword(color: string): Chalk, - bgRgb(r: number, g: number, b: number): Chalk, - bgHsl(h: number, s: number, l: number): Chalk, - bgHsv(h: number, s: number, v: number): Chalk, - bgHwb(h: number, w: number, b: number): Chalk, + bgRgb(red: number, green: number, blue: number): Chalk, + bgHsl(hue: number, saturation: number, lightness: number): Chalk, + bgHsv(hue: number, saturation: number, value: number): Chalk, + bgHwb(hue: number, whiteness: number, blackness: number): Chalk, hex(color: string): Chalk, keyword(color: string): Chalk, @@ -85,7 +85,7 @@ export interface Chalk { +bgBlueBright: Chalk, +bgMagentaBright: Chalk, +bgCyanBright: Chalk, - +bgWhiteBrigh: Chalk, + +bgWhiteBright: Chalk, supportsColor: ColorSupport }; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..5920526 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,138 @@ +import {expectType} from 'tsd-check'; +import chalk, {Level, Chalk, ColorSupport} from '.'; + +// - Helpers - +type colorReturn = Chalk & {supportsColor: ColorSupport}; + +// - Level - +expectType(Level.None); +expectType(Level.Basic); +expectType(Level.Ansi256); +expectType(Level.TrueColor); + +// - supportsColor - +expectType(chalk.supportsColor.hasBasic); +expectType(chalk.supportsColor.has256); +expectType(chalk.supportsColor.has16m); + +// - Chalk - +// -- Constructor -- +expectType(new chalk.constructor({level: 1})); + +// -- Properties -- +expectType(chalk.enabled); +expectType(chalk.level); + +// -- Template literal -- +expectType(chalk``); +const name = 'John'; +expectType(chalk`Hello {bold.red ${name}}`); +expectType(chalk`Works with numbers {bold.red ${1}}`); + +// -- Color methods -- +expectType(chalk.hex('#DEADED')); +expectType(chalk.keyword('orange')); +expectType(chalk.rgb(0, 0, 0)); +expectType(chalk.hsl(0, 0, 0)); +expectType(chalk.hsv(0, 0, 0)); +expectType(chalk.hwb(0, 0, 0)); +expectType(chalk.bgHex('#DEADED')); +expectType(chalk.bgKeyword('orange')); +expectType(chalk.bgRgb(0, 0, 0)); +expectType(chalk.bgHsl(0, 0, 0)); +expectType(chalk.bgHsv(0, 0, 0)); +expectType(chalk.bgHwb(0, 0, 0)); + +// -- Modifiers -- +expectType(chalk.reset('foo')); +expectType(chalk.bold('foo')); +expectType(chalk.dim('foo')); +expectType(chalk.italic('foo')); +expectType(chalk.underline('foo')); +expectType(chalk.inverse('foo')); +expectType(chalk.hidden('foo')); +expectType(chalk.strikethrough('foo')); +expectType(chalk.visible('foo')); +expectType(chalk.reset`foo`); +expectType(chalk.bold`foo`); +expectType(chalk.dim`foo`); +expectType(chalk.italic`foo`); +expectType(chalk.underline`foo`); +expectType(chalk.inverse`foo`); +expectType(chalk.hidden`foo`); +expectType(chalk.strikethrough`foo`); +expectType(chalk.visible`foo`); + +// -- Colors -- +expectType(chalk.black('foo')); +expectType(chalk.red('foo')); +expectType(chalk.green('foo')); +expectType(chalk.yellow('foo')); +expectType(chalk.blue('foo')); +expectType(chalk.magenta('foo')); +expectType(chalk.cyan('foo')); +expectType(chalk.white('foo')); +expectType(chalk.gray('foo')); +expectType(chalk.grey('foo')); +expectType(chalk.blackBright('foo')); +expectType(chalk.redBright('foo')); +expectType(chalk.greenBright('foo')); +expectType(chalk.yellowBright('foo')); +expectType(chalk.blueBright('foo')); +expectType(chalk.magentaBright('foo')); +expectType(chalk.cyanBright('foo')); +expectType(chalk.whiteBright('foo')); +expectType(chalk.bgBlack('foo')); +expectType(chalk.bgRed('foo')); +expectType(chalk.bgGreen('foo')); +expectType(chalk.bgYellow('foo')); +expectType(chalk.bgBlue('foo')); +expectType(chalk.bgMagenta('foo')); +expectType(chalk.bgCyan('foo')); +expectType(chalk.bgWhite('foo')); +expectType(chalk.bgBlackBright('foo')); +expectType(chalk.bgRedBright('foo')); +expectType(chalk.bgGreenBright('foo')); +expectType(chalk.bgYellowBright('foo')); +expectType(chalk.bgBlueBright('foo')); +expectType(chalk.bgMagentaBright('foo')); +expectType(chalk.bgCyanBright('foo')); +expectType(chalk.bgWhiteBright('foo')); +expectType(chalk.black`foo`); +expectType(chalk.red`foo`); +expectType(chalk.green`foo`); +expectType(chalk.yellow`foo`); +expectType(chalk.blue`foo`); +expectType(chalk.magenta`foo`); +expectType(chalk.cyan`foo`); +expectType(chalk.white`foo`); +expectType(chalk.gray`foo`); +expectType(chalk.grey`foo`); +expectType(chalk.blackBright`foo`); +expectType(chalk.redBright`foo`); +expectType(chalk.greenBright`foo`); +expectType(chalk.yellowBright`foo`); +expectType(chalk.blueBright`foo`); +expectType(chalk.magentaBright`foo`); +expectType(chalk.cyanBright`foo`); +expectType(chalk.whiteBright`foo`); +expectType(chalk.bgBlack`foo`); +expectType(chalk.bgRed`foo`); +expectType(chalk.bgGreen`foo`); +expectType(chalk.bgYellow`foo`); +expectType(chalk.bgBlue`foo`); +expectType(chalk.bgMagenta`foo`); +expectType(chalk.bgCyan`foo`); +expectType(chalk.bgWhite`foo`); +expectType(chalk.bgBlackBright`foo`); +expectType(chalk.bgRedBright`foo`); +expectType(chalk.bgGreenBright`foo`); +expectType(chalk.bgYellowBright`foo`); +expectType(chalk.bgBlueBright`foo`); +expectType(chalk.bgMagentaBright`foo`); +expectType(chalk.bgCyanBright`foo`); +expectType(chalk.bgWhiteBright`foo`); + +// -- Complex -- +expectType(chalk.red.bgGreen.underline('foo')); +expectType(chalk.underline.red.bgGreen('foo')); diff --git a/package.json b/package.json index bc32468..82d1b9a 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,16 @@ "license": "MIT", "repository": "chalk/chalk", "engines": { - "node": ">=4" + "node": ">=6" }, "scripts": { - "test": "xo && tsc --project types && flow --max-warnings=0 && nyc ava", - "bench": "matcha benchmark.js", - "coveralls": "nyc report --reporter=text-lcov | coveralls" + "test": "xo && nyc ava && tsd-check && flow", + "bench": "matcha benchmark.js" }, "files": [ "index.js", "templates.js", - "types/index.d.ts", + "index.d.ts", "index.js.flow" ], "keywords": [ @@ -44,26 +43,23 @@ "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "supports-color": "^6.0.0" }, "devDependencies": { - "ava": "*", - "coveralls": "^3.0.0", - "execa": "^0.9.0", - "flow-bin": "^0.68.0", - "import-fresh": "^2.0.0", + "@sindresorhus/tsconfig": "^0.1.1", + "ava": "^1.0.1", + "coveralls": "^3.0.2", + "execa": "^1.0.0", + "flow-bin": "^0.89.0", + "import-fresh": "^3.0.0", "matcha": "^0.7.0", - "nyc": "^11.0.2", + "nyc": "^13.1.0", "resolve-from": "^4.0.0", - "typescript": "^2.5.3", - "xo": "*" + "tsd-check": "^0.3.0", + "xo": "^0.23.0" }, - "types": "types/index.d.ts", + "types": "index.d.ts", "xo": { - "envs": [ - "node", - "mocha" - ], "ignores": [ "test/_flow.js" ] diff --git a/readme.md b/readme.md index d298e2c..d8ca070 100644 --- a/readme.md +++ b/readme.md @@ -9,11 +9,23 @@ > Terminal string styling done right -[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs) +[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![npm dependents](https://badgen.net/npm/dependents/chalk)](https://www.npmjs.com/package/chalk?activeTab=dependents) [![Downloads](https://badgen.net/npm/dt/chalk)](https://www.npmjs.com/package/chalk) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) ![TypeScript- and Flow-ready](https://img.shields.io/npm/types/chalk.svg) -### [See what's new in Chalk 2](https://github.com/chalk/chalk/releases/tag/v2.0.0) + - +--- + +
+ + Get professional support for Chalk with a Tidelift subscription + +
+ + Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. +
+
+ +--- ## Highlights @@ -26,7 +38,7 @@ - Doesn't extend `String.prototype` - Clean and focused - Actively maintained -- [Used by ~23,000 packages](https://www.npmjs.com/browse/depended/chalk) as of December 31, 2017 +- [Used by ~30,000 packages](https://www.npmjs.com/browse/depended/chalk) as of August 6, 2018 ## Install @@ -126,7 +138,7 @@ Multiple arguments will be separated by space. ### chalk.enabled -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. +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`. @@ -138,7 +150,7 @@ const ctx = new chalk.constructor({enabled: false}); ### chalk.level -Color support is automatically detected, but you can override it by setting the `level` property. You should however only do this in your own code as it applies globally to all Chalk consumers. +Color support is automatically detected, but you can override it by setting the `level` property. You should however only do this in your own code as it applies globally to all Chalk consumers. When `chalk.level` is greater than `0`, `chalk.enabled` must *also* be `true` for colored output to be produced. If you need to change this in a reusable module, create a new instance: @@ -166,15 +178,15 @@ Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color= ### Modifiers -- `reset` -- `bold` -- `dim` -- `italic` *(Not widely supported)* -- `underline` -- `inverse` -- `hidden` -- `strikethrough` *(Not widely supported)* -- `visible` (Text is emitted only if enabled) +- `reset` - Resets the current color chain. +- `bold` - Make text bold. +- `dim` - Emitting only a small amount of light. +- `italic` - Make text italic. *(Not widely supported)* +- `underline` - Make text underline. *(Not widely supported)* +- `inverse`- Inverse background and foreground colors. +- `hidden` - Prints the text, but makes it invisible. +- `strikethrough` - Puts a horizontal line through the center of the text. *(Not widely supported)* +- `visible`- Prints the text only when Chalk is enabled. Can be useful for things that are purely cosmetic. ### Colors @@ -285,6 +297,11 @@ If you're on Windows, do yourself a favor and use [`cmder`](http://cmder.net/) i [colors.js](https://github.com/Marak/colors.js) used to be the most popular string styling module, but it has serious deficiencies like extending `String.prototype` which causes all kinds of [problems](https://github.com/yeoman/yo/issues/68) and the package is unmaintained. Although there are other packages, they either do too much or not enough. Chalk is a clean and focused alternative. +## Security + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. + + ## Related - [chalk-cli](https://github.com/chalk/chalk-cli) - CLI for this module diff --git a/templates.js b/templates.js index dbdf9b2..bbc614d 100644 --- a/templates.js +++ b/templates.js @@ -31,10 +31,11 @@ function parseArguments(name, args) { let matches; for (const chunk of chunks) { - if (!isNaN(chunk)) { - results.push(Number(chunk)); + const number = Number(chunk); + if (!Number.isNaN(number)) { + results.push(number); } else if ((matches = chunk.match(STRING_REGEX))) { - results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character)); } else { throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); } @@ -50,7 +51,7 @@ function parseStyle(style) { let matches; while ((matches = STYLE_REGEX.exec(style)) !== null) { - const name = matches[1]; + const name = matches[1]; // eslint-disable-line prefer-destructuring if (matches[2]) { const args = parseArguments(name, matches[2]); @@ -73,17 +74,20 @@ function buildStyle(chalk, styles) { } let current = chalk; + // TODO: Use `Object.entries` when targeting Node.js 8 for (const styleName of Object.keys(enabled)) { - if (Array.isArray(enabled[styleName])) { - if (!(styleName in current)) { - throw new Error(`Unknown Chalk style: ${styleName}`); - } + if (!Array.isArray(enabled[styleName])) { + continue; + } - if (enabled[styleName].length > 0) { - current = current[styleName].apply(current, enabled[styleName]); - } else { - current = current[styleName]; - } + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + if (enabled[styleName].length > 0) { + current = current[styleName](...enabled[styleName]); + } else { + current = current[styleName]; } } @@ -96,13 +100,13 @@ module.exports = (chalk, tmp) => { let chunk = []; // eslint-disable-next-line max-params - tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { - if (escapeChar) { - chunk.push(unescape(escapeChar)); + tmp.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => { + if (escapeCharacter) { + chunk.push(unescape(escapeCharacter)); } else if (style) { - const str = chunk.join(''); + const string = chunk.join(''); chunk = []; - chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); + chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string)); styles.push({inverse, styles: parseStyle(style)}); } else if (close) { if (styles.length === 0) { @@ -113,7 +117,7 @@ module.exports = (chalk, tmp) => { chunk = []; styles.pop(); } else { - chunk.push(chr); + chunk.push(character); } }); diff --git a/test/_flow.js b/test/_flow.js index 2cf3cf2..ac76e85 100644 --- a/test/_flow.js +++ b/test/_flow.js @@ -9,10 +9,6 @@ chalk.constructor({level: 1}); new chalk.constructor({enabled: 'true'}); new chalk.constructor({enabled: true}); -// $ExpectError (Can't pass in null) -chalk.underline(null); -chalk.underline('foo'); - // $ExpectError (Can't have typo in chalk method) chalk.rd('foo'); chalk.red('foo'); @@ -29,10 +25,6 @@ chalk.red.bgBlue.underline('foo'); const badCtx = chalk.constructor({level: 4}); const ctx = chalk.constructor({level: 3}); -// $ExpectError (Can't pass in null) -ctx(null); -ctx('foo'); - // $ExpectError (Can't have typo in method name) ctx.gry('foo'); ctx.grey('foo'); diff --git a/test/_supports-color.js b/test/_supports-color.js index cfca6cc..490c323 100644 --- a/test/_supports-color.js +++ b/test/_supports-color.js @@ -1,15 +1,15 @@ 'use strict'; const resolveFrom = require('resolve-from'); -module.exports = dir => { - require.cache[resolveFrom(dir, 'supports-color')] = { - exports: { - stdout: { - level: 3, - hasBasic: true, - has256: true, - has16m: true - } - } - }; +const DEFAULT = { + stdout: { + level: 3, + hasBasic: true, + has256: true, + has16m: true + } +}; + +module.exports = (dir, override) => { + require.cache[resolveFrom(dir, 'supports-color')] = {exports: override || DEFAULT}; }; diff --git a/test/chalk.js b/test/chalk.js index 8480dad..a53b6f5 100644 --- a/test/chalk.js +++ b/test/chalk.js @@ -3,51 +3,50 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); console.log('TERM:', process.env.TERM || '[none]'); console.log('platform:', process.platform || '[unknown]'); test('don\'t add any styling when called as the base function', t => { - t.is(m('foo'), 'foo'); + t.is(chalk('foo'), 'foo'); }); test('support multiple arguments in base function', t => { - t.is(m('hello', 'there'), 'hello there'); + t.is(chalk('hello', 'there'), 'hello there'); }); test('style string', t => { - t.is(m.underline('foo'), '\u001B[4mfoo\u001B[24m'); - t.is(m.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(m.bgRed('foo'), '\u001B[41mfoo\u001B[49m'); + t.is(chalk.underline('foo'), '\u001B[4mfoo\u001B[24m'); + t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(chalk.bgRed('foo'), '\u001B[41mfoo\u001B[49m'); }); test('support applying multiple styles at once', t => { - t.is(m.red.bgGreen.underline('foo'), '\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m'); - t.is(m.underline.red.bgGreen('foo'), '\u001B[4m\u001B[31m\u001B[42mfoo\u001B[49m\u001B[39m\u001B[24m'); + t.is(chalk.red.bgGreen.underline('foo'), '\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m'); + t.is(chalk.underline.red.bgGreen('foo'), '\u001B[4m\u001B[31m\u001B[42mfoo\u001B[49m\u001B[39m\u001B[24m'); }); test('support nesting styles', t => { t.is( - m.red('foo' + m.underline.bgBlue('bar') + '!'), + chalk.red('foo' + chalk.underline.bgBlue('bar') + '!'), '\u001B[31mfoo\u001B[4m\u001B[44mbar\u001B[49m\u001B[24m!\u001B[39m' ); }); test('support nesting styles of the same type (color, underline, bg)', t => { t.is( - m.red('a' + m.yellow('b' + m.green('c') + 'b') + 'c'), + chalk.red('a' + chalk.yellow('b' + chalk.green('c') + 'b') + 'c'), '\u001B[31ma\u001B[33mb\u001B[32mc\u001B[33mb\u001B[31mc\u001B[39m' ); }); test('reset all styles with `.reset()`', t => { - t.is(m.reset(m.red.bgGreen.underline('foo') + 'foo'), '\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m'); + t.is(chalk.reset(chalk.red.bgGreen.underline('foo') + 'foo'), '\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m'); }); test('support caching multiple styles', t => { - const red = m.red; - const green = m.green; + const {red, green} = chalk.red; const redBold = red.bold; const greenBold = green.bold; @@ -57,44 +56,44 @@ test('support caching multiple styles', t => { }); test('alias gray to grey', t => { - t.is(m.grey('foo'), '\u001B[90mfoo\u001B[39m'); + t.is(chalk.grey('foo'), '\u001B[90mfoo\u001B[39m'); }); test('support variable number of arguments', t => { - t.is(m.red('foo', 'bar'), '\u001B[31mfoo bar\u001B[39m'); + t.is(chalk.red('foo', 'bar'), '\u001B[31mfoo bar\u001B[39m'); }); test('support falsy values', t => { - t.is(m.red(0), '\u001B[31m0\u001B[39m'); + t.is(chalk.red(0), '\u001B[31m0\u001B[39m'); }); test('don\'t output escape codes if the input is empty', t => { - t.is(m.red(), ''); - t.is(m.red.blue.black(), ''); + t.is(chalk.red(), ''); + t.is(chalk.red.blue.black(), ''); }); test('keep Function.prototype methods', t => { - t.is(m.grey.apply(null, ['foo']), '\u001B[90mfoo\u001B[39m'); - t.is(m.reset(m.red.bgGreen.underline.bind(null)('foo') + 'foo'), '\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m'); - t.is(m.red.blue.black.call(null), ''); + t.is(chalk.grey.apply(null, ['foo']), '\u001B[90mfoo\u001B[39m'); + t.is(chalk.reset(chalk.red.bgGreen.underline.bind(null)('foo') + 'foo'), '\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m'); + t.is(chalk.red.blue.black.call(null), ''); }); test('line breaks should open and close colors', t => { - t.is(m.grey('hello\nworld'), '\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m'); + t.is(chalk.grey('hello\nworld'), '\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m'); }); test('properly convert RGB to 16 colors on basic color terminals', t => { - t.is(new m.constructor({level: 1}).hex('#FF0000')('hello'), '\u001B[91mhello\u001B[39m'); - t.is(new m.constructor({level: 1}).bgHex('#FF0000')('hello'), '\u001B[101mhello\u001B[49m'); + 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'); }); test('properly convert RGB to 256 colors on basic color terminals', t => { - t.is(new m.constructor({level: 2}).hex('#FF0000')('hello'), '\u001B[38;5;196mhello\u001B[39m'); - t.is(new m.constructor({level: 2}).bgHex('#FF0000')('hello'), '\u001B[48;5;196mhello\u001B[49m'); - t.is(new m.constructor({level: 3}).bgHex('#FF0000')('hello'), '\u001B[48;2;255;0;0mhello\u001B[49m'); + 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'); }); test('don\'t emit RGB codes if level is 0', t => { - t.is(new m.constructor({level: 0}).hex('#FF0000')('hello'), 'hello'); - t.is(new m.constructor({level: 0}).bgHex('#FF0000')('hello'), 'hello'); + t.is(new chalk.constructor({level: 0}).hex('#FF0000')('hello'), 'hello'); + t.is(new chalk.constructor({level: 0}).bgHex('#FF0000')('hello'), 'hello'); }); diff --git a/test/constructor.js b/test/constructor.js index d592e82..bda0137 100644 --- a/test/constructor.js +++ b/test/constructor.js @@ -3,20 +3,32 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); test('create an isolated context where colors can be disabled (by level)', t => { - const ctx = new m.constructor({level: 0, enabled: true}); - t.is(ctx.red('foo'), 'foo'); - t.is(m.red('foo'), '\u001B[31mfoo\u001B[39m'); - ctx.level = 2; - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); + 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('create an isolated context where colors can be disabled (by enabled flag)', t => { - const ctx = new m.constructor({enabled: false}); - t.is(ctx.red('foo'), 'foo'); - t.is(m.red('foo'), '\u001B[31mfoo\u001B[39m'); - ctx.enabled = true; - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); + 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.throws(() => { + new chalk.constructor({level: -1}); + }, /should be an integer from 0 to 3/); + /* eslint-enable no-new */ }); diff --git a/test/enabled.js b/test/enabled.js index 3270e54..e596621 100644 --- a/test/enabled.js +++ b/test/enabled.js @@ -3,33 +3,33 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); test('don\'t output colors when manually disabled', t => { - m.enabled = false; - t.is(m.red('foo'), 'foo'); - m.enabled = true; + chalk.enabled = false; + t.is(chalk.red('foo'), 'foo'); + chalk.enabled = true; }); test('enable/disable colors based on overall chalk enabled property, not individual instances', t => { - m.enabled = false; - const red = m.red; + chalk.enabled = false; + const {red} = chalk; t.false(red.enabled); - m.enabled = true; + chalk.enabled = true; t.true(red.enabled); - m.enabled = true; + chalk.enabled = true; }); test('propagate enable/disable changes from child colors', t => { - m.enabled = false; - const red = m.red; + chalk.enabled = false; + const {red} = chalk; t.false(red.enabled); - t.false(m.enabled); + t.false(chalk.enabled); red.enabled = true; t.true(red.enabled); - t.true(m.enabled); - m.enabled = false; + t.true(chalk.enabled); + chalk.enabled = false; t.false(red.enabled); - t.false(m.enabled); - m.enabled = true; + t.false(chalk.enabled); + chalk.enabled = true; }); diff --git a/test/level.js b/test/level.js index 85624ea..06fdb59 100644 --- a/test/level.js +++ b/test/level.js @@ -5,38 +5,38 @@ import execa from 'execa'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); test('don\'t output colors when manually disabled', t => { - const oldLevel = m.level; - m.level = 0; - t.is(m.red('foo'), 'foo'); - m.level = oldLevel; + const oldLevel = chalk.level; + chalk.level = 0; + t.is(chalk.red('foo'), 'foo'); + chalk.level = oldLevel; }); test('enable/disable colors based on overall chalk enabled property, not individual instances', t => { - const oldLevel = m.level; - m.level = 1; - const red = m.red; + const oldLevel = chalk.level; + chalk.level = 1; + const {red} = chalk; t.is(red.level, 1); - m.level = 0; - t.is(red.level, m.level); - m.level = oldLevel; + chalk.level = 0; + t.is(red.level, chalk.level); + chalk.level = oldLevel; }); test('propagate enable/disable changes from child colors', t => { - const oldLevel = m.level; - m.level = 1; - const red = m.red; + const oldLevel = chalk.level; + chalk.level = 1; + const {red} = chalk; t.is(red.level, 1); - t.is(m.level, 1); + t.is(chalk.level, 1); red.level = 0; t.is(red.level, 0); - t.is(m.level, 0); - m.level = 1; + t.is(chalk.level, 0); + chalk.level = 1; t.is(red.level, 1); - t.is(m.level, 1); - m.level = oldLevel; + t.is(chalk.level, 1); + chalk.level = oldLevel; }); test('disable colors if they are not supported', async t => { diff --git a/test/no-color-support.js b/test/no-color-support.js new file mode 100644 index 0000000..90d934d --- /dev/null +++ b/test/no-color-support.js @@ -0,0 +1,16 @@ +import test from 'ava'; + +// Spoof supports-color +require('./_supports-color')(__dirname, { + level: 0, + hasBasic: false, + has256: false, + has16m: false +}); + +const chalk = require('..'); + +test.failing('colors can be forced by using chalk.enabled', t => { + chalk.enabled = true; + t.is(chalk.green('hello'), '\u001B[32mhello\u001B[39m'); +}); diff --git a/test/template-literal.js b/test/template-literal.js index 71cc106..34588d2 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -4,82 +4,82 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); test('return an empty string for an empty literal', t => { - const ctx = m.constructor(); - t.is(ctx``, ''); + const instance = chalk.constructor(); + t.is(instance``, ''); }); test('return a regular string for a literal with no templates', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`hello`, 'hello'); + const instance = chalk.constructor({level: 0}); + t.is(instance`hello`, 'hello'); }); test('correctly perform template parsing', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`{bold Hello, {cyan World!} This is a} test. {green Woo!}`, - ctx.bold('Hello,', ctx.cyan('World!'), 'This is a') + ' test. ' + ctx.green('Woo!')); + const instance = chalk.constructor({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 ctx = m.constructor({level: 0}); + const instance = chalk.constructor({level: 0}); const name = 'Sindre'; const exclamation = 'Neat'; - t.is(ctx`{bold Hello, {cyan.inverse ${name}!} This is a} test. {green ${exclamation}!}`, - ctx.bold('Hello,', ctx.cyan.inverse(name + '!'), 'This is a') + ' test. ' + ctx.green(exclamation + '!')); + 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 ctx = m.constructor({level: 3}); - t.is(ctx`{bold.rgb(144,10,178).inverse Hello, {~inverse there!}}`, + const instance = chalk.constructor({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' + '\u001B[38;2;144;10;178mthere!\u001B[39m\u001B[22m'); - t.is(ctx`{bold.bgRgb(144,10,178).inverse Hello, {~inverse there!}}`, + t.is(instance`{bold.bgRgb(144,10,178).inverse Hello, {~inverse there!}}`, '\u001B[1m\u001B[48;2;144;10;178m\u001B[7mHello, ' + '\u001B[27m\u001B[49m\u001B[22m\u001B[1m' + '\u001B[48;2;144;10;178mthere!\u001B[49m\u001B[22m'); }); test('properly handle escapes', t => { - const ctx = m.constructor({level: 3}); - t.is(ctx`{bold hello \{in brackets\}}`, + const instance = chalk.constructor({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 ctx = m.constructor({level: 3}); + const instance = chalk.constructor({level: 3}); try { - console.log(ctx`{bold this shouldn't appear ever\}`); + console.log(instance`{bold this shouldn't appear ever\}`); t.fail(); - } catch (err) { - t.is(err.message, 'Chalk template literal is missing 1 closing bracket (`}`)'); + } catch (error) { + t.is(error.message, 'Chalk template literal is missing 1 closing bracket (`}`)'); } try { - console.log(ctx`{bold this shouldn't {inverse appear {underline ever\} :) \}`); + console.log(instance`{bold this shouldn't {inverse appear {underline ever\} :) \}`); t.fail(); - } catch (err) { - t.is(err.message, 'Chalk template literal is missing 3 closing brackets (`}`)'); + } catch (error) { + t.is(error.message, 'Chalk template literal is missing 3 closing brackets (`}`)'); } }); test('throw if there is an invalid style', t => { - const ctx = m.constructor({level: 3}); + const instance = chalk.constructor({level: 3}); try { - console.log(ctx`{abadstylethatdoesntexist this shouldn't appear ever}`); + console.log(instance`{abadstylethatdoesntexist this shouldn't appear ever}`); t.fail(); - } catch (err) { - t.is(err.message, 'Unknown Chalk style: abadstylethatdoesntexist'); + } catch (error) { + t.is(error.message, 'Unknown Chalk style: abadstylethatdoesntexist'); } }); test('properly style multiline color blocks', t => { - const ctx = m.constructor({level: 3}); + const instance = chalk.constructor({level: 3}); t.is( - ctx`{bold + instance`{bold Hello! This is a ${'multiline'} block! :) @@ -97,74 +97,74 @@ test('properly style multiline color blocks', t => { }); test('escape interpolated values', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`Hello {bold hi}`, 'Hello hi'); - t.is(ctx`Hello ${'{bold hi}'}`, 'Hello {bold hi}'); + const instance = chalk.constructor({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 ctx = m.constructor({level: 3}); - ctx.rose = ctx.hex('#F6D9D9'); - t.is(ctx`Hello, {rose Rose}.`, 'Hello, \u001B[38;2;246;217;217mRose\u001B[39m.'); + const instance = chalk.constructor({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 ctx = m.constructor({level: 0}); - t.is(ctx`Hello + const instance = chalk.constructor({level: 0}); + t.is(instance`Hello {red there}`, 'Hello\nthere'); }); test('correctly parse newline escapes (bug #177)', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`Hello\nthere!`, `Hello\nthere!`); + const instance = chalk.constructor({level: 0}); + t.is(instance`Hello\nthere!`, 'Hello\nthere!'); }); test('correctly parse escape in parameters (bug #177 comment 318622809)', t => { - const ctx = m.constructor({level: 0}); + const instance = chalk.constructor({level: 0}); const str = '\\'; - t.is(ctx`{blue ${str}}`, '\\'); + t.is(instance`{blue ${str}}`, '\\'); }); test('correctly parses unicode/hex escapes', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`\u0078ylophones are fo\x78y! {magenta.inverse \u0078ylophones are fo\x78y!}`, + const instance = chalk.constructor({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 ctx = m.constructor({level: 3}); - t.is(ctx`{keyword('black').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m'); - t.is(ctx`{keyword('blac\x6B').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m'); - t.is(ctx`{keyword('blac\u006B').bold can haz cheezburger}`, '\u001B[38;2;0;0;0m\u001B[1mcan haz cheezburger\u001B[22m\u001B[39m'); + const instance = chalk.constructor({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 ctx = m.constructor({level: 3}); // Keep level at least 1 in case we optimize for disabled chalk instances + const instance = chalk.constructor({level: 3}); // Keep level at least 1 in case we optimize for disabled chalk instances try { - console.log(ctx`{keyword(????) hi}`); + console.log(instance`{keyword(????) hi}`); t.fail(); - } catch (err) { - t.is(err.message, 'Invalid Chalk template style argument: ???? (in style \'keyword\')'); + } catch (error) { + t.is(error.message, 'Invalid Chalk template style argument: ???? (in style \'keyword\')'); } }); test('throws if an extra unescaped } is found', t => { - const ctx = m.constructor({level: 0}); + const instance = chalk.constructor({level: 0}); try { - console.log(ctx`{red hi!}}`); + console.log(instance`{red hi!}}`); t.fail(); - } catch (err) { - t.is(err.message, 'Found extraneous } in Chalk template literal'); + } catch (error) { + t.is(error.message, 'Found extraneous } in Chalk template literal'); } }); test('should not parse upper-case escapes', t => { - const ctx = m.constructor({level: 0}); - t.is(ctx`\N\n\T\t\X07\x07\U000A\u000A\U000a\u000a`, 'N\nT\tX07\x07U000A\u000AU000a\u000A'); + const instance = chalk.constructor({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 ctx = m.constructor({level: 0}); - t.is(ctx`hello ${undefined}`, 'hello undefined'); - t.is(ctx`hello ${null}`, 'hello null'); + const instance = chalk.constructor({level: 0}); + t.is(instance`hello ${undefined}`, 'hello undefined'); + t.is(instance`hello ${null}`, 'hello null'); }); diff --git a/test/visible.js b/test/visible.js index 5d53bce..3266295 100644 --- a/test/visible.js +++ b/test/visible.js @@ -3,45 +3,45 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const m = require('..'); +const chalk = require('..'); test('visible: normal output when enabled', t => { - const ctx = new m.constructor({level: 3, enabled: true}); - t.is(ctx.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); + const instance = new chalk.constructor({level: 3, enabled: true}); + t.is(instance.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); }); test('visible: no output when disabled', t => { - const ctx = new m.constructor({level: 3, enabled: false}); - t.is(ctx.red.visible('foo'), ''); - t.is(ctx.visible.red('foo'), ''); + const instance = new chalk.constructor({level: 3, enabled: false}); + t.is(instance.red.visible('foo'), ''); + t.is(instance.visible.red('foo'), ''); }); test('visible: no output when level is too low', t => { - const ctx = new m.constructor({level: 0, enabled: true}); - t.is(ctx.visible.red('foo'), ''); - t.is(ctx.red.visible('foo'), ''); + const instance = new chalk.constructor({level: 0, enabled: true}); + t.is(instance.visible.red('foo'), ''); + t.is(instance.red.visible('foo'), ''); }); test('test switching back and forth between enabled and disabled', t => { - const ctx = new m.constructor({level: 3, enabled: true}); - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.visible('foo'), 'foo'); - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); + const instance = new chalk.constructor({level: 3, enabled: true}); + t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.visible('foo'), 'foo'); + t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m'); - ctx.enabled = false; - t.is(ctx.red('foo'), 'foo'); - t.is(ctx.visible('foo'), ''); - t.is(ctx.visible.red('foo'), ''); - t.is(ctx.red.visible('foo'), ''); - t.is(ctx.red('foo'), 'foo'); + instance.enabled = false; + t.is(instance.red('foo'), 'foo'); + t.is(instance.visible('foo'), ''); + t.is(instance.visible.red('foo'), ''); + t.is(instance.red.visible('foo'), ''); + t.is(instance.red('foo'), 'foo'); - ctx.enabled = true; - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); - t.is(ctx.visible('foo'), 'foo'); - t.is(ctx.red('foo'), '\u001B[31mfoo\u001B[39m'); + instance.enabled = true; + t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.visible.red('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.red.visible('foo'), '\u001B[31mfoo\u001B[39m'); + t.is(instance.visible('foo'), 'foo'); + t.is(instance.red('foo'), '\u001B[31mfoo\u001B[39m'); }); diff --git a/test/windows.js b/test/windows.js index 2f2815e..3ede386 100644 --- a/test/windows.js +++ b/test/windows.js @@ -28,36 +28,36 @@ test.beforeEach(() => { test('detect a simple term if TERM isn\'t set', t => { delete process.env.TERM; - const m = importFresh('..'); - t.is(m.blue('foo'), '\u001B[94mfoo\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.blue('foo'), '\u001B[94mfoo\u001B[39m'); }); test('replace blue foreground color in cmd.exe', t => { process.env.TERM = 'dumb'; - const m = importFresh('..'); - t.is(m.blue('foo'), '\u001B[94mfoo\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.blue('foo'), '\u001B[94mfoo\u001B[39m'); }); test('don\'t replace blue foreground color in xterm based terminals', t => { process.env.TERM = 'xterm-256color'; - const m = importFresh('..'); - t.is(m.blue('foo'), '\u001B[34mfoo\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.blue('foo'), '\u001B[34mfoo\u001B[39m'); }); test('don\'t apply dimmed styling on gray strings, see https://github.com/chalk/chalk/issues/58', t => { process.env.TERM = 'dumb'; - const m = importFresh('..'); - t.is(m.gray.dim('foo'), '\u001B[90mfoo\u001B[22m\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.gray.dim('foo'), '\u001B[90mfoo\u001B[22m\u001B[39m'); }); test('apply dimmed styling on xterm compatible terminals', t => { process.env.TERM = 'xterm'; - const m = importFresh('..'); - t.is(m.gray.dim('foo'), '\u001B[90m\u001B[2mfoo\u001B[22m\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.gray.dim('foo'), '\u001B[90m\u001B[2mfoo\u001B[22m\u001B[39m'); }); test('apply dimmed styling on strings of other colors', t => { process.env.TERM = 'dumb'; - const m = importFresh('..'); - t.is(m.blue.dim('foo'), '\u001B[94m\u001B[2mfoo\u001B[22m\u001B[39m'); + const chalk = importFresh('..'); + t.is(chalk.blue.dim('foo'), '\u001B[94m\u001B[2mfoo\u001B[22m\u001B[39m'); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3d73ee9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@sindresorhus/tsconfig", + "compilerOptions": { + "noEmit": true, + "allowJs": true + } +} diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index b4e4dc5..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Type definitions for Chalk -// Definitions by: Thomas Sauer - -export const enum Level { - None = 0, - Basic = 1, - Ansi256 = 2, - TrueColor = 3 -} - -export interface ChalkOptions { - enabled?: boolean; - level?: Level; -} - -export interface ChalkConstructor { - new (options?: ChalkOptions): Chalk; - (options?: ChalkOptions): Chalk; -} - -export interface ColorSupport { - level: Level; - hasBasic: boolean; - has256: boolean; - has16m: boolean; -} - -export interface Chalk { - (...text: string[]): string; - (text: TemplateStringsArray, ...placeholders: string[]): string; - constructor: ChalkConstructor; - enabled: boolean; - level: Level; - rgb(r: number, g: number, b: number): this; - hsl(h: number, s: number, l: number): this; - hsv(h: number, s: number, v: number): this; - hwb(h: number, w: number, b: number): this; - bgHex(color: string): this; - bgKeyword(color: string): this; - bgRgb(r: number, g: number, b: number): this; - bgHsl(h: number, s: number, l: number): this; - bgHsv(h: number, s: number, v: number): this; - bgHwb(h: number, w: number, b: number): this; - hex(color: string): this; - keyword(color: string): this; - - readonly reset: this; - readonly bold: this; - readonly dim: this; - readonly italic: this; - readonly underline: this; - readonly inverse: this; - readonly hidden: this; - readonly strikethrough: this; - - readonly visible: this; - - readonly black: this; - readonly red: this; - readonly green: this; - readonly yellow: this; - readonly blue: this; - readonly magenta: this; - readonly cyan: this; - readonly white: this; - readonly gray: this; - readonly grey: this; - readonly blackBright: this; - readonly redBright: this; - readonly greenBright: this; - readonly yellowBright: this; - readonly blueBright: this; - readonly magentaBright: this; - readonly cyanBright: this; - readonly whiteBright: this; - - readonly bgBlack: this; - readonly bgRed: this; - readonly bgGreen: this; - readonly bgYellow: this; - readonly bgBlue: this; - readonly bgMagenta: this; - readonly bgCyan: this; - readonly bgWhite: this; - readonly bgBlackBright: this; - readonly bgRedBright: this; - readonly bgGreenBright: this; - readonly bgYellowBright: this; - readonly bgBlueBright: this; - readonly bgMagentaBright: this; - readonly bgCyanBright: this; - readonly bgWhiteBright: this; -} - -declare const chalk: Chalk & { supportsColor: ColorSupport }; - -export default chalk diff --git a/types/test.ts b/types/test.ts deleted file mode 100644 index cedb39a..0000000 --- a/types/test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import chalk, {Level} from '..'; - -chalk.underline('foo'); -chalk.red('foo'); -chalk.bgRed('foo'); - -const name = 'Josh'; -chalk`Hello {bold.red ${name}}`; - -chalk.red`foo`; -chalk.underline`foo`; -chalk`foo`; - -chalk.red.bgGreen.underline('foo'); -chalk.underline.red.bgGreen('foo'); - -chalk.grey('foo'); - -chalk.constructor({level: 1}); -const ctx = chalk.constructor({level: Level.TrueColor }); -ctx('foo'); -ctx.red('foo'); -ctx`foo`; - -chalk.enabled = true; -chalk.level = 1; -chalk.level = Level.Ansi256; - -chalk.level === Level.Ansi256; - -let chalkInstance = new chalk.constructor(); -chalkInstance = chalk.constructor(); - -chalkInstance.blue('foo'); -chalkInstance`foo`; - -let x = 'imastring'; -x = chalk(); - -chalk.enabled; -chalk.level; -chalk.supportsColor.level; -chalk.supportsColor.has16m; -chalk.supportsColor.has256; -chalk.supportsColor.hasBasic; - -chalk.keyword('orange').bgBlue('foo'); -chalk.hex('#123456').bgBlue('foo'); -chalk.rgb(1, 14, 9).bgBlue('foo'); -chalk.hsl(1, 14, 9).bgBlue('foo'); -chalk.hsv(1, 14, 9).bgBlue('foo'); -chalk.hwb(1, 14, 9).bgBlue('foo'); - -chalk.visible('foo'); -chalk.red.visible('foo'); -chalk.visible.red('foo'); diff --git a/types/tsconfig.json b/types/tsconfig.json deleted file mode 100644 index b73840f..0000000 --- a/types/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "noImplicitAny": true, - "noEmit": true, - "allowJs": true - } -}