diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml index 291ab09..0198fc3 100644 --- a/.github/workflows/bb.yml +++ b/.github/workflows/bb.yml @@ -2,7 +2,7 @@ name: bb on: issues: types: [opened, reopened, edited, closed, labeled, unlabeled] - pull_request: + pull_request_target: types: [opened, reopened, edited, closed, labeled, unlabeled] jobs: main: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe284ad..ee318ca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,15 +7,15 @@ jobs: name: ${{matrix.node}} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: dcodeIO/setup-node-nvm@master + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{matrix.node}} - run: npm install - run: npm test - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 strategy: matrix: node: - - lts/erbium + - lts/hydrogen - node diff --git a/.npmrc b/.npmrc index 43c97e7..9951b11 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +ignore-scripts=true diff --git a/index.js b/index.js index 765976e..a286e7d 100644 --- a/index.js +++ b/index.js @@ -1,298 +1,5 @@ /** - * @typedef {import('assert').AssertionError} AssertionError - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent - * @typedef {import('unist').Literal} Literal - * @typedef {import('unist').Position} Position - * @typedef {import('unist').Point} Point - * @typedef {Node & {children: never, value: never}} _Void + * @typedef {import('./lib/index.js').AssertionError} AssertionError */ -import nodeAssert from 'assert' -import {inspect} from './inspect.js' - -var own = {}.hasOwnProperty - -/** - * Assert that `node` is a valid unist node. - * If `node` is a parent, all children will be asserted too. - * - * @param {unknown} [node] - * @param {Parent} [parent] - * @returns {asserts node is Node} - */ -export function assert(node, parent) { - return wrap(assertNode)(node, parent) -} - -/** - * Assert that `node` is a valid unist parent. - * All children will be asserted too. - * - * @param {unknown} [node] - * @param {Parent} [parent] - * @returns {asserts node is Parent} - */ -export function parent(node, parent) { - return wrap(assertParent)(node, parent) -} - -/** - * Assert that `node` is a valid unist literal. - * - * @param {unknown} [node] - * @param {Parent} [parent] - * @returns {asserts node is Literal} - */ -export function literal(node, parent) { - return wrap(assertLiteral)(node, parent) -} - -/** - * Assert that `node` is a valid unist node, but neither parent nor literal. - * - * @param {unknown} [node] - * @param {Parent} [parent] - * @returns {asserts node is _Void} - */ -export function _void(node, parent) { - return wrap(assertVoid)(node, parent) -} - -// Identifier to check if a value is seen. -var ID = '__unist__' - -// List of specced properties. -var defined = new Set(['type', 'value', 'children', 'position']) - -/** - * Wrapper that adds the current node (and parent, if available) to error - * messages. - * - * @param {(node?: unknown, parent?: Parent|null) => asserts node is Node} fn - * @returns {(node?: unknown, parent?: Parent|null) => asserts node is Node} - */ -export function wrap(fn) { - return wrapped - - /** - * @param {unknown} node - * @param {Parent} [parent] - * @throws {AssertionError} - * @returns {asserts node is T} - */ - function wrapped(node, parent) { - try { - fn(node, parent) - } catch (error) { - if (!own.call(error, ID)) { - error[ID] = true - error.message += ': `' + view(node) + '`' - if (parent) error.message += ' in `' + view(parent) + '`' - } - - throw error - } - } -} - -/** - * Assert. - * - * @param {unknown} node - * @returns {asserts node is Node} - */ -function assertNode(node) { - var index = -1 - /** @type {Parent} */ - var parent - /** @type {unknown} */ - var child - /** @type {string} */ - var key - - nodeAssert.ok( - node && typeof node === 'object' && !Array.isArray(node), - 'node should be an object' - ) - - nodeAssert.ok(own.call(node, 'type'), 'node should have a type') - nodeAssert.strictEqual( - // @ts-expect-error Looks like an indexed object. - typeof node.type, - 'string', - '`type` should be a string' - ) - // @ts-expect-error Looks like an indexed object. - nodeAssert.notStrictEqual(node.type, '', '`type` should not be empty') - - // @ts-expect-error Looks like an indexed object. - if (node.value !== null && node.value !== undefined) { - nodeAssert.strictEqual( - // @ts-expect-error Looks like an indexed object. - typeof node.value, - 'string', - '`value` should be a string' - ) - } - - // @ts-expect-error Looks like an indexed object. - position(node.position) - - for (key in node) { - if (!defined.has(key)) { - vanilla(key, node[key]) - } - } - - // @ts-expect-error Looks like an indexed object. - if (node.children !== null && node.children !== undefined) { - // @ts-expect-error Looks like parent. - parent = node - nodeAssert.ok( - Array.isArray(parent.children), - '`children` should be an array' - ) - index = -1 - - while (++index < parent.children.length) { - child = parent.children[index] - assert(child, parent) - } - } -} - -/** - * Assert `value` (which lives at `key`) can be stringified and re-parsed to the - * same (deep) value. - * - * @param {string} key - * @param {unknown} value - */ -function vanilla(key, value) { - try { - nodeAssert.deepStrictEqual(value, JSON.parse(JSON.stringify(value))) - } catch { - nodeAssert.fail('non-specced property `' + key + '` should be JSON') - } -} - -/** - * Stringify a value to inspect it. - * Tries `JSON.stringify()`, and if that fails uses `String()` instead. - * - * @param {unknown} value - * @returns {string} - */ -function view(value) { - try { - return inspect(value) - /* c8 ignore next 3 */ - } catch { - return String(value) - } -} - -/** - * Assert `node` is a parent node. - * - * @param {Node} node - * @returns {asserts node is Parent} - */ -function assertParent(node) { - assertNode(node) - - nodeAssert.strictEqual( - 'value' in node, - false, - 'parent should not have `value`' - ) - nodeAssert.ok('children' in node, 'parent should have `children`') -} - -/** - * Assert `node` is a literal node. - * - * @param {Node} node - * @returns {asserts node is Literal} - */ -function assertLiteral(node) { - assertNode(node) - - nodeAssert.strictEqual( - 'children' in node, - false, - 'literal should not have `children`' - ) - nodeAssert.ok('value' in node, 'literal should have `value`') -} - -/** - * Assert `node` is a unist node, but neither parent nor literal. - * - * @param {Node} node - * @returns {asserts node is _Void} - */ -function assertVoid(node) { - assertNode(node) - - nodeAssert.strictEqual('value' in node, false, 'void should not have `value`') - nodeAssert.strictEqual( - 'children' in node, - false, - 'void should not have `children`' - ) -} - -/** - * Assert `position` is a unist position. - * - * @param {Position} position - * @returns {asserts position is Position} - */ -function position(position) { - if (position !== null && position !== undefined) { - nodeAssert.ok( - position === Object(position), - '`position` should be an object' - ) - - point(position.start, 'position.start') - point(position.end, 'position.end') - } -} - -/** - * Assert `point` is a unist point. - * - * @param {Point} point - * @param {string} label - * @returns {asserts point is Point} - */ -function point(point, label) { - if (point !== null && point !== undefined) { - nodeAssert.ok( - point === Object(point), - '`' + label + '` should be an object' - ) - - if (point.line !== null && point.line !== undefined) { - nodeAssert.ok( - 'line' in point, - '`' + label + '` should have numeric `line`' - ) - nodeAssert.ok(point.line >= 1, '`' + label + '.line` should be gte `1`') - } - - if (point.column !== null && point.column !== undefined) { - nodeAssert.ok( - 'column' in point, - '`' + label + '` should have numeric `column`' - ) - nodeAssert.ok( - point.column >= 1, - '`' + label + '.column` should be gte `1`' - ) - } - } -} +export {_void, assert, literal, parent, wrap} from './lib/index.js' diff --git a/index.test-d.ts b/index.test-d.ts index a763e3b..e1b4525 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,10 +1,10 @@ import {expectType, expectNotType} from 'tsd' -import {Node, Parent} from 'unist' +import type {Node, Parent} from 'unist' import {assert, parent} from './index.js' -var emptyNode = {type: 'a'} -var literalNode = {type: 'b', value: 'c'} -var parentNode = {type: 'd', children: [emptyNode, literalNode]} +const emptyNode = {type: 'a'} +const literalNode = {type: 'b', value: 'c'} +const parentNode = {type: 'd', children: [emptyNode, literalNode]} expectNotType(emptyNode) expectNotType(literalNode) diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..dbdcd71 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,376 @@ +/** + * @typedef {import('assert').AssertionError} AssertionError + * Assertion error from `node:assert`. + * + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + * @typedef {import('unist').Literal} Literal + * @typedef {import('unist').Position} Position + * @typedef {import('unist').Point} Point + * @typedef {Node & {children: never, value: never}} _Void + * + * @typedef SeenErrorFields + * @property {true} [__unist__] + * + * @typedef {Error & SeenErrorFields} SeenError + */ + +import nodeAssert from 'node:assert' +import {inspect} from './inspect.js' + +const own = {}.hasOwnProperty + +/** + * Assert that `tree` is a valid unist node. + * + * If `tree` is a parent, all children will be asserted too. + * + * @param {unknown} [tree] + * Thing to assert. + * @param {Parent | null | undefined} [parent] + * Optional, valid parent. + * @returns {asserts tree is Node} + * Nothing. + * @throws {AssertionError} + * When `tree` (or its descendants) is not a node. + */ +export function assert(tree, parent) { + return wrap(assertNode)(tree, parent) +} + +/** + * Assert that `tree` is a valid unist parent. + * + * All children will be asserted too. + * + * @param {unknown} [tree] + * Thing to assert. + * @param {Parent | null | undefined} [parent] + * Optional, valid parent. + * @returns {asserts tree is Parent} + * Nothing. + * @throws {AssertionError} + * When `tree` is not a parent or its descendants are not nodes. + */ +export function parent(tree, parent) { + return wrap(assertParent)(tree, parent) +} + +/** + * Assert that `node` is a valid unist literal. + * + * @param {unknown} [node] + * Thing to assert. + * @param {Parent | null | undefined} [parent] + * Optional, valid parent. + * @returns {asserts node is Literal} + * Nothing. + * @throws {AssertionError} + * When `node` is not a literal. + */ +export function literal(node, parent) { + return wrap(assertLiteral)(node, parent) +} + +/** + * Assert that `node` is a valid void node. + * + * @param {unknown} [node] + * Thing to assert. + * @param {Parent | null | undefined} [parent] + * Optional, valid parent. + * @returns {asserts node is _Void} + * Nothing. + * @throws {AssertionError} + * When `node` is not a node, a parent, or a literal. + */ +export function _void(node, parent) { + return wrap(assertVoid)(node, parent) +} + +// Identifier to check if a value is seen. +const ID = '__unist__' + +// List of specced properties. +const defined = new Set(['type', 'value', 'children', 'position']) + +/** + * Wrapper that adds the current node (and parent, if available) to error + * messages. + * + * @template {Node} T + * Node type. + * @param {(node?: any, parent?: Parent | null | undefined) => asserts node is T} fn + * Custom assertion. + * @returns {(node?: any, parent?: Parent | null | undefined) => asserts node is T} + * Assertion. + */ +export function wrap(fn) { + return wrapped + + /** + * @param {unknown} node + * Thing to check. + * @param {Parent | null | undefined} [parent] + * Optional, valid parent. + * @throws {AssertionError} + * Whether `node` is a node but neither parent nor literal. + * @returns {void} + * Nothing. + */ + function wrapped(node, parent) { + try { + fn(node, parent) + } catch (error) { + const exception = /** @type {SeenError} */ (error) + if (!own.call(exception, ID)) { + exception[ID] = true + exception.message += ': `' + view(node) + '`' + if (parent) exception.message += ' in `' + view(parent) + '`' + } + + throw error + } + } +} + +/** + * Assert that `node` is a valid unist parent. + * + * All children will be asserted too. + * + * @param {unknown} node + * Thing to assert. + * @returns {asserts node is Node} + * Nothing. + * @throws {AssertionError} + * When `node` (or its descendants) is not a node. + */ +function assertNode(node) { + let index = -1 + + nodeAssert.ok( + node && typeof node === 'object' && !Array.isArray(node), + 'node should be an object' + ) + + nodeAssert.ok(own.call(node, 'type'), 'node should have a type') + nodeAssert.strictEqual( + // @ts-expect-error Looks like an indexed object. + typeof node.type, + 'string', + '`type` should be a string' + ) + // @ts-expect-error Looks like an indexed object. + nodeAssert.notStrictEqual(node.type, '', '`type` should not be empty') + + // @ts-expect-error Looks like an indexed object. + if (node.value !== null && node.value !== undefined) { + nodeAssert.strictEqual( + // @ts-expect-error Looks like an indexed object. + typeof node.value, + 'string', + '`value` should be a string' + ) + } + + // @ts-expect-error Looks like an indexed object. + position(node.position) + + /** @type {string} */ + let key + + for (key in node) { + if (!defined.has(key)) { + /** @type {unknown} */ + // @ts-expect-error: hush. + const value = node[key] + vanilla(key, value) + } + } + + // @ts-expect-error Looks like an indexed object. + if (node.children !== null && node.children !== undefined) { + /** @type {Parent} */ + // @ts-expect-error Looks like parent. + const parent = node + nodeAssert.ok( + Array.isArray(parent.children), + '`children` should be an array' + ) + index = -1 + + while (++index < parent.children.length) { + assert(parent.children[index], parent) + } + } +} + +/** + * Assert `value` (which lives at `key`) can be stringified and re-parsed to the + * same (deep) value. + * + * @param {string} key + * Name of field. + * @param {unknown} value + * Value of field. + */ +function vanilla(key, value) { + try { + nodeAssert.deepStrictEqual(value, JSON.parse(JSON.stringify(value))) + } catch { + nodeAssert.fail('non-specced property `' + key + '` should be JSON') + } +} + +/** + * Stringify a value to inspect it. + * + * Tries `JSON.stringify()`, and if that fails uses `String()` instead. + * + * @param {unknown} value + * Anything (should be JSON). + * @returns {string} + * User-visible preresentation. + */ +function view(value) { + try { + return inspect(value) + /* c8 ignore next 3 */ + } catch { + return String(value) + } +} + +/** + * Assert that `node` is a valid unist parent. + * + * All children will be asserted too. + * + * @param {Node} node + * Thing to assert. + * @returns {asserts node is Parent} + * Nothing. + * @throws {AssertionError} + * When `node` is not a parent or its descendants are not nodes. + */ +function assertParent(node) { + assertNode(node) + + nodeAssert.strictEqual( + 'value' in node, + false, + 'parent should not have `value`' + ) + nodeAssert.ok('children' in node, 'parent should have `children`') +} + +/** + * Assert that `node` is a valid unist literal. + * + * @param {unknown} [node] + * Thing to assert. + * @returns {asserts node is Literal} + * Nothing. + * @throws {AssertionError} + * When `node` is not a literal. + */ +function assertLiteral(node) { + assertNode(node) + + nodeAssert.strictEqual( + 'children' in node, + false, + 'literal should not have `children`' + ) + nodeAssert.ok('value' in node, 'literal should have `value`') +} + +/** + * Assert that `node` is a valid void node. + * + * @param {unknown} [node] + * Thing to assert. + * @returns {asserts node is _Void} + * Nothing. + * @throws {AssertionError} + * When `node` is not a node, a parent, or a literal. + */ +function assertVoid(node) { + assertNode(node) + + nodeAssert.strictEqual('value' in node, false, 'void should not have `value`') + nodeAssert.strictEqual( + 'children' in node, + false, + 'void should not have `children`' + ) +} + +/** + * Assert that `position` is a unist position. + * + * @param {unknown} position + * Thing to assert. + * @returns {asserts position is Position} + * Nothing. + * @throws {AssertionError} + * When `position` is not a position. + */ +function position(position) { + if (position !== null && position !== undefined) { + nodeAssert.ok( + typeof position === 'object' && position === Object(position), + '`position` should be an object' + ) + + // @ts-expect-error: indexable. + point(position.start, 'position.start') + // @ts-expect-error: indexable. + point(position.end, 'position.end') + } +} + +/** + * Assert `point` is a unist point. + * + * @param {unknown} point + * Thing to assert. + * @param {string} label + * `start` or `end` + * @returns {asserts point is Point} + * Nothing. + * @throws {AssertionError} + * When `point` is not a position. + */ +function point(point, label) { + if (point !== null && point !== undefined) { + nodeAssert.ok( + typeof point === 'object' && point === Object(point), + '`' + label + '` should be an object' + ) + + if ('line' in point && point.line !== null && point.line !== undefined) { + nodeAssert.ok( + typeof point.line === 'number', + '`' + label + '` should have numeric `line`' + ) + nodeAssert.ok(point.line >= 1, '`' + label + '.line` should be gte `1`') + } + + if ( + 'column' in point && + point.column !== null && + point.column !== undefined + ) { + nodeAssert.ok( + typeof point.column === 'number', + '`' + label + '` should have numeric `column`' + ) + nodeAssert.ok( + point.column >= 1, + '`' + label + '.column` should be gte `1`' + ) + } + } +} diff --git a/inspect.browser.js b/lib/inspect.browser.js similarity index 100% rename from inspect.browser.js rename to lib/inspect.browser.js diff --git a/inspect.js b/lib/inspect.js similarity index 73% rename from inspect.js rename to lib/inspect.js index 99f3c98..389b9aa 100644 --- a/inspect.js +++ b/lib/inspect.js @@ -1,4 +1,4 @@ -import {inspect as utilInspect} from 'util' +import {inspect as utilInspect} from 'node:util' /** * @param {unknown} value diff --git a/package.json b/package.json index 7072d53..9ecb287 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-assert", - "version": "3.0.1", + "version": "3.0.2", "description": "unist utility to assert nodes", "license": "MIT", "keywords": [ @@ -26,42 +26,37 @@ "type": "module", "main": "index.js", "browser": { - "./inspect.js": "./inspect.browser.js" + "./lib/inspect.js": "./lib/inspect.browser.js" }, "react-native": { - "./inspect.js": "./inspect.browser.js" + "./lib/inspect.js": "./lib/inspect.browser.js" }, "types": "index.d.ts", "files": [ + "lib/", "index.d.ts", - "index.js", - "inspect.d.ts", - "inspect.js", - "inspect.browser.d.ts", - "inspect.browser.js" + "index.js" ], "dependencies": { "@types/unist": "^2.0.0" }, "devDependencies": { - "@types/tape": "^4.0.0", + "@types/node": "^18.0.0", "c8": "^7.0.0", "prettier": "^2.0.0", - "remark-cli": "^9.0.0", - "remark-preset-wooorm": "^8.0.0", - "rimraf": "^3.0.0", - "tape": "^5.0.0", - "tsd": "^0.14.0", + "remark-cli": "^11.0.0", + "remark-preset-wooorm": "^9.0.0", + "tsd": "^0.25.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "xo": "^0.40.0" + "xo": "^0.53.0" }, "scripts": { "prepack": "npm run build && npm run format", - "build": "rimraf \"{test/**,}*.d.ts\" && tsc && tsd && type-coverage", + "build": "tsc --build --clean && tsc --build && tsd && type-coverage", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", - "test-api": "node test/index.js", - "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js", + "test-api": "node --conditions development test/index.js", + "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { @@ -73,12 +68,7 @@ "trailingComma": "none" }, "xo": { - "prettier": true, - "rules": { - "no-var": "off", - "prefer-arrow-callback": "off", - "unicorn/prefer-node-protocol": "off" - } + "prettier": true }, "remarkConfig": { "plugins": [ @@ -89,6 +79,10 @@ "atLeast": 100, "detail": true, "strict": true, - "ignoreCatch": true + "ignoreCatch": true, + "#": "Couple of needed any’s", + "ignoreFiles": [ + "lib/index.d.ts" + ] } } diff --git a/readme.md b/readme.md index 839bf47..384f48b 100644 --- a/readme.md +++ b/readme.md @@ -8,19 +8,64 @@ [![Backers][backers-badge]][collective] [![Chat][chat-badge]][chat] -[**unist**][unist] utility to assert trees. +[unist][] utility to assert trees. -## Install +## Contents + +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`assert(tree[, parent])`](#asserttree-parent) + * [`parent(tree[, parent])`](#parenttree-parent) + * [`literal(node[, parent])`](#literalnode-parent) + * [`_void(node[, parent])`](#_voidnode-parent) + * [`wrap(fn)`](#wrapfn) + * [`AssertionError`](#assertionerror) +* [Extensions](#extensions) +* [Types](#types) +* [Compatibility](#compatibility) +* [Related](#related) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This package is a tiny utility that helps you deal with nodes. + +## When should I use this? -This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): -Node 12+ is needed to use it and it must be `import`ed instead of `require`d. +This utility is typically useful when you expect certain nodes in your APIs +and want to make sure they’re valid and as expected. -[npm][]: +Different utilities, [`mdast-util-assert`][mdast-util-assert], +[`hast-util-assert`][hast-util-assert], and [`nlcst-test`][nlcst-test], +do the same but for mdast, hast, and nlcst nodes, respectively. + +## Install + +This package is [ESM only][esm]. +In Node.js (version 14.14+ and 16.0+), install with [npm][]: ```sh npm install unist-util-assert ``` +In Deno with [`esm.sh`][esmsh]: + +```js +import {assert} from 'https://esm.sh/unist-util-assert@3' +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + ## Use ```js @@ -49,57 +94,148 @@ assert({type: 'paragraph', children: ['foo']}) ## API -This package exports the following identifiers: `assert`, `parent`, `literal`, -`_void`, `wrap`. +This package exports the identifiers [`_void`][void], [`assert`][assert], +[`literal`][literal], [`parent`][parent], and [`wrap`][wrap]. There is no default export. -### `assert(node[, parent])` +### `assert(tree[, parent])` + +Assert that `tree` is a valid unist [`Node`][node]. + +If `tree` is a parent, all children will be asserted too. + +###### Parameters -Assert that `node` is a valid [unist][] [node][]. -If `node` is a [parent][], all [child][]ren will be asserted too. +* `tree` (`unknown`) + — thing to assert +* `parent` ([`Parent`][parent-node], optional) + — optional, valid parent -### `parent(node[, parent])` +###### Returns -Assert that `node` is a valid [unist][] [parent][]. -All [child][]ren will be asserted too. +Nothing. + +###### Throws + +When `tree` (or its descendants) is not a node. + +### `parent(tree[, parent])` + +Assert that `tree` is a valid unist [`Parent`][parent-node]. + +All children will be asserted too. + +###### Parameters + +* `tree` (`unknown`) + — thing to assert +* `parent` ([`Parent`][parent-node], optional) + — optional, valid parent + +###### Returns + +Nothing. + +###### Throws + +When `tree` is not a parent or its descendants are not nodes. ### `literal(node[, parent])` -Assert that `node` is a valid [unist][] [literal][]. +Assert that `node` is a valid unist [`Literal`][literal-node]. + +###### Parameters + +* `tree` (`unknown`) + — thing to assert +* `parent` ([`Parent`][parent-node], optional) + — optional, valid parent + +###### Returns + +Nothing. + +###### Throws + +When `node` is not a literal. ### `_void(node[, parent])` -Assert that `node` is a valid [unist][] [node][], but neither [parent][] nor -[literal][]. +Assert that `node` is a valid void node. + +###### Parameters + +* `tree` (`unknown`) + — thing to assert +* `parent` ([`Parent`][parent-node], optional) + — optional, valid parent + +###### Returns + +Nothing. + +###### Throws + +When `node` is not a node, a parent, or a literal. + +### `wrap(fn)` + +Wrapper that adds the current node (and parent, if available) to error +messages. + +###### Parameters + +* `fn` (`(node?: any, parent?: Parent | null | undefined) => asserts node is Node)`) + — custom assertion + +###### Returns + +Wrapped `fn`. + +### `AssertionError` + +Assertion error from `node:assert` (TypeScript type). + +###### Type + +```ts +type AssertionError = import('node:assert').AssertionError +``` ## Extensions -This module can be used as a base to test subsets of [unist][] (for an example, -see [`mdast-util-assert`][mdast-util-assert]). +This module can be used as a base to test subsets of unist (for an example, see +[`mdast-util-assert`][mdast-util-assert]). All functions that are exposed from such a module, and functions used internally -to test [child][]ren, should be wrapped in `wrap`, which adds an inspectable -string of the respective node, and its parent when available, to the exposed -error message. +to test children, should be wrapped in `wrap`, which adds an inspectable string +of the respective node, and its parent when available, to the exposed error +message. -### `wrap(fn)` +## Types -Wraps `fn` (which is passed a node, and an optional parent node), so that any -errors thrown inside it will contain information regarding the node (and the -parent, when given). +This package is fully typed with [TypeScript][]. +It exports the additional type [`AssertionError`][assertionerror]. + +## Compatibility + +Projects maintained by the unified collective are compatible with all 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. ## Related * [`mdast-util-assert`][mdast-util-assert] - — Check [mdast](https://github.com/syntax-tree/mdast) nodes + — assert mdast nodes * [`hast-util-assert`](https://github.com/syntax-tree/hast-util-assert) - — Check [hast](https://github.com/syntax-tree/hast) nodes + — assert hast nodes * [`nlcst-test`](https://github.com/syntax-tree/nlcst-test) - — Check [nlcst](https://github.com/syntax-tree/nlcst) nodes + — assert nlcst nodes ## Contribute -See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get -started. +See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for +ways to get started. See [`support.md`][support] for ways to get help. This project has a [code of conduct][coc]. @@ -140,24 +276,46 @@ abide by its terms. [npm]: https://docs.npmjs.com/cli/install +[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +[esmsh]: https://esm.sh + +[typescript]: https://www.typescriptlang.org + [license]: license [author]: https://wooorm.com -[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md +[health]: https://github.com/syntax-tree/.github -[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md +[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md -[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md +[support]: https://github.com/syntax-tree/.github/blob/main/support.md -[unist]: https://github.com/syntax-tree/unist - -[parent]: https://github.com/syntax-tree/unist#parent +[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md -[literal]: https://github.com/syntax-tree/unist#literal +[unist]: https://github.com/syntax-tree/unist [node]: https://github.com/syntax-tree/unist#node -[child]: https://github.com/syntax-tree/unist#child +[parent-node]: https://github.com/syntax-tree/unist#parent + +[literal-node]: https://github.com/syntax-tree/unist#literal [mdast-util-assert]: https://github.com/syntax-tree/mdast-util-assert + +[hast-util-assert]: https://github.com/syntax-tree/hast-util-assert + +[nlcst-test]: https://github.com/syntax-tree/nlcst-test + +[assert]: #asserttree-parent + +[parent]: #parenttree-parent + +[literal]: #literalnode-parent + +[void]: #_voidnode-parent + +[wrap]: #wrapfn + +[assertionerror]: #assertionerror diff --git a/test/children.js b/test/children.js index 2e0ab62..6a8e27c 100644 --- a/test/children.js +++ b/test/children.js @@ -1,29 +1,30 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('children', function (t) { - t.throws( - function () { +test('children', () => { + nodeAssert.throws( + () => { assert({type: 'foo', children: {alpha: 'bravo'}}) }, /`children` should be an array: `{ type: 'foo', children: { alpha: 'bravo' } }`$/, 'should throw if given a non-node child in children' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', children: ['one']}) }, /node should be an object: `'one'` in `{ type: 'foo', children: \[ 'one' ] }`$/, 'should throw if given a non-node child in children' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'parent', children: [{type: 'text', value: 'alpha'}]}) }, 'should not throw on vald children') - t.throws( - function () { + nodeAssert.throws( + () => { assert({ type: 'foo', children: [ @@ -37,6 +38,4 @@ test('children', function (t) { /node should be an object: `'one'` in `{ type: 'bar', children: \[ 'one' ] }`$/, 'should throw on invalid descendants' ) - - t.end() }) diff --git a/test/index.js b/test/index.js index 63a1f69..4f863a5 100644 --- a/test/index.js +++ b/test/index.js @@ -9,3 +9,15 @@ import './parent.js' import './literal.js' import './void.js' /* eslint-enable import/no-unassigned-import */ + +import assert from 'node:assert/strict' +import test from 'node:test' +import * as mod from '../index.js' + +test('assert', () => { + assert.deepEqual( + Object.keys(mod).sort(), + ['_void', 'assert', 'literal', 'parent', 'wrap'], + 'should expose the public api' + ) +}) diff --git a/test/literal.js b/test/literal.js index 1ea51fb..b0c11fd 100644 --- a/test/literal.js +++ b/test/literal.js @@ -1,34 +1,33 @@ -import test from 'tape' +import assert from 'node:assert/strict' +import test from 'node:test' import {literal} from '../index.js' -test('literal()', function (t) { - t.throws( - function () { +test('literal()', () => { + assert.throws( + () => { literal({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' ) - t.throws( - function () { + assert.throws( + () => { literal({type: 'strong', children: []}) }, /literal should not have `children`: `{ type: 'strong', children: \[] }`$/, 'should throw if the given node has `children`' ) - t.throws( - function () { + assert.throws( + () => { literal({type: 'break'}) }, /literal should have `value`: `{ type: 'break' }`$/, 'should throw if the given node has no `value`' ) - t.doesNotThrow(function () { + assert.doesNotThrow(() => { literal({type: 'text', value: 'foo'}) }, 'should not throw on valid text nodes') - - t.end() }) diff --git a/test/node.js b/test/node.js index a557703..f9da6d6 100644 --- a/test/node.js +++ b/test/node.js @@ -1,38 +1,37 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('node', function (t) { - t.throws( - function () { +test('node', () => { + nodeAssert.throws( + () => { assert() }, /node should be an object: `undefined`$/, 'should throw if not given a node (#1)' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert(null) }, /node should be an object: `null`$/, 'should throw if not given a node (#2)' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert('foo') }, /node should be an object: `'foo'`$/, 'should throw if given a non-node (#1)' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert(6) }, /node should be an object: `6`$/, 'should throw if not given a non-node (#2)' ) - - t.end() }) diff --git a/test/non-defined.js b/test/non-defined.js index 5632088..abe7431 100644 --- a/test/non-defined.js +++ b/test/non-defined.js @@ -1,8 +1,9 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('non-defined', function (t) { - t.doesNotThrow(function () { +test('non-defined', () => { + nodeAssert.doesNotThrow(() => { assert({ type: 'element', properties: { @@ -17,8 +18,8 @@ test('non-defined', function (t) { }) }, 'should not throw if non-defined properties are found') - t.throws( - function () { + nodeAssert.throws( + () => { assert({ type: 'break', data: {foo: Function} @@ -27,6 +28,4 @@ test('non-defined', function (t) { /non-specced property `data` should be JSON: `{ type: 'break', data: { foo: \[Function: Function] } }`$/, 'should throw if non-defined properties are not serialisable' ) - - t.end() }) diff --git a/test/parent.js b/test/parent.js index 0879528..c247a19 100644 --- a/test/parent.js +++ b/test/parent.js @@ -1,34 +1,33 @@ -import test from 'tape' +import assert from 'node:assert/strict' +import test from 'node:test' import {parent} from '../index.js' -test('parent()', function (t) { - t.throws( - function () { +test('parent()', () => { + assert.throws( + () => { parent({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' ) - t.throws( - function () { + assert.throws( + () => { parent({type: 'text', value: 'foo'}) }, /parent should not have `value`: `{ type: 'text', value: 'foo' }`$/, 'should throw if the given node has a `value`' ) - t.throws( - function () { + assert.throws( + () => { parent({type: 'break'}) }, /parent should have `children`: `{ type: 'break' }`$/, 'should throw if the given node has `children`' ) - t.doesNotThrow(function () { + assert.doesNotThrow(() => { parent({type: 'strong', children: []}) }, 'should not throw on valid void nodes') - - t.end() }) diff --git a/test/position.js b/test/position.js index 55244c7..7c09655 100644 --- a/test/position.js +++ b/test/position.js @@ -1,102 +1,101 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('position', function (t) { - t.throws( - function () { +test('position', () => { + nodeAssert.throws( + () => { assert({type: 'foo', position: 1}) }, /`position` should be an object: `{ type: 'foo', position: 1 }`$/, 'should throw if given a non-object `position`' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: null}) }, 'should not throw if given a null `position`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {}}) }, 'should not throw if given an empty `position` object') - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {start: 1}}) }, /`position.start` should be an object: `{ type: 'foo', position: { start: 1 } }`$/, 'should throw if given a non-object `position.start`' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {start: null}}) }, 'should not throw if given a null `position.start`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {start: {}}}) }, 'should not throw if given an empty `position.start` object') - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {end: 1}}) }, /`position.end` should be an object: `{ type: 'foo', position: { end: 1 } }`$/, 'should throw if given a non-object `position.end`' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {end: null}}) }, 'should not throw if given a null `position.end`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {end: {}}}) }, 'should not throw if given an empty `position.end` object') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {start: {line: null}}}) }, 'should not throw if given a `position.start.line` to `null`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {start: {column: null}}}) }, 'should not throw if given a `position.start.column` to `null`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {end: {line: null}}}) }, 'should not throw if given a `position.end.line` to `null`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', position: {end: {column: null}}}) }, 'should not throw if given a `position.end.column` to `null`') - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {start: {line: 0}}}) }, /`position.start.line` should be gte `1`: `{ type: 'foo', position: { start: { line: 0 } } }`$/, 'should throw if `position.start.line` is less than 1' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {start: {column: 0}}}) }, /`position.start.column` should be gte `1`: `{ type: 'foo', position: { start: { column: 0 } } }`$/, 'should throw if `position.start.column` is less than 1' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {end: {line: 0}}}) }, /`position.end.line` should be gte `1`: `{ type: 'foo', position: { end: { line: 0 } } }`$/, 'should throw if `position.end.line` is less than 1' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 'foo', position: {end: {column: 0}}}) }, /`position.end.column` should be gte `1`: `{ type: 'foo', position: { end: { column: 0 } } }`$/, 'should throw if `position.end.column` is less than 1' ) - - t.end() }) diff --git a/test/type.js b/test/type.js index 5112d5d..52e2afd 100644 --- a/test/type.js +++ b/test/type.js @@ -1,42 +1,41 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('type', function (t) { - t.throws( - function () { +test('type', () => { + nodeAssert.throws( + () => { assert({}) }, /node should have a type: `{}`$/, 'should throw if not given a `type` (#1)' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({value: 'foo'}) }, /node should have a type: `{ value: 'foo' }`$/, 'should throw if not given a type (#2)' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: 1}) }, /`type` should be a string: `{ type: 1 }`$/, 'should throw if not given a non-string type' ) - t.throws( - function () { + nodeAssert.throws( + () => { assert({type: ''}) }, /`type` should not be empty: `{ type: '' }`$/, 'should throw if given an empty string type' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo'}) }, 'should not throw if given a non-empty type string') - - t.end() }) diff --git a/test/value.js b/test/value.js index e180874..f2f50e5 100644 --- a/test/value.js +++ b/test/value.js @@ -1,30 +1,29 @@ -import test from 'tape' +import nodeAssert from 'node:assert/strict' +import test from 'node:test' import {assert} from '../index.js' -test('value', function (t) { - t.throws( - function () { +test('value', () => { + nodeAssert.throws( + () => { assert({type: 'foo', value: 1}) }, /`value` should be a string: `{ type: 'foo', value: 1 }`$/, 'should throw if given a non-string `value`' ) - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', value: ''}) }, 'should not throw if given an empty string `value`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', value: 'foo'}) }, 'should not throw if given an string `value`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', value: undefined}) }, 'should not throw if given an undefined `value`') - t.doesNotThrow(function () { + nodeAssert.doesNotThrow(() => { assert({type: 'foo', value: null}) }, 'should not throw if given an null `value`') - - t.end() }) diff --git a/test/void.js b/test/void.js index 2cb03d4..7384f2c 100644 --- a/test/void.js +++ b/test/void.js @@ -1,34 +1,33 @@ -import test from 'tape' +import assert from 'node:assert/strict' +import test from 'node:test' import {_void} from '../index.js' -test('_void()', function (t) { - t.throws( - function () { +test('_void()', () => { + assert.throws( + () => { _void({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' ) - t.throws( - function () { + assert.throws( + () => { _void({type: 'text', value: 'foo'}) }, /void should not have `value`: `{ type: 'text', value: 'foo' }`$/, 'should throw if the given node has a `value`' ) - t.throws( - function () { + assert.throws( + () => { _void({type: 'strong', children: []}) }, /void should not have `children`: `{ type: 'strong', children: \[] }`$/, 'should throw if the given node has `children`' ) - t.doesNotThrow(function () { + assert.doesNotThrow(() => { _void({type: 'break'}) }, 'should not throw on valid void nodes') - - t.end() }) diff --git a/tsconfig.json b/tsconfig.json index 1cb61bf..1bc9e99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,17 @@ { - "include": ["*.js", "test/**/*.js"], + "include": ["**/**.js"], + "exclude": ["coverage/", "node_modules/"], "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020"], - "module": "ES2020", - "moduleResolution": "node", - "allowJs": true, "checkJs": true, "declaration": true, "emitDeclarationOnly": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "node16", + "newLine": "lf", + "skipLibCheck": true, + "strict": true, + "target": "es2020" } }