From 6882e93d842d809da83651e4d8271dd0c54dbaec Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 13:45:19 +0200 Subject: [PATCH 01/11] Update dev-dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d45aae1..fa48094 100644 --- a/package.json +++ b/package.json @@ -44,15 +44,15 @@ "unist-util-visit-parents": "^5.0.0" }, "devDependencies": { - "@types/node": "^18.0.0", - "c8": "^7.0.0", + "@types/node": "^20.0.0", + "c8": "^8.0.0", "prettier": "^2.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", "type-coverage": "^2.0.0", - "typescript": "^4.0.0", + "typescript": "^5.0.0", "unist-builder": "^3.0.0", - "xo": "^0.53.0" + "xo": "^0.54.0" }, "scripts": { "prepack": "npm run build && npm run format", From 42828682ed65358aa04814aa128447e70f05d204 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 13:45:51 +0200 Subject: [PATCH 02/11] Refactor `package.json`, `tsconfig.json` --- package.json | 19 ++++++++++--------- tsconfig.json | 10 ++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index fa48094..1bc4b5e 100644 --- a/package.json +++ b/package.json @@ -59,28 +59,29 @@ "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/tsconfig.json b/tsconfig.json index ebe8889..870d82c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,15 @@ { - "include": ["**/*.js"], - "exclude": ["coverage/", "node_modules/"], "compilerOptions": { "checkJs": true, + "customConditions": ["development"], "declaration": true, "emitDeclarationOnly": true, "exactOptionalPropertyTypes": true, - "forceConsistentCasingInFileNames": true, "lib": ["es2020"], "module": "node16", - "newLine": "lf", - "skipLibCheck": true, "strict": true, "target": "es2020" - } + }, + "exclude": ["coverage/", "node_modules/"], + "include": ["**/*.js"] } From 855818d164241526c5425d3a6158309faa69c022 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 13:46:05 +0200 Subject: [PATCH 03/11] Refactor `.npmrc` --- .npmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 7811233ff0a8ffa2dc997aaf6b0f3e80de926e9f Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 15:47:49 +0200 Subject: [PATCH 04/11] Refactor code-style --- lib/index.js | 135 +++++++++++++------------- readme.md | 5 +- test.js | 268 +++++++++++++++++++++++++-------------------------- 3 files changed, 203 insertions(+), 205 deletions(-) diff --git a/lib/index.js b/lib/index.js index fad00aa..c8e3de6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,100 +2,105 @@ * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test - * + */ + +/** * @typedef Options * Configuration. * @property {boolean | null | undefined} [cascade=true] * Whether to drop parent nodes if they had children, but all their children - * were filtered out. + * were filtered out (default: `true`). */ import {convert} from 'unist-util-is' -/** @type {Array} */ -const empty = [] - /** * Change the given `tree` by removing all nodes that pass `test`. * * The tree is walked in preorder (NLR), visiting the node itself, then its * head, etc. * - * @param tree + * @template {Node} Tree + * Node kind. + * + * @overload + * @param {Tree} node + * @param {Test} [test] + * @returns {Tree | undefined} + * + * @overload + * @param {Tree} node + * @param {Options | null | undefined} options + * @param {Test} [test] + * @returns {Tree | undefined} + * + * @param {Tree} tree * Tree to change. - * @param options + * @param {Options | Test} options * Configuration (optional). - * @param test + * @param {Test} [test] * `unist-util-is` compatible test. - * @returns + * @returns {Tree | undefined} * The given `tree` without nodes that pass `test`. * - * `null` is returned if `tree` itself didn’t pass the test or is cascaded - * away. + * `undefined` is returned if `tree` itself didn’t pass the test or is + * cascaded away. */ // To do: next major: don’t return `tree`. -export const remove = - /** - * @type {( - * ((node: Tree, options: Options, test: Test) => Tree | null) & - * ((node: Tree, test: Test) => Tree | null) - * )} - */ - ( - /** - * @param {Node} tree - * @param {Options | null | undefined} [options] - * @param {Test | null | undefined} [test] - * @returns {Node | null} - */ - function (tree, options, test) { - const is = convert(test || options) - const cascade = - !options || options.cascade === undefined || options.cascade === null - ? true - : options.cascade +export function remove(tree, options, test) { + const is = convert(test || options) + let cascade = true - return preorder(tree) + if ( + options && + typeof options === 'object' && + 'cascade' in options && + typeof options.cascade === 'boolean' + ) { + cascade = options.cascade + } - /** - * Check and remove nodes recursively in preorder. - * For each composite node, modify its children array in-place. - * - * @param {Node} node - * @param {number | null | undefined} [index] - * @param {Parent | null | undefined} [parent] - * @returns {Node | null} - */ - function preorder(node, index, parent) { - /** @type {Array} */ - // @ts-expect-error looks like a parent. - const children = node.children || empty - let childIndex = -1 - let position = 0 + return preorder(tree) - if (is(node, index, parent)) { - return null - } + /** + * Check and remove nodes recursively in preorder. + * For each composite node, modify its children array in-place. + * + * @template {Node} Kind + * @param {Kind} node + * @param {number | undefined} [index] + * @param {Parent | undefined} [parent] + * @returns {Kind | undefined} + */ + function preorder(node, index, parent) { + if (is(node, index, parent)) { + return undefined + } - if (children.length > 0) { - // Move all living children to the beginning of the children array. - while (++childIndex < children.length) { - // @ts-expect-error looks like a parent. - if (preorder(children[childIndex], childIndex, node)) { - children[position++] = children[childIndex] - } - } + if ('children' in node && Array.isArray(node.children)) { + const nodeAsParent = /** @type {Parent} */ (node) + const children = nodeAsParent.children + let oldChildIndex = -1 + let newChildIndex = 0 - // Cascade delete. - if (cascade && !position) { - return null + if (children.length > 0) { + // Move all living children to the beginning of the children array. + while (++oldChildIndex < children.length) { + if (preorder(children[oldChildIndex], oldChildIndex, nodeAsParent)) { + children[newChildIndex++] = children[oldChildIndex] } + } - // Drop other nodes. - children.length = position + // Cascade delete. + if (cascade && !newChildIndex) { + return undefined } - return node + // Drop other nodes. + children.length = newChildIndex } } - ) + + return node + } +} diff --git a/readme.md b/readme.md index d9b7af3..529ae89 100644 --- a/readme.md +++ b/readme.md @@ -83,7 +83,7 @@ const tree = u('root', [ // Remove all nodes of type `leaf`. remove(tree, 'leaf') -console.dir(tree, {depth: null}) +console.dir(tree, {depth: undefined}) ``` Yields: @@ -128,7 +128,8 @@ head, etc. A changed given `tree`, without nodes that pass `test`. -`null` is returned if `tree` itself didn’t pass the test or is cascaded away. +`undefined` is returned if `tree` itself didn’t pass the test or is cascaded +away. ### `Options` diff --git a/test.js b/test.js index 226f45b..f683e5d 100644 --- a/test.js +++ b/test.js @@ -1,157 +1,149 @@ /** - * @typedef {import('unist').Node} Node * @typedef {import('unist').Literal} Literal + * @typedef {import('unist').Node} Node */ import assert from 'node:assert/strict' import test from 'node:test' import {u} from 'unist-builder' import {remove} from './index.js' -import * as mod from './index.js' -test('core', () => { - assert.deepEqual( - Object.keys(mod).sort(), - ['remove'], - 'should expose the public api' +test('remove', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('./index.js')).sort(), ['remove']) + }) + + await t.test('should compare nodes by partial properties', function () { + const leaf1 = u('leaf', '1') + const leaf2 = u('leaf', '2') + const children = [leaf1, leaf2] + const tree = u('node', children) + + const next = remove(tree, {value: '2'}) + + assert.deepEqual(tree, u('node', [leaf1])) + assert.equal(next, tree) + assert.equal(next.children, children) + assert.equal(next.children[0], leaf1) + }) + + await t.test('should remove parent nodes', function () { + const leaf1 = u('leaf', '1') + const leaf2 = u('leaf', '2') + const parent = u('parent', [leaf1]) + const children = [parent, leaf2] + const tree = u('root', children) + + const next = remove(tree, test) + + assert.deepEqual(tree, u('root', [leaf2])) + assert.equal(next, tree) + assert.equal(next.children, children) + assert.equal(next.children[0], leaf2) + + /** + * @param {Node} node + * @returns {boolean} + */ + function test(node) { + return node === parent + } + }) + + await t.test( + 'should return `undefined` if root node is removed', + function () { + const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + const next = remove(tree, 'root') + + assert.equal(next, undefined) + } ) -}) - -test('should compare nodes by partial properties', () => { - const tree = u('node', [u('leaf', '1'), u('leaf', '2')]) - const children = tree.children - const first = tree.children[0] - - const next = remove(tree, {value: '2'}) - - assert.equal(next, tree) - assert.deepEqual(tree, u('node', [first])) - assert.equal(tree.children, children) - assert.equal(tree.children[0], first) -}) - -test('should remove nodes with children', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - const children = tree.children - const first = tree.children[0] - const last = tree.children[1] - - const next = remove(tree, test) - - assert.equal(next, tree) - assert.deepEqual(tree, u('root', [last])) - assert.equal(tree.children, children) - assert.equal(tree.children[0], last) - - /** - * @param {Node} node - * @returns {boolean} - */ - function test(node) { - return node === first - } -}) -test('should return `null` if root node is removed', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + await t.test('should cascade (remove) root nodes', function () { + const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + const next = remove(tree, 'leaf') - assert.equal(remove(tree, 'root'), null) -}) - -test('should cascade-remove parent nodes', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - const children = tree.children - // @ts-expect-error it exists! - const first = children[0].children[0] - const last = children[1] - - const next = remove(tree, test) - - assert.equal(next, tree) - assert.deepEqual(tree, u('root', [last])) - assert.equal(tree.children, children) - assert.equal(tree.children[0], last) - - /** - * @param {Node} node - * @returns {boolean} - */ - function test(node) { - return node === first - } -}) + assert.equal(next, undefined) + }) -test('should cascade-remove root nodes', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + await t.test( + 'should not cascade (remove) nodes that were empty initially', + function () { + const tree = u('node', [u('node', []), u('node', [u('leaf')])]) - const next = remove(tree, 'leaf') - - assert.equal(next, null) -}) - -test('should not cascade-remove nodes that were empty initially', () => { - const tree = u('node', [u('node', []), u('node', [u('leaf')])]) - - remove(tree, 'leaf') - - assert.deepEqual(tree, u('node', [u('node', [])])) -}) + remove(tree, 'leaf') -test('should support type tests', () => { - const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - - remove(tree, {cascade: false}, 'leaf') - - assert.deepEqual(tree, u('node', [u('node', [])])) -}) - -test('should support function tests', () => { - const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - - remove(tree, {cascade: false}, (node) => literal(node) && node.value === '1') - - assert.deepEqual(tree, u('node', [u('node', []), u('leaf', '2')])) -}) - -test('opts.cascade = true', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - - const next = remove(tree, {cascade: true}, 'leaf') - - assert.equal(next, null) -}) - -test('opts.cascade = false', () => { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - const siblings = tree.children - const node = siblings[0] - // @ts-expect-error it exists! - const children = node.children - - const next = remove(tree, {cascade: false}, 'leaf') - - assert.equal(next, tree) - assert.deepEqual(tree, u('root', [u('node', [])])) - assert.equal(tree.children, siblings) - assert.equal(tree.children[0], node) - assert.equal(tree.children[0].children, children) -}) - -test('example from readme', () => { - const tree = u('root', [ - u('leaf', '1'), - u('node', [ - u('leaf', '2'), - u('node', [u('leaf', '3'), u('other', '4')]), - u('node', [u('leaf', '5')]) - ]), - u('leaf', '6') - ]) - - assert.deepEqual( - remove(tree, 'leaf'), - u('root', [u('node', [u('node', [u('other', '4')])])]) + assert.deepEqual(tree, u('node', [u('node', [])])) + } ) + + await t.test('should support type tests', function () { + const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + + remove(tree, {cascade: false}, 'leaf') + + assert.deepEqual(tree, u('node', [u('node', [])])) + }) + + await t.test('should support function tests', function () { + const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + + remove(tree, {cascade: false}, test) + + assert.deepEqual(tree, u('node', [u('node', []), u('leaf', '2')])) + + /** + * @param {Node} node + * @returns {boolean} + */ + function test(node) { + return literal(node) && node.value === '1' + } + }) + + await t.test('should support `cascade = true`', function () { + const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + const next = remove(tree, {cascade: true}, 'leaf') + + assert.equal(next, undefined) + }) + + await t.test('should support `cascade = false`', function () { + const leaf1 = u('leaf', '1') + const leaf2 = u('leaf', '2') + const nodeChildren = [leaf1] + const node = u('node', nodeChildren) + const siblings = [node, leaf2] + const tree = u('root', siblings) + + const next = remove(tree, {cascade: false}, 'leaf') + + assert.deepEqual(tree, u('root', [u('node', [])])) + assert.equal(next, tree) + assert.equal(next.children, siblings) + assert.equal(next.children[0], node) + assert.equal(next.children[0].children, nodeChildren) + }) + + await t.test('should support the example from readme', function () { + const tree = u('root', [ + u('leaf', '1'), + u('node', [ + u('leaf', '2'), + u('node', [u('leaf', '3'), u('other', '4')]), + u('node', [u('leaf', '5')]) + ]), + u('leaf', '6') + ]) + + remove(tree, 'leaf') + + assert.deepEqual( + tree, + u('root', [u('node', [u('node', [u('other', '4')])])]) + ) + }) }) /** From 47738fe44d017464a09f4afdfd9d8acd8b44cf17 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 15:51:52 +0200 Subject: [PATCH 05/11] Refactor docs --- readme.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 529ae89..5037968 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ To create trees, use [`unist-builder`][unist-builder]. ## 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-remove @@ -148,10 +148,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 12.20+, 14.14+, 16.0+, and 18.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-remove@^3`, +compatible with Node.js 12. ## Related @@ -184,21 +187,21 @@ abide by its terms. -[build-badge]: https://github.com/syntax-tree/unist-util-filter/workflows/main/badge.svg +[build-badge]: https://github.com/syntax-tree/unist-util-remove/workflows/main/badge.svg -[build]: https://github.com/syntax-tree/unist-util-filter/actions +[build]: https://github.com/syntax-tree/unist-util-remove/actions -[coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/unist-util-filter.svg +[coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/unist-util-remove.svg -[coverage]: https://codecov.io/github/syntax-tree/unist-util-filter +[coverage]: https://codecov.io/github/syntax-tree/unist-util-remove -[downloads-badge]: https://img.shields.io/npm/dm/unist-util-filter.svg +[downloads-badge]: https://img.shields.io/npm/dm/unist-util-remove.svg -[downloads]: https://www.npmjs.com/package/unist-util-filter +[downloads]: https://www.npmjs.com/package/unist-util-remove -[size-badge]: https://img.shields.io/bundlephobia/minzip/unist-util-filter.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unist-util-remove -[size]: https://bundlephobia.com/result?p=unist-util-filter +[size]: https://bundlejs.com/?q=unist-util-remove [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg From b2d67dd7bd9eaca9af55d4d73a5872795389b9f9 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 15:52:21 +0200 Subject: [PATCH 06/11] Change to use `export` map --- package.json | 3 +-- test.js | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1bc4b5e..82e8eca 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,7 @@ ], "sideEffects": false, "type": "module", - "main": "index.js", - "types": "index.d.ts", + "exports": "./index.js", "files": [ "lib/", "index.d.ts", diff --git a/test.js b/test.js index f683e5d..efc3591 100644 --- a/test.js +++ b/test.js @@ -6,11 +6,13 @@ import assert from 'node:assert/strict' import test from 'node:test' import {u} from 'unist-builder' -import {remove} from './index.js' +import {remove} from 'unist-util-remove' test('remove', async function (t) { await t.test('should expose the public api', async function () { - assert.deepEqual(Object.keys(await import('./index.js')).sort(), ['remove']) + assert.deepEqual(Object.keys(await import('unist-util-remove')).sort(), [ + 'remove' + ]) }) await t.test('should compare nodes by partial properties', function () { From ec2db0889d64a830c23113998037c2e25b1585af Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 15:52:49 +0200 Subject: [PATCH 07/11] Change to require Node.js 16 --- readme.md | 4 ++-- tsconfig.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 5037968..0b93d24 100644 --- a/readme.md +++ b/readme.md @@ -153,8 +153,8 @@ versions of Node.js. 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-remove@^3`, -compatible with Node.js 12. +This means we try to keep the current release line, `unist-util-remove@^4`, +compatible with Node.js 16. ## Related diff --git a/tsconfig.json b/tsconfig.json index 870d82c..82cc749 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,10 @@ "declaration": true, "emitDeclarationOnly": true, "exactOptionalPropertyTypes": true, - "lib": ["es2020"], + "lib": ["es2022"], "module": "node16", "strict": true, - "target": "es2020" + "target": "es2022" }, "exclude": ["coverage/", "node_modules/"], "include": ["**/*.js"] From b63722bcc14f1c44f4f70a0c6bf0d6dbb61e0dd8 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 3 Jul 2023 15:53:10 +0200 Subject: [PATCH 08/11] Change to remove `RemoveOptions` type --- index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/index.js b/index.js index c14c910..cd20d3f 100644 --- a/index.js +++ b/index.js @@ -2,9 +2,4 @@ * @typedef {import('./lib/index.js').Options} Options */ -// To do: next major: remove. -/** - * @typedef {Options} RemoveOptions - */ - export {remove} from './lib/index.js' From 9e86da11ec2329c3e0fe16e32ebd60fba243ca65 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 7 Jul 2023 15:23:59 +0200 Subject: [PATCH 09/11] Update `@types/unist` --- package.json | 9 ++--- test.js | 96 +++++++++++++++++++++++++++++----------------------- 2 files changed, 58 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 82e8eca..cc4a91f 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,12 @@ "index.js" ], "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.0.0" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "devDependencies": { + "@types/mdast": "^4.0.0", "@types/node": "^20.0.0", "c8": "^8.0.0", "prettier": "^2.0.0", @@ -50,7 +51,7 @@ "remark-preset-wooorm": "^9.0.0", "type-coverage": "^2.0.0", "typescript": "^5.0.0", - "unist-builder": "^3.0.0", + "unist-builder": "^4.0.0", "xo": "^0.54.0" }, "scripts": { diff --git a/test.js b/test.js index efc3591..d795fad 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,7 @@ /** - * @typedef {import('unist').Literal} Literal + * @typedef {import('mdast').Text} Text + * @typedef {import('mdast').Emphasis} Emphasis + * @typedef {import('mdast').Root} Root * @typedef {import('unist').Node} Node */ @@ -16,24 +18,26 @@ test('remove', async function (t) { }) await t.test('should compare nodes by partial properties', function () { - const leaf1 = u('leaf', '1') - const leaf2 = u('leaf', '2') + const leaf1 = u('text', '1') + const leaf2 = u('text', '2') const children = [leaf1, leaf2] - const tree = u('node', children) + /** @type {Emphasis} */ + const tree = u('emphasis', children) const next = remove(tree, {value: '2'}) - assert.deepEqual(tree, u('node', [leaf1])) + assert.deepEqual(tree, u('emphasis', [leaf1])) assert.equal(next, tree) assert.equal(next.children, children) assert.equal(next.children[0], leaf1) }) await t.test('should remove parent nodes', function () { - const leaf1 = u('leaf', '1') - const leaf2 = u('leaf', '2') - const parent = u('parent', [leaf1]) + const leaf1 = u('text', '1') + const leaf2 = u('text', '2') + const parent = u('emphasis', [leaf1]) const children = [parent, leaf2] + /** @type {Root} */ const tree = u('root', children) const next = remove(tree, test) @@ -55,7 +59,8 @@ test('remove', async function (t) { await t.test( 'should return `undefined` if root node is removed', function () { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + /** @type {Root} */ + const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) const next = remove(tree, 'root') assert.equal(next, undefined) @@ -63,8 +68,9 @@ test('remove', async function (t) { ) await t.test('should cascade (remove) root nodes', function () { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - const next = remove(tree, 'leaf') + /** @type {Root} */ + const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) + const next = remove(tree, 'text') assert.equal(next, undefined) }) @@ -72,56 +78,67 @@ test('remove', async function (t) { await t.test( 'should not cascade (remove) nodes that were empty initially', function () { - const tree = u('node', [u('node', []), u('node', [u('leaf')])]) + /** @type {Root} */ + const tree = u('root', [ + u('emphasis', []), + u('emphasis', [u('text', 'x')]) + ]) - remove(tree, 'leaf') + remove(tree, 'text') - assert.deepEqual(tree, u('node', [u('node', [])])) + assert.deepEqual(tree, u('root', [u('emphasis', [])])) } ) await t.test('should support type tests', function () { - const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + /** @type {Root} */ + const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) - remove(tree, {cascade: false}, 'leaf') + remove(tree, {cascade: false}, 'text') - assert.deepEqual(tree, u('node', [u('node', [])])) + assert.deepEqual(tree, u('root', [u('emphasis', [])])) }) await t.test('should support function tests', function () { - const tree = u('node', [u('node', [u('leaf', '1')]), u('leaf', '2')]) + /** @type {Emphasis} */ + const tree = u('emphasis', [ + u('emphasis', [u('text', '1')]), + u('text', '2') + ]) remove(tree, {cascade: false}, test) - assert.deepEqual(tree, u('node', [u('node', []), u('leaf', '2')])) + assert.deepEqual(tree, u('emphasis', [u('emphasis', []), u('text', '2')])) /** * @param {Node} node * @returns {boolean} */ function test(node) { - return literal(node) && node.value === '1' + return node.type === 'text' && 'value' in node && node.value === '1' } }) await t.test('should support `cascade = true`', function () { - const tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')]) - const next = remove(tree, {cascade: true}, 'leaf') + /** @type {Root} */ + const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) + const next = remove(tree, {cascade: true}, 'text') assert.equal(next, undefined) }) await t.test('should support `cascade = false`', function () { - const leaf1 = u('leaf', '1') - const leaf2 = u('leaf', '2') + const leaf1 = u('text', '1') + const leaf2 = u('text', '2') const nodeChildren = [leaf1] - const node = u('node', nodeChildren) + const node = u('emphasis', nodeChildren) const siblings = [node, leaf2] + /** @type {Root} */ const tree = u('root', siblings) - const next = remove(tree, {cascade: false}, 'leaf') + const next = remove(tree, {cascade: false}, 'text') - assert.deepEqual(tree, u('root', [u('node', [])])) + assert.deepEqual(tree, u('root', [u('emphasis', [])])) assert.equal(next, tree) assert.equal(next.children, siblings) assert.equal(next.children[0], node) @@ -129,29 +146,22 @@ test('remove', async function (t) { }) await t.test('should support the example from readme', function () { + /** @type {Root} */ const tree = u('root', [ - u('leaf', '1'), - u('node', [ - u('leaf', '2'), - u('node', [u('leaf', '3'), u('other', '4')]), - u('node', [u('leaf', '5')]) + u('text', '1'), + u('emphasis', [ + u('text', '2'), + u('emphasis', [u('text', '3'), u('inlineCode', '4')]), + u('emphasis', [u('text', '5')]) ]), - u('leaf', '6') + u('text', '6') ]) - remove(tree, 'leaf') + remove(tree, 'text') assert.deepEqual( tree, - u('root', [u('node', [u('node', [u('other', '4')])])]) + u('root', [u('emphasis', [u('emphasis', [u('inlineCode', '4')])])]) ) }) }) - -/** - * @param {Node} node - * @returns {node is Literal} - */ -function literal(node) { - return 'value' in node -} From 32d313937d77d7f0bcadc72072b9574672e728f0 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 7 Jul 2023 15:57:39 +0200 Subject: [PATCH 10/11] Change to return `undefined`, never test `tree` --- lib/index.js | 39 ++++++++++++++++----------------------- readme.md | 6 ++---- test.js | 45 ++++++++++++++++++--------------------------- 3 files changed, 36 insertions(+), 54 deletions(-) diff --git a/lib/index.js b/lib/index.js index c8e3de6..f8f5c8b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,36 +17,30 @@ import {convert} from 'unist-util-is' /** * Change the given `tree` by removing all nodes that pass `test`. * + * `tree` itself is never tested. * The tree is walked in preorder (NLR), visiting the node itself, then its * head, etc. * - * @template {Node} Tree - * Node kind. - * * @overload - * @param {Tree} node + * @param {Node} node * @param {Test} [test] - * @returns {Tree | undefined} + * @returns {undefined} * * @overload - * @param {Tree} node + * @param {Node} node * @param {Options | null | undefined} options * @param {Test} [test] - * @returns {Tree | undefined} + * @returns {undefined} * - * @param {Tree} tree + * @param {Node} tree * Tree to change. * @param {Options | Test} options * Configuration (optional). * @param {Test} [test] * `unist-util-is` compatible test. - * @returns {Tree | undefined} - * The given `tree` without nodes that pass `test`. - * - * `undefined` is returned if `tree` itself didn’t pass the test or is - * cascaded away. + * @returns {undefined} + * Nothing. */ -// To do: next major: don’t return `tree`. export function remove(tree, options, test) { const is = convert(test || options) let cascade = true @@ -60,21 +54,20 @@ export function remove(tree, options, test) { cascade = options.cascade } - return preorder(tree) + preorder(tree) /** * Check and remove nodes recursively in preorder. * For each composite node, modify its children array in-place. * - * @template {Node} Kind - * @param {Kind} node + * @param {Node} node * @param {number | undefined} [index] * @param {Parent | undefined} [parent] - * @returns {Kind | undefined} + * @returns {boolean} */ function preorder(node, index, parent) { - if (is(node, index, parent)) { - return undefined + if (node !== tree && is(node, index, parent)) { + return false } if ('children' in node && Array.isArray(node.children)) { @@ -92,8 +85,8 @@ export function remove(tree, options, test) { } // Cascade delete. - if (cascade && !newChildIndex) { - return undefined + if (node !== tree && cascade && !newChildIndex) { + return false } // Drop other nodes. @@ -101,6 +94,6 @@ export function remove(tree, options, test) { } } - return node + return true } } diff --git a/readme.md b/readme.md index 0b93d24..ba84ad3 100644 --- a/readme.md +++ b/readme.md @@ -112,6 +112,7 @@ There is no default export. Change the given `tree` by removing all nodes that pass `test`. +`tree` itself is never tested. The tree is walked in *[preorder][]* (NLR), visiting the node itself, then its head, etc. @@ -126,10 +127,7 @@ head, etc. ###### Returns -A changed given `tree`, without nodes that pass `test`. - -`undefined` is returned if `tree` itself didn’t pass the test or is cascaded -away. +Nothing (`undefined`). ### `Options` diff --git a/test.js b/test.js index d795fad..0225700 100644 --- a/test.js +++ b/test.js @@ -24,12 +24,9 @@ test('remove', async function (t) { /** @type {Emphasis} */ const tree = u('emphasis', children) - const next = remove(tree, {value: '2'}) + remove(tree, {value: '2'}) assert.deepEqual(tree, u('emphasis', [leaf1])) - assert.equal(next, tree) - assert.equal(next.children, children) - assert.equal(next.children[0], leaf1) }) await t.test('should remove parent nodes', function () { @@ -40,12 +37,9 @@ test('remove', async function (t) { /** @type {Root} */ const tree = u('root', children) - const next = remove(tree, test) + remove(tree, test) assert.deepEqual(tree, u('root', [leaf2])) - assert.equal(next, tree) - assert.equal(next.children, children) - assert.equal(next.children[0], leaf2) /** * @param {Node} node @@ -56,23 +50,25 @@ test('remove', async function (t) { } }) - await t.test( - 'should return `undefined` if root node is removed', - function () { - /** @type {Root} */ - const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) - const next = remove(tree, 'root') + await t.test('should not check root nodes', function () { + /** @type {Root} */ + const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) - assert.equal(next, undefined) - } - ) + remove(tree, 'root') + + assert.deepEqual( + tree, + u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) + ) + }) await t.test('should cascade (remove) root nodes', function () { /** @type {Root} */ const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) - const next = remove(tree, 'text') - assert.equal(next, undefined) + remove(tree, 'text') + + assert.deepEqual(tree, u('root', [])) }) await t.test( @@ -122,9 +118,8 @@ test('remove', async function (t) { await t.test('should support `cascade = true`', function () { /** @type {Root} */ const tree = u('root', [u('emphasis', [u('text', '1')]), u('text', '2')]) - const next = remove(tree, {cascade: true}, 'text') - - assert.equal(next, undefined) + remove(tree, {cascade: true}, 'text') + assert.deepEqual(tree, u('root', [])) }) await t.test('should support `cascade = false`', function () { @@ -136,13 +131,9 @@ test('remove', async function (t) { /** @type {Root} */ const tree = u('root', siblings) - const next = remove(tree, {cascade: false}, 'text') + remove(tree, {cascade: false}, 'text') assert.deepEqual(tree, u('root', [u('emphasis', [])])) - assert.equal(next, tree) - assert.equal(next.children, siblings) - assert.equal(next.children[0], node) - assert.equal(next.children[0].children, nodeChildren) }) await t.test('should support the example from readme', function () { From 3d26ef7b59218850649e0275c6c7fae92a214d74 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 7 Jul 2023 15:58:58 +0200 Subject: [PATCH 11/11] 4.0.0 --- package.json | 2 +- readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cc4a91f..84df410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-remove", - "version": "3.1.1", + "version": "4.0.0", "description": "unist utility to remove nodes from a tree", "license": "MIT", "keywords": [ diff --git a/readme.md b/readme.md index ba84ad3..ee9d47a 100644 --- a/readme.md +++ b/readme.md @@ -53,14 +53,14 @@ npm install unist-util-remove In Deno with [`esm.sh`][esmsh]: ```js -import {remove} from 'https://esm.sh/unist-util-remove@3' +import {remove} from 'https://esm.sh/unist-util-remove@4' ``` In browsers with [`esm.sh`][esmsh]: ```html ``` pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy