From 47e5e8139abf2b6011b91ff15497a13a6957ca37 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Sat, 23 Sep 2023 15:46:06 -0700 Subject: [PATCH 1/4] internal: factor colors into style object --- lib/index.js | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/index.js b/lib/index.js index e0a61a0..bc7560c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,6 +8,17 @@ * @property {boolean | null | undefined} [showPositions=true] * Whether to include positional information (default: `true`). * + * @typedef Style + * Styling functions. + * @property {(_: string) => string} bold + * Style a node type. + * @property {(_: string) => string} dim + * Style structural punctuation. + * @property {(_: string) => string} yellow + * Style the numeric count of node children. + * @property {(_: string) => string} green + * Style a non-tree value. + * * @typedef State * Info passed around. * @property {boolean} showPositions @@ -31,10 +42,15 @@ export const inspect = color ? inspectColor : inspectNoColor const own = {}.hasOwnProperty -const bold = ansiColor(1, 22) -const dim = ansiColor(2, 22) -const yellow = ansiColor(33, 39) -const green = ansiColor(32, 39) +/** + * @type Style + */ +const style = { + bold: ansiColor(1, 22), + dim: ansiColor(2, 22), + yellow: ansiColor(33, 39), + green: ansiColor(32, 39) +} // ANSI color regex. /* eslint-disable no-control-regex */ @@ -132,7 +148,7 @@ function inspectNodes(nodes, state) { while (++index < nodes.length) { result.push( - dim( + style.dim( (index < nodes.length - 1 ? '├' : '└') + '─' + String(index).padEnd(size) @@ -140,7 +156,8 @@ function inspectNodes(nodes, state) { ' ' + indent( inspectValue(nodes[index], state), - (index < nodes.length - 1 ? dim('│') : ' ') + ' '.repeat(size + 2), + (index < nodes.length - 1 ? style.dim('│') : ' ') + + ' '.repeat(size + 2), true ) ) @@ -205,14 +222,17 @@ function inspectFields(object, state) { } result.push( - key + dim(':') + (/\s/.test(formatted.charAt(0)) ? '' : ' ') + formatted + key + + style.dim(':') + + (/\s/.test(formatted.charAt(0)) ? '' : ' ') + + formatted ) } return indent( result.join('\n'), (isArrayUnknown(object.children) && object.children.length > 0 - ? dim('│') + ? style.dim('│') : ' ') + ' ' ) } @@ -253,7 +273,7 @@ function inspectTree(node, state) { * Formatted node. */ function formatNode(node, state) { - const result = [bold(node.type)] + const result = [style.bold(node.type)] // Cast as record to allow indexing. const map = /** @type {Record} */ ( /** @type {unknown} */ (node) @@ -266,13 +286,17 @@ function formatNode(node, state) { } if (isArrayUnknown(map.children)) { - result.push(dim('['), yellow(String(map.children.length)), dim(']')) + result.push( + style.dim('['), + style.yellow(String(map.children.length)), + style.dim(']') + ) } else if (typeof map.value === 'string') { - result.push(' ', green(inspectNonTree(map.value))) + result.push(' ', style.green(inspectNonTree(map.value))) } if (position) { - result.push(' ', dim('('), position, dim(')')) + result.push(' ', style.dim('('), position, style.dim(')')) } return result.join('') From b6a9fa390426bc1caece98ca0cee6c357f7ea5f1 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Sat, 23 Sep 2023 15:46:06 -0700 Subject: [PATCH 2/4] store stylization functions in state --- lib/index.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/index.js b/lib/index.js index bc7560c..4ba8faf 100644 --- a/lib/index.js +++ b/lib/index.js @@ -23,6 +23,8 @@ * Info passed around. * @property {boolean} showPositions * Whether to include positional information. + * @property {Style} style + * Rendering stylization. */ import {color} from '#conditional-color' @@ -42,16 +44,6 @@ export const inspect = color ? inspectColor : inspectNoColor const own = {}.hasOwnProperty -/** - * @type Style - */ -const style = { - bold: ansiColor(1, 22), - dim: ansiColor(2, 22), - yellow: ansiColor(33, 39), - green: ansiColor(32, 39) -} - // ANSI color regex. /* eslint-disable no-control-regex */ const colorExpression = @@ -85,6 +77,12 @@ export function inspectNoColor(tree, options) { export function inspectColor(tree, options) { /** @type {State} */ const state = { + style: { + bold: ansiColor(1, 22), + dim: ansiColor(2, 22), + yellow: ansiColor(33, 39), + green: ansiColor(32, 39) + }, showPositions: !options || options.showPositions === null || @@ -141,6 +139,7 @@ function inspectNonTree(value) { * Formatted nodes. */ function inspectNodes(nodes, state) { + const style = state.style const size = String(nodes.length - 1).length /** @type {Array} */ const result = [] @@ -178,6 +177,8 @@ function inspectNodes(nodes, state) { */ // eslint-disable-next-line complexity function inspectFields(object, state) { + const style = state.style + /** @type {Array} */ const result = [] /** @type {string} */ @@ -273,6 +274,7 @@ function inspectTree(node, state) { * Formatted node. */ function formatNode(node, state) { + const style = state.style const result = [style.bold(node.type)] // Cast as record to allow indexing. const map = /** @type {Record} */ ( From aee7cba2f1e2d9e68fa7d76c43b8681246ab07c9 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Sat, 23 Sep 2023 15:46:06 -0700 Subject: [PATCH 3/4] render with pass-through style for no color, not post-hoc stripping --- lib/index.js | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/index.js b/lib/index.js index 4ba8faf..a6324e1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -44,12 +44,6 @@ export const inspect = color ? inspectColor : inspectNoColor const own = {}.hasOwnProperty -// ANSI color regex. -/* eslint-disable no-control-regex */ -const colorExpression = - /(?:(?:\u001B\[)|\u009B)(?:\d{1,3})?(?:(?:;\d{0,3})*)?[A-M|f-m]|\u001B[A-M]/g -/* eslint-enable no-control-regex */ - /** * Inspect a node, without color. * @@ -61,7 +55,23 @@ const colorExpression = * Pretty printed `tree`. */ export function inspectNoColor(tree, options) { - return inspectColor(tree, options).replace(colorExpression, '') + /** @type {State} */ + const state = { + style: { + bold: noColor, + dim: noColor, + yellow: noColor, + green: noColor + }, + showPositions: + !options || + options.showPositions === null || + options.showPositions === undefined + ? true + : options.showPositions + } + + return inspectValue(tree, state) } /** @@ -403,6 +413,16 @@ function ansiColor(open, close) { } } +/** + * Style function which does not perform colorization. + * + * @param {string} value + * @return {string} + */ +function noColor(value) { + return value +} + /** * @param {unknown} value * @returns {value is Node} From cda8857909f97e1d983f1743e178acfdda92ab76 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Sat, 23 Sep 2023 15:46:06 -0700 Subject: [PATCH 4/4] add 'colors: boolean' property to Options --- lib/index.js | 98 ++++++++++++++++++++++++++++++---------------------- test.js | 55 +++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 41 deletions(-) diff --git a/lib/index.js b/lib/index.js index a6324e1..3ddbdaa 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,6 +7,8 @@ * Configuration. * @property {boolean | null | undefined} [showPositions=true] * Whether to include positional information (default: `true`). + * @property {boolean | null | undefined} [colors] + * Whether to include ANSI colors (default: `true` on Node, `false` otherwise). * * @typedef Style * Styling functions. @@ -27,26 +29,11 @@ * Rendering stylization. */ -import {color} from '#conditional-color' +import {color as useColorByDefault} from '#conditional-color' /** * Inspect a node, with color in Node, without color in browsers. * - * @param tree - * Tree to inspect. - * @param options - * Configuration (optional). - * @returns - * Pretty printed `tree`. - */ -/* c8 ignore next */ -export const inspect = color ? inspectColor : inspectNoColor - -const own = {}.hasOwnProperty - -/** - * Inspect a node, without color. - * * @param {unknown} tree * Tree to inspect. * @param {Options | null | undefined} [options] @@ -54,15 +41,30 @@ const own = {}.hasOwnProperty * @returns {string} * Pretty printed `tree`. */ -export function inspectNoColor(tree, options) { +/* c8 ignore next */ +export function inspect(tree, options) { + const useColor = + !options || options.colors === null || options.colors === undefined + ? useColorByDefault + : options.colors + + const style = useColor + ? { + bold: ansiColor(1, 22), + dim: ansiColor(2, 22), + yellow: ansiColor(33, 39), + green: ansiColor(32, 39) + } + : { + bold: noColor, + dim: noColor, + yellow: noColor, + green: noColor + } + /** @type {State} */ const state = { - style: { - bold: noColor, - dim: noColor, - yellow: noColor, - green: noColor - }, + style, showPositions: !options || options.showPositions === null || @@ -74,34 +76,48 @@ export function inspectNoColor(tree, options) { return inspectValue(tree, state) } +const own = {}.hasOwnProperty + +/** + * Inspect a node, without color. + * + * @deprecated + * Use `inspect` with the option `{colors: false}`. + * + * @param {unknown} tree + * Tree to inspect. + * @param {Omit | null | undefined} [options] + * Configuration. + * @returns {string} + * Pretty printed `tree`. + */ +export function inspectNoColor(tree, options) { + /* c8 ignore next 3 */ + const optionsWithNoColor = useColorByDefault + ? Object.assign({}, options, {colors: false}) + : options + return inspect(tree, optionsWithNoColor) +} + /** * Inspects a node, using color. * + * @deprecated + * Use `inspect` with the option `{colors: true}`. + * * @param {unknown} tree * Tree to inspect. - * @param {Options | null | undefined} [options] + * @param {Omit | null | undefined} [options] * Configuration (optional). * @returns {string} * Pretty printed `tree`. */ export function inspectColor(tree, options) { - /** @type {State} */ - const state = { - style: { - bold: ansiColor(1, 22), - dim: ansiColor(2, 22), - yellow: ansiColor(33, 39), - green: ansiColor(32, 39) - }, - showPositions: - !options || - options.showPositions === null || - options.showPositions === undefined - ? true - : options.showPositions - } - - return inspectValue(tree, state) + /* c8 ignore next 3 */ + const optionsWithColor = useColorByDefault + ? options + : Object.assign({}, options, {colors: true}) + return inspect(tree, optionsWithColor) } /** diff --git a/test.js b/test.js index 3451006..4749f55 100644 --- a/test.js +++ b/test.js @@ -18,6 +18,16 @@ const chalkEnabled = new Chalk({level: 1}) const paragraph = 'Some simple text. Other “sentence”.' +/** + * Split `text` on newlines, keeping the first `count`. + * + * @param {string} text + * @param {number} count + * @return string + * The first `count` lines of `text`. + */ +const lines = (text, count) => text.split('\n').slice(0, count).join('\n') + test('inspect()', async function (t) { await t.test('should expose the public api', async function () { assert.deepEqual(Object.keys(await import('unist-util-inspect')).sort(), [ @@ -420,6 +430,51 @@ test('inspect()', async function (t) { ].join('\n') ) }) + + await t.test('inspect(…, {colors: false})', async function () { + assert.equal( + lines(inspect(retext().parse(paragraph), {colors: false}), 2), + [ + 'RootNode[1] (1:1-1:36, 0-35)', + '└─0 ParagraphNode[3] (1:1-1:36, 0-35)' + ].join('\n') + ) + }) + + await t.test( + 'inspect(…, {colors?: true | null | undefined})', + async function () { + const expectedOutput = [ + chalkEnabled.bold('RootNode') + + chalkEnabled.dim('[') + + chalkEnabled.yellow('1') + + chalkEnabled.dim(']') + + ' ' + + chalkEnabled.dim('(') + + '1:1-1:36, 0-35' + + chalkEnabled.dim(')'), + chalkEnabled.dim('└─0') + + ' ' + + chalkEnabled.bold('ParagraphNode') + + chalkEnabled.dim('[') + + chalkEnabled.yellow('3') + + chalkEnabled.dim(']') + + ' ' + + chalkEnabled.dim('(') + + '1:1-1:36, 0-35' + + chalkEnabled.dim(')') + ].join('\n') + + const parsed = retext().parse(paragraph) + + assert.equal(lines(inspect(parsed, {colors: true}), 2), expectedOutput) + assert.equal(lines(inspect(parsed, {colors: null}), 2), expectedOutput) + assert.equal( + lines(inspect(parsed, {colors: undefined}), 2), + expectedOutput + ) + } + ) }) test('inspectNoColor()', async function () {