diff --git a/.gitignore b/.gitignore index 735f4af..c977c85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +*.d.ts *.log coverage/ node_modules/ diff --git a/index.js b/index.js index 9323d23..38ed0dd 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,63 @@ +/** + * @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 + */ + import nodeAssert from 'assert' import {inspect} from './inspect.js' -export var assert = wrap(assertNode) -assert.parent = wrap(parent) -assert.text = wrap(text) -assert.void = wrap(empty) +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__' @@ -12,23 +65,30 @@ var ID = '__unist__' // List of specced properties. var defined = new Set(['type', 'value', 'children', 'position']) -// Wrapper around `Node` which adds the current node (and parent, if available), -// to the message. +/** + * 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 (!error[ID]) { + if (!own.call(error, ID)) { error[ID] = true - error.message += ': `' + view(node) + '`' - - if (parent) { - error.message += ' in `' + view(parent) + '`' - } + if (parent) error.message += ' in `' + view(parent) + '`' } throw error @@ -36,29 +96,47 @@ export function wrap(fn) { } } -// Assert. +/** + * Assert. + * + * @param {unknown} node + * @returns {asserts node is Node} + */ function assertNode(node) { - var type - var children - var value + var index = -1 + /** @type {Parent} */ + var parent + /** @type {unknown} */ + var child + /** @type {string} */ var key - var index - var length - - nodeAssert.ok(node === Object(node), 'node should be an object') - type = node.type - children = node.children - value = node.value + nodeAssert.ok( + node && typeof node === 'object' && !Array.isArray(node), + 'node should be an object' + ) - nodeAssert.ok('type' in node, 'node should have a type') - nodeAssert.strictEqual(typeof type, 'string', '`type` should be a string') - nodeAssert.notStrictEqual(type, '', '`type` should not be empty') + 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') - if (value !== null && value !== undefined) { - nodeAssert.strictEqual(typeof value, 'string', '`value` should be a string') + // @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) { @@ -67,19 +145,30 @@ function assertNode(node) { } } - if (children !== null && children !== undefined) { - nodeAssert.ok(Array.isArray(children), '`children` should be an array') + // @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 - length = children.length - while (++index < length) { - assert(children[index], node) + 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. +/** + * 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))) @@ -88,8 +177,13 @@ function vanilla(key, value) { } } -// Stringify a value to inspect it. -// Tries `JSON.stringify()`, and if that fails uses `String()` instead. +/** + * 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) @@ -99,8 +193,13 @@ function view(value) { } } -// Assert `node` is a parent node. -function parent(node) { +/** + * Assert `node` is a parent node. + * + * @param {Node} node + * @returns {asserts node is Parent} + */ +function assertParent(node) { assertNode(node) nodeAssert.strictEqual( @@ -111,20 +210,30 @@ function parent(node) { nodeAssert.ok('children' in node, 'parent should have `children`') } -// Assert `node` is a text node. -function text(node) { +/** + * 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, - 'text should not have `children`' + 'literal should not have `children`' ) - nodeAssert.ok('value' in node, 'text should have `value`') + nodeAssert.ok('value' in node, 'literal should have `value`') } -// Assert `node` is a unist node, but neither parent nor text. -function empty(node) { +/** + * 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`') @@ -135,7 +244,12 @@ function empty(node) { ) } -// Assert `position` is a unist position. +/** + * Assert `position` is a unist position. + * + * @param {Position} position + * @returns {asserts position is Position} + */ function position(position) { if (position !== null && position !== undefined) { nodeAssert.ok( @@ -148,27 +262,36 @@ function position(position) { } } -// Assert `point` is a unist point. -function point(point, name) { +/** + * 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), '`' + name + '` should be an object') + nodeAssert.ok( + point === Object(point), + '`' + label + '` should be an object' + ) if (point.line !== null && point.line !== undefined) { nodeAssert.ok( 'line' in point, - '`' + name + '` should have numeric `line`' + '`' + label + '` should have numeric `line`' ) - nodeAssert.ok(point.line >= 1, '`' + name + '.line` should be gte `1`') + nodeAssert.ok(point.line >= 1, '`' + label + '.line` should be gte `1`') } if (point.column !== null && point.column !== undefined) { nodeAssert.ok( 'column' in point, - '`' + name + '` should have numeric `column`' + '`' + label + '` should have numeric `column`' ) nodeAssert.ok( point.column >= 1, - '`' + name + '.column` should be gte `1`' + '`' + label + '.column` should be gte `1`' ) } } diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..a763e3b --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,18 @@ +import {expectType, expectNotType} from 'tsd' +import {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]} + +expectNotType(emptyNode) +expectNotType(literalNode) +expectNotType(parentNode) + +assert(emptyNode) +expectType(emptyNode) + +expectNotType(parentNode) +parent(parentNode) +expectType(parentNode) diff --git a/inspect.browser.js b/inspect.browser.js index ba997b8..ae54a93 100644 --- a/inspect.browser.js +++ b/inspect.browser.js @@ -1,3 +1,7 @@ +/** + * @param {unknown} value + * @returns {string} + */ export function inspect(value) { return JSON.stringify(value) } diff --git a/inspect.js b/inspect.js index e1fa1c9..99f3c98 100644 --- a/inspect.js +++ b/inspect.js @@ -1,5 +1,9 @@ import {inspect as utilInspect} from 'util' +/** + * @param {unknown} value + * @returns {string} + */ export function inspect(value) { return utilInspect(value, {colors: false}) } diff --git a/package.json b/package.json index 4b3b9b6..3eb6858 100644 --- a/package.json +++ b/package.json @@ -31,29 +31,38 @@ "react-native": { "./inspect.js": "./inspect.browser.js" }, - "types": "types/index.d.ts", + "types": "index.d.ts", "files": [ + "index.d.ts", "index.js", + "inspect.d.ts", "inspect.js", - "inspect.browser.js", - "types/index.d.ts" + "inspect.browser.d.ts", + "inspect.browser.js" ], - "dependencies": {}, + "dependencies": { + "@types/unist": "^2.0.0" + }, "devDependencies": { + "@types/tape": "^4.0.0", "c8": "^7.0.0", - "dtslint": "^4.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", + "type-coverage": "^2.0.0", + "typescript": "^4.0.0", "xo": "^0.38.0" }, "scripts": { + "prepack": "npm run build && npm run format", + "build": "rimraf \"{test/**,}*.d.ts\" && tsc && 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-types": "dtslint types", - "test": "npm run format && npm run test-coverage && npm run test-types" + "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -69,14 +78,17 @@ "import/no-mutable-exports": "off", "no-var": "off", "prefer-arrow-callback": "off" - }, - "ignore": [ - "types/" - ] + } }, "remarkConfig": { "plugins": [ "preset-wooorm" ] + }, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true, + "ignoreCatch": true } } diff --git a/readme.md b/readme.md index e40f06f..839bf47 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ npm install unist-util-assert ## Use ```js -import {assert} from 'unist-util-assert' +import {assert, parent, _void} from 'unist-util-assert' assert({type: 'root', children: []}) assert({type: 'break'}) @@ -34,13 +34,13 @@ assert({type: 'element', properties: {}, children: []}) assert({children: []}) // AssertionError: node should have a type: `{ children: [] }` -assert.parent({type: 'break'}) +parent({type: 'break'}) // AssertionError: parent should have `children`: `{ type: 'break' }` assert({type: 'element', properties: function() {}}) // AssertionError: non-specced property `properties` should be JSON: `{ type: 'element', properties: [Function] }` -assert.void({type: 'text', value: 'Alpha'}) +_void({type: 'text', value: 'Alpha'}) // AssertionError: void should not have `value`: `{ type: 'text', value: 'Alpha' }` assert({type: 'paragraph', children: ['foo']}) @@ -49,24 +49,25 @@ assert({type: 'paragraph', children: ['foo']}) ## API -This package exports the following identifiers: `assert`, `wrap`. +This package exports the following identifiers: `assert`, `parent`, `literal`, +`_void`, `wrap`. There is no default export. -### `assert(tree)` +### `assert(node[, parent])` -Assert that [`tree`][tree] is a valid [unist][] [node][]. -If `tree` is a [parent][], all [child][]ren will be asserted as well. +Assert that `node` is a valid [unist][] [node][]. +If `node` is a [parent][], all [child][]ren will be asserted too. -### `assert.parent(tree)` +### `parent(node[, parent])` -Assert that `tree` is a valid [unist][] [parent][]. -All [child][]ren will be asserted as well. +Assert that `node` is a valid [unist][] [parent][]. +All [child][]ren will be asserted too. -### `assert.text(node)` +### `literal(node[, parent])` Assert that `node` is a valid [unist][] [literal][]. -### `assert.void(node)` +### `_void(node[, parent])` Assert that `node` is a valid [unist][] [node][], but neither [parent][] nor [literal][]. @@ -157,8 +158,6 @@ abide by its terms. [node]: https://github.com/syntax-tree/unist#node -[tree]: https://github.com/syntax-tree/unist#tree - [child]: https://github.com/syntax-tree/unist#child [mdast-util-assert]: https://github.com/syntax-tree/mdast-util-assert diff --git a/test/index.js b/test/index.js index d1b5062..63a1f69 100644 --- a/test/index.js +++ b/test/index.js @@ -6,6 +6,6 @@ import './children.js' import './position.js' import './non-defined.js' import './parent.js' -import './text.js' +import './literal.js' import './void.js' /* eslint-enable import/no-unassigned-import */ diff --git a/test/text.js b/test/literal.js similarity index 54% rename from test/text.js rename to test/literal.js index 0fb9d2b..1ea51fb 100644 --- a/test/text.js +++ b/test/literal.js @@ -1,10 +1,10 @@ import test from 'tape' -import {assert} from '../index.js' +import {literal} from '../index.js' -test('assert.text()', function (t) { +test('literal()', function (t) { t.throws( function () { - assert.text({}) + literal({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' @@ -12,22 +12,22 @@ test('assert.text()', function (t) { t.throws( function () { - assert.text({type: 'strong', children: []}) + literal({type: 'strong', children: []}) }, - /text should not have `children`: `{ type: 'strong', children: \[] }`$/, + /literal should not have `children`: `{ type: 'strong', children: \[] }`$/, 'should throw if the given node has `children`' ) t.throws( function () { - assert.text({type: 'break'}) + literal({type: 'break'}) }, - /text should have `value`: `{ type: 'break' }`$/, + /literal should have `value`: `{ type: 'break' }`$/, 'should throw if the given node has no `value`' ) t.doesNotThrow(function () { - assert.text({type: 'text', value: 'foo'}) + literal({type: 'text', value: 'foo'}) }, 'should not throw on valid text nodes') t.end() diff --git a/test/parent.js b/test/parent.js index 70085e9..0879528 100644 --- a/test/parent.js +++ b/test/parent.js @@ -1,10 +1,10 @@ import test from 'tape' -import {assert} from '../index.js' +import {parent} from '../index.js' -test('assert.parent()', function (t) { +test('parent()', function (t) { t.throws( function () { - assert.parent({}) + parent({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' @@ -12,7 +12,7 @@ test('assert.parent()', function (t) { t.throws( function () { - assert.parent({type: 'text', value: 'foo'}) + parent({type: 'text', value: 'foo'}) }, /parent should not have `value`: `{ type: 'text', value: 'foo' }`$/, 'should throw if the given node has a `value`' @@ -20,14 +20,14 @@ test('assert.parent()', function (t) { t.throws( function () { - assert.parent({type: 'break'}) + parent({type: 'break'}) }, /parent should have `children`: `{ type: 'break' }`$/, 'should throw if the given node has `children`' ) t.doesNotThrow(function () { - assert.parent({type: 'strong', children: []}) + parent({type: 'strong', children: []}) }, 'should not throw on valid void nodes') t.end() diff --git a/test/type.js b/test/type.js index 3e87b42..5112d5d 100644 --- a/test/type.js +++ b/test/type.js @@ -4,9 +4,9 @@ import {assert} from '../index.js' test('type', function (t) { t.throws( function () { - assert([1, 5]) + assert({}) }, - /node should have a type: `\[ 1, 5 ]`$/, + /node should have a type: `{}`$/, 'should throw if not given a `type` (#1)' ) diff --git a/test/void.js b/test/void.js index 9ff1d25..2cb03d4 100644 --- a/test/void.js +++ b/test/void.js @@ -1,10 +1,10 @@ import test from 'tape' -import {assert} from '../index.js' +import {_void} from '../index.js' -test('assert.void()', function (t) { +test('_void()', function (t) { t.throws( function () { - assert.void({}) + _void({}) }, /node should have a type: `{}`$/, 'should throw the same errors as `assert()`' @@ -12,7 +12,7 @@ test('assert.void()', function (t) { t.throws( function () { - assert.void({type: 'text', value: 'foo'}) + _void({type: 'text', value: 'foo'}) }, /void should not have `value`: `{ type: 'text', value: 'foo' }`$/, 'should throw if the given node has a `value`' @@ -20,14 +20,14 @@ test('assert.void()', function (t) { t.throws( function () { - assert.void({type: 'strong', children: []}) + _void({type: 'strong', children: []}) }, /void should not have `children`: `{ type: 'strong', children: \[] }`$/, 'should throw if the given node has `children`' ) t.doesNotThrow(function () { - assert.void({type: 'break'}) + _void({type: 'break'}) }, 'should not throw on valid void nodes') t.end() diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1cb61bf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["*.js", "test/**/*.js"], + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "ES2020", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true + } +} diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 590b0f0..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -// TypeScript Version: 3.7 - -import {Node, Parent, Literal} from 'unist' - -declare namespace unistUtilAssert { - /** - * A unist Node that is neither a Parent nor a Literal. - */ - interface Void extends Node { - children: never - value: never - } -} - -declare const unistUtilAssert: { - /** - * Assert that tree is a valid unist node. - * If tree is a parent, all children will be asserted as well. - */ - (tree: unknown): asserts tree is Node - - /** - * Assert that tree is a valid unist parent. - * All children will be asserted as well. - */ - parent(tree: unknown): asserts tree is Parent - - /** - * Assert that node is a valid unist literal. - */ - text(tree: unknown): asserts tree is Literal - - /** - * Assert that node is a valid unist node, but neither parent nor literal. - */ - void(tree: unknown): asserts tree is unistUtilAssert.Void -} - -export = unistUtilAssert diff --git a/types/tsconfig.json b/types/tsconfig.json deleted file mode 100644 index 6c5f15b..0000000 --- a/types/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "lib": ["es2015"], - "strict": true, - "baseUrl": ".", - "paths": { - "unist-util-assert": ["index.d.ts"] - } - } -} diff --git a/types/tslint.json b/types/tslint.json deleted file mode 100644 index 70c4494..0000000 --- a/types/tslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "dtslint/dtslint.json", - "rules": { - "semicolon": false, - "whitespace": false - } -} diff --git a/types/unist-util-assert-test.ts b/types/unist-util-assert-test.ts deleted file mode 100644 index 4ca0e8a..0000000 --- a/types/unist-util-assert-test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as assert from 'unist-util-assert' - -function testAssert() { - const node = {} - assert(node) - node // $ExpectType Node -} - -function testParentAssert() { - const node = {} - assert.parent(node) - node // $ExpectType Parent -} - -function testTextAssert() { - const node = {} - assert.text(node) - node // $ExpectType Literal -} - -function testVoidAssert() { - const node = {} - assert.void(node) - node // $ExpectType Void -}