diff --git a/.editorconfig b/.editorconfig index c6c8b36..0f17867 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,9 @@ root = true [*] -indent_style = space -indent_size = 2 -end_of_line = lf charset = utf-8 -trim_trailing_whitespace = true +end_of_line = lf +indent_size = 2 +indent_style = space insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb63387..8fdea18 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,13 +7,13 @@ jobs: name: ${{matrix.node}} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{matrix.node}} - run: npm install - run: npm test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 strategy: matrix: node: diff --git a/.gitignore b/.gitignore index c977c85..ceb3f17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store +*.d.ts.map *.d.ts *.log coverage/ node_modules/ yarn.lock +!/lib/types.d.ts +!/index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..453894e --- /dev/null +++ b/index.d.ts @@ -0,0 +1,2 @@ +export type {Options, Options as InspectOptions} from './lib/types.js' +export {inspectColor, inspectNoColor, inspect} from './lib/index.js' diff --git a/index.js b/index.js index 3a0b54d..64fae41 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,2 @@ -/** - * @typedef {import('./lib/index.js')} Options - * - * @typedef {Options} InspectOptions - * Deprecated, use `Options`. - */ - -export {inspect, inspectColor, inspectNoColor} from './lib/index.js' +// Note: types exposed from `index.d.ts`. +export {inspectColor, inspectNoColor, inspect} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js index 5388cde..f7fccca 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,83 +1,77 @@ /** - * @typedef {import('unist').Node} Node + * @import {Options} from 'unist-util-inspect' + * @import {Node} from 'unist' + * @import {State} from './types.js' */ -/** - * @typedef Options - * Configuration. - * @property {boolean | null | undefined} [showPositions=true] - * Whether to include positional information (default: `true`). - * - * @typedef State - * Info passed around. - * @property {boolean} showPositions - * Whether to include positional information. - */ +import {color as colorDefault} from '#conditional-color' + +/** @type {Readonly} */ +const emptyOptions = {} -import {color} from 'unist-util-inspect/do-not-use-conditional-color' +// To do: next major (?): use `Object.hasOwn`. +const own = {}.hasOwnProperty /** - * Inspect a node, with color in Node, without color in browsers. + * Inspect a tree. * - * @param tree + * @param {unknown} tree * Tree to inspect. - * @param options - * Configuration (optional). - * @returns + * @param {Readonly | null | undefined} [options] + * Configuration. + * @returns {string} * Pretty printed `tree`. */ -/* c8 ignore next */ -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) +export function inspect(tree, options) { + const settings = options || emptyOptions + const color = + typeof settings.color === 'boolean' ? settings.color : colorDefault + const showPositions = + typeof settings.showPositions === 'boolean' ? settings.showPositions : true + /** @type {State} */ + const state = { + bold: color ? ansiColor(1, 22) : identity, + dim: color ? ansiColor(2, 22) : identity, + green: color ? ansiColor(32, 39) : identity, + showPositions, + yellow: color ? ansiColor(33, 39) : identity + } -// 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 */ + return inspectValue(tree, state) +} +// To do: remove. /** * Inspect a node, without color. * + * @deprecated + * Use `inspect` instead, with `color: false`. * @param {unknown} tree * Tree to inspect. - * @param {Options | null | undefined} [options] + * @param {Readonly> | null | undefined} [options] * Configuration. * @returns {string} * Pretty printed `tree`. */ export function inspectNoColor(tree, options) { - return inspectColor(tree, options).replace(colorExpression, '') + return inspect(tree, {...options, color: false}) } +// To do: remove. /** * Inspects a node, using color. * + * @deprecated + * Use `inspect` instead, with `color: true`. * @param {unknown} tree * Tree to inspect. - * @param {Options | null | undefined} [options] + * @param {Readonly> | null | undefined} [options] * Configuration (optional). * @returns {string} * Pretty printed `tree`. */ export function inspectColor(tree, options) { - /** @type {State} */ - const state = { - showPositions: - !options || - options.showPositions === null || - options.showPositions === undefined - ? true - : options.showPositions - } - - return inspectValue(tree, state) + return inspect(tree, {...options, color: true}) } /** @@ -132,7 +126,7 @@ function inspectNodes(nodes, state) { while (++index < nodes.length) { result.push( - dim( + state.dim( (index < nodes.length - 1 ? '├' : '└') + '─' + String(index).padEnd(size) @@ -140,7 +134,8 @@ function inspectNodes(nodes, state) { ' ' + indent( inspectValue(nodes[index], state), - (index < nodes.length - 1 ? dim('│') : ' ') + ' '.repeat(size + 2), + (index < nodes.length - 1 ? state.dim('│') : ' ') + + ' '.repeat(size + 2), true ) ) @@ -205,14 +200,17 @@ function inspectFields(object, state) { } result.push( - key + dim(':') + (/\s/.test(formatted.charAt(0)) ? '' : ' ') + formatted + key + + state.dim(':') + + (/\s/.test(formatted.charAt(0)) ? '' : ' ') + + formatted ) } return indent( result.join('\n'), (isArrayUnknown(object.children) && object.children.length > 0 - ? dim('│') + ? state.dim('│') : ' ') + ' ' ) } @@ -253,7 +251,7 @@ function inspectTree(node, state) { * Formatted node. */ function formatNode(node, state) { - const result = [bold(node.type)] + const result = [state.bold(node.type)] // Cast as record to allow indexing. const map = /** @type {Record} */ ( /** @type {unknown} */ (node) @@ -266,13 +264,17 @@ function formatNode(node, state) { } if (isArrayUnknown(map.children)) { - result.push(dim('['), yellow(String(map.children.length)), dim(']')) + result.push( + state.dim('['), + state.yellow(String(map.children.length)), + state.dim(']') + ) } else if (typeof map.value === 'string') { - result.push(' ', green(inspectNonTree(map.value))) + result.push(' ', state.green(inspectNonTree(map.value))) } if (position) { - result.push(' ', dim('('), position, dim(')')) + result.push(' ', state.dim('('), position, state.dim(')')) } return result.join('') @@ -397,3 +399,12 @@ function isNode(value) { function isArrayUnknown(node) { return Array.isArray(node) } + +/** + * @template T + * @param {T} value + * @returns {T} + */ +function identity(value) { + return value +} diff --git a/lib/types.d.ts b/lib/types.d.ts new file mode 100644 index 0000000..0eeea05 --- /dev/null +++ b/lib/types.d.ts @@ -0,0 +1,49 @@ +/** + * Configuration. + */ +export interface Options { + /** + * Whether to use ANSI colors (default: `true` in Node, `false` otherwise). + */ + color?: boolean | null | undefined + /** + * Whether to include positional info (default: `true`). + */ + showPositions?: boolean | null | undefined +} + +/** + * Info passed around. + */ +export interface State { + /** + * Node type. + */ + bold: Style + /** + * Punctuation. + */ + dim: Style + /** + * Non-tree value. + */ + green: Style + /** + * Whether to include positional info. + */ + showPositions: boolean + /** + * Numeric count of node children. + */ + yellow: Style +} + +/** + * Style a string. + * + * @param value + * Value to style. + * @returns + * Styled value. + */ +type Style = (value: string) => string diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..7402850 --- /dev/null +++ b/lib/types.js @@ -0,0 +1,2 @@ +// Types only. +export {} diff --git a/package.json b/package.json index dc61f9b..5db9176 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-inspect", - "version": "8.0.0", + "version": "8.1.0", "description": "unist utility to inspect nodes", "license": "MIT", "keywords": [ @@ -25,9 +25,9 @@ ], "sideEffects": false, "type": "module", - "exports": { - ".": "./index.js", - "./do-not-use-conditional-color": { + "exports": "./index.js", + "imports": { + "#conditional-color": { "node": "./lib/color.node.js", "default": "./lib/color.default.js" } @@ -43,25 +43,25 @@ "devDependencies": { "@types/nlcst": "^2.0.0", "@types/node": "^20.0.0", - "c8": "^8.0.0", + "c8": "^10.0.0", "chalk": "^5.0.0", - "hastscript": "^7.0.0", - "prettier": "^2.0.0", - "remark-cli": "^11.0.0", - "remark-preset-wooorm": "^9.0.0", - "retext": "^8.0.0", + "hastscript": "^9.0.0", + "prettier": "^3.0.0", + "remark-cli": "^12.0.0", + "remark-preset-wooorm": "^10.0.0", + "retext": "^9.0.0", "strip-ansi": "^7.0.0", "type-coverage": "^2.0.0", "typescript": "^5.0.0", "unist-builder": "^4.0.0", - "xast-util-from-xml": "^3.0.0", - "xastscript": "^3.0.0", - "xo": "^0.54.0" + "xast-util-from-xml": "^4.0.0", + "xastscript": "^4.0.0", + "xo": "^0.58.0" }, "scripts": { "prepack": "npm run build && npm run format", "build": "tsc --build --clean && tsc --build && type-coverage", - "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", + "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", "test-api": "node --conditions development test.js", "test-coverage": "c8 --100 --reporter lcov npm run test-api", "test": "npm run build && npm run format && npm run test-coverage" @@ -86,6 +86,34 @@ "strict": true }, "xo": { - "prettier": true + "overrides": [ + { + "files": [ + "**/*.d.ts" + ], + "rules": { + "@typescript-eslint/array-type": [ + "error", + { + "default": "generic" + } + ], + "@typescript-eslint/ban-types": [ + "error", + { + "extendDefaults": true + } + ], + "@typescript-eslint/consistent-type-definitions": [ + "error", + "interface" + ] + } + } + ], + "prettier": true, + "rules": { + "unicorn/prefer-string-replace-all": "off" + } } } diff --git a/readme.md b/readme.md index 7216c78..2371300 100644 --- a/readme.md +++ b/readme.md @@ -12,19 +12,19 @@ ## Contents -* [What is this?](#what-is-this) -* [When should I use this?](#when-should-i-use-this) -* [Install](#install) -* [Use](#use) -* [API](#api) - * [`inspect(tree[, options])`](#inspecttree-options) - * [`inspectColor(tree[, options])`](#inspectcolortree-options) - * [`inspectNoColor(tree[, options])`](#inspectnocolortree-options) - * [`Options`](#options) -* [Types](#types) -* [Compatibility](#compatibility) -* [Contribute](#contribute) -* [License](#license) +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`inspect(tree[, options])`](#inspecttree-options) + * [`inspectColor(tree[, options])`](#inspectcolortree-options) + * [`inspectNoColor(tree[, options])`](#inspectnocolortree-options) + * [`Options`](#options) +* [Types](#types) +* [Compatibility](#compatibility) +* [Contribute](#contribute) +* [License](#license) ## What is this? @@ -98,14 +98,14 @@ There is no default export. ### `inspect(tree[, options])` -Inspect a tree, with color in Node, without color in browsers. +Inspect a tree. ###### Parameters -* `tree` ([`Node`][node]) - — tree to inspect -* `options` ([`Options`][api-options], optional) - — configuration +* `tree` ([`Node`][node]) + — tree to inspect +* `options` ([`Options`][api-options], optional) + — configuration ###### Returns @@ -113,11 +113,15 @@ Pretty printed `tree` (`string`). ### `inspectColor(tree[, options])` +> 🪦 **Deprecated**: use `color` option of `inspect`. + Inspect a tree, with color. Otherwise same as [`inspect`][api-inspect]. ### `inspectNoColor(tree[, options])` +> 🪦 **Deprecated**: use `color` option of `inspect`. + Inspect a tree, without color. Otherwise same as [`inspect`][api-inspect]. @@ -127,8 +131,10 @@ Configuration (TypeScript type). ###### Fields -* `showPositions` (`boolean`, default: `true`) - — whether to include positional information +* `color` (`boolean`, default: `true` in Node, `false` otherwise) + — whether to use ANSI colors +* `showPositions` (`boolean`, default: `true`) + — whether to include positional information ## Types diff --git a/test.js b/test.js index 3451006..7b83240 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ /** - * @typedef {import('nlcst').Root} NlcstRoot + * @import {Root as NlcstRoot} from 'nlcst' */ import assert from 'node:assert/strict' @@ -79,16 +79,15 @@ test('inspect()', async function (t) { assert.equal( strip( inspect( - Array.from({length: 11}).map(function ( - /** @type {undefined} */ d, - i - ) { - return { - type: 'text', - value: String(i), - data: {id: String.fromCodePoint(97 + i)} + Array.from({length: 11}).map( + function (/** @type {undefined} */ d, i) { + return { + type: 'text', + value: String(i), + data: {id: String.fromCodePoint(97 + i)} + } } - }) + ) ) ), [ diff --git a/tsconfig.json b/tsconfig.json index ac5b4aa..0fac7b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,15 +3,14 @@ "checkJs": true, "customConditions": ["development"], "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "exactOptionalPropertyTypes": true, "lib": ["es2022"], "module": "node16", - // Remove after `retext` update. - "skipLibCheck": true, "strict": true, "target": "es2022" }, "exclude": ["coverage/", "node_modules/"], - "include": ["**/*.js"] + "include": ["**/*.js", "lib/types.d.ts", "index.d.ts"] }