diff --git a/.npmrc b/.npmrc index 9951b11..3757b30 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ -package-lock=false ignore-scripts=true +package-lock=false diff --git a/lib/color-browser.js b/lib/color.default.js similarity index 100% rename from lib/color-browser.js rename to lib/color.default.js diff --git a/lib/color.js b/lib/color.node.js similarity index 100% rename from lib/color.js rename to lib/color.node.js diff --git a/lib/index.js b/lib/index.js index 477a9e9..5388cde 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,10 +1,12 @@ /** * @typedef {import('unist').Node} Node - * + */ + +/** * @typedef Options * Configuration. * @property {boolean | null | undefined} [showPositions=true] - * Whether to include positional information. + * Whether to include positional information (default: `true`). * * @typedef State * Info passed around. @@ -12,7 +14,7 @@ * Whether to include positional information. */ -import {color} from './color.js' +import {color} from 'unist-util-inspect/do-not-use-conditional-color' /** * Inspect a node, with color in Node, without color in browsers. @@ -20,7 +22,7 @@ import {color} from './color.js' * @param tree * Tree to inspect. * @param options - * Configuration. + * Configuration (optional). * @returns * Pretty printed `tree`. */ @@ -60,7 +62,7 @@ export function inspectNoColor(tree, options) { * @param {unknown} tree * Tree to inspect. * @param {Options | null | undefined} [options] - * Configuration. + * Configuration (optional). * @returns {string} * Pretty printed `tree`. */ @@ -284,7 +286,7 @@ function formatNode(node, state) { * @param {string} indentation * Indent to use. * @param {boolean | undefined} [ignoreFirst=false] - * Whether to ignore indenting the first line. + * Whether to ignore indenting the first line (default: `false`). * @returns {string} * Indented `value`. */ @@ -304,7 +306,7 @@ function indent(value, indentation, ignoreFirst) { /** * Serialize a position. * - * @param {unknown | null | undefined} [value] + * @param {unknown} [value] * Position to serialize. * @returns {string} * Serialized position. diff --git a/package.json b/package.json index 75467aa..dc61f9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-inspect", - "version": "7.0.2", + "version": "8.0.0", "description": "unist utility to inspect nodes", "license": "MIT", "keywords": [ @@ -25,25 +25,25 @@ ], "sideEffects": false, "type": "module", - "main": "index.js", - "browser": { - "./lib/color.js": "./lib/color-browser.js" + "exports": { + ".": "./index.js", + "./do-not-use-conditional-color": { + "node": "./lib/color.node.js", + "default": "./lib/color.default.js" + } }, - "react-native": { - "./lib/color.js": "./lib/color-browser.js" - }, - "types": "index.d.ts", "files": [ "lib/", "index.d.ts", "index.js" ], "dependencies": { - "@types/unist": "^2.0.0" + "@types/unist": "^3.0.0" }, "devDependencies": { - "@types/node": "^18.0.0", - "c8": "^7.0.0", + "@types/nlcst": "^2.0.0", + "@types/node": "^20.0.0", + "c8": "^8.0.0", "chalk": "^5.0.0", "hastscript": "^7.0.0", "prettier": "^2.0.0", @@ -52,39 +52,40 @@ "retext": "^8.0.0", "strip-ansi": "^7.0.0", "type-coverage": "^2.0.0", - "typescript": "^4.0.0", - "unist-builder": "^3.0.0", - "xast-util-from-xml": "^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.53.0" + "xo": "^0.54.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", "test-api": "node --conditions development test.js", - "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", + "test-coverage": "c8 --100 --reporter lcov npm run test-api", "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, "bracketSpacing": false, "semi": false, - "trailingComma": "none" - }, - "xo": { - "prettier": true + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false }, "remarkConfig": { "plugins": [ - "preset-wooorm" + "remark-preset-wooorm" ] }, "typeCoverage": { "atLeast": 100, "detail": true, + "ignoreCatch": true, "strict": true + }, + "xo": { + "prettier": true } } diff --git a/readme.md b/readme.md index 572a337..7216c78 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ to more easily spot bugs and see what’s going on in the tree. ## Install This package is [ESM only][esm]. -In Node.js (version 14.14+ and 16.0+), install with [npm][]: +In Node.js (version 16+), install with [npm][]: ```sh npm install unist-util-inspect @@ -48,14 +48,14 @@ npm install unist-util-inspect In Deno with [`esm.sh`][esmsh]: ```js -import {inspect} from 'https://esm.sh/unist-util-inspect@7' +import {inspect} from 'https://esm.sh/unist-util-inspect@8' ``` In browsers with [`esm.sh`][esmsh]: ```html ``` @@ -137,10 +137,13 @@ It exports the additional type [`Options`][api-options]. ## Compatibility -Projects maintained by the unified collective are compatible with all maintained +Projects maintained by the unified collective are compatible with maintained versions of Node.js. -As of now, that is Node.js 14.14+ and 16.0+. -Our projects sometimes work with older versions, but this is not guaranteed. + +When we cut a new major release, we drop support for unmaintained versions of +Node. +This means we try to keep the current release line, `unist-util-inspect@^8`, +compatible with Node.js 16. ## Contribute @@ -170,9 +173,9 @@ abide by its terms. [downloads]: https://www.npmjs.com/package/unist-util-inspect -[size-badge]: https://img.shields.io/bundlephobia/minzip/unist-util-inspect.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unist-util-inspect -[size]: https://bundlephobia.com/result?p=unist-util-inspect +[size]: https://bundlejs.com/?q=unist-util-inspect [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg diff --git a/test.js b/test.js index 7531e4c..3451006 100644 --- a/test.js +++ b/test.js @@ -1,384 +1,428 @@ +/** + * @typedef {import('nlcst').Root} NlcstRoot + */ + import assert from 'node:assert/strict' import test from 'node:test' /* eslint-disable-next-line unicorn/import-style */ import {Chalk} from 'chalk' +import {h} from 'hastscript' +import {retext} from 'retext' import strip from 'strip-ansi' import {u} from 'unist-builder' -import {h} from 'hastscript' +import {inspect, inspectColor, inspectNoColor} from 'unist-util-inspect' import {x} from 'xastscript' -import {retext} from 'retext' import {fromXml} from 'xast-util-from-xml' -import {inspect, inspectColor, inspectNoColor} from './index.js' -import * as mod from './index.js' const chalkEnabled = new Chalk({level: 1}) const paragraph = 'Some simple text. Other “sentence”.' -test('core', () => { - assert.deepEqual( - Object.keys(mod).sort(), - ['inspect', 'inspectColor', 'inspectNoColor'], - 'should expose the public api' - ) -}) +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(), [ + 'inspect', + 'inspectColor', + 'inspectNoColor' + ]) + }) -test('inspect()', () => { - assert.equal( - strip(inspect(retext().parse(paragraph))), - [ - 'RootNode[1] (1:1-1:36, 0-35)', - '└─0 ParagraphNode[3] (1:1-1:36, 0-35)', - ' ├─0 SentenceNode[6] (1:1-1:18, 0-17)', - ' │ ├─0 WordNode[1] (1:1-1:5, 0-4)', - ' │ │ └─0 TextNode "Some" (1:1-1:5, 0-4)', - ' │ ├─1 WhiteSpaceNode " " (1:5-1:6, 4-5)', - ' │ ├─2 WordNode[1] (1:6-1:12, 5-11)', - ' │ │ └─0 TextNode "simple" (1:6-1:12, 5-11)', - ' │ ├─3 WhiteSpaceNode " " (1:12-1:13, 11-12)', - ' │ ├─4 WordNode[1] (1:13-1:17, 12-16)', - ' │ │ └─0 TextNode "text" (1:13-1:17, 12-16)', - ' │ └─5 PunctuationNode "." (1:17-1:18, 16-17)', - ' ├─1 WhiteSpaceNode " " (1:18-1:19, 17-18)', - ' └─2 SentenceNode[6] (1:19-1:36, 18-35)', - ' ├─0 WordNode[1] (1:19-1:24, 18-23)', - ' │ └─0 TextNode "Other" (1:19-1:24, 18-23)', - ' ├─1 WhiteSpaceNode " " (1:24-1:25, 23-24)', - ' ├─2 PunctuationNode "“" (1:25-1:26, 24-25)', - ' ├─3 WordNode[1] (1:26-1:34, 25-33)', - ' │ └─0 TextNode "sentence" (1:26-1:34, 25-33)', - ' ├─4 PunctuationNode "”" (1:34-1:35, 33-34)', - ' └─5 PunctuationNode "." (1:35-1:36, 34-35)' - ].join('\n'), - 'should work on `RootNode`' - ) + await t.test('should work on `RootNode`', async function () { + assert.equal( + strip(inspect(retext().parse(paragraph))), + [ + 'RootNode[1] (1:1-1:36, 0-35)', + '└─0 ParagraphNode[3] (1:1-1:36, 0-35)', + ' ├─0 SentenceNode[6] (1:1-1:18, 0-17)', + ' │ ├─0 WordNode[1] (1:1-1:5, 0-4)', + ' │ │ └─0 TextNode "Some" (1:1-1:5, 0-4)', + ' │ ├─1 WhiteSpaceNode " " (1:5-1:6, 4-5)', + ' │ ├─2 WordNode[1] (1:6-1:12, 5-11)', + ' │ │ └─0 TextNode "simple" (1:6-1:12, 5-11)', + ' │ ├─3 WhiteSpaceNode " " (1:12-1:13, 11-12)', + ' │ ├─4 WordNode[1] (1:13-1:17, 12-16)', + ' │ │ └─0 TextNode "text" (1:13-1:17, 12-16)', + ' │ └─5 PunctuationNode "." (1:17-1:18, 16-17)', + ' ├─1 WhiteSpaceNode " " (1:18-1:19, 17-18)', + ' └─2 SentenceNode[6] (1:19-1:36, 18-35)', + ' ├─0 WordNode[1] (1:19-1:24, 18-23)', + ' │ └─0 TextNode "Other" (1:19-1:24, 18-23)', + ' ├─1 WhiteSpaceNode " " (1:24-1:25, 23-24)', + ' ├─2 PunctuationNode "“" (1:25-1:26, 24-25)', + ' ├─3 WordNode[1] (1:26-1:34, 25-33)', + ' │ └─0 TextNode "sentence" (1:26-1:34, 25-33)', + ' ├─4 PunctuationNode "”" (1:34-1:35, 33-34)', + ' └─5 PunctuationNode "." (1:35-1:36, 34-35)' + ].join('\n') + ) + }) - assert.equal( - strip(inspect([u('SymbolNode', '$'), u('WordNode', [u('text', '5,00')])])), - '├─0 SymbolNode "$"\n└─1 WordNode[1]\n └─0 text "5,00"', - 'should work with a list of nodes' - ) + await t.test('should work with a list of nodes', async function () { + assert.equal( + strip( + inspect([u('SymbolNode', '$'), u('WordNode', [u('text', '5,00')])]) + ), + '├─0 SymbolNode "$"\n└─1 WordNode[1]\n └─0 text "5,00"' + ) + }) - assert.doesNotThrow(() => { - assert.equal(strip(inspect('foo')), '"foo"') - assert.equal(strip(inspect(null)), 'null') - assert.equal(strip(inspect(Number.NaN)), 'null') - assert.equal(strip(inspect(3)), '3') - }, 'should work on non-nodes') + await t.test('should work on non-nodes', async function () { + assert.doesNotThrow(function () { + assert.equal(strip(inspect('foo')), '"foo"') + assert.equal(strip(inspect(null)), 'null') + assert.equal(strip(inspect(Number.NaN)), 'null') + assert.equal(strip(inspect(3)), '3') + }) + }) - assert.equal( - strip( - inspect( - Array.from({length: 11}).map((/** @type {undefined} */ d, i) => ({ - type: 'text', - value: String(i), - data: {id: String.fromCodePoint(97 + i)} - })) + await t.test('should align and indent large numbers', async function () { + 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)} + } + }) + ) + ), + [ + '├─0 text "0"', + '│ data: {"id":"a"}', + '├─1 text "1"', + '│ data: {"id":"b"}', + '├─2 text "2"', + '│ data: {"id":"c"}', + '├─3 text "3"', + '│ data: {"id":"d"}', + '├─4 text "4"', + '│ data: {"id":"e"}', + '├─5 text "5"', + '│ data: {"id":"f"}', + '├─6 text "6"', + '│ data: {"id":"g"}', + '├─7 text "7"', + '│ data: {"id":"h"}', + '├─8 text "8"', + '│ data: {"id":"i"}', + '├─9 text "9"', + '│ data: {"id":"j"}', + '└─10 text "10"', + ' data: {"id":"k"}' + ].join('\n') + ) + }) + + await t.test('should work with data attributes', async function () { + assert.equal( + strip( + inspect({ + type: 'SymbolNode', + value: '$', + data: {test: true} + }) + ), + 'SymbolNode "$"\n data: {"test":true}' + ) + }) + + await t.test('should work with other attributes', async function () { + assert.equal( + strip( + inspect({ + type: 'table', + align: ['left', 'center'], + children: [ + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{type: 'text', value: 'foo'}] + }, + { + type: 'tableCell', + children: [{type: 'text', value: 'bar'}] + } + ] + }, + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{type: 'text', value: 'baz'}] + }, + { + type: 'tableCell', + children: [{type: 'text', value: 'qux'}] + } + ] + } + ] + }) + ), + [ + 'table[2]', + '│ align: ["left","center"]', + '├─0 tableRow[2]', + '│ ├─0 tableCell[1]', + '│ │ └─0 text "foo"', + '│ └─1 tableCell[1]', + '│ └─0 text "bar"', + '└─1 tableRow[2]', + ' ├─0 tableCell[1]', + ' │ └─0 text "baz"', + ' └─1 tableCell[1]', + ' └─0 text "qux"' + ].join('\n') + ) + }) + + await t.test( + 'should work on parent nodes without children', + async function () { + assert.equal( + strip( + inspect({ + type: 'element', + tagName: 'br', + children: [] + }) + ), + 'element
[0]' ) - ), - [ - '├─0 text "0"', - '│ data: {"id":"a"}', - '├─1 text "1"', - '│ data: {"id":"b"}', - '├─2 text "2"', - '│ data: {"id":"c"}', - '├─3 text "3"', - '│ data: {"id":"d"}', - '├─4 text "4"', - '│ data: {"id":"e"}', - '├─5 text "5"', - '│ data: {"id":"f"}', - '├─6 text "6"', - '│ data: {"id":"g"}', - '├─7 text "7"', - '│ data: {"id":"h"}', - '├─8 text "8"', - '│ data: {"id":"i"}', - '├─9 text "9"', - '│ data: {"id":"j"}', - '└─10 text "10"', - ' data: {"id":"k"}' - ].join('\n'), - 'should align and indent large numbers' + } ) - assert.equal( - strip( - inspect({ - type: 'SymbolNode', - value: '$', - data: {test: true} - }) - ), - 'SymbolNode "$"\n data: {"test":true}', - 'should work with data attributes' - ) + await t.test('should work on text nodes without value', async function () { + assert.equal(strip(inspect({type: 'text', value: ''})), 'text ""') + }) - assert.equal( - strip( - inspect({ - type: 'table', - align: ['left', 'center'], - children: [ - { - type: 'tableRow', - children: [ - { - type: 'tableCell', - children: [{type: 'text', value: 'foo'}] - }, - { - type: 'tableCell', - children: [{type: 'text', value: 'bar'}] - } - ] - }, - { - type: 'tableRow', - children: [ - { - type: 'tableCell', - children: [{type: 'text', value: 'baz'}] - }, - { - type: 'tableCell', - children: [{type: 'text', value: 'qux'}] - } - ] - } - ] - }) - ), - [ - 'table[2]', - '│ align: ["left","center"]', - '├─0 tableRow[2]', - '│ ├─0 tableCell[1]', - '│ │ └─0 text "foo"', - '│ └─1 tableCell[1]', - '│ └─0 text "bar"', - '└─1 tableRow[2]', - ' ├─0 tableCell[1]', - ' │ └─0 text "baz"', - ' └─1 tableCell[1]', - ' └─0 text "qux"' - ].join('\n'), - 'should work with other attributes' - ) + await t.test('should work on void nodes', async function () { + assert.equal(strip(inspect({type: 'thematicBreak'})), 'thematicBreak') + }) - assert.equal( - strip( - inspect({ - type: 'element', - tagName: 'br', - children: [] - }) - ), - 'element
[0]', - 'should work on parent nodes without children' - ) + await t.test('should see properties as data', async function (t) { + await t.test('should work', async function () { + assert.equal( + strip(inspect(h('button', {type: 'submit', value: 'Send'}))), + [ + 'element