From 1dd3f688271bd328e0d7d596d30e31771e6867b8 Mon Sep 17 00:00:00 2001 From: Titus Date: Fri, 21 Aug 2020 11:11:49 +0200 Subject: [PATCH 01/63] Add Discussions --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 60464ce..b58dce4 100644 --- a/readme.md +++ b/readme.md @@ -111,9 +111,9 @@ abide by its terms. [collective]: https://opencollective.com/unified -[chat-badge]: https://img.shields.io/badge/chat-spectrum-7b16ff.svg +[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg -[chat]: https://spectrum.chat/unified/syntax-tree +[chat]: https://github.com/syntax-tree/unist/discussions [npm]: https://docs.npmjs.com/cli/install From ba62aae62ef677252bcaeb63992d67be91a6e8dd Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 29 Oct 2020 19:17:04 +0100 Subject: [PATCH 02/63] Update dev-dependencies --- package.json | 19 ++++++++++--------- test.js | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 080ee61..f765324 100644 --- a/package.json +++ b/package.json @@ -49,21 +49,22 @@ "unist-util-visit-parents": "^3.0.0" }, "devDependencies": { - "browserify": "^16.0.0", - "dtslint": "^3.0.0", + "browserify": "^17.0.0", + "dtslint": "^4.0.0", "nyc": "^15.0.0", "prettier": "^2.0.0", - "remark": "^12.0.0", - "remark-cli": "^8.0.0", - "remark-preset-wooorm": "^7.0.0", + "remark": "^13.0.0", + "remark-cli": "^9.0.0", + "remark-gfm": "^1.0.0", + "remark-preset-wooorm": "^8.0.0", "tape": "^5.0.0", - "tinyify": "^2.0.0", - "typescript": "^3.0.0", + "tinyify": "^3.0.0", + "typescript": "^4.0.0", "unified": "^9.0.0", - "xo": "^0.32.0" + "xo": "^0.34.0" }, "scripts": { - "format": "remark . -qfo && prettier . --write && xo --fix", + "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", "build-bundle": "browserify . -s unistUtilVisit > unist-util-visit.js", "build-mangle": "browserify . -s unistUtilVisit -p tinyify > unist-util-visit.min.js", "build": "npm run build-bundle && npm run build-mangle", diff --git a/test.js b/test.js index 84a1c79..f610a72 100644 --- a/test.js +++ b/test.js @@ -3,6 +3,7 @@ var assert = require('assert') var test = require('tape') var remark = require('remark') +var gfm = require('remark-gfm') var visit = require('.') var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') @@ -378,7 +379,7 @@ test('unist-util-visit', function (t) { t.test('should visit added nodes', function (st) { var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') - var other = remark().parse('Another ~~sentence~~.').children[0] + var other = remark().use(gfm).parse('Another ~~sentence~~.').children[0] var l = types.length + 5 // (p, text, delete, text, text) var n = 0 From 585ce986ea286dc488fd34582cb075eac93e12e2 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 29 Oct 2020 19:18:56 +0100 Subject: [PATCH 03/63] Refactor to improve bundle size --- index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 39970e7..b28c49b 100644 --- a/index.js +++ b/index.js @@ -4,13 +4,9 @@ module.exports = visit var visitParents = require('unist-util-visit-parents') -var CONTINUE = visitParents.CONTINUE -var SKIP = visitParents.SKIP -var EXIT = visitParents.EXIT - -visit.CONTINUE = CONTINUE -visit.SKIP = SKIP -visit.EXIT = EXIT +visit.CONTINUE = visitParents.CONTINUE +visit.SKIP = visitParents.SKIP +visit.EXIT = visitParents.EXIT function visit(tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { From 2ccc1eecec0509e563fe6832fc466669e32d5838 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 2 Dec 2020 12:00:29 +0100 Subject: [PATCH 04/63] Update dev-dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f765324..fdcf0f7 100644 --- a/package.json +++ b/package.json @@ -61,12 +61,12 @@ "tinyify": "^3.0.0", "typescript": "^4.0.0", "unified": "^9.0.0", - "xo": "^0.34.0" + "xo": "^0.35.0" }, "scripts": { "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", - "build-bundle": "browserify . -s unistUtilVisit > unist-util-visit.js", - "build-mangle": "browserify . -s unistUtilVisit -p tinyify > unist-util-visit.min.js", + "build-bundle": "browserify . -s unistUtilVisit -o unist-util-visit.js", + "build-mangle": "browserify . -s unistUtilVisit -o unist-util-visit.min.js -p tinyify", "build": "npm run build-bundle && npm run build-mangle", "test-api": "node test", "test-coverage": "nyc --reporter lcov tape test.js", From 98a78303d46e28039737006cd401641fa89d06a6 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 2 Dec 2020 12:00:33 +0100 Subject: [PATCH 05/63] Use Actions --- .github/workflows/main.yml | 21 +++++++++++++++++++++ .travis.yml | 15 --------------- readme.md | 4 ++-- 3 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..ffb6759 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,21 @@ +name: main +on: + - pull_request + - push +jobs: + main: + name: ${{matrix.node}} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: dcodeIO/setup-node-nvm@master + with: + node-version: ${{matrix.node}} + - run: npm install + - run: npm test + - uses: codecov/codecov-action@v1 + strategy: + matrix: + node: + - lts/dubnium + - node diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c6792cc..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: node_js -node_js: - - lts/dubnium - - node -after_script: bash <(curl -s https://codecov.io/bash) -deploy: - provider: releases - skip_cleanup: true - api_key: - secure: gRby4w9AX4L8lYpN9Yf5yyxEL0/kuRr+uYp9GgZwkGlrMGa0Fx1GBCFMbtItiLshJ6fkrkoTyBp6ttbUpInSUA5OYtRsxL+98+OdZ7qwHHqLAbLzUnYZk23hkWjm/MczQMPaOvhCO9Jtms1KgcVlIQkV7Ba9uOEBOAiF9wMxzQVI/mzNYz+r5/WRSrjQX1Iu7eJhLCvG7OJ0zPvzZpXdBV8T7M9N6FvDvog9YM6VmWzvIBY4bRHEtzrku5cK6NqkrKtK0tzQHHESSSO1m6UWV1/tUM34oSiudZBVK/0OLIJakwwz9HFaKtCEhMGR2Io1YMgfDKXsoLfGZpIJ7tAwc/ca6UyO5dk2TgIQiDlFSEtF7Xlvz2lBU2kQFrWDksWWWkE37Lsh/2PxUMPXhManiuoXNh01tSqPG9qfPJuOt83+P6kLlIzSvY3yLSrrs/5SXRPyJRGEhL2Pt5bvrkqF3ObHfYlO5T6ST1YT0z56/4wM6LKBY/jv+6J57Na7uXjCEeMJI2WcZOxLK5LfGrLK+5jF43ipB8wXhVkCWNDroADg9zBsxha2itAy6seXdeClBJNRLGyx/5aLz+ja5VjpP0jMcnKr/WY6sYXzwnDBo/+MvAEVDBpmA9OtdjtIqiX0Zk2bARmRrwEFK7Jh6fi59oUy1aA9AyoHVu9kNVDC+sU= - file: - - 'unist-util-visit.js' - - 'unist-util-visit.min.js' - on: - tags: true diff --git a/readme.md b/readme.md index b58dce4..e354558 100644 --- a/readme.md +++ b/readme.md @@ -89,9 +89,9 @@ abide by its terms. -[build-badge]: https://img.shields.io/travis/syntax-tree/unist-util-visit.svg +[build-badge]: https://github.com/syntax-tree/unist-util-visit/workflows/main/badge.svg -[build]: https://travis-ci.org/syntax-tree/unist-util-visit +[build]: https://github.com/syntax-tree/unist-util-visit/actions [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/unist-util-visit.svg From 662dc290f8ef6b6c8e75a5ba4d793fa4d05310cc Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 2 Dec 2020 12:01:42 +0100 Subject: [PATCH 06/63] Refactor to improve bundle size --- index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.js b/index.js index b28c49b..e505d23 100644 --- a/index.js +++ b/index.js @@ -19,7 +19,6 @@ function visit(tree, test, visitor, reverse) { function overload(node, parents) { var parent = parents[parents.length - 1] - var index = parent ? parent.children.indexOf(node) : null - return visitor(node, index, parent) + return visitor(node, parent ? parent.children.indexOf(node) : null, parent) } } From 1e7db9e733c199487bedd2f06033e86327604069 Mon Sep 17 00:00:00 2001 From: Benjamin Atkin Date: Sat, 19 Dec 2020 08:44:43 -0500 Subject: [PATCH 07/63] Update docs to more clearly suggest visit-parents Closes GH-20. Closes GH-21. Reviewed-by: Christian Murphy --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e354558..15bd8c6 100644 --- a/readme.md +++ b/readme.md @@ -54,7 +54,8 @@ but `visitor` has a different signature. #### `next? = visitor(node, index, parent)` Instead of being passed an array of ancestors, `visitor` is invoked with the -node’s [`index`][index] and its [`parent`][parent]. +`node`’s [`index`][index] and its [`parent`][parent]. The optional return value +`next` is documented in [`unist-util-visit-parents`][vp]’s README. Otherwise the same as [`unist-util-visit-parents`][vp]. From c727d9a12d26adbf6beca69ba4100e52e791f21b Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 11:47:44 +0200 Subject: [PATCH 08/63] Add bb --- .github/workflows/bb.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/bb.yml diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml new file mode 100644 index 0000000..291ab09 --- /dev/null +++ b/.github/workflows/bb.yml @@ -0,0 +1,13 @@ +name: bb +on: + issues: + types: [opened, reopened, edited, closed, labeled, unlabeled] + pull_request: + types: [opened, reopened, edited, closed, labeled, unlabeled] +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: unifiedjs/beep-boop-beta@main + with: + repo-token: ${{secrets.GITHUB_TOKEN}} From a2ed1e43908d99a5f59295f4809d09137f595341 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 11:47:54 +0200 Subject: [PATCH 09/63] Update Node in Actions --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ffb6759..fe284ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/dubnium + - lts/erbium - node From fa9a42f89b1c21f279ab9e847f8a9c13e9f35d55 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 11:49:38 +0200 Subject: [PATCH 10/63] Update dev-dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdcf0f7..3b3f614 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "tinyify": "^3.0.0", "typescript": "^4.0.0", "unified": "^9.0.0", - "xo": "^0.35.0" + "xo": "^0.38.0" }, "scripts": { "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", From dfbcafb75da7bbbaaefc30cceec18d61dc8be5a2 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 11:54:50 +0200 Subject: [PATCH 11/63] Use ESM --- .gitignore | 3 --- .prettierignore | 3 --- index.js | 12 +++--------- package.json | 43 ++++++++++++++++--------------------------- readme.md | 7 +++++++ test.js | 42 ++++++++++++++++++++---------------------- 6 files changed, 46 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index 613c7be..735f4af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ .DS_Store *.log -.nyc_output/ coverage/ node_modules/ -unist-util-visit.js -unist-util-visit.min.js yarn.lock diff --git a/.prettierignore b/.prettierignore index 9d7acee..cebe81f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,2 @@ coverage/ -unist-util-visit.js -unist-util-visit.min.js -*.json *.md diff --git a/index.js b/index.js index e505d23..4f8a188 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,8 @@ -'use strict' +import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' -module.exports = visit +export {CONTINUE, SKIP, EXIT} -var visitParents = require('unist-util-visit-parents') - -visit.CONTINUE = visitParents.CONTINUE -visit.SKIP = visitParents.SKIP -visit.EXIT = visitParents.EXIT - -function visit(tree, test, visitor, reverse) { +export function visit(tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { reverse = visitor visitor = test diff --git a/package.json b/package.json index 3b3f614..b6b7bbf 100644 --- a/package.json +++ b/package.json @@ -38,46 +38,36 @@ "Eugene Sharygin ", "Richard Gibson " ], + "sideEffects": false, + "type": "module", + "main": "index.js", + "types": "types/index.d.ts", "files": [ - "index.js", - "types/index.d.ts" + "types/index.d.ts", + "index.js" ], - "types": "types/index.d.ts", "dependencies": { "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" }, "devDependencies": { - "browserify": "^17.0.0", - "dtslint": "^4.0.0", - "nyc": "^15.0.0", + "c8": "^7.0.0", "prettier": "^2.0.0", "remark": "^13.0.0", "remark-cli": "^9.0.0", "remark-gfm": "^1.0.0", "remark-preset-wooorm": "^8.0.0", "tape": "^5.0.0", - "tinyify": "^3.0.0", "typescript": "^4.0.0", "unified": "^9.0.0", "xo": "^0.38.0" }, "scripts": { "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", - "build-bundle": "browserify . -s unistUtilVisit -o unist-util-visit.js", - "build-mangle": "browserify . -s unistUtilVisit -o unist-util-visit.min.js -p tinyify", - "build": "npm run build-bundle && npm run build-mangle", - "test-api": "node test", - "test-coverage": "nyc --reporter lcov tape test.js", - "test-types": "dtslint types", - "test": "npm run format && npm run build && npm run test-coverage && npm run test-types" - }, - "nyc": { - "check-coverage": true, - "lines": 100, - "functions": 100, - "branches": 100 + "test-api": "node test.js", + "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", + "test": "npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -89,13 +79,12 @@ }, "xo": { "prettier": true, - "esnext": false, "rules": { - "unicorn/prefer-set-has": "off" + "no-var": "off", + "prefer-arrow-callback": "off" }, - "ignores": [ - "unist-util-visit.js", - "types" + "ignore": [ + "types/" ] }, "remarkConfig": { diff --git a/readme.md b/readme.md index 15bd8c6..6343b6a 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,9 @@ ## Install +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. + [npm][]: ```sh @@ -46,6 +49,10 @@ Yields: ## API +This package exports the following identifiers: `visit`, `CONTINUE`, `SKIP`, and +`EXIT`. +There is no default export. + ### `visit(tree[, test], visitor[, reverse])` This function works exactly the same as [`unist-util-visit-parents`][vp], diff --git a/test.js b/test.js index f610a72..2a05210 100644 --- a/test.js +++ b/test.js @@ -1,16 +1,14 @@ -'use strict' - -var assert = require('assert') -var test = require('tape') -var remark = require('remark') -var gfm = require('remark-gfm') -var visit = require('.') +import assert from 'assert' +import test from 'tape' +import remark from 'remark' +import gfm from 'remark-gfm' +import {visit, CONTINUE, EXIT, SKIP} from './index.js' var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') -var STOP = 5 -var SKIP = 7 -var SKIP_REVERSE = 6 +var stopIndex = 5 +var skipIndex = 7 +var skipReverseIndex = 6 var texts = 6 var codes = 1 @@ -146,7 +144,7 @@ test('unist-util-visit', function (t) { }) t.test('should accept an array of `is`-compatible tests', function (st) { - var expected = ['root', 'paragraph', 'emphasis', 'strong'] + var expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) var tests = [test, 'paragraph', {value: '.'}, ['emphasis', 'strong']] var n = 0 @@ -157,7 +155,7 @@ test('unist-util-visit', function (t) { st.end() function visitor(node) { - var ok = expected.includes(node.type) || node.value === '.' + var ok = expected.has(node.type) || node.value === '.' assert.ok(ok, 'should be a requested type: ' + node.type) n++ } @@ -172,13 +170,13 @@ test('unist-util-visit', function (t) { visit(tree, visitor) - st.equal(n, STOP, 'should visit nodes until `visit.EXIT` is given') + st.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') st.end() function visitor(node) { assert.strictEqual(node.type, types[n++], 'should be the expected type') - return n === STOP ? visit.EXIT : visit.CONTINUE + return n === stopIndex ? EXIT : CONTINUE } }) @@ -187,7 +185,7 @@ test('unist-util-visit', function (t) { visit(tree, visitor, true) - st.equal(n, STOP, 'should visit nodes until `visit.EXIT` is given') + st.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') st.end() @@ -197,7 +195,7 @@ test('unist-util-visit', function (t) { reverseTypes[n++], 'should be the expected type' ) - return n === STOP ? visit.EXIT : visit.CONTINUE + return n === stopIndex ? EXIT : CONTINUE } }) @@ -210,7 +208,7 @@ test('unist-util-visit', function (t) { st.equal( count, types.length - 1, - 'should visit nodes except when `visit.SKIP` is given' + 'should visit nodes except when `SKIP` is given' ) st.end() @@ -219,9 +217,9 @@ test('unist-util-visit', function (t) { assert.strictEqual(node.type, types[n++], 'should be the expected type') count++ - if (n === SKIP) { + if (n === skipIndex) { n++ // The one node inside it. - return visit.SKIP + return SKIP } } }) @@ -235,7 +233,7 @@ test('unist-util-visit', function (t) { st.equal( count, reverseTypes.length - 1, - 'should visit nodes except when `visit.SKIP` is given' + 'should visit nodes except when `SKIP` is given' ) st.end() @@ -248,9 +246,9 @@ test('unist-util-visit', function (t) { ) count++ - if (n === SKIP_REVERSE) { + if (n === skipReverseIndex) { n++ // The one node inside it. - return visit.SKIP + return SKIP } } }) From ed0bccde7a3c0d0a0d540b5eb5104ea4a6c7044d Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 12:09:21 +0200 Subject: [PATCH 12/63] Add JSDoc based types --- .gitignore | 1 + index.js | 80 +++++++++-- index.test-d.ts | 103 ++++++++++++++ package.json | 22 ++- test.js | 71 +++++++++- tsconfig.json | 15 ++ types/index.d.ts | 88 ------------ types/tsconfig.json | 10 -- types/tslint.json | 16 --- types/unist-util-visit-tests.ts | 235 -------------------------------- 10 files changed, 269 insertions(+), 372 deletions(-) create mode 100644 index.test-d.ts create mode 100644 tsconfig.json delete mode 100644 types/index.d.ts delete mode 100644 types/tsconfig.json delete mode 100644 types/tslint.json delete mode 100644 types/unist-util-visit-tests.ts 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 4f8a188..7564830 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,74 @@ +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + * @typedef {import('unist-util-is').Type} Type + * @typedef {import('unist-util-is').Props} Props + * @typedef {import('unist-util-is').TestFunctionAnything} TestFunctionAnything + * @typedef {import('unist-util-visit-parents').Action} Action + * @typedef {import('unist-util-visit-parents').Index} Index + * @typedef {import('unist-util-visit-parents').ActionTuple} ActionTuple + */ + +/** + * Invoked when a node (matching test, if given) is found. + * Visitors are free to transform node. + * They can also transform the parent of node (the last of ancestors). + * Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited. + * If adding or removing previous siblings (or next siblings, in case of reverse) of node, + * visitor should return a new index (number) to specify the sibling to traverse after node is traversed. + * Adding or removing next siblings of node (or previous siblings, in case of reverse) + * is handled as expected without needing to return a new index. + * Removing the children property of an ancestor still results in them being traversed. + * + * @template {Node} V + * @callback Visitor + * @param {V} node Found node + * @param {number|null} index Position of `node` in `parent` + * @param {Parent|null} parent Parent of `node` + * @returns {null|undefined|Action|Index|ActionTuple|void} + */ + import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' export {CONTINUE, SKIP, EXIT} -export function visit(tree, test, visitor, reverse) { - if (typeof test === 'function' && typeof visitor !== 'function') { - reverse = visitor - visitor = test - test = null - } +export const visit = + /** + * @type {( + * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & + * ((tree: Node, test: null|undefined|Type|Props|TestFunctionAnything|Array, visitor: Visitor, reverse?: boolean) => void) & + * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) + * )} + */ + ( + /** + * Visit children of tree which pass a test + * + * @param {Node} tree Abstract syntax tree to walk + * @param {null|undefined|Type|Props|TestFunctionAnything|Array} test test Test node + * @param {Visitor} visitor Function to run for each node + * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false + */ + function (tree, test, visitor, reverse) { + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor + visitor = test + test = null + } - visitParents(tree, test, overload, reverse) + visitParents(tree, test, overload, reverse) - function overload(node, parents) { - var parent = parents[parents.length - 1] - return visitor(node, parent ? parent.children.indexOf(node) : null, parent) - } -} + /** + * @param {Node} node + * @param {Array.} parents + */ + function overload(node, parents) { + var parent = parents[parents.length - 1] + return visitor( + node, + parent ? parent.children.indexOf(node) : null, + parent + ) + } + } + ) diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..f117507 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-empty-function */ + +import {expectError} from 'tsd' +import {Node, Parent} from 'unist' +import {visit, SKIP, EXIT, CONTINUE} from './index.js' + +/* Setup */ +const sampleTree = { + type: 'root', + children: [{type: 'heading', depth: 1, children: []}] +} + +interface Heading extends Parent { + type: 'heading' + depth: number + children: Node[] +} + +interface Element extends Parent { + type: 'element' + tagName: string + properties: Record + content: Node + children: Node[] +} + +const isNode = (node: unknown): node is Node => + typeof node === 'object' && node !== null && 'type' in node +const headingTest = (node: unknown): node is Heading => + isNode(node) && node.type === 'heading' +const elementTest = (node: unknown): node is Element => + isNode(node) && node.type === 'element' + +/* Missing params. */ +expectError(visit()) +expectError(visit(sampleTree)) + +/* Visit without test. */ +visit(sampleTree, (_) => {}) +visit(sampleTree, (_: Node) => {}) +expectError(visit(sampleTree, (_: Element) => {})) +expectError(visit(sampleTree, (_: Heading) => {})) + +/* Visit with type test. */ +visit(sampleTree, 'heading', (_) => {}) +visit(sampleTree, 'heading', (_: Heading) => {}) +expectError(visit(sampleTree, 'not-a-heading', (_: Heading) => {})) +expectError(visit(sampleTree, 'element', (_: Heading) => {})) + +visit(sampleTree, 'element', (_) => {}) +visit(sampleTree, 'element', (_: Element) => {}) +expectError(visit(sampleTree, 'not-an-element', (_: Element) => {})) +expectError(visit(sampleTree, 'heading', (_: Element) => {})) + +/* Visit with object test. */ +visit(sampleTree, {type: 'heading'}, (_) => {}) +visit(sampleTree, {random: 'property'}, (_) => {}) + +visit(sampleTree, {type: 'heading'}, (_: Heading) => {}) +visit(sampleTree, {type: 'heading', depth: 2}, (_: Heading) => {}) +expectError(visit(sampleTree, {type: 'element'}, (_: Heading) => {})) +expectError( + visit(sampleTree, {type: 'heading', depth: '2'}, (_: Heading) => {}) +) + +visit(sampleTree, {type: 'element'}, (_: Element) => {}) +visit(sampleTree, {type: 'element', tagName: 'section'}, (_: Element) => {}) + +expectError(visit(sampleTree, {type: 'heading'}, (_: Element) => {})) + +expectError( + visit(sampleTree, {type: 'element', tagName: true}, (_: Element) => {}) +) + +/* Visit with function test. */ +visit(sampleTree, headingTest, (_) => {}) +visit(sampleTree, headingTest, (_: Heading) => {}) +expectError(visit(sampleTree, headingTest, (_: Element) => {})) + +visit(sampleTree, elementTest, (_) => {}) +visit(sampleTree, elementTest, (_: Element) => {}) +expectError(visit(sampleTree, elementTest, (_: Heading) => {})) + +/* Visit with array of tests. */ +visit(sampleTree, ['ParagraphNode', {type: 'element'}, headingTest], (_) => {}) + +/* Visit returns action. */ +visit(sampleTree, 'heading', (_) => CONTINUE) +visit(sampleTree, 'heading', (_) => EXIT) +visit(sampleTree, 'heading', (_) => SKIP) +expectError(visit(sampleTree, 'heading', (_) => 'random')) + +/* Visit returns index. */ +visit(sampleTree, 'heading', (_) => 0) +visit(sampleTree, 'heading', (_) => 1) + +/* Visit returns tuple. */ +visit(sampleTree, 'heading', (_) => [CONTINUE, 1]) +visit(sampleTree, 'heading', (_) => [EXIT, 1]) +visit(sampleTree, 'heading', (_) => [SKIP, 1]) +visit(sampleTree, 'heading', (_) => [SKIP]) +expectError(visit(sampleTree, 'heading', (_) => [1])) +expectError(visit(sampleTree, 'heading', (_) => ['random', 1])) diff --git a/package.json b/package.json index b6b7bbf..d92e422 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "sideEffects": false, "type": "module", "main": "index.js", - "types": "types/index.d.ts", + "types": "index.d.ts", "files": [ - "types/index.d.ts", + "index.d.ts", "index.js" ], "dependencies": { @@ -52,22 +52,28 @@ "unist-util-visit-parents": "^4.0.0" }, "devDependencies": { + "@types/tape": "^4.0.0", "c8": "^7.0.0", "prettier": "^2.0.0", "remark": "^13.0.0", "remark-cli": "^9.0.0", "remark-gfm": "^1.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", "unified": "^9.0.0", "xo": "^0.38.0" }, "scripts": { + "prepack": "npm run build && npm run format", + "build": "rimraf \"*.d.ts\" && tsc && tsd && type-coverage", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", "test-api": "node test.js", "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", - "test": "npm run format && npm run test-coverage" + "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -82,14 +88,16 @@ "rules": { "no-var": "off", "prefer-arrow-callback": "off" - }, - "ignore": [ - "types/" - ] + } }, "remarkConfig": { "plugins": [ "preset-wooorm" ] + }, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true } } diff --git a/test.js b/test.js index 2a05210..1f53766 100644 --- a/test.js +++ b/test.js @@ -1,3 +1,8 @@ +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + */ + import assert from 'assert' import test from 'tape' import remark from 'remark' @@ -44,6 +49,7 @@ var reverseTypes = [ test('unist-util-visit', function (t) { t.throws( function () { + // @ts-ignore runtime. visit() }, /TypeError: visitor is not a function/, @@ -52,6 +58,7 @@ test('unist-util-visit', function (t) { t.throws( function () { + // @ts-ignore runtime. visit(tree) }, /TypeError: visitor is not a function/, @@ -67,6 +74,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual(node.type, types[n], 'should be the expected type') n++ @@ -82,6 +92,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual( node.type, @@ -101,6 +114,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual(node.type, 'text', 'should be the expected type') n++ @@ -117,6 +133,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { n++ assert.notStrictEqual(types.indexOf(node.type), -1, 'should match') @@ -132,20 +151,29 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + * @param {number|null} index + * @param {Parent|null} parent + */ function visitor(node, index, parent) { var info = '(' + (parent && parent.type) + ':' + index + ')' assert.ok(test(node, index), 'should be a requested node ' + info) n++ } - function test(node, index) { + /** + * @param {Node} _ + * @param {number|null} index + */ + function test(_, index) { return index > 3 } }) t.test('should accept an array of `is`-compatible tests', function (st) { var expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) - var tests = [test, 'paragraph', {value: '.'}, ['emphasis', 'strong']] + var tests = [test, 'paragraph', {value: '.'}, 'emphasis', 'strong'] var n = 0 visit(tree, tests, visitor) @@ -154,12 +182,18 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { var ok = expected.has(node.type) || node.value === '.' assert.ok(ok, 'should be a requested type: ' + node.type) n++ } + /** + * @param {Node} node + */ function test(node) { return node.type === 'root' } @@ -174,6 +208,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual(node.type, types[n++], 'should be the expected type') return n === stopIndex ? EXIT : CONTINUE @@ -189,6 +226,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual( node.type, @@ -213,6 +253,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual(node.type, types[n++], 'should be the expected type') count++ @@ -238,6 +281,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual( node.type, @@ -284,6 +330,9 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + */ function visitor(node) { assert.strictEqual( node.type, @@ -321,7 +370,12 @@ test('unist-util-visit', function (t) { st.end() - function visitor(node, index, parent) { + /** + * @param {Node} node + * @param {number|null} _ + * @param {Parent|null} parent + */ + function visitor(node, _, parent) { assert.strictEqual( node.type, expected[n++], @@ -360,6 +414,10 @@ test('unist-util-visit', function (t) { st.end() + /** + * @param {Node} node + * @param {number|null} index + */ function visitor(node, index) { assert.strictEqual( node.type, @@ -387,7 +445,12 @@ test('unist-util-visit', function (t) { st.end() - function visitor(node, index, parent) { + /** + * @param {Node} _1 + * @param {number|null} _2 + * @param {Parent|null} parent + */ + function visitor(_1, _2, parent) { n++ if (n === 2) { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..be08abe --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["*.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 75e23ec..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -// TypeScript Version: 3.5 - -import {Node, Parent} from 'unist' -import {Test} from 'unist-util-is' -import { - Action, - ActionTuple, - Continue, - Exit, - Index, - Skip -} from 'unist-util-visit-parents' - -declare namespace visit { - /** - * Invoked when a node (matching test, if given) is found. - * Visitors are free to transform node. - * They can also transform the parent of node. - * Replacing node itself, if visit.SKIP is not returned, still causes its descendants to be visited. - * If adding or removing previous siblings (or next siblings, in case of reverse) of node, - * visitor should return a new index (number) to specify the sibling to traverse after node is traversed. - * Adding or removing next siblings of node (or previous siblings, in case of reverse) - * is handled as expected without needing to return a new index. - * Removing the children property of the parent still result in them being traversed. - * - * @param node Found node - * @param index Position of found node within Parent - * @param parent Parent of found node - * @paramType V node type found - * @returns - * When Action is passed, treated as a tuple of [Action] - * When Index is passed, treated as a tuple of [CONTINUE, Index] - * When ActionTuple is passed, - * Note that passing a tuple only makes sense if the action is SKIP. - * If the action is EXIT, that action can be returned. - * If the action is CONTINUE, index can be returned. - */ - type Visitor = ( - node: V, - index: number, - parent: Parent | undefined - ) => void | Action | Index | ActionTuple -} - -declare const visit: { - /** - * Visit children of tree which pass a test - * - * @param tree abstract syntax tree to visit - * @param test test node - * @param visitor function to run for each node - * @param reverse visit the tree in reverse, defaults to false - * @typeParam T tree node - * @typeParam V node type found - */ - ( - tree: Node, - test: Test | Array>, - visitor: visit.Visitor, - reverse?: boolean - ): void - - /** - * Visit children of a tree - * - * @param tree abstract syntax tree to visit - * @param visitor function to run for each node - * @param reverse visit the tree in reverse, defaults to false - */ - (tree: Node, visitor: visit.Visitor, reverse?: boolean): void - - /** - * Continue traversing as normal - */ - CONTINUE: Continue - - /** - * Do not traverse this node’s children - */ - SKIP: Skip - - /** - * Stop traversing immediately - */ - EXIT: Exit -} - -export = visit diff --git a/types/tsconfig.json b/types/tsconfig.json deleted file mode 100644 index f5ac63d..0000000 --- a/types/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "lib": ["es2015"], - "strict": true, - "baseUrl": ".", - "paths": { - "unist-util-visit": ["index.d.ts"] - } - } -} diff --git a/types/tslint.json b/types/tslint.json deleted file mode 100644 index bb13a5b..0000000 --- a/types/tslint.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "dtslint/dtslint.json", - "rules": { - "callable-types": false, - "max-line-length": false, - "no-redundant-jsdoc": false, - "no-void-expression": false, - "only-arrow-functions": false, - "semicolon": false, - "unified-signatures": false, - "whitespace": false, - "interface-over-type-literal": false, - "no-unnecessary-generics": false, - "strict-export-declare-modifiers": false - } -} diff --git a/types/unist-util-visit-tests.ts b/types/unist-util-visit-tests.ts deleted file mode 100644 index 02200a5..0000000 --- a/types/unist-util-visit-tests.ts +++ /dev/null @@ -1,235 +0,0 @@ -import {Node, Parent} from 'unist' -import unified = require('unified') -import visit = require('unist-util-visit') - -/*=== setup ===*/ -const sampleTree = { - type: 'root', - children: [ - { - type: 'heading', - depth: 1, - children: [] - } - ] -} - -interface Heading extends Parent { - type: 'heading' - depth: number - children: Node[] -} - -interface Element extends Parent { - type: 'element' - tagName: string - properties: { - [key: string]: unknown - } - content: Node - children: Node[] -} - -const isNode = (node: unknown): node is Node => - typeof node === 'object' && !!node && 'type' in node -const headingTest = (node: unknown): node is Heading => - isNode(node) && node.type === 'heading' -const elementTest = (node: unknown): node is Element => - isNode(node) && node.type === 'element' - -/*=== missing params ===*/ -// $ExpectError -visit() -// $ExpectError -visit(sampleTree) - -/*=== visit without test ===*/ -visit(sampleTree, (node) => {}) -visit(sampleTree, (node: Node) => {}) -// $ExpectError -visit(sampleTree, (node: Element) => {}) -// $ExpectError -visit(sampleTree, (node: Heading) => {}) - -/*=== visit with type test ===*/ -visit(sampleTree, 'heading', (node) => {}) -visit(sampleTree, 'heading', (node: Heading) => {}) -// $ExpectError -visit(sampleTree, 'not-a-heading', (node: Heading) => {}) -// $ExpectError -visit(sampleTree, 'element', (node: Heading) => {}) - -visit(sampleTree, 'element', (node) => {}) -visit(sampleTree, 'element', (node: Element) => {}) -// $ExpectError -visit(sampleTree, 'not-an-element', (node: Element) => {}) -// $ExpectError -visit(sampleTree, 'heading', (node: Element) => {}) - -/*=== visit with object test ===*/ -visit(sampleTree, {type: 'heading'}, (node) => {}) -visit(sampleTree, {random: 'property'}, (node) => {}) - -visit(sampleTree, {type: 'heading'}, (node: Heading) => {}) -visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {}) -// $ExpectError -visit(sampleTree, {type: 'element'}, (node: Heading) => {}) -// $ExpectError -visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {}) - -visit(sampleTree, {type: 'element'}, (node: Element) => {}) -visit(sampleTree, {type: 'element', tagName: 'section'}, (node: Element) => {}) -// $ExpectError -visit(sampleTree, {type: 'heading'}, (node: Element) => {}) -// $ExpectError -visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {}) - -/*=== visit with function test ===*/ -visit(sampleTree, headingTest, (node) => {}) -visit(sampleTree, headingTest, (node: Heading) => {}) -// $ExpectError -visit(sampleTree, headingTest, (node: Element) => {}) - -visit(sampleTree, elementTest, (node) => {}) -visit(sampleTree, elementTest, (node: Element) => {}) -// $ExpectError -visit(sampleTree, elementTest, (node: Heading) => {}) - -/*=== visit with array of tests ===*/ -visit( - sampleTree, - ['ParagraphNode', {type: 'element'}, headingTest], - (node) => {} -) - -/*=== visit returns action ===*/ -visit(sampleTree, 'heading', (node) => visit.CONTINUE) -visit(sampleTree, 'heading', (node) => visit.EXIT) -visit(sampleTree, 'heading', (node) => visit.SKIP) -visit(sampleTree, 'heading', (node) => true) -visit(sampleTree, 'heading', (node) => false) -visit(sampleTree, 'heading', (node) => 'skip') -// $ExpectError -visit(sampleTree, 'heading', (node) => 'random') - -/*=== visit returns index ===*/ -visit(sampleTree, 'heading', (node) => 0) -visit(sampleTree, 'heading', (node) => 1) - -/*=== visit returns tuple ===*/ -visit(sampleTree, 'heading', (node) => [visit.CONTINUE, 1]) -visit(sampleTree, 'heading', (node) => [visit.EXIT, 1]) -visit(sampleTree, 'heading', (node) => [visit.SKIP, 1]) -visit(sampleTree, 'heading', (node) => [true, 1]) -visit(sampleTree, 'heading', (node) => [false, 1]) -visit(sampleTree, 'heading', (node) => ['skip', 1]) -// $ExpectError -visit(sampleTree, 'heading', (node) => ['skip']) -// $ExpectError -visit(sampleTree, 'heading', (node) => [1]) -// $ExpectError -visit(sampleTree, 'heading', (node) => ['random', 1]) - -/*=== usage as unified plugin ===*/ -unified().use(() => (sampleTree) => { - // duplicates the above type tests but passes in the unified transformer input - - /*=== missing params ===*/ - // $ExpectError - visit() - // $ExpectError - visit(sampleTree) - - /*=== visit without test ===*/ - visit(sampleTree, (node) => {}) - visit(sampleTree, (node: Node) => {}) - // $ExpectError - visit(sampleTree, (node: Element) => {}) - // $ExpectError - visit(sampleTree, (node: Heading) => {}) - - /*=== visit with type test ===*/ - visit(sampleTree, 'heading', (node) => {}) - visit(sampleTree, 'heading', (node: Heading) => {}) - // $ExpectError - visit(sampleTree, 'not-a-heading', (node: Heading) => {}) - // $ExpectError - visit(sampleTree, 'element', (node: Heading) => {}) - - visit(sampleTree, 'element', (node) => {}) - visit(sampleTree, 'element', (node: Element) => {}) - // $ExpectError - visit(sampleTree, 'not-an-element', (node: Element) => {}) - // $ExpectError - visit(sampleTree, 'heading', (node: Element) => {}) - - /*=== visit with object test ===*/ - visit(sampleTree, {type: 'heading'}, (node) => {}) - visit(sampleTree, {random: 'property'}, (node) => {}) - - visit(sampleTree, {type: 'heading'}, (node: Heading) => {}) - visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {}) - // $ExpectError - visit(sampleTree, {type: 'element'}, (node: Heading) => {}) - // $ExpectError - visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {}) - - visit(sampleTree, {type: 'element'}, (node: Element) => {}) - visit( - sampleTree, - {type: 'element', tagName: 'section'}, - (node: Element) => {} - ) - // $ExpectError - visit(sampleTree, {type: 'heading'}, (node: Element) => {}) - // $ExpectError - visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {}) - - /*=== visit with function test ===*/ - visit(sampleTree, headingTest, (node) => {}) - visit(sampleTree, headingTest, (node: Heading) => {}) - // $ExpectError - visit(sampleTree, headingTest, (node: Element) => {}) - - visit(sampleTree, elementTest, (node) => {}) - visit(sampleTree, elementTest, (node: Element) => {}) - // $ExpectError - visit(sampleTree, elementTest, (node: Heading) => {}) - - /*=== visit with array of tests ===*/ - visit( - sampleTree, - ['ParagraphNode', {type: 'element'}, headingTest], - (node) => {} - ) - - /*=== visit returns action ===*/ - visit(sampleTree, 'heading', (node) => visit.CONTINUE) - visit(sampleTree, 'heading', (node) => visit.EXIT) - visit(sampleTree, 'heading', (node) => visit.SKIP) - visit(sampleTree, 'heading', (node) => true) - visit(sampleTree, 'heading', (node) => false) - visit(sampleTree, 'heading', (node) => 'skip') - // $ExpectError - visit(sampleTree, 'heading', (node) => 'random') - - /*=== visit returns index ===*/ - visit(sampleTree, 'heading', (node) => 0) - visit(sampleTree, 'heading', (node) => 1) - - /*=== visit returns tuple ===*/ - visit(sampleTree, 'heading', (node) => [visit.CONTINUE, 1]) - visit(sampleTree, 'heading', (node) => [visit.EXIT, 1]) - visit(sampleTree, 'heading', (node) => [visit.SKIP, 1]) - visit(sampleTree, 'heading', (node) => [true, 1]) - visit(sampleTree, 'heading', (node) => [false, 1]) - visit(sampleTree, 'heading', (node) => ['skip', 1]) - // $ExpectError - visit(sampleTree, 'heading', (node) => ['skip']) - // $ExpectError - visit(sampleTree, 'heading', (node) => [1]) - // $ExpectError - visit(sampleTree, 'heading', (node) => ['random', 1]) - - return sampleTree -}) From 2fd73b826ca4b69ae0dbbd5ebef181002f668a90 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 12:10:20 +0200 Subject: [PATCH 13/63] Fix typo --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6343b6a..0b69e06 100644 --- a/readme.md +++ b/readme.md @@ -62,7 +62,7 @@ but `visitor` has a different signature. Instead of being passed an array of ancestors, `visitor` is invoked with the `node`’s [`index`][index] and its [`parent`][parent]. The optional return value -`next` is documented in [`unist-util-visit-parents`][vp]’s README. +`next` is documented in [`unist-util-visit-parents`][vp]’s readme. Otherwise the same as [`unist-util-visit-parents`][vp]. From cd4f350b4e5ea5f9b81c5a68853893d205e78d51 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 20 Apr 2021 12:10:55 +0200 Subject: [PATCH 14/63] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d92e422..e4d446e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "2.0.3", + "version": "3.0.0", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From a14d311f746413c6328aec94b3fb97e25d2680a1 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Tue, 20 Apr 2021 19:56:20 +0300 Subject: [PATCH 15/63] Fix example to use ESM Closes GH-22. Reviewed-by: Titus Wormer --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 0b69e06..04c96d8 100644 --- a/readme.md +++ b/readme.md @@ -24,17 +24,17 @@ npm install unist-util-visit ## Use ```js -var u = require('unist-builder') -var visit = require('unist-util-visit') +import {u} from 'unist-builder' +import {visit} from 'unist-util-visit' -var tree = u('tree', [ +const tree = u('tree', [ u('leaf', '1'), u('node', [u('leaf', '2')]), u('void'), u('leaf', '3') ]) -visit(tree, 'leaf', function(node) { +visit(tree, 'leaf', (node) => { console.log(node) }) ``` From e0853fd999747439057d0da186649fa42cbe240f Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 4 May 2021 23:14:07 +0200 Subject: [PATCH 16/63] Update dev-dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4d446e..24134ee 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "type-coverage": "^2.0.0", "typescript": "^4.0.0", "unified": "^9.0.0", - "xo": "^0.38.0" + "xo": "^0.39.0" }, "scripts": { "prepack": "npm run build && npm run format", From 4270b9d1456a4fb3ba50bcf123c9a78a49fe3e47 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 4 May 2021 23:15:29 +0200 Subject: [PATCH 17/63] 3.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 24134ee..65ec55e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "3.0.0", + "version": "3.0.1", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From 5e98354d091a93944bfc3c6374d13861b0351712 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 10 May 2021 16:00:22 +0200 Subject: [PATCH 18/63] Add `VisitorResult` type --- index.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 7564830..e016a33 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,8 @@ /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent - * @typedef {import('unist-util-is').Type} Type - * @typedef {import('unist-util-is').Props} Props - * @typedef {import('unist-util-is').TestFunctionAnything} TestFunctionAnything - * @typedef {import('unist-util-visit-parents').Action} Action - * @typedef {import('unist-util-visit-parents').Index} Index - * @typedef {import('unist-util-visit-parents').ActionTuple} ActionTuple + * @typedef {import('unist-util-is').Test} Test + * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult */ /** @@ -25,7 +21,7 @@ * @param {V} node Found node * @param {number|null} index Position of `node` in `parent` * @param {Parent|null} parent Parent of `node` - * @returns {null|undefined|Action|Index|ActionTuple|void} + * @returns {VisitorResult} */ import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' @@ -36,7 +32,7 @@ export const visit = /** * @type {( * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, test: null|undefined|Type|Props|TestFunctionAnything|Array, visitor: Visitor, reverse?: boolean) => void) & + * ((tree: Node, test: Test, visitor: Visitor, reverse?: boolean) => void) & * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) * )} */ @@ -45,7 +41,7 @@ export const visit = * Visit children of tree which pass a test * * @param {Node} tree Abstract syntax tree to walk - * @param {null|undefined|Type|Props|TestFunctionAnything|Array} test test Test node + * @param {Test} test test Test node * @param {Visitor} visitor Function to run for each node * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false */ From bd0b101af4d77ecab471d166c9dcc9c7e556cd56 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 10 May 2021 16:03:52 +0200 Subject: [PATCH 19/63] 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 65ec55e..cd92ccb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "3.0.1", + "version": "3.1.0", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From 3ab9e902ed47b4a7798016f5413deeefb295bfd6 Mon Sep 17 00:00:00 2001 From: Titus Date: Tue, 1 Jun 2021 10:44:57 +0200 Subject: [PATCH 20/63] Use `pull_request_target` in bb --- .github/workflows/bb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 0d24628c2da96913cc03d985ed55d10855bdb652 Mon Sep 17 00:00:00 2001 From: teinett Date: Fri, 2 Jul 2021 19:55:42 +0300 Subject: [PATCH 21/63] Update `readme.md` Closes GH-27. Reviewed-by: Christian Murphy Reviewed-by: Titus Wormer --- readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/readme.md b/readme.md index 04c96d8..710497b 100644 --- a/readme.md +++ b/readme.md @@ -47,6 +47,12 @@ Yields: { type: 'leaf', value: '3' } ``` +Note: this example also uses `unist-builder`, to run the example ensure both `unist-builder` and `unist-util-visit` are installed: + +```sh +npm install unist-builder unist-util-visit +``` + ## API This package exports the following identifiers: `visit`, `CONTINUE`, `SKIP`, and From 3b197e4a6437bf8b6651813cf2429982372d1746 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Tue, 6 Jul 2021 08:39:15 +0100 Subject: [PATCH 22/63] Update tests for changes in `@types/unist` Reviewed-by: Christian Murphy Reviewed-by: Titus Wormer Closes GH-28. --- test.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test.js b/test.js index 1f53766..65ca14d 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,5 @@ /** + * @typedef {import('unist').Literal} Literal * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent */ @@ -183,7 +184,7 @@ test('unist-util-visit', function (t) { st.end() /** - * @param {Node} node + * @param {Literal} node */ function visitor(node) { var ok = expected.has(node.type) || node.value === '.' @@ -435,7 +436,14 @@ test('unist-util-visit', function (t) { t.test('should visit added nodes', function (st) { var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') - var other = remark().use(gfm).parse('Another ~~sentence~~.').children[0] + + // Unified doesn't know parse result type, + // all we know is that it's a node, but we know it is a parent, so we + // assert that here + var other = /** @type{Parent} */ ( + remark().use(gfm).parse('Another ~~sentence~~.') + ).children[0] + var l = types.length + 5 // (p, text, delete, text, text) var n = 0 From 22be7e159be8715ccbb1c7c61d53ec663d0c80c6 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 24 Jul 2021 13:11:54 +0200 Subject: [PATCH 23/63] Fix types for changes in `@types/unist` --- test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.js b/test.js index 65ca14d..c04ba51 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ /** - * @typedef {import('unist').Literal} Literal + * @typedef {import('unist').Literal} Literal * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent */ From 095a93ad0de0165dc38a5fd369295417c3b4b5ed Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 24 Jul 2021 13:13:08 +0200 Subject: [PATCH 24/63] Refactor code-style --- index.js | 4 +- package.json | 6 +- readme.md | 2 +- test.js | 221 +++++++++++++++++++++++++-------------------------- 4 files changed, 113 insertions(+), 120 deletions(-) diff --git a/index.js b/index.js index e016a33..771f122 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,7 @@ */ /** - * Invoked when a node (matching test, if given) is found. + * Called when a node (matching test, if given) is found. * Visitors are free to transform node. * They can also transform the parent of node (the last of ancestors). * Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited. @@ -59,7 +59,7 @@ export const visit = * @param {Array.} parents */ function overload(node, parents) { - var parent = parents[parents.length - 1] + const parent = parents[parents.length - 1] return visitor( node, parent ? parent.children.indexOf(node) : null, diff --git a/package.json b/package.json index cd92ccb..fdb9b3e 100644 --- a/package.json +++ b/package.json @@ -84,11 +84,7 @@ "trailingComma": "none" }, "xo": { - "prettier": true, - "rules": { - "no-var": "off", - "prefer-arrow-callback": "off" - } + "prettier": true }, "remarkConfig": { "plugins": [ diff --git a/readme.md b/readme.md index 710497b..71e22f4 100644 --- a/readme.md +++ b/readme.md @@ -66,7 +66,7 @@ but `visitor` has a different signature. #### `next? = visitor(node, index, parent)` -Instead of being passed an array of ancestors, `visitor` is invoked with the +Instead of being passed an array of ancestors, `visitor` is called with the `node`’s [`index`][index] and its [`parent`][parent]. The optional return value `next` is documented in [`unist-util-visit-parents`][vp]’s readme. diff --git a/test.js b/test.js index c04ba51..78bd3e6 100644 --- a/test.js +++ b/test.js @@ -10,16 +10,16 @@ import remark from 'remark' import gfm from 'remark-gfm' import {visit, CONTINUE, EXIT, SKIP} from './index.js' -var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') +const tree = remark().parse('Some _emphasis_, **importance**, and `code`.') -var stopIndex = 5 -var skipIndex = 7 -var skipReverseIndex = 6 +const stopIndex = 5 +const skipIndex = 7 +const skipReverseIndex = 6 -var texts = 6 -var codes = 1 +const texts = 6 +const codes = 1 -var types = [ +const types = [ 'root', 'paragraph', 'text', @@ -33,7 +33,7 @@ var types = [ 'text' ] -var reverseTypes = [ +const reverseTypes = [ 'root', 'paragraph', 'text', @@ -47,9 +47,9 @@ var reverseTypes = [ 'text' ] -test('unist-util-visit', function (t) { +test('unist-util-visit', (t) => { t.throws( - function () { + () => { // @ts-ignore runtime. visit() }, @@ -58,7 +58,7 @@ test('unist-util-visit', function (t) { ) t.throws( - function () { + () => { // @ts-ignore runtime. visit(tree) }, @@ -66,14 +66,14 @@ test('unist-util-visit', function (t) { 'should fail without visitor' ) - t.test('should iterate over all nodes', function (st) { - var n = 0 + t.test('should iterate over all nodes', (t) => { + let n = 0 visit(tree, visitor) - st.equal(n, types.length, 'should visit all nodes') + t.equal(n, types.length, 'should visit all nodes') - st.end() + t.end() /** * @param {Node} node @@ -84,14 +84,14 @@ test('unist-util-visit', function (t) { } }) - t.test('should iterate over all nodes, backwards', function (st) { - var n = 0 + t.test('should iterate over all nodes, backwards', (t) => { + let n = 0 visit(tree, visitor, true) - st.equal(n, reverseTypes.length, 'should visit all nodes in reverse') + t.equal(n, reverseTypes.length, 'should visit all nodes in reverse') - st.end() + t.end() /** * @param {Node} node @@ -106,14 +106,14 @@ test('unist-util-visit', function (t) { } }) - t.test('should only visit a given `type`', function (st) { - var n = 0 + t.test('should only visit a given `type`', (t) => { + let n = 0 visit(tree, 'text', visitor) - st.equal(n, texts, 'should visit all matching nodes') + t.equal(n, texts, 'should visit all matching nodes') - st.end() + t.end() /** * @param {Node} node @@ -124,15 +124,15 @@ test('unist-util-visit', function (t) { } }) - t.test('should only visit given `type`s', function (st) { - var types = ['text', 'inlineCode'] - var n = 0 + t.test('should only visit given `type`s', (t) => { + const types = ['text', 'inlineCode'] + let n = 0 visit(tree, types, visitor) - st.equal(n, texts + codes, 'should visit all matching nodes') + t.equal(n, texts + codes, 'should visit all matching nodes') - st.end() + t.end() /** * @param {Node} node @@ -143,14 +143,14 @@ test('unist-util-visit', function (t) { } }) - t.test('should accept any `is`-compatible test function', function (st) { - var n = 0 + t.test('should accept any `is`-compatible test function', (t) => { + let n = 0 visit(tree, test, visitor) - st.equal(n, 3, 'should visit all passing nodes') + t.equal(n, 3, 'should visit all passing nodes') - st.end() + t.end() /** * @param {Node} node @@ -158,7 +158,7 @@ test('unist-util-visit', function (t) { * @param {Parent|null} parent */ function visitor(node, index, parent) { - var info = '(' + (parent && parent.type) + ':' + index + ')' + const info = '(' + (parent && parent.type) + ':' + index + ')' assert.ok(test(node, index), 'should be a requested node ' + info) n++ } @@ -172,22 +172,22 @@ test('unist-util-visit', function (t) { } }) - t.test('should accept an array of `is`-compatible tests', function (st) { - var expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) - var tests = [test, 'paragraph', {value: '.'}, 'emphasis', 'strong'] - var n = 0 + t.test('should accept an array of `is`-compatible tests', (t) => { + const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) + const tests = [test, 'paragraph', {value: '.'}, 'emphasis', 'strong'] + let n = 0 visit(tree, tests, visitor) - st.equal(n, 5, 'should visit all passing nodes') + t.equal(n, 5, 'should visit all passing nodes') - st.end() + t.end() /** * @param {Literal} node */ function visitor(node) { - var ok = expected.has(node.type) || node.value === '.' + const ok = expected.has(node.type) || node.value === '.' assert.ok(ok, 'should be a requested type: ' + node.type) n++ } @@ -200,14 +200,14 @@ test('unist-util-visit', function (t) { } }) - t.test('should stop if `visitor` stops', function (st) { - var n = 0 + t.test('should stop if `visitor` stops', (t) => { + let n = 0 visit(tree, visitor) - st.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') + t.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') - st.end() + t.end() /** * @param {Node} node @@ -218,14 +218,14 @@ test('unist-util-visit', function (t) { } }) - t.test('should stop if `visitor` stops, backwards', function (st) { - var n = 0 + t.test('should stop if `visitor` stops, backwards', (t) => { + let n = 0 visit(tree, visitor, true) - st.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') + t.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') - st.end() + t.end() /** * @param {Node} node @@ -240,19 +240,19 @@ test('unist-util-visit', function (t) { } }) - t.test('should skip if `visitor` skips', function (st) { - var n = 0 - var count = 0 + t.test('should skip if `visitor` skips', (t) => { + let n = 0 + let count = 0 visit(tree, visitor) - st.equal( + t.equal( count, types.length - 1, 'should visit nodes except when `SKIP` is given' ) - st.end() + t.end() /** * @param {Node} node @@ -268,19 +268,19 @@ test('unist-util-visit', function (t) { } }) - t.test('should skip if `visitor` skips, backwards', function (st) { - var n = 0 - var count = 0 + t.test('should skip if `visitor` skips, backwards', (t) => { + let n = 0 + let count = 0 visit(tree, visitor, true) - st.equal( + t.equal( count, reverseTypes.length - 1, 'should visit nodes except when `SKIP` is given' ) - st.end() + t.end() /** * @param {Node} node @@ -302,10 +302,10 @@ test('unist-util-visit', function (t) { t.test( 'should support a given `index` to iterate over next (`0` to reiterate)', - function (st) { - var n = 0 - var again = false - var expected = [ + (t) => { + let n = 0 + let again = false + const expected = [ 'root', 'paragraph', 'text', @@ -327,9 +327,9 @@ test('unist-util-visit', function (t) { visit(tree, visitor) - st.equal(n, expected.length, 'should visit nodes again') + t.equal(n, expected.length, 'should visit nodes again') - st.end() + t.end() /** * @param {Node} node @@ -351,10 +351,10 @@ test('unist-util-visit', function (t) { t.test( 'should support a given `index` to iterate over next (`children.length` to skip further children)', - function (st) { - var n = 0 - var again = false - var expected = [ + (t) => { + let n = 0 + let again = false + const expected = [ 'root', 'paragraph', 'text', @@ -367,9 +367,9 @@ test('unist-util-visit', function (t) { visit(tree, visitor) - st.equal(n, expected.length, 'should skip nodes') + t.equal(n, expected.length, 'should skip nodes') - st.end() + t.end() /** * @param {Node} node @@ -391,67 +391,64 @@ test('unist-util-visit', function (t) { } ) - t.test( - 'should support any other given `index` to iterate over next', - function (st) { - var n = 0 - var again = false - var expected = [ - 'root', - 'paragraph', - 'text', - 'emphasis', - 'text', - 'text', - 'strong', - 'text', - 'inlineCode', // Skip to here. - 'text' - ] + t.test('should support any other given `index` to iterate over next', (t) => { + let n = 0 + let again = false + const expected = [ + 'root', + 'paragraph', + 'text', + 'emphasis', + 'text', + 'text', + 'strong', + 'text', + 'inlineCode', // Skip to here. + 'text' + ] - visit(tree, visitor) + visit(tree, visitor) - st.equal(n, expected.length, 'should skip nodes') + t.equal(n, expected.length, 'should skip nodes') - st.end() + t.end() - /** - * @param {Node} node - * @param {number|null} index - */ - function visitor(node, index) { - assert.strictEqual( - node.type, - expected[n++], - 'should be the expected type' - ) + /** + * @param {Node} node + * @param {number|null} index + */ + function visitor(node, index) { + assert.strictEqual( + node.type, + expected[n++], + 'should be the expected type' + ) - if (again === false && node.type === 'strong') { - again = true - return index + 2 // Skip to `inlineCode`. - } + if (again === false && node.type === 'strong') { + again = true + return index + 2 // Skip to `inlineCode`. } } - ) + }) - t.test('should visit added nodes', function (st) { - var tree = remark().parse('Some _emphasis_, **importance**, and `code`.') + t.test('should visit added nodes', (t) => { + const tree = remark().parse('Some _emphasis_, **importance**, and `code`.') // Unified doesn't know parse result type, // all we know is that it's a node, but we know it is a parent, so we // assert that here - var other = /** @type{Parent} */ ( + const other = /** @type{Parent} */ ( remark().use(gfm).parse('Another ~~sentence~~.') ).children[0] - var l = types.length + 5 // (p, text, delete, text, text) - var n = 0 + const l = types.length + 5 // (p, text, delete, text, text) + let n = 0 visit(tree, visitor) - st.equal(n, l, 'should walk over all nodes') + t.equal(n, l, 'should walk over all nodes') - st.end() + t.end() /** * @param {Node} _1 From beb9b6640670ab01452a9ac19ef5af67a7860dc4 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 24 Jul 2021 13:17:07 +0200 Subject: [PATCH 25/63] Add `strict` to `tsconfig.json` --- test.js | 67 +++++++++++++++++++++++---------------------------- tsconfig.json | 3 ++- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/test.js b/test.js index 78bd3e6..6425ac6 100644 --- a/test.js +++ b/test.js @@ -50,7 +50,7 @@ const reverseTypes = [ test('unist-util-visit', (t) => { t.throws( () => { - // @ts-ignore runtime. + // @ts-expect-error runtime. visit() }, /TypeError: visitor is not a function/, @@ -59,7 +59,7 @@ test('unist-util-visit', (t) => { t.throws( () => { - // @ts-ignore runtime. + // @ts-expect-error runtime. visit(tree) }, /TypeError: visitor is not a function/, @@ -146,58 +146,47 @@ test('unist-util-visit', (t) => { t.test('should accept any `is`-compatible test function', (t) => { let n = 0 - visit(tree, test, visitor) + visit(tree, test, (node, index, parent) => { + const info = '(' + (parent && parent.type) + ':' + index + ')' + assert.ok(test(node, index), 'should be a requested node ' + info) + n++ + }) t.equal(n, 3, 'should visit all passing nodes') t.end() - /** - * @param {Node} node - * @param {number|null} index - * @param {Parent|null} parent - */ - function visitor(node, index, parent) { - const info = '(' + (parent && parent.type) + ':' + index + ')' - assert.ok(test(node, index), 'should be a requested node ' + info) - n++ - } - /** * @param {Node} _ - * @param {number|null} index + * @param {number|null|undefined} index */ function test(_, index) { - return index > 3 + return typeof index === 'number' && index > 3 } }) t.test('should accept an array of `is`-compatible tests', (t) => { const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) - const tests = [test, 'paragraph', {value: '.'}, 'emphasis', 'strong'] + const tests = [ + /** @param {Node} node */ + (node) => node.type === 'root', + 'paragraph', + {value: '.'}, + 'emphasis', + 'strong' + ] let n = 0 - visit(tree, tests, visitor) - - t.equal(n, 5, 'should visit all passing nodes') - - t.end() - - /** - * @param {Literal} node - */ - function visitor(node) { + visit(tree, tests, (node) => { + // @ts-expect-error: indexable. const ok = expected.has(node.type) || node.value === '.' assert.ok(ok, 'should be a requested type: ' + node.type) n++ - } + }) - /** - * @param {Node} node - */ - function test(node) { - return node.type === 'root' - } + t.equal(n, 5, 'should visit all passing nodes') + + t.end() }) t.test('should stop if `visitor` stops', (t) => { @@ -383,7 +372,7 @@ test('unist-util-visit', (t) => { 'should be the expected type' ) - if (again === false && node.type === 'strong') { + if (parent && again === false && node.type === 'strong') { again = true return parent.children.length // Skip siblings. } @@ -424,7 +413,11 @@ test('unist-util-visit', (t) => { 'should be the expected type' ) - if (again === false && node.type === 'strong') { + if ( + typeof index === 'number' && + again === false && + node.type === 'strong' + ) { again = true return index + 2 // Skip to `inlineCode`. } @@ -458,7 +451,7 @@ test('unist-util-visit', (t) => { function visitor(_1, _2, parent) { n++ - if (n === 2) { + if (parent && n === 2) { parent.children.push(other) } } diff --git a/tsconfig.json b/tsconfig.json index be08abe..e31adf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "declaration": true, "emitDeclarationOnly": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "skipLibCheck": true, + "strict": true } } From 9db6226970d56b5e3f454101ea9d79d2ca4cd028 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 24 Jul 2021 13:19:06 +0200 Subject: [PATCH 26/63] Update dev-dependencies --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index fdb9b3e..147ce1f 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,9 @@ "remark-preset-wooorm": "^8.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", - "tsd": "^0.14.0", + "tsd": "^0.17.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "unified": "^9.0.0", "xo": "^0.39.0" }, "scripts": { From b4624b55337eeb89233a8ac73a8d6dc9e143c20b Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 24 Jul 2021 13:19:42 +0200 Subject: [PATCH 27/63] Update `xo` --- package.json | 2 +- test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 147ce1f..b1d4f39 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "tsd": "^0.17.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "xo": "^0.39.0" + "xo": "^0.42.0" }, "scripts": { "prepack": "npm run build && npm run format", diff --git a/test.js b/test.js index 6425ac6..c3e757e 100644 --- a/test.js +++ b/test.js @@ -4,7 +4,7 @@ * @typedef {import('unist').Parent} Parent */ -import assert from 'assert' +import assert from 'node:assert' import test from 'tape' import remark from 'remark' import gfm from 'remark-gfm' From 050fdc318d8742cd494c6a83ed90465843c8bc8f Mon Sep 17 00:00:00 2001 From: Titus Date: Fri, 30 Jul 2021 10:10:10 +0200 Subject: [PATCH 28/63] Change types to base what `visitor` gets on `tree` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, to define what `visitor` received could only be done through a TypeScript type parameter: ```js // This used to work but no longer!! visitParents(tree, 'heading', (node) => { expectType(node) }) ``` This did not look at `tree` at all (even if `tree` was hast, and as `Heading` is mdast, it would pass `Heading` to `visitor`). It also made it impossible to narrow types in JS. Given that more and more of unist and friends is now strongly typed, we can expect `tree` to be some kind of implementation of `Node` rather than the abstract `Node` interface itself. With that, we can also find all possible node types inside `tree`. This commit changes to perform the test (`'heading'`) in the type system and actually narrow down which nodes that are in `tree` match `test`. This gives us: ```js // This now works: const tree: Root = {/* … */} visitParents(tree, 'heading', (node) => { expectType(node) }) ``` Closes GH-29. --- index.js | 23 +++--- index.test-d.ts | 194 ++++++++++++++++++++++++++++++++++-------------- package.json | 2 +- 3 files changed, 153 insertions(+), 66 deletions(-) diff --git a/index.js b/index.js index 771f122..9598d2d 100644 --- a/index.js +++ b/index.js @@ -28,22 +28,27 @@ import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' export {CONTINUE, SKIP, EXIT} +/** + * Visit children of tree which pass a test + * + * @param tree Abstract syntax tree to walk + * @param test Test, optional + * @param visitor Function to run for each node + * @param reverse Fisit the tree in reverse, defaults to false + */ export const visit = /** * @type {( - * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, test: Test, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) + * ((tree: Tree, test: Check, visitor: Visitor, Check>>, reverse?: boolean) => void) & + * ((tree: Tree, visitor: Visitor>, reverse?: boolean) => void) * )} */ ( /** - * Visit children of tree which pass a test - * - * @param {Node} tree Abstract syntax tree to walk - * @param {Test} test test Test node - * @param {Visitor} visitor Function to run for each node - * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false + * @param {Node} tree + * @param {Test} test + * @param {Visitor} visitor + * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { diff --git a/index.test-d.ts b/index.test-d.ts index f117507..fa13772 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,19 +1,34 @@ /* eslint-disable @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-empty-function */ -import {expectError} from 'tsd' -import {Node, Parent} from 'unist' +import {expectError, expectType} from 'tsd' +import {Node, Parent, Literal} from 'unist' +import {is} from 'unist-util-is' import {visit, SKIP, EXIT, CONTINUE} from './index.js' /* Setup */ -const sampleTree = { +const sampleTree: Root = { type: 'root', children: [{type: 'heading', depth: 1, children: []}] } -interface Heading extends Parent { - type: 'heading' - depth: number - children: Node[] +const complexTree: Root = { + type: 'root', + children: [ + { + type: 'blockquote', + children: [{type: 'paragraph', children: [{type: 'text', value: 'a'}]}] + }, + { + type: 'paragraph', + children: [ + { + type: 'emphasis', + children: [{type: 'emphasis', children: [{type: 'text', value: 'b'}]}] + }, + {type: 'text', value: 'c'} + ] + } + ] } interface Element extends Parent { @@ -24,6 +39,43 @@ interface Element extends Parent { children: Node[] } +type Content = Flow | Phrasing + +interface Root extends Parent { + type: 'root' + children: Flow[] +} + +type Flow = Blockquote | Heading | Paragraph + +interface Blockquote extends Parent { + type: 'blockquote' + children: Flow[] +} + +interface Heading extends Parent { + type: 'heading' + depth: number + children: Phrasing[] +} + +interface Paragraph extends Parent { + type: 'paragraph' + children: Phrasing[] +} + +type Phrasing = Text | Emphasis + +interface Emphasis extends Parent { + type: 'emphasis' + children: Phrasing[] +} + +interface Text extends Literal { + type: 'text' + value: string +} + const isNode = (node: unknown): node is Node => typeof node === 'object' && node !== null && 'type' in node const headingTest = (node: unknown): node is Heading => @@ -36,68 +88,98 @@ expectError(visit()) expectError(visit(sampleTree)) /* Visit without test. */ -visit(sampleTree, (_) => {}) -visit(sampleTree, (_: Node) => {}) -expectError(visit(sampleTree, (_: Element) => {})) -expectError(visit(sampleTree, (_: Heading) => {})) +visit(sampleTree, (node) => { + expectType(node) +}) /* Visit with type test. */ -visit(sampleTree, 'heading', (_) => {}) -visit(sampleTree, 'heading', (_: Heading) => {}) -expectError(visit(sampleTree, 'not-a-heading', (_: Heading) => {})) -expectError(visit(sampleTree, 'element', (_: Heading) => {})) - -visit(sampleTree, 'element', (_) => {}) -visit(sampleTree, 'element', (_: Element) => {}) -expectError(visit(sampleTree, 'not-an-element', (_: Element) => {})) +visit(sampleTree, 'heading', (node) => { + expectType(node) +}) +visit(sampleTree, 'element', (node) => { + // Not in tree. + expectType(node) +}) expectError(visit(sampleTree, 'heading', (_: Element) => {})) /* Visit with object test. */ -visit(sampleTree, {type: 'heading'}, (_) => {}) -visit(sampleTree, {random: 'property'}, (_) => {}) - -visit(sampleTree, {type: 'heading'}, (_: Heading) => {}) -visit(sampleTree, {type: 'heading', depth: 2}, (_: Heading) => {}) -expectError(visit(sampleTree, {type: 'element'}, (_: Heading) => {})) -expectError( - visit(sampleTree, {type: 'heading', depth: '2'}, (_: Heading) => {}) -) - -visit(sampleTree, {type: 'element'}, (_: Element) => {}) -visit(sampleTree, {type: 'element', tagName: 'section'}, (_: Element) => {}) - -expectError(visit(sampleTree, {type: 'heading'}, (_: Element) => {})) - -expectError( - visit(sampleTree, {type: 'element', tagName: true}, (_: Element) => {}) -) +visit(sampleTree, {depth: 1}, (node) => { + expectType(node) +}) +visit(sampleTree, {random: 'property'}, (node) => { + expectType(node) +}) +visit(sampleTree, {type: 'heading', depth: '2'}, (node) => { + // Not in tree. + expectType(node) +}) +visit(sampleTree, {tagName: 'section'}, (node) => { + // Not in tree. + expectType(node) +}) +visit(sampleTree, {type: 'element', tagName: 'section'}, (node) => { + // Not in tree. + expectType(node) +}) /* Visit with function test. */ -visit(sampleTree, headingTest, (_) => {}) -visit(sampleTree, headingTest, (_: Heading) => {}) +visit(sampleTree, headingTest, (node) => { + expectType(node) +}) expectError(visit(sampleTree, headingTest, (_: Element) => {})) - -visit(sampleTree, elementTest, (_) => {}) -visit(sampleTree, elementTest, (_: Element) => {}) -expectError(visit(sampleTree, elementTest, (_: Heading) => {})) +visit(sampleTree, elementTest, (node) => { + // Not in tree. + expectType(node) +}) /* Visit with array of tests. */ -visit(sampleTree, ['ParagraphNode', {type: 'element'}, headingTest], (_) => {}) +visit(sampleTree, ['heading', {depth: 1}, headingTest], (node) => { + // Unfortunately TS casts things in arrays too vague. + expectType(node) +}) /* Visit returns action. */ -visit(sampleTree, 'heading', (_) => CONTINUE) -visit(sampleTree, 'heading', (_) => EXIT) -visit(sampleTree, 'heading', (_) => SKIP) -expectError(visit(sampleTree, 'heading', (_) => 'random')) +visit(sampleTree, () => CONTINUE) +visit(sampleTree, () => EXIT) +visit(sampleTree, () => SKIP) +expectError(visit(sampleTree, () => 'random')) /* Visit returns index. */ -visit(sampleTree, 'heading', (_) => 0) -visit(sampleTree, 'heading', (_) => 1) +visit(sampleTree, () => 0) +visit(sampleTree, () => 1) /* Visit returns tuple. */ -visit(sampleTree, 'heading', (_) => [CONTINUE, 1]) -visit(sampleTree, 'heading', (_) => [EXIT, 1]) -visit(sampleTree, 'heading', (_) => [SKIP, 1]) -visit(sampleTree, 'heading', (_) => [SKIP]) -expectError(visit(sampleTree, 'heading', (_) => [1])) -expectError(visit(sampleTree, 'heading', (_) => ['random', 1])) +visit(sampleTree, () => [CONTINUE, 1]) +visit(sampleTree, () => [EXIT, 1]) +visit(sampleTree, () => [SKIP, 1]) +visit(sampleTree, () => [SKIP]) +expectError(visit(sampleTree, () => [1])) +expectError(visit(sampleTree, () => ['random', 1])) + +/* Should infer children from the given tree. */ +visit(complexTree, (node) => { + expectType(node) +}) + +const blockquote = complexTree.children[0] +if (is
(blockquote, 'blockquote')) { + visit(blockquote, (node) => { + expectType(node) + }) +} + +const paragraph = complexTree.children[1] +if (is(paragraph, 'paragraph')) { + visit(paragraph, (node) => { + expectType(node) + }) + + const child = paragraph.children[1] + + if (is(child, 'emphasis')) { + visit(child, 'blockquote', (node) => { + // `blockquote` does not exist in phrasing. + expectType(node) + }) + } +} diff --git a/package.json b/package.json index b1d4f39..ac9e735 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^4.0.0" + "unist-util-visit-parents": "^5.0.0" }, "devDependencies": { "@types/tape": "^4.0.0", From f64a48206aa8d8d0c89fd4ddc93998b87ea82f2f Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 30 Jul 2021 10:13:10 +0200 Subject: [PATCH 29/63] 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac9e735..c557571 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "3.1.0", + "version": "4.0.0", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From 6ca3b6321e41d82cb627c0fa8b9628c2078c2dc2 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 21 Sep 2021 09:28:15 +0200 Subject: [PATCH 30/63] Update dev-dependencies --- index.test-d.ts | 2 +- package.json | 10 +++++----- test.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index fa13772..5c9f337 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-empty-function */ import {expectError, expectType} from 'tsd' import {Node, Parent, Literal} from 'unist' diff --git a/package.json b/package.json index c557571..48d75a8 100644 --- a/package.json +++ b/package.json @@ -55,16 +55,16 @@ "@types/tape": "^4.0.0", "c8": "^7.0.0", "prettier": "^2.0.0", - "remark": "^13.0.0", - "remark-cli": "^9.0.0", - "remark-gfm": "^1.0.0", - "remark-preset-wooorm": "^8.0.0", + "remark": "^14.0.0", + "remark-cli": "^10.0.0", + "remark-gfm": "^2.0.0", + "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", "tsd": "^0.17.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "xo": "^0.42.0" + "xo": "^0.44.0" }, "scripts": { "prepack": "npm run build && npm run format", diff --git a/test.js b/test.js index c3e757e..1e872c6 100644 --- a/test.js +++ b/test.js @@ -6,7 +6,7 @@ import assert from 'node:assert' import test from 'tape' -import remark from 'remark' +import {remark} from 'remark' import gfm from 'remark-gfm' import {visit, CONTINUE, EXIT, SKIP} from './index.js' From a76200b158249143f207012fae2ee6e756c6d292 Mon Sep 17 00:00:00 2001 From: Titus Date: Thu, 23 Sep 2021 15:45:55 +0200 Subject: [PATCH 31/63] Add improved parent type Previously, a basic `Parent` from `@types/unist` was used for the third parameter of a visitor (`parent`). This changes that to instead use an array of descendants in `tree` which implement the abstract `Parent` interface and can have `node` as a child. Closes GH-30. Closes GH-31. Related-to: syntax-tree/unist-util-visit-parents#11. --- .gitignore | 3 ++- complex-types.d.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++++ index.js | 26 ++++------------------ index.test-d.ts | 23 ++++++++++++++------ package.json | 3 ++- tsconfig.json | 2 +- 6 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 complex-types.d.ts diff --git a/.gitignore b/.gitignore index c977c85..e149f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store -*.d.ts +index.d.ts +test.d.ts *.log coverage/ node_modules/ diff --git a/complex-types.d.ts b/complex-types.d.ts new file mode 100644 index 0000000..117eaa5 --- /dev/null +++ b/complex-types.d.ts @@ -0,0 +1,54 @@ +import type {Node, Parent} from 'unist' +import type {Test} from 'unist-util-is' +import type { + VisitorResult, + Matches, + InclusiveDescendant +} from 'unist-util-visit-parents/complex-types' + +/** + * Called when a node (matching test, if given) is found. + * Visitors are free to transform node. + * They can also transform the parent of node (the last of ancestors). + * Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited. + * If adding or removing previous siblings (or next siblings, in case of reverse) of node, + * visitor should return a new index (number) to specify the sibling to traverse after node is traversed. + * Adding or removing next siblings of node (or previous siblings, in case of reverse) + * is handled as expected without needing to return a new index. + * Removing the children property of an ancestor still results in them being traversed. + */ +export type Visitor< + Visited extends Node = Node, + Ancestor extends Parent = Parent +> = ( + node: Visited, + index: Visited extends Node ? number | null : never, + parent: Ancestor extends Node ? Ancestor | null : Ancestor +) => VisitorResult + +type ParentsOf< + Ancestor extends Node, + Child extends Node +> = Ancestor extends Parent + ? Child extends Ancestor['children'][number] + ? Ancestor + : never + : never + +type BuildVisitorFromMatch< + Visited extends Node, + Ancestor extends Parent +> = Visitor> + +type BuildVisitorFromDescendants< + Descendant extends Node, + Check extends Test +> = BuildVisitorFromMatch< + Matches, + Extract +> + +export type BuildVisitor< + Tree extends Node = Node, + Check extends Test = string +> = BuildVisitorFromDescendants, Check> diff --git a/index.js b/index.js index 9598d2d..f334d7a 100644 --- a/index.js +++ b/index.js @@ -3,25 +3,7 @@ * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult - */ - -/** - * Called when a node (matching test, if given) is found. - * Visitors are free to transform node. - * They can also transform the parent of node (the last of ancestors). - * Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited. - * If adding or removing previous siblings (or next siblings, in case of reverse) of node, - * visitor should return a new index (number) to specify the sibling to traverse after node is traversed. - * Adding or removing next siblings of node (or previous siblings, in case of reverse) - * is handled as expected without needing to return a new index. - * Removing the children property of an ancestor still results in them being traversed. - * - * @template {Node} V - * @callback Visitor - * @param {V} node Found node - * @param {number|null} index Position of `node` in `parent` - * @param {Parent|null} parent Parent of `node` - * @returns {VisitorResult} + * @typedef {import('./complex-types').Visitor} Visitor */ import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' @@ -39,15 +21,15 @@ export {CONTINUE, SKIP, EXIT} export const visit = /** * @type {( - * ((tree: Tree, test: Check, visitor: Visitor, Check>>, reverse?: boolean) => void) & - * ((tree: Tree, visitor: Visitor>, reverse?: boolean) => void) + * ((tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) & + * ((tree: Tree, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test - * @param {Visitor} visitor + * @param {import('./complex-types').Visitor} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { diff --git a/index.test-d.ts b/index.test-d.ts index 5c9f337..d796213 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -88,17 +88,21 @@ expectError(visit()) expectError(visit(sampleTree)) /* Visit without test. */ -visit(sampleTree, (node) => { +visit(sampleTree, (node, _, parent) => { expectType(node) + expectType | null>(parent) }) /* Visit with type test. */ -visit(sampleTree, 'heading', (node) => { +visit(sampleTree, 'heading', (node, _, parent) => { expectType(node) + expectType(parent) }) -visit(sampleTree, 'element', (node) => { +visit(sampleTree, 'element', (node, index, parent) => { // Not in tree. expectType(node) + expectType(index) + expectType(parent) }) expectError(visit(sampleTree, 'heading', (_: Element) => {})) @@ -157,29 +161,34 @@ expectError(visit(sampleTree, () => [1])) expectError(visit(sampleTree, () => ['random', 1])) /* Should infer children from the given tree. */ -visit(complexTree, (node) => { +visit(complexTree, (node, _, parent) => { expectType(node) + expectType | null>(parent) }) const blockquote = complexTree.children[0] if (is
(blockquote, 'blockquote')) { - visit(blockquote, (node) => { + visit(blockquote, (node, _, parent) => { expectType(node) + expectType | null>(parent) }) } const paragraph = complexTree.children[1] if (is(paragraph, 'paragraph')) { - visit(paragraph, (node) => { + visit(paragraph, (node, _, parent) => { expectType(node) + expectType(parent) }) const child = paragraph.children[1] if (is(child, 'emphasis')) { - visit(child, 'blockquote', (node) => { + visit(child, 'blockquote', (node, index, parent) => { // `blockquote` does not exist in phrasing. expectType(node) + expectType(index) + expectType(parent) }) } } diff --git a/package.json b/package.json index 48d75a8..c845ae8 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "main": "index.js", "types": "index.d.ts", "files": [ + "complex-types.d.ts", "index.d.ts", "index.js" ], @@ -68,7 +69,7 @@ }, "scripts": { "prepack": "npm run build && npm run format", - "build": "rimraf \"*.d.ts\" && tsc && tsd && type-coverage", + "build": "rimraf \"{index,test}.d.ts\" && tsc && tsd && type-coverage", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", "test-api": "node test.js", "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", diff --git a/tsconfig.json b/tsconfig.json index e31adf8..1b3ab1f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["*.js"], + "include": ["index.js", "test.js"], "compilerOptions": { "target": "ES2020", "lib": ["ES2020"], From 5abd634adc89a235294711776c1cc32a3555bb49 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 23 Sep 2021 15:47:59 +0200 Subject: [PATCH 32/63] 4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c845ae8..943e939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "4.0.0", + "version": "4.1.0", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From d190ee7c562e1380097985df05c469bf85aff2fc Mon Sep 17 00:00:00 2001 From: "Kim, Jang-hwan" Date: Mon, 7 Mar 2022 04:02:18 +0900 Subject: [PATCH 33/63] Fix typo Closes GH-34. Reviewed-by: Titus Wormer --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index f334d7a..be0f696 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ export {CONTINUE, SKIP, EXIT} * @param tree Abstract syntax tree to walk * @param test Test, optional * @param visitor Function to run for each node - * @param reverse Fisit the tree in reverse, defaults to false + * @param reverse Visit the tree in reverse, defaults to false */ export const visit = /** From 940247aefa89372da2dac854abee40b49151587d Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 5 Jun 2022 19:11:42 +0200 Subject: [PATCH 34/63] Update dev-dependencies --- index.js | 7 +++---- index.test-d.ts | 12 ++++++------ package.json | 12 ++++++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index be0f696..be90cf7 100644 --- a/index.js +++ b/index.js @@ -6,9 +6,7 @@ * @typedef {import('./complex-types').Visitor} Visitor */ -import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents' - -export {CONTINUE, SKIP, EXIT} +import {visitParents} from 'unist-util-visit-parents' /** * Visit children of tree which pass a test @@ -43,7 +41,7 @@ export const visit = /** * @param {Node} node - * @param {Array.} parents + * @param {Array} parents */ function overload(node, parents) { const parent = parents[parents.length - 1] @@ -55,3 +53,4 @@ export const visit = } } ) +export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' diff --git a/index.test-d.ts b/index.test-d.ts index d796213..2340979 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -36,39 +36,39 @@ interface Element extends Parent { tagName: string properties: Record content: Node - children: Node[] + children: Array } type Content = Flow | Phrasing interface Root extends Parent { type: 'root' - children: Flow[] + children: Array } type Flow = Blockquote | Heading | Paragraph interface Blockquote extends Parent { type: 'blockquote' - children: Flow[] + children: Array } interface Heading extends Parent { type: 'heading' depth: number - children: Phrasing[] + children: Array } interface Paragraph extends Parent { type: 'paragraph' - children: Phrasing[] + children: Array } type Phrasing = Text | Emphasis interface Emphasis extends Parent { type: 'emphasis' - children: Phrasing[] + children: Array } interface Text extends Literal { diff --git a/package.json b/package.json index 943e939..eed4f8b 100644 --- a/package.json +++ b/package.json @@ -58,14 +58,14 @@ "prettier": "^2.0.0", "remark": "^14.0.0", "remark-cli": "^10.0.0", - "remark-gfm": "^2.0.0", + "remark-gfm": "^3.0.0", "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", - "tsd": "^0.17.0", + "tsd": "^0.20.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "xo": "^0.44.0" + "xo": "^0.49.0" }, "scripts": { "prepack": "npm run build && npm run format", @@ -84,7 +84,11 @@ "trailingComma": "none" }, "xo": { - "prettier": true + "prettier": true, + "rules": { + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/array-type": "off" + } }, "remarkConfig": { "plugins": [ From 1ee0c2878518f6928e54ac7a271ed741d1a7f338 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 5 Jun 2022 19:16:06 +0200 Subject: [PATCH 35/63] Replace some dev-dependencies --- package.json | 5 +++-- test.js | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index eed4f8b..1393306 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,11 @@ "devDependencies": { "@types/tape": "^4.0.0", "c8": "^7.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", "prettier": "^2.0.0", - "remark": "^14.0.0", "remark-cli": "^10.0.0", - "remark-gfm": "^3.0.0", "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", diff --git a/test.js b/test.js index 1e872c6..95c6a69 100644 --- a/test.js +++ b/test.js @@ -6,11 +6,12 @@ import assert from 'node:assert' import test from 'tape' -import {remark} from 'remark' -import gfm from 'remark-gfm' +import {fromMarkdown} from 'mdast-util-from-markdown' +import {gfmFromMarkdown} from 'mdast-util-gfm' +import {gfm} from 'micromark-extension-gfm' import {visit, CONTINUE, EXIT, SKIP} from './index.js' -const tree = remark().parse('Some _emphasis_, **importance**, and `code`.') +const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') const stopIndex = 5 const skipIndex = 7 @@ -425,14 +426,13 @@ test('unist-util-visit', (t) => { }) t.test('should visit added nodes', (t) => { - const tree = remark().parse('Some _emphasis_, **importance**, and `code`.') - - // Unified doesn't know parse result type, - // all we know is that it's a node, but we know it is a parent, so we - // assert that here - const other = /** @type{Parent} */ ( - remark().use(gfm).parse('Another ~~sentence~~.') - ).children[0] + const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') + const other = /** @type {Parent} */ ( + fromMarkdown('Another ~~sentence~~.', { + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()] + }).children[0] + ) const l = types.length + 5 // (p, text, delete, text, text) let n = 0 From 2f0494b2173f0b19964983420154c42584bb80ad Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 5 Jun 2022 19:18:27 +0200 Subject: [PATCH 36/63] Add improved jsdoc --- index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index be90cf7..904eda2 100644 --- a/index.js +++ b/index.js @@ -9,12 +9,16 @@ import {visitParents} from 'unist-util-visit-parents' /** - * Visit children of tree which pass a test + * Visit children of tree which pass test. * - * @param tree Abstract syntax tree to walk - * @param test Test, optional - * @param visitor Function to run for each node - * @param reverse Visit the tree in reverse, defaults to false + * @param tree + * Tree to walk + * @param [test] + * `unist-util-is`-compatible test + * @param visitor + * Function called for nodes that pass `test`. + * @param reverse + * Traverse in reverse preorder (NRL) instead of preorder (NLR) (default). */ export const visit = /** @@ -53,4 +57,5 @@ export const visit = } } ) + export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' From 6789ad511f0ff02d4e31367f564b7ea4913234e7 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 5 Jun 2022 19:23:35 +0200 Subject: [PATCH 37/63] Add improved docs --- readme.md | 113 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/readme.md b/readme.md index 71e22f4..d443a00 100644 --- a/readme.md +++ b/readme.md @@ -8,19 +8,56 @@ [![Backers][backers-badge]][collective] [![Chat][chat-badge]][chat] -[**unist**][unist] utility to visit nodes. +[unist][] utility to walk the tree. -## Install +## Contents + +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`visit(tree[, test], visitor[, reverse])`](#visittree-test-visitor-reverse) +* [Types](#types) +* [Compatibility](#compatibility) +* [Related](#related) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This is a very important utility for working with unist as it lets you walk the +tree. -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. +## When should I use this? + +You can use this utility when you want to walk the tree. +You can use [`unist-util-visit-parents`][vp] if you care about the entire stack +of parents. + +## Install -[npm][]: +This package is [ESM only][esm]. +In Node.js (version 12.20+, 14.14+, 16.0+, 18.0+), install with [npm][]: ```sh npm install unist-util-visit ``` +In Deno with [`esm.sh`][esmsh]: + +```js +import {visit} from "https://esm.sh/unist-util-visit@4" +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + ## Use ```js @@ -42,21 +79,14 @@ visit(tree, 'leaf', (node) => { Yields: ```js -{ type: 'leaf', value: '1' } -{ type: 'leaf', value: '2' } -{ type: 'leaf', value: '3' } -``` - -Note: this example also uses `unist-builder`, to run the example ensure both `unist-builder` and `unist-util-visit` are installed: - -```sh -npm install unist-builder unist-util-visit +{type: 'leaf', value: '1'} +{type: 'leaf', value: '2'} +{type: 'leaf', value: '3'} ``` ## API -This package exports the following identifiers: `visit`, `CONTINUE`, `SKIP`, and -`EXIT`. +This package exports the identifiers `visit`, `CONTINUE`, `SKIP`, and `EXIT`. There is no default export. ### `visit(tree[, test], visitor[, reverse])` @@ -67,30 +97,45 @@ but `visitor` has a different signature. #### `next? = visitor(node, index, parent)` Instead of being passed an array of ancestors, `visitor` is called with the -`node`’s [`index`][index] and its [`parent`][parent]. The optional return value -`next` is documented in [`unist-util-visit-parents`][vp]’s readme. +`node`’s [`index`][index] and its [`parent`][parent]. + +Please see [`unist-util-visit-parents`][vp]. + +## Types + +This package is fully typed with [TypeScript][]. +It exports the additional types `Test`, `VisitorResult`, and `Visitor`. -Otherwise the same as [`unist-util-visit-parents`][vp]. +It also exports the types `BuildVisitor` to properly type visitors from a tree and a test, from +`unist-util-visit-parents/complex-types.d.ts`. + +## Compatibility + +Projects maintained by the unified collective are compatible with all 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. ## Related * [`unist-util-visit-parents`][vp] - — Like `visit`, but with a stack of parents + — walk the tree with a stack of parents * [`unist-util-filter`](https://github.com/syntax-tree/unist-util-filter) - — Create a new tree with all nodes that pass a test + — create a new tree with all nodes that pass a test * [`unist-util-map`](https://github.com/syntax-tree/unist-util-map) - — Create a new tree with all nodes mapped by a given function + — create a new tree with all nodes mapped by a given function * [`unist-util-flatmap`](https://gitlab.com/staltz/unist-util-flatmap) - — Create a new tree by mapping (to an array) with the given function + — create a new tree by mapping (to an array) with the given function * [`unist-util-remove`](https://github.com/syntax-tree/unist-util-remove) - — Remove nodes from a tree that pass a test + — remove nodes from a tree that pass a test * [`unist-util-select`](https://github.com/syntax-tree/unist-util-select) - — Select nodes with CSS-like selectors + — select nodes with CSS-like selectors ## 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]. @@ -131,15 +176,23 @@ 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 + +[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md -[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md +[support]: https://github.com/syntax-tree/.github/blob/main/support.md -[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md +[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md [unist]: https://github.com/syntax-tree/unist From 6742b2d0714fd40f8b34ca614fb19b78d770fa6f Mon Sep 17 00:00:00 2001 From: Alec Mev Date: Sun, 21 Aug 2022 11:57:24 +0100 Subject: [PATCH 38/63] Fix TypeScript `node16` and ESM Closes GH-35. Related-to: syntax-tree/mdast-util-mdxjs-esm#3. Related-to: syntax-tree/unist-util-visit-parents#12. Reviewed-by: JounQin Reviewed-by: Titus Wormer --- complex-types.d.ts | 2 +- index.js | 8 ++++---- package.json | 6 +++--- tsconfig.json | 3 +-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/complex-types.d.ts b/complex-types.d.ts index 117eaa5..b27edc3 100644 --- a/complex-types.d.ts +++ b/complex-types.d.ts @@ -4,7 +4,7 @@ import type { VisitorResult, Matches, InclusiveDescendant -} from 'unist-util-visit-parents/complex-types' +} from 'unist-util-visit-parents/complex-types.js' /** * Called when a node (matching test, if given) is found. diff --git a/index.js b/index.js index 904eda2..ba3c754 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult - * @typedef {import('./complex-types').Visitor} Visitor + * @typedef {import('./complex-types.js').Visitor} Visitor */ import {visitParents} from 'unist-util-visit-parents' @@ -23,15 +23,15 @@ import {visitParents} from 'unist-util-visit-parents' export const visit = /** * @type {( - * ((tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) & - * ((tree: Tree, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) + * ((tree: Tree, test: Check, visitor: import('./complex-types.js').BuildVisitor, reverse?: boolean) => void) & + * ((tree: Tree, visitor: import('./complex-types.js').BuildVisitor, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test - * @param {import('./complex-types').Visitor} visitor + * @param {import('./complex-types.js').Visitor} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { diff --git a/package.json b/package.json index 1393306..f71eb6d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.0.0" + "unist-util-visit-parents": "^5.1.1" }, "devDependencies": { "@types/tape": "^4.0.0", @@ -63,9 +63,9 @@ "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", - "tsd": "^0.20.0", + "tsd": "^0.22.0", "type-coverage": "^2.0.0", - "typescript": "^4.0.0", + "typescript": "^4.7.0", "xo": "^0.49.0" }, "scripts": { diff --git a/tsconfig.json b/tsconfig.json index 1b3ab1f..0abbaea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "target": "ES2020", "lib": ["ES2020"], - "module": "ES2020", - "moduleResolution": "node", + "module": "Node16", "allowJs": true, "checkJs": true, "declaration": true, From 64d8740793b362af225e7d0d36e8ec1617d8ddfc Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 21 Aug 2022 12:58:23 +0200 Subject: [PATCH 39/63] Update dev-dependencies --- .github/workflows/main.yml | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe284ad..69924a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/erbium + - lts/fermium - node diff --git a/package.json b/package.json index f71eb6d..6db2fa6 100644 --- a/package.json +++ b/package.json @@ -59,14 +59,14 @@ "mdast-util-gfm": "^2.0.0", "micromark-extension-gfm": "^2.0.0", "prettier": "^2.0.0", - "remark-cli": "^10.0.0", + "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", "tsd": "^0.22.0", "type-coverage": "^2.0.0", "typescript": "^4.7.0", - "xo": "^0.49.0" + "xo": "^0.51.0" }, "scripts": { "prepack": "npm run build && npm run format", From 6eff10cb8b1c1afec1156d6b8acf5b5e684efbaa Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 21 Aug 2022 12:59:02 +0200 Subject: [PATCH 40/63] 4.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6db2fa6..4d9a283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "4.1.0", + "version": "4.1.1", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From c0a433bab50a0e5f69d34a7d30df021d00c7b655 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 14:45:13 +0100 Subject: [PATCH 41/63] Update dev-dependencies --- index.test-d.ts | 9 ++++++++- package.json | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 2340979..9a7de1d 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import {expectError, expectType} from 'tsd' -import {Node, Parent, Literal} from 'unist' +import type {Node, Parent, Literal} from 'unist' import {is} from 'unist-util-is' import {visit, SKIP, EXIT, CONTINUE} from './index.js' @@ -31,6 +31,7 @@ const complexTree: Root = { ] } +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Element extends Parent { type: 'element' tagName: string @@ -41,6 +42,7 @@ interface Element extends Parent { type Content = Flow | Phrasing +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Root extends Parent { type: 'root' children: Array @@ -48,17 +50,20 @@ interface Root extends Parent { type Flow = Blockquote | Heading | Paragraph +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Blockquote extends Parent { type: 'blockquote' children: Array } +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Heading extends Parent { type: 'heading' depth: number children: Array } +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Paragraph extends Parent { type: 'paragraph' children: Array @@ -66,11 +71,13 @@ interface Paragraph extends Parent { type Phrasing = Text | Emphasis +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Emphasis extends Parent { type: 'emphasis' children: Array } +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Text extends Literal { type: 'text' value: string diff --git a/package.json b/package.json index 4d9a283..466beac 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,10 @@ "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", - "tsd": "^0.22.0", + "tsd": "^0.25.0", "type-coverage": "^2.0.0", "typescript": "^4.7.0", - "xo": "^0.51.0" + "xo": "^0.53.0" }, "scripts": { "prepack": "npm run build && npm run format", From 9a2399d26878accdaed4658568a7b3644c816b11 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 14:45:44 +0100 Subject: [PATCH 42/63] Update Actions --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69924a4..89dc06c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,13 +7,13 @@ jobs: name: ${{matrix.node}} runs-on: ubuntu-latest steps: - - uses: actions/checkout@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: From 69cbec670079d80168c3f42302c60052d4f1cebc Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 14:46:43 +0100 Subject: [PATCH 43/63] Update `tsconfig.json` --- package.json | 7 +++---- tsconfig.json | 16 +++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 466beac..54d3d3f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "prettier": "^2.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", - "rimraf": "^3.0.0", "tape": "^5.0.0", "tsd": "^0.25.0", "type-coverage": "^2.0.0", @@ -70,10 +69,10 @@ }, "scripts": { "prepack": "npm run build && npm run format", - "build": "rimraf \"{index,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.js", - "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", + "test-api": "node --conditions development test.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": { diff --git a/tsconfig.json b/tsconfig.json index 0abbaea..da782de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,17 @@ { - "include": ["index.js", "test.js"], + "include": ["**/*.js", "complex-types.d.ts"], + "exclude": ["coverage/", "node_modules/"], "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020"], - "module": "Node16", - "allowJs": true, "checkJs": true, "declaration": true, "emitDeclarationOnly": true, - "allowSyntheticDefaultImports": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "node16", + "newLine": "lf", "skipLibCheck": true, - "strict": true + "strict": true, + "target": "es2020" } } From 9f4813b746f5ecf05b543228fb228ee64a61c532 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:12:58 +0100 Subject: [PATCH 44/63] Refactor to move implementation to `lib/` --- .gitignore | 2 +- complex-types.d.ts | 56 +--------------- index.d.ts | 10 +++ index.js | 62 +---------------- lib/index.js | 164 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- tsconfig.json | 2 +- 7 files changed, 182 insertions(+), 117 deletions(-) create mode 100644 index.d.ts create mode 100644 lib/index.js diff --git a/.gitignore b/.gitignore index e149f7c..07ec94c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -index.d.ts +lib/index.d.ts test.d.ts *.log coverage/ diff --git a/complex-types.d.ts b/complex-types.d.ts index b27edc3..5085b4b 100644 --- a/complex-types.d.ts +++ b/complex-types.d.ts @@ -1,54 +1,2 @@ -import type {Node, Parent} from 'unist' -import type {Test} from 'unist-util-is' -import type { - VisitorResult, - Matches, - InclusiveDescendant -} from 'unist-util-visit-parents/complex-types.js' - -/** - * Called when a node (matching test, if given) is found. - * Visitors are free to transform node. - * They can also transform the parent of node (the last of ancestors). - * Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited. - * If adding or removing previous siblings (or next siblings, in case of reverse) of node, - * visitor should return a new index (number) to specify the sibling to traverse after node is traversed. - * Adding or removing next siblings of node (or previous siblings, in case of reverse) - * is handled as expected without needing to return a new index. - * Removing the children property of an ancestor still results in them being traversed. - */ -export type Visitor< - Visited extends Node = Node, - Ancestor extends Parent = Parent -> = ( - node: Visited, - index: Visited extends Node ? number | null : never, - parent: Ancestor extends Node ? Ancestor | null : Ancestor -) => VisitorResult - -type ParentsOf< - Ancestor extends Node, - Child extends Node -> = Ancestor extends Parent - ? Child extends Ancestor['children'][number] - ? Ancestor - : never - : never - -type BuildVisitorFromMatch< - Visited extends Node, - Ancestor extends Parent -> = Visitor> - -type BuildVisitorFromDescendants< - Descendant extends Node, - Check extends Test -> = BuildVisitorFromMatch< - Matches, - Extract -> - -export type BuildVisitor< - Tree extends Node = Node, - Check extends Test = string -> = BuildVisitorFromDescendants, Check> +// To do: next major: remove this file. +export type {Visitor, BuildVisitor} from './index.js' diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..e2a025f --- /dev/null +++ b/index.d.ts @@ -0,0 +1,10 @@ +export type {Test} from 'unist-util-is' +export type { + Action, + ActionTuple, + Index, + VisitorResult +} from 'unist-util-visit-parents' +export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' +export type {Visitor, BuildVisitor} from './lib/index.js' +export {visit} from './lib/index.js' diff --git a/index.js b/index.js index ba3c754..8eb53a3 100644 --- a/index.js +++ b/index.js @@ -1,61 +1,3 @@ -/** - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent - * @typedef {import('unist-util-is').Test} Test - * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult - * @typedef {import('./complex-types.js').Visitor} Visitor - */ - -import {visitParents} from 'unist-util-visit-parents' - -/** - * Visit children of tree which pass test. - * - * @param tree - * Tree to walk - * @param [test] - * `unist-util-is`-compatible test - * @param visitor - * Function called for nodes that pass `test`. - * @param reverse - * Traverse in reverse preorder (NRL) instead of preorder (NLR) (default). - */ -export const visit = - /** - * @type {( - * ((tree: Tree, test: Check, visitor: import('./complex-types.js').BuildVisitor, reverse?: boolean) => void) & - * ((tree: Tree, visitor: import('./complex-types.js').BuildVisitor, reverse?: boolean) => void) - * )} - */ - ( - /** - * @param {Node} tree - * @param {Test} test - * @param {import('./complex-types.js').Visitor} visitor - * @param {boolean} [reverse] - */ - function (tree, test, visitor, reverse) { - if (typeof test === 'function' && typeof visitor !== 'function') { - reverse = visitor - visitor = test - test = null - } - - visitParents(tree, test, overload, reverse) - - /** - * @param {Node} node - * @param {Array} parents - */ - function overload(node, parents) { - const parent = parents[parents.length - 1] - return visitor( - node, - parent ? parent.children.indexOf(node) : null, - parent - ) - } - } - ) - +// Note: types exported from `index.d.ts` export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' +export {visit} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..0602c81 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,164 @@ +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + * @typedef {import('unist-util-is').Test} Test + * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult + */ + +/** + * Check if `Child` can be a child of `Ancestor`. + * + * Returns the ancestor when `Child` can be a child of `Ancestor`, or returns + * `never`. + * + * @template {Node} Ancestor + * Node type. + * @template {Node} Child + * Node type. + * @typedef {( + * Ancestor extends Parent + * ? Child extends Ancestor['children'][number] + * ? Ancestor + * : never + * : never + * )} ParentsOf + */ + +/** + * @template {Node} [Visited=Node] + * Visited node type. + * @template {Parent} [Ancestor=Parent] + * Ancestor type. + * @callback Visitor + * Handle a node (matching `test`, if given). + * + * Visitors are free to transform `node`. + * They can also transform `parent`. + * + * Replacing `node` itself, if `SKIP` is not returned, still causes its + * descendants to be walked (which is a bug). + * + * When adding or removing previous siblings of `node` (or next siblings, in + * case of reverse), the `Visitor` should return a new `Index` to specify the + * sibling to traverse after `node` is traversed. + * Adding or removing next siblings of `node` (or previous siblings, in case + * of reverse) is handled as expected without needing to return a new `Index`. + * + * Removing the children property of `parent` still results in them being + * traversed. + * @param {Visited} node + * Found node. + * @param {Visited extends Node ? number | null : never} index + * Index of `node` in `parent`. + * @param {Ancestor extends Node ? Ancestor | null : never} parent + * Parent of `node`. + * @returns {VisitorResult} + * What to do next. + * + * An `Index` is treated as a tuple of `[CONTINUE, Index]`. + * An `Action` is treated as a tuple of `[Action]`. + * + * Passing a tuple back only makes sense if the `Action` is `SKIP`. + * When the `Action` is `EXIT`, that action can be returned. + * When the `Action` is `CONTINUE`, `Index` can be returned. + */ + +/** + * Build a typed `Visitor` function from a node and all possible parents. + * + * It will infer which values are passed as `node` and which as `parent`. + * + * @template {Node} Visited + * Node type. + * @template {Parent} Ancestor + * Parent type. + * @typedef {Visitor>} BuildVisitorFromMatch + */ + +/** + * Build a typed `Visitor` function from a list of descendants and a test. + * + * It will infer which values are passed as `node` and which as `parent`. + * + * @template {Node} Descendant + * Node type. + * @template {Test} Check + * Test type. + * @typedef {( + * BuildVisitorFromMatch< + * import('unist-util-visit-parents/complex-types.js').Matches, + * Extract + * > + * )} BuildVisitorFromDescendants + */ + +/** + * Build a typed `Visitor` function from a tree and a test. + * + * It will infer which values are passed as `node` and which as `parent`. + * + * @template {Node} [Tree=Node] + * Node type. + * @template {Test} [Check=string] + * Test type. + * @typedef {( + * BuildVisitorFromDescendants< + * import('unist-util-visit-parents/complex-types.js').InclusiveDescendant, + * Check + * > + * )} BuildVisitor + */ + +import {visitParents} from 'unist-util-visit-parents' + +/** + * Visit children of tree which pass test. + * + * @param tree + * Tree to walk + * @param [test] + * `unist-util-is`-compatible test + * @param visitor + * Function called for nodes that pass `test`. + * @param reverse + * Traverse in reverse preorder (NRL) instead of preorder (NLR) (default). + */ +export const visit = + /** + * @type {( + * ((tree: Tree, test: Check, visitor: BuildVisitor, reverse?: boolean) => void) & + * ((tree: Tree, visitor: BuildVisitor, reverse?: boolean) => void) + * )} + */ + ( + /** + * @param {Node} tree + * @param {Test} test + * @param {Visitor} visitor + * @param {boolean} [reverse] + */ + function (tree, test, visitor, reverse) { + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor + visitor = test + test = null + } + + visitParents(tree, test, overload, reverse) + + /** + * @param {Node} node + * @param {Array} parents + */ + function overload(node, parents) { + const parent = parents[parents.length - 1] + return visitor( + node, + parent ? parent.children.indexOf(node) : null, + parent + ) + } + } + ) + +export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' diff --git a/package.json b/package.json index 54d3d3f..2aa53fb 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "main": "index.js", "types": "index.d.ts", "files": [ + "lib/", "complex-types.d.ts", "index.d.ts", "index.js" @@ -92,7 +93,7 @@ }, "remarkConfig": { "plugins": [ - "preset-wooorm" + "remark-preset-wooorm" ] }, "typeCoverage": { diff --git a/tsconfig.json b/tsconfig.json index da782de..e584b73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["**/*.js", "complex-types.d.ts"], + "include": ["**/*.js", "complex-types.d.ts", "index.d.ts"], "exclude": ["coverage/", "node_modules/"], "compilerOptions": { "checkJs": true, From 754d03816abe2ed0b4ab46ab5f19d208b3079654 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:17:56 +0100 Subject: [PATCH 45/63] Refactor code-style * Add more docs to JSDoc * Add support for `null` in input of API types --- index.d.ts | 3 +-- index.js | 3 +-- lib/index.js | 34 ++++++++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/index.d.ts b/index.d.ts index e2a025f..c6ebdb0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,6 +5,5 @@ export type { Index, VisitorResult } from 'unist-util-visit-parents' -export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' export type {Visitor, BuildVisitor} from './lib/index.js' -export {visit} from './lib/index.js' +export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js' diff --git a/index.js b/index.js index 8eb53a3..4747999 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,2 @@ // Note: types exported from `index.d.ts` -export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' -export {visit} from './lib/index.js' +export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js index 0602c81..180bd14 100644 --- a/lib/index.js +++ b/lib/index.js @@ -112,22 +112,39 @@ import {visitParents} from 'unist-util-visit-parents' /** - * Visit children of tree which pass test. + * Visit nodes. + * + * This algorithm performs *depth-first* *tree traversal* in *preorder* + * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**). + * + * You can choose for which nodes `visitor` is called by passing a `test`. + * For complex tests, you should test yourself in `visitor`, as it will be + * faster and will have improved type information. + * + * Walking the tree is an intensive task. + * Make use of the return values of the visitor when possible. + * Instead of walking a tree multiple times, walk it once, use `unist-util-is` + * to check if a node matches, and then perform different operations. + * + * You can change the tree. + * See `Visitor` for more info. * * @param tree - * Tree to walk - * @param [test] + * Tree to traverse. + * @param test * `unist-util-is`-compatible test * @param visitor - * Function called for nodes that pass `test`. + * Handle each node. * @param reverse - * Traverse in reverse preorder (NRL) instead of preorder (NLR) (default). + * Traverse in reverse preorder (NRL) instead of the default preorder (NLR). + * @returns + * Nothing. */ export const visit = /** * @type {( - * ((tree: Tree, test: Check, visitor: BuildVisitor, reverse?: boolean) => void) & - * ((tree: Tree, visitor: BuildVisitor, reverse?: boolean) => void) + * ((tree: Tree, test: Check, visitor: BuildVisitor, reverse?: boolean | null | undefined) => void) & + * ((tree: Tree, visitor: BuildVisitor, reverse?: boolean | null | undefined) => void) * )} */ ( @@ -135,7 +152,8 @@ export const visit = * @param {Node} tree * @param {Test} test * @param {Visitor} visitor - * @param {boolean} [reverse] + * @param {boolean | null | undefined} [reverse] + * @returns {void} */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { From 7b3adaf96efe77cb3bc8844266cc5d7fdb4cfbc0 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:18:02 +0100 Subject: [PATCH 46/63] Add `ignore-scripts` to `.npmrc` --- .npmrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmrc b/.npmrc index 43c97e7..9951b11 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +ignore-scripts=true From 169f93832af4eeb4bb9d64f13646dc80a5dce76b Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:20:48 +0100 Subject: [PATCH 47/63] Use Node test runner --- .github/workflows/main.yml | 2 +- package.json | 3 +- test.js | 170 ++++++++++++++++--------------------- 3 files changed, 73 insertions(+), 102 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 89dc06c..fb63387 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/fermium + - lts/gallium - node diff --git a/package.json b/package.json index 2aa53fb..2222fd8 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "unist-util-visit-parents": "^5.1.1" }, "devDependencies": { - "@types/tape": "^4.0.0", + "@types/node": "^18.0.0", "c8": "^7.0.0", "mdast-util-from-markdown": "^1.0.0", "mdast-util-gfm": "^2.0.0", @@ -62,7 +62,6 @@ "prettier": "^2.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", - "tape": "^5.0.0", "tsd": "^0.25.0", "type-coverage": "^2.0.0", "typescript": "^4.7.0", diff --git a/test.js b/test.js index 95c6a69..8065d9b 100644 --- a/test.js +++ b/test.js @@ -1,11 +1,10 @@ /** - * @typedef {import('unist').Literal} Literal * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent */ -import assert from 'node:assert' -import test from 'tape' +import assert from 'node:assert/strict' +import test from 'node:test' import {fromMarkdown} from 'mdast-util-from-markdown' import {gfmFromMarkdown} from 'mdast-util-gfm' import {gfm} from 'micromark-extension-gfm' @@ -48,8 +47,8 @@ const reverseTypes = [ 'text' ] -test('unist-util-visit', (t) => { - t.throws( +test('visit', async (t) => { + assert.throws( () => { // @ts-expect-error runtime. visit() @@ -58,7 +57,7 @@ test('unist-util-visit', (t) => { 'should fail without tree' ) - t.throws( + assert.throws( () => { // @ts-expect-error runtime. visit(tree) @@ -67,14 +66,12 @@ test('unist-util-visit', (t) => { 'should fail without visitor' ) - t.test('should iterate over all nodes', (t) => { + await t.test('should iterate over all nodes', () => { let n = 0 visit(tree, visitor) - t.equal(n, types.length, 'should visit all nodes') - - t.end() + assert.equal(n, types.length, 'should visit all nodes') /** * @param {Node} node @@ -85,14 +82,12 @@ test('unist-util-visit', (t) => { } }) - t.test('should iterate over all nodes, backwards', (t) => { + await t.test('should iterate over all nodes, backwards', () => { let n = 0 visit(tree, visitor, true) - t.equal(n, reverseTypes.length, 'should visit all nodes in reverse') - - t.end() + assert.equal(n, reverseTypes.length, 'should visit all nodes in reverse') /** * @param {Node} node @@ -107,14 +102,12 @@ test('unist-util-visit', (t) => { } }) - t.test('should only visit a given `type`', (t) => { + await t.test('should only visit a given `type`', () => { let n = 0 visit(tree, 'text', visitor) - t.equal(n, texts, 'should visit all matching nodes') - - t.end() + assert.equal(n, texts, 'should visit all matching nodes') /** * @param {Node} node @@ -125,15 +118,13 @@ test('unist-util-visit', (t) => { } }) - t.test('should only visit given `type`s', (t) => { + await t.test('should only visit given `type`s', () => { const types = ['text', 'inlineCode'] let n = 0 visit(tree, types, visitor) - t.equal(n, texts + codes, 'should visit all matching nodes') - - t.end() + assert.equal(n, texts + codes, 'should visit all matching nodes') /** * @param {Node} node @@ -144,7 +135,7 @@ test('unist-util-visit', (t) => { } }) - t.test('should accept any `is`-compatible test function', (t) => { + await t.test('should accept any `is`-compatible test function', () => { let n = 0 visit(tree, test, (node, index, parent) => { @@ -153,9 +144,7 @@ test('unist-util-visit', (t) => { n++ }) - t.equal(n, 3, 'should visit all passing nodes') - - t.end() + assert.equal(n, 3, 'should visit all passing nodes') /** * @param {Node} _ @@ -166,7 +155,7 @@ test('unist-util-visit', (t) => { } }) - t.test('should accept an array of `is`-compatible tests', (t) => { + await t.test('should accept an array of `is`-compatible tests', () => { const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) const tests = [ /** @param {Node} node */ @@ -185,19 +174,15 @@ test('unist-util-visit', (t) => { n++ }) - t.equal(n, 5, 'should visit all passing nodes') - - t.end() + assert.equal(n, 5, 'should visit all passing nodes') }) - t.test('should stop if `visitor` stops', (t) => { + await t.test('should stop if `visitor` stops', () => { let n = 0 visit(tree, visitor) - t.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') - - t.end() + assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') /** * @param {Node} node @@ -208,14 +193,12 @@ test('unist-util-visit', (t) => { } }) - t.test('should stop if `visitor` stops, backwards', (t) => { + await t.test('should stop if `visitor` stops, backwards', () => { let n = 0 visit(tree, visitor, true) - t.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') - - t.end() + assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') /** * @param {Node} node @@ -230,20 +213,18 @@ test('unist-util-visit', (t) => { } }) - t.test('should skip if `visitor` skips', (t) => { + await t.test('should skip if `visitor` skips', () => { let n = 0 let count = 0 visit(tree, visitor) - t.equal( + assert.equal( count, types.length - 1, 'should visit nodes except when `SKIP` is given' ) - t.end() - /** * @param {Node} node */ @@ -258,20 +239,18 @@ test('unist-util-visit', (t) => { } }) - t.test('should skip if `visitor` skips, backwards', (t) => { + await t.test('should skip if `visitor` skips, backwards', () => { let n = 0 let count = 0 visit(tree, visitor, true) - t.equal( + assert.equal( count, reverseTypes.length - 1, 'should visit nodes except when `SKIP` is given' ) - t.end() - /** * @param {Node} node */ @@ -290,9 +269,9 @@ test('unist-util-visit', (t) => { } }) - t.test( + await t.test( 'should support a given `index` to iterate over next (`0` to reiterate)', - (t) => { + () => { let n = 0 let again = false const expected = [ @@ -317,9 +296,7 @@ test('unist-util-visit', (t) => { visit(tree, visitor) - t.equal(n, expected.length, 'should visit nodes again') - - t.end() + assert.equal(n, expected.length, 'should visit nodes again') /** * @param {Node} node @@ -339,9 +316,9 @@ test('unist-util-visit', (t) => { } ) - t.test( + await t.test( 'should support a given `index` to iterate over next (`children.length` to skip further children)', - (t) => { + () => { let n = 0 let again = false const expected = [ @@ -357,9 +334,7 @@ test('unist-util-visit', (t) => { visit(tree, visitor) - t.equal(n, expected.length, 'should skip nodes') - - t.end() + assert.equal(n, expected.length, 'should skip nodes') /** * @param {Node} node @@ -381,51 +356,52 @@ test('unist-util-visit', (t) => { } ) - t.test('should support any other given `index` to iterate over next', (t) => { - let n = 0 - let again = false - const expected = [ - 'root', - 'paragraph', - 'text', - 'emphasis', - 'text', - 'text', - 'strong', - 'text', - 'inlineCode', // Skip to here. - 'text' - ] - - visit(tree, visitor) + await t.test( + 'should support any other given `index` to iterate over next', + () => { + let n = 0 + let again = false + const expected = [ + 'root', + 'paragraph', + 'text', + 'emphasis', + 'text', + 'text', + 'strong', + 'text', + 'inlineCode', // Skip to here. + 'text' + ] - t.equal(n, expected.length, 'should skip nodes') + visit(tree, visitor) - t.end() + assert.equal(n, expected.length, 'should skip nodes') - /** - * @param {Node} node - * @param {number|null} index - */ - function visitor(node, index) { - assert.strictEqual( - node.type, - expected[n++], - 'should be the expected type' - ) + /** + * @param {Node} node + * @param {number|null} index + */ + function visitor(node, index) { + assert.strictEqual( + node.type, + expected[n++], + 'should be the expected type' + ) - if ( - typeof index === 'number' && - again === false && - node.type === 'strong' - ) { - again = true - return index + 2 // Skip to `inlineCode`. + if ( + typeof index === 'number' && + again === false && + node.type === 'strong' + ) { + again = true + return index + 2 // Skip to `inlineCode`. + } } } - }) + ) - t.test('should visit added nodes', (t) => { + await t.test('should visit added nodes', () => { const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') const other = /** @type {Parent} */ ( fromMarkdown('Another ~~sentence~~.', { @@ -439,9 +415,7 @@ test('unist-util-visit', (t) => { visit(tree, visitor) - t.equal(n, l, 'should walk over all nodes') - - t.end() + assert.equal(n, l, 'should walk over all nodes') /** * @param {Node} _1 @@ -456,6 +430,4 @@ test('unist-util-visit', (t) => { } } }) - - t.end() }) From 83df9b345530319b4dd21cfdae4f2bff23ef5518 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:21:59 +0100 Subject: [PATCH 48/63] Add tests for exposed identifiers --- test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test.js b/test.js index 8065d9b..df4f6bc 100644 --- a/test.js +++ b/test.js @@ -9,6 +9,7 @@ import {fromMarkdown} from 'mdast-util-from-markdown' import {gfmFromMarkdown} from 'mdast-util-gfm' import {gfm} from 'micromark-extension-gfm' import {visit, CONTINUE, EXIT, SKIP} from './index.js' +import * as mod from './index.js' const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') @@ -47,7 +48,13 @@ const reverseTypes = [ 'text' ] -test('visit', async (t) => { +test('visit', async function (t) { + assert.deepEqual( + Object.keys(mod).sort(), + ['CONTINUE', 'EXIT', 'SKIP', 'visit'], + 'should expose the public api' + ) + assert.throws( () => { // @ts-expect-error runtime. From 332b6e06644568ba9ff124ff467858dff34ba712 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:55:15 +0100 Subject: [PATCH 49/63] Add improved docs --- readme.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 16 deletions(-) diff --git a/readme.md b/readme.md index d443a00..6e1c145 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,16 @@ * [Use](#use) * [API](#api) * [`visit(tree[, test], visitor[, reverse])`](#visittree-test-visitor-reverse) + * [`CONTINUE`](#continue) + * [`EXIT`](#exit) + * [`SKIP`](#skip) + * [`Action`](#action) + * [`ActionTuple`](#actiontuple) + * [`BuildVisitor`](#buildvisitor) + * [`Index`](#index) + * [`Test`](#test) + * [`Visitor`](#visitor) + * [`VisitorResult`](#visitorresult) * [Types](#types) * [Compatibility](#compatibility) * [Related](#related) @@ -38,7 +48,7 @@ of parents. ## Install This package is [ESM only][esm]. -In Node.js (version 12.20+, 14.14+, 16.0+, 18.0+), install with [npm][]: +In Node.js (version 14.14+ and 16.0+), install with [npm][]: ```sh npm install unist-util-visit @@ -47,14 +57,14 @@ npm install unist-util-visit In Deno with [`esm.sh`][esmsh]: ```js -import {visit} from "https://esm.sh/unist-util-visit@4" +import {visit} from 'https://esm.sh/unist-util-visit@4' ``` In browsers with [`esm.sh`][esmsh]: ```html ``` @@ -86,29 +96,102 @@ Yields: ## API -This package exports the identifiers `visit`, `CONTINUE`, `SKIP`, and `EXIT`. +This package exports the identifiers [`CONTINUE`][api-continue], +[`EXIT`][api-exit], [`SKIP`][api-skip], and [`visit`][api-visit]. There is no default export. ### `visit(tree[, test], visitor[, reverse])` This function works exactly the same as [`unist-util-visit-parents`][vp], -but `visitor` has a different signature. +but [`Visitor`][api-visitor] has a different signature. -#### `next? = visitor(node, index, parent)` +### `CONTINUE` -Instead of being passed an array of ancestors, `visitor` is called with the -`node`’s [`index`][index] and its [`parent`][parent]. +Continue traversing as normal (`true`). -Please see [`unist-util-visit-parents`][vp]. +### `EXIT` + +Stop traversing immediately (`false`). + +### `SKIP` + +Do not traverse this node’s children (`'skip'`). + +### `Action` + +Union of the action types (TypeScript type). +See [`Action` in `unist-util-visit-parents`][vp-action]. + +### `ActionTuple` + +List with an action and an index (TypeScript type). +See [`ActionTuple` in `unist-util-visit-parents`][vp-actiontuple]. + +### `BuildVisitor` + +Build a typed `Visitor` function from a tree and a test (TypeScript type). +See [`BuildVisitor` in `unist-util-visit-parents`][vp-buildvisitor]. + +### `Index` + +Move to the sibling at `index` next (TypeScript type). +See [`Index` in `unist-util-visit-parents`][vp-index]. + +### `Test` + +[`unist-util-is`][unist-util-is] compatible test (TypeScript type). + +### `Visitor` + +Handle a node (matching `test`, if given) (TypeScript type). + +Visitors are free to transform `node`. +They can also transform `parent`. + +Replacing `node` itself, if `SKIP` is not returned, still causes its +descendants to be walked (which is a bug). + +When adding or removing previous siblings of `node` (or next siblings, in +case of reverse), the `Visitor` should return a new `Index` to specify the +sibling to traverse after `node` is traversed. +Adding or removing next siblings of `node` (or previous siblings, in case +of reverse) is handled as expected without needing to return a new `Index`. + +Removing the children property of `parent` still results in them being +traversed. + +###### Parameters + +* `node` ([`Node`][node]) + — found node +* `index` (`number` or `null`) + — index of `node` in `parent` +* `parent` ([`Node`][node] or `null`) + — parent of `node` + +###### Returns + +What to do next. + +An `Index` is treated as a tuple of `[CONTINUE, Index]`. +An `Action` is treated as a tuple of `[Action]`. + +Passing a tuple back only makes sense if the `Action` is `SKIP`. +When the `Action` is `EXIT`, that action can be returned. +When the `Action` is `CONTINUE`, `Index` can be returned. + +### `VisitorResult` + +Any value that can be returned from a visitor (TypeScript type). +See [`VisitorResult` in `unist-util-visit-parents`][vp-visitorresult]. ## Types This package is fully typed with [TypeScript][]. -It exports the additional types `Test`, `VisitorResult`, and `Visitor`. - -It also exports the types `BuildVisitor` to properly type visitors from a tree and a test, from -`unist-util-visit-parents/complex-types.d.ts`. +It exports the additional types [`Action`][api-action], +[`ActionTuple`][api-actiontuple], [`BuildVisitor`][api-buildvisitor], +[`Index`][api-index], [`Test`][api-test], [`Visitor`][api-visitor], and +[`VisitorResult`][api-visitorresult]. ## Compatibility @@ -196,8 +279,40 @@ abide by its terms. [unist]: https://github.com/syntax-tree/unist +[node]: https://github.com/syntax-tree/unist#nodes + +[unist-util-is]: https://github.com/syntax-tree/unist-util-is + [vp]: https://github.com/syntax-tree/unist-util-visit-parents -[index]: https://github.com/syntax-tree/unist#index +[vp-action]: https://github.com/syntax-tree/unist-util-visit-parents#action + +[vp-actiontuple]: https://github.com/syntax-tree/unist-util-visit-parents#actiontuple + +[vp-buildvisitor]: https://github.com/syntax-tree/unist-util-visit-parents#buildvisitor + +[vp-index]: https://github.com/syntax-tree/unist-util-visit-parents#index + +[vp-visitorresult]: https://github.com/syntax-tree/unist-util-visit-parents#visitorresult + +[api-visit]: #visittree-test-visitor-reverse + +[api-continue]: #continue + +[api-exit]: #exit + +[api-skip]: #skip + +[api-action]: #action + +[api-actiontuple]: #actiontuple + +[api-buildvisitor]: #buildvisitor + +[api-index]: #index + +[api-test]: #test + +[api-visitor]: #visitor -[parent]: https://github.com/syntax-tree/unist#parent-1 +[api-visitorresult]: #visitorresult From 3ef009be60a9739c0127a7274ea88f5d3fc21894 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Jan 2023 15:56:49 +0100 Subject: [PATCH 50/63] 4.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2222fd8..3cb969c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "4.1.1", + "version": "4.1.2", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ From dcc26d2623ec55a1c1bd06fda99839472c759573 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 10:08:00 +0200 Subject: [PATCH 51/63] Update dev-dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3cb969c..00ef244 100644 --- a/package.json +++ b/package.json @@ -54,18 +54,18 @@ "unist-util-visit-parents": "^5.1.1" }, "devDependencies": { - "@types/node": "^18.0.0", - "c8": "^7.0.0", + "@types/node": "^20.0.0", + "c8": "^8.0.0", "mdast-util-from-markdown": "^1.0.0", "mdast-util-gfm": "^2.0.0", "micromark-extension-gfm": "^2.0.0", "prettier": "^2.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", - "tsd": "^0.25.0", + "tsd": "^0.28.0", "type-coverage": "^2.0.0", - "typescript": "^4.7.0", - "xo": "^0.53.0" + "typescript": "^5.0.0", + "xo": "^0.54.0" }, "scripts": { "prepack": "npm run build && npm run format", From 7e897ebe1223b39a06c894cad1a35c71115958ab Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 10:28:27 +0200 Subject: [PATCH 52/63] Refactor `package.json`, `tsconfig.json` --- package.json | 25 +++++++++++++------------ tsconfig.json | 10 ++++------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 00ef244..0e0969b 100644 --- a/package.json +++ b/package.json @@ -72,23 +72,16 @@ "build": "tsc --build --clean && tsc --build && tsd && 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, - "rules": { - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/array-type": "off" - } + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false }, "remarkConfig": { "plugins": [ @@ -98,6 +91,14 @@ "typeCoverage": { "atLeast": 100, "detail": true, + "ignoreCatch": true, "strict": true + }, + "xo": { + "prettier": true, + "rules": { + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/array-type": "off" + } } } diff --git a/tsconfig.json b/tsconfig.json index e584b73..f4022c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,15 @@ { - "include": ["**/*.js", "complex-types.d.ts", "index.d.ts"], - "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", "complex-types.d.ts", "index.d.ts"] } From c191c56e6877a2f3865bc04b305bf4f86c3d600c Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 10:30:20 +0200 Subject: [PATCH 53/63] 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 57992a37b89c3136288ba7e225f779e0226508d5 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:04:23 +0200 Subject: [PATCH 54/63] Refactor code-style --- index.test-d.ts | 333 ++++++++++++++++++++++-------------------- lib/index.js | 181 ++++++++++++----------- package.json | 20 ++- readme.md | 2 +- test.js | 377 ++++++++++++++++++++++-------------------------- 5 files changed, 462 insertions(+), 451 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 9a7de1d..4ba8020 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,201 +1,228 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ - -import {expectError, expectType} from 'tsd' -import type {Node, Parent, Literal} from 'unist' -import {is} from 'unist-util-is' -import {visit, SKIP, EXIT, CONTINUE} from './index.js' +import {expectAssignable, expectNotType, expectType} from 'tsd' +import type { + Blockquote, + Content, + Definition, + Delete, + Emphasis, + Footnote, + FootnoteDefinition, + Heading, + Link, + LinkReference, + ListItem, + PhrasingContent, + Root, + Strong, + TableCell, + TableRow +} from 'mdast' +import type {Node, Parent} from 'unist' +import {CONTINUE, EXIT, SKIP, visit} from './index.js' + +// To do: use `mdast` when released. +type Nodes = Root | Content + +// To do: use `mdast` when released. +type Parents = Extract /* Setup */ -const sampleTree: Root = { +const implicitTree = { type: 'root', children: [{type: 'heading', depth: 1, children: []}] } -const complexTree: Root = { +const sampleTree: Root = { type: 'root', - children: [ - { - type: 'blockquote', - children: [{type: 'paragraph', children: [{type: 'text', value: 'a'}]}] - }, - { - type: 'paragraph', - children: [ - { - type: 'emphasis', - children: [{type: 'emphasis', children: [{type: 'text', value: 'b'}]}] - }, - {type: 'text', value: 'c'} - ] - } - ] -} - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Element extends Parent { - type: 'element' - tagName: string - properties: Record - content: Node - children: Array -} - -type Content = Flow | Phrasing - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Root extends Parent { - type: 'root' - children: Array -} - -type Flow = Blockquote | Heading | Paragraph - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Blockquote extends Parent { - type: 'blockquote' - children: Array -} - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Heading extends Parent { - type: 'heading' - depth: number - children: Array -} - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Paragraph extends Parent { - type: 'paragraph' - children: Array -} - -type Phrasing = Text | Emphasis - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Emphasis extends Parent { - type: 'emphasis' - children: Array + children: [{type: 'heading', depth: 1, children: []}] } -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface Text extends Literal { - type: 'text' - value: string -} +// ## Missing parameters +// @ts-expect-error: check that `node` is passed. +visit() +// @ts-expect-error: check that `visitor` is passed. +visit(sampleTree) + +// ## No test +visit(sampleTree, function (node, index, parent) { + expectType(node) + expectType(index) + expectType(parent) +}) -const isNode = (node: unknown): node is Node => - typeof node === 'object' && node !== null && 'type' in node -const headingTest = (node: unknown): node is Heading => - isNode(node) && node.type === 'heading' -const elementTest = (node: unknown): node is Element => - isNode(node) && node.type === 'element' +visit(implicitTree, function (node, index, parent) { + // Objects are too loose. + expectAssignable(node) + expectNotType(node) + expectType(index) + expectType(parent) +}) -/* Missing params. */ -expectError(visit()) -expectError(visit(sampleTree)) +// ## String test -/* Visit without test. */ -visit(sampleTree, (node, _, parent) => { - expectType(node) - expectType | null>(parent) +// Knows it’s a heading and its parents. +visit(sampleTree, 'heading', function (node, index, parent) { + expectType(node) + expectType(index) + expectType
(parent) }) -/* Visit with type test. */ -visit(sampleTree, 'heading', (node, _, parent) => { - expectType(node) - expectType(parent) +// Not in tree. +visit(sampleTree, 'element', function (node, index, parent) { + expectType(node) + expectType(index) + expectType(parent) }) -visit(sampleTree, 'element', (node, index, parent) => { - // Not in tree. + +// Implicit nodes are too loose. +visit(implicitTree, 'heading', function (node, index, parent) { expectType(node) expectType(index) expectType(parent) }) -expectError(visit(sampleTree, 'heading', (_: Element) => {})) -/* Visit with object test. */ -visit(sampleTree, {depth: 1}, (node) => { - expectType(node) +visit(sampleTree, 'tableCell', function (node, index, parent) { + expectType(node) + expectType(index) + expectType(parent) }) -visit(sampleTree, {random: 'property'}, (node) => { - expectType(node) + +// ## Props test + +// Knows that headings have depth, but TS doesn’t infer the depth normally. +visit(sampleTree, {depth: 1}, function (node) { + expectType(node) + expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) }) -visit(sampleTree, {type: 'heading', depth: '2'}, (node) => { - // Not in tree. - expectType(node) + +// This goes fine. +visit(sampleTree, {type: 'heading'} as const, function (node) { + expectType(node) + expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) }) -visit(sampleTree, {tagName: 'section'}, (node) => { - // Not in tree. + +// For some reason the const goes wrong. +visit(sampleTree, {depth: 1} as const, function (node) { + // Note: something going wrong here, to do: investigate. expectType(node) }) -visit(sampleTree, {type: 'element', tagName: 'section'}, (node) => { - // Not in tree. + +// For some reason the const goes wrong. +visit(sampleTree, {type: 'heading', depth: 1} as const, function (node) { + // Note: something going wrong here, to do: investigate. expectType(node) }) -/* Visit with function test. */ -visit(sampleTree, headingTest, (node) => { +// Function test (implicit assertion). +visit(sampleTree, isHeadingLoose, function (node) { + expectType(node) +}) +// Function test (explicit assertion). +visit(sampleTree, isHeading, function (node) { expectType(node) + expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) }) -expectError(visit(sampleTree, headingTest, (_: Element) => {})) -visit(sampleTree, elementTest, (node) => { - // Not in tree. +// Function test (explicit assertion). +visit(sampleTree, isHeading2, function (node) { + // To do: improving `InclusiveDescendant` should use `Heading & {depth: 2}`. expectType(node) }) -/* Visit with array of tests. */ -visit(sampleTree, ['heading', {depth: 1}, headingTest], (node) => { +// ## Combined tests +visit(sampleTree, ['heading', {depth: 1}, isHeading], function (node) { // Unfortunately TS casts things in arrays too vague. expectType(node) }) -/* Visit returns action. */ -visit(sampleTree, () => CONTINUE) -visit(sampleTree, () => EXIT) -visit(sampleTree, () => SKIP) -expectError(visit(sampleTree, () => 'random')) +// To do: update to `unist-util-is` should make this work? +// visit( +// sampleTree, +// ['heading', {depth: 1}, isHeading] as const, +// function (node) { +// // Unfortunately TS casts things in arrays too vague. +// expectType(node) +// } +// ) + +// ## Return type: incorrect. +// @ts-expect-error: not an action. +visit(sampleTree, function () { + return 'random' +}) +// @ts-expect-error: not a tuple: missing action. +visit(sampleTree, function () { + return [1] +}) +// @ts-expect-error: not a tuple: incorrect action. +visit(sampleTree, function () { + return ['random', 1] +}) -/* Visit returns index. */ -visit(sampleTree, () => 0) -visit(sampleTree, () => 1) +// ## Return type: action. +visit(sampleTree, function () { + return CONTINUE +}) +visit(sampleTree, function () { + return EXIT +}) +visit(sampleTree, function () { + return SKIP +}) -/* Visit returns tuple. */ -visit(sampleTree, () => [CONTINUE, 1]) -visit(sampleTree, () => [EXIT, 1]) -visit(sampleTree, () => [SKIP, 1]) -visit(sampleTree, () => [SKIP]) -expectError(visit(sampleTree, () => [1])) -expectError(visit(sampleTree, () => ['random', 1])) +// ## Return type: index. +visit(sampleTree, function () { + return 0 +}) +visit(sampleTree, function () { + return 1 +}) -/* Should infer children from the given tree. */ -visit(complexTree, (node, _, parent) => { - expectType(node) - expectType | null>(parent) +// ## Return type: tuple. +visit(sampleTree, function () { + return [CONTINUE, 1] +}) +visit(sampleTree, function () { + return [EXIT, 1] +}) +visit(sampleTree, function () { + return [SKIP, 1] +}) +visit(sampleTree, function () { + return [SKIP] }) -const blockquote = complexTree.children[0] -if (is
(blockquote, 'blockquote')) { - visit(blockquote, (node, _, parent) => { - expectType(node) - expectType | null>(parent) +// ## Infer on tree +visit(sampleTree, 'tableCell', function (node) { + visit(node, function (node, _, parent) { + expectType(node) + expectType< + | Delete + | Emphasis + | Footnote + | Link + | LinkReference + | Strong + | TableCell + | null + >(parent) }) -} +}) -const paragraph = complexTree.children[1] -if (is(paragraph, 'paragraph')) { - visit(paragraph, (node, _, parent) => { - expectType(node) - expectType(parent) +visit(sampleTree, 'definition', function (node) { + visit(node, function (node, _, parent) { + expectType(node) + expectType(parent) }) +}) - const child = paragraph.children[1] +function isHeading(node: Node): node is Heading { + return node ? node.type === 'heading' : false +} + +function isHeading2(node: Node): node is Heading & {depth: 2} { + return isHeading(node) && node.depth === 2 +} - if (is(child, 'emphasis')) { - visit(child, 'blockquote', (node, index, parent) => { - // `blockquote` does not exist in phrasing. - expectType(node) - expectType(index) - expectType(parent) - }) - } +function isHeadingLoose(node: Node) { + return node ? node.type === 'heading' : false } diff --git a/lib/index.js b/lib/index.js index 180bd14..dfb451d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,34 +1,29 @@ /** - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent + * @typedef {import('unist').Node} UnistNode + * @typedef {import('unist').Parent} UnistParent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult */ /** - * Check if `Child` can be a child of `Ancestor`. - * - * Returns the ancestor when `Child` can be a child of `Ancestor`, or returns - * `never`. - * - * @template {Node} Ancestor - * Node type. - * @template {Node} Child - * Node type. * @typedef {( - * Ancestor extends Parent + * Ancestor extends UnistParent * ? Child extends Ancestor['children'][number] * ? Ancestor * : never * : never * )} ParentsOf + * Check if `Child` can be a child of `Ancestor`. + * + * Returns the ancestor when `Child` can be a child of `Ancestor`, or returns + * `never`. + * @template {UnistNode} Ancestor + * Node type. + * @template {UnistNode} Child + * Node type. */ /** - * @template {Node} [Visited=Node] - * Visited node type. - * @template {Parent} [Ancestor=Parent] - * Ancestor type. * @callback Visitor * Handle a node (matching `test`, if given). * @@ -48,9 +43,9 @@ * traversed. * @param {Visited} node * Found node. - * @param {Visited extends Node ? number | null : never} index + * @param {Visited extends UnistNode ? number | null : never} index * Index of `node` in `parent`. - * @param {Ancestor extends Node ? Ancestor | null : never} parent + * @param {Ancestor extends UnistNode ? Ancestor | null : never} parent * Parent of `node`. * @returns {VisitorResult} * What to do next. @@ -61,56 +56,59 @@ * Passing a tuple back only makes sense if the `Action` is `SKIP`. * When the `Action` is `EXIT`, that action can be returned. * When the `Action` is `CONTINUE`, `Index` can be returned. + * @template {UnistNode} [Visited=UnistNode] + * Visited node type. + * @template {UnistParent} [Ancestor=UnistParent] + * Ancestor type. */ /** - * Build a typed `Visitor` function from a node and all possible parents. - * - * It will infer which values are passed as `node` and which as `parent`. + * @typedef {Visitor>} BuildVisitorFromMatch + * Build a typed `Visitor` function from a node and all possible parents. * - * @template {Node} Visited + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} Visited * Node type. - * @template {Parent} Ancestor + * @template {UnistParent} Ancestor * Parent type. - * @typedef {Visitor>} BuildVisitorFromMatch */ /** - * Build a typed `Visitor` function from a list of descendants and a test. - * - * It will infer which values are passed as `node` and which as `parent`. - * - * @template {Node} Descendant - * Node type. - * @template {Test} Check - * Test type. * @typedef {( * BuildVisitorFromMatch< * import('unist-util-visit-parents/complex-types.js').Matches, - * Extract + * Extract * > * )} BuildVisitorFromDescendants - */ - -/** - * Build a typed `Visitor` function from a tree and a test. - * - * It will infer which values are passed as `node` and which as `parent`. + * Build a typed `Visitor` function from a list of descendants and a test. * - * @template {Node} [Tree=Node] + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} Descendant * Node type. - * @template {Test} [Check=string] + * @template {Test} Check * Test type. + */ + +/** * @typedef {( * BuildVisitorFromDescendants< * import('unist-util-visit-parents/complex-types.js').InclusiveDescendant, * Check * > * )} BuildVisitor + * Build a typed `Visitor` function from a tree and a test. + * + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} [Tree=UnistNode] + * Node type. + * @template {Test} [Check=Test] + * Test type. */ import {visitParents} from 'unist-util-visit-parents' +export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' + /** * Visit nodes. * @@ -129,54 +127,67 @@ import {visitParents} from 'unist-util-visit-parents' * You can change the tree. * See `Visitor` for more info. * - * @param tree + * @overload + * @param {Tree} tree + * @param {Check} check + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @overload + * @param {Tree} tree + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @param {UnistNode} tree * Tree to traverse. - * @param test - * `unist-util-is`-compatible test - * @param visitor - * Handle each node. - * @param reverse + * @param {Visitor | Test} testOrVisitor + * `unist-util-is`-compatible test (optional, omit to pass a visitor). + * @param {Visitor | boolean | null | undefined} [visitorOrReverse] + * Handle each node (when test is omitted, pass `reverse`). + * @param {boolean | null | undefined} [maybeReverse=false] * Traverse in reverse preorder (NRL) instead of the default preorder (NLR). - * @returns + * @returns {undefined} * Nothing. + * + * @template {UnistNode} Tree + * Node type. + * @template {Test} Check + * `unist-util-is`-compatible test. */ -export const visit = - /** - * @type {( - * ((tree: Tree, test: Check, visitor: BuildVisitor, reverse?: boolean | null | undefined) => void) & - * ((tree: Tree, visitor: BuildVisitor, reverse?: boolean | null | undefined) => void) - * )} - */ - ( - /** - * @param {Node} tree - * @param {Test} test - * @param {Visitor} visitor - * @param {boolean | null | undefined} [reverse] - * @returns {void} - */ - function (tree, test, visitor, reverse) { - if (typeof test === 'function' && typeof visitor !== 'function') { - reverse = visitor - visitor = test - test = null - } +export function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) { + /** @type {boolean | null | undefined} */ + let reverse + /** @type {Test} */ + let test + /** @type {Visitor} */ + let visitor - visitParents(tree, test, overload, reverse) + if ( + typeof testOrVisitor === 'function' && + typeof visitorOrReverse !== 'function' + ) { + test = undefined + visitor = testOrVisitor + reverse = visitorOrReverse + } else { + // @ts-expect-error: assume the overload with test was given. + test = testOrVisitor + // @ts-expect-error: assume the overload with test was given. + visitor = visitorOrReverse + reverse = maybeReverse + } - /** - * @param {Node} node - * @param {Array} parents - */ - function overload(node, parents) { - const parent = parents[parents.length - 1] - return visitor( - node, - parent ? parent.children.indexOf(node) : null, - parent - ) - } - } - ) + visitParents(tree, test, overload, reverse) -export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' + /** + * @param {UnistNode} node + * @param {Array} parents + */ + function overload(node, parents) { + const parent = parents[parents.length - 1] + const index = parent ? parent.children.indexOf(node) : null + return visitor(node, index, parent) + } +} diff --git a/package.json b/package.json index 0e0969b..5cb6d0e 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "unist-util-visit-parents": "^5.1.1" }, "devDependencies": { + "@types/mdast": "^3.0.0", "@types/node": "^20.0.0", "c8": "^8.0.0", "mdast-util-from-markdown": "^1.0.0", @@ -95,10 +96,19 @@ "strict": true }, "xo": { - "prettier": true, - "rules": { - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/array-type": "off" - } + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "rules": { + "@typescript-eslint/array-type": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/consistent-type-definitions": "off", + "@typescript-eslint/no-empty-function": "off" + } + } + ], + "prettier": true } } diff --git a/readme.md b/readme.md index 6e1c145..2735cb7 100644 --- a/readme.md +++ b/readme.md @@ -81,7 +81,7 @@ const tree = u('tree', [ u('leaf', '3') ]) -visit(tree, 'leaf', (node) => { +visit(tree, 'leaf', function (node) { console.log(node) }) ``` diff --git a/test.js b/test.js index df4f6bc..de571e3 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,5 @@ /** + * @typedef {import('mdast').Root} Root * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent */ @@ -8,8 +9,7 @@ import test from 'node:test' import {fromMarkdown} from 'mdast-util-from-markdown' import {gfmFromMarkdown} from 'mdast-util-gfm' import {gfm} from 'micromark-extension-gfm' -import {visit, CONTINUE, EXIT, SKIP} from './index.js' -import * as mod from './index.js' +import {CONTINUE, EXIT, SKIP, visit} from './index.js' const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') @@ -49,193 +49,177 @@ const reverseTypes = [ ] test('visit', async function (t) { - assert.deepEqual( - Object.keys(mod).sort(), - ['CONTINUE', 'EXIT', 'SKIP', 'visit'], - 'should expose the public api' - ) + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('./index.js')).sort(), [ + 'CONTINUE', + 'EXIT', + 'SKIP', + 'visit' + ]) + }) - assert.throws( - () => { - // @ts-expect-error runtime. + await t.test('should fail without tree', async function () { + assert.throws(function () { + // @ts-expect-error: check that the runtime throws an error. visit() - }, - /TypeError: visitor is not a function/, - 'should fail without tree' - ) + }, /TypeError: visitor is not a function/) + }) - assert.throws( - () => { - // @ts-expect-error runtime. + await t.test('should fail without visitor', async function () { + assert.throws(function () { + // @ts-expect-error: check that the runtime throws an error. visit(tree) - }, - /TypeError: visitor is not a function/, - 'should fail without visitor' - ) + }, /TypeError: visitor is not a function/) + }) - await t.test('should iterate over all nodes', () => { + await t.test('should iterate over all nodes', async function () { let n = 0 - visit(tree, visitor) - - assert.equal(n, types.length, 'should visit all nodes') - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, function (node) { assert.strictEqual(node.type, types[n], 'should be the expected type') n++ - } + }) + + assert.equal(n, types.length, 'should visit all nodes') }) - await t.test('should iterate over all nodes, backwards', () => { + await t.test('should iterate over all nodes, backwards', async function () { let n = 0 - visit(tree, visitor, true) + visit( + tree, + function (node) { + assert.strictEqual( + node.type, + reverseTypes[n], + 'should be the expected type' + ) + n++ + }, + true + ) assert.equal(n, reverseTypes.length, 'should visit all nodes in reverse') - - /** - * @param {Node} node - */ - function visitor(node) { - assert.strictEqual( - node.type, - reverseTypes[n], - 'should be the expected type' - ) - n++ - } }) - await t.test('should only visit a given `type`', () => { + await t.test('should only visit a given `type`', async function () { let n = 0 - visit(tree, 'text', visitor) - - assert.equal(n, texts, 'should visit all matching nodes') - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, 'text', function (node) { assert.strictEqual(node.type, 'text', 'should be the expected type') n++ - } + }) + + assert.equal(n, texts, 'should visit all matching nodes') }) - await t.test('should only visit given `type`s', () => { + await t.test('should only visit given `type`s', async function () { const types = ['text', 'inlineCode'] let n = 0 - visit(tree, types, visitor) - - assert.equal(n, texts + codes, 'should visit all matching nodes') - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, types, function (node) { n++ assert.notStrictEqual(types.indexOf(node.type), -1, 'should match') - } + }) + + assert.equal(n, texts + codes, 'should visit all matching nodes') }) - await t.test('should accept any `is`-compatible test function', () => { - let n = 0 + await t.test( + 'should accept any `is`-compatible test function', + async function () { + let n = 0 - visit(tree, test, (node, index, parent) => { - const info = '(' + (parent && parent.type) + ':' + index + ')' - assert.ok(test(node, index), 'should be a requested node ' + info) - n++ - }) + visit( + tree, + test, + /** + * @returns {undefined} + */ + function (node, index, parent) { + const info = '(' + (parent && parent.type) + ':' + index + ')' + assert.ok(test(node, index), 'should be a requested node ' + info) + n++ + } + ) - assert.equal(n, 3, 'should visit all passing nodes') + assert.equal(n, 3, 'should visit all passing nodes') - /** - * @param {Node} _ - * @param {number|null|undefined} index - */ - function test(_, index) { - return typeof index === 'number' && index > 3 + /** + * @param {Node} _ + * @param {number | null | undefined} index + */ + function test(_, index) { + return typeof index === 'number' && index > 3 + } } - }) + ) - await t.test('should accept an array of `is`-compatible tests', () => { - const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) - const tests = [ - /** @param {Node} node */ - (node) => node.type === 'root', - 'paragraph', - {value: '.'}, - 'emphasis', - 'strong' - ] - let n = 0 + await t.test( + 'should accept an array of `is`-compatible tests', + async function () { + const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) + let n = 0 - visit(tree, tests, (node) => { - // @ts-expect-error: indexable. - const ok = expected.has(node.type) || node.value === '.' - assert.ok(ok, 'should be a requested type: ' + node.type) - n++ - }) + visit( + tree, + [ + function (node) { + return node.type === 'root' + }, + 'paragraph', + {value: '.'}, + 'emphasis', + 'strong' + ], + function (node) { + const ok = + expected.has(node.type) || ('value' in node && node.value === '.') + assert.ok(ok, 'should be a requested type: ' + node.type) + n++ + } + ) - assert.equal(n, 5, 'should visit all passing nodes') - }) + assert.equal(n, 5, 'should visit all passing nodes') + } + ) - await t.test('should stop if `visitor` stops', () => { + await t.test('should stop if `visitor` stops', async function () { let n = 0 - visit(tree, visitor) - - assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, function (node) { assert.strictEqual(node.type, types[n++], 'should be the expected type') return n === stopIndex ? EXIT : CONTINUE - } + }) + + assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') }) - await t.test('should stop if `visitor` stops, backwards', () => { + await t.test('should stop if `visitor` stops, backwards', async function () { let n = 0 - visit(tree, visitor, true) + visit( + tree, + function (node) { + assert.strictEqual( + node.type, + reverseTypes[n++], + 'should be the expected type' + ) - assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') + return n === stopIndex ? EXIT : CONTINUE + }, + true + ) - /** - * @param {Node} node - */ - function visitor(node) { - assert.strictEqual( - node.type, - reverseTypes[n++], - 'should be the expected type' - ) - return n === stopIndex ? EXIT : CONTINUE - } + assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') }) - await t.test('should skip if `visitor` skips', () => { + await t.test('should skip if `visitor` skips', async function () { let n = 0 let count = 0 - visit(tree, visitor) - - assert.equal( - count, - types.length - 1, - 'should visit nodes except when `SKIP` is given' - ) - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, function (node) { assert.strictEqual(node.type, types[n++], 'should be the expected type') count++ @@ -243,42 +227,47 @@ test('visit', async function (t) { n++ // The one node inside it. return SKIP } - } + }) + + assert.equal( + count, + types.length - 1, + 'should visit nodes except when `SKIP` is given' + ) }) - await t.test('should skip if `visitor` skips, backwards', () => { + await t.test('should skip if `visitor` skips, backwards', async function () { let n = 0 let count = 0 - visit(tree, visitor, true) + visit( + tree, + function (node) { + assert.strictEqual( + node.type, + reverseTypes[n++], + 'should be the expected type' + ) + count++ + + if (n === skipReverseIndex) { + n++ // The one node inside it. + return SKIP + } + }, + true + ) assert.equal( count, reverseTypes.length - 1, 'should visit nodes except when `SKIP` is given' ) - - /** - * @param {Node} node - */ - function visitor(node) { - assert.strictEqual( - node.type, - reverseTypes[n++], - 'should be the expected type' - ) - count++ - - if (n === skipReverseIndex) { - n++ // The one node inside it. - return SKIP - } - } }) await t.test( 'should support a given `index` to iterate over next (`0` to reiterate)', - () => { + async function () { let n = 0 let again = false const expected = [ @@ -301,14 +290,7 @@ test('visit', async function (t) { 'text' ] - visit(tree, visitor) - - assert.equal(n, expected.length, 'should visit nodes again') - - /** - * @param {Node} node - */ - function visitor(node) { + visit(tree, function (node) { assert.strictEqual( node.type, expected[n++], @@ -319,13 +301,15 @@ test('visit', async function (t) { again = true return 0 // Start over. } - } + }) + + assert.equal(n, expected.length, 'should visit nodes again') } ) await t.test( 'should support a given `index` to iterate over next (`children.length` to skip further children)', - () => { + async function () { let n = 0 let again = false const expected = [ @@ -339,16 +323,7 @@ test('visit', async function (t) { 'text' ] - visit(tree, visitor) - - assert.equal(n, expected.length, 'should skip nodes') - - /** - * @param {Node} node - * @param {number|null} _ - * @param {Parent|null} parent - */ - function visitor(node, _, parent) { + visit(tree, function (node, _, parent) { assert.strictEqual( node.type, expected[n++], @@ -359,13 +334,15 @@ test('visit', async function (t) { again = true return parent.children.length // Skip siblings. } - } + }) + + assert.equal(n, expected.length, 'should skip nodes') } ) await t.test( 'should support any other given `index` to iterate over next', - () => { + async function () { let n = 0 let again = false const expected = [ @@ -381,15 +358,7 @@ test('visit', async function (t) { 'text' ] - visit(tree, visitor) - - assert.equal(n, expected.length, 'should skip nodes') - - /** - * @param {Node} node - * @param {number|null} index - */ - function visitor(node, index) { + visit(tree, function (node, index) { assert.strictEqual( node.type, expected[n++], @@ -404,37 +373,31 @@ test('visit', async function (t) { again = true return index + 2 // Skip to `inlineCode`. } - } + }) + + assert.equal(n, expected.length, 'should skip nodes') } ) - await t.test('should visit added nodes', () => { + await t.test('should visit added nodes', async function () { const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') - const other = /** @type {Parent} */ ( - fromMarkdown('Another ~~sentence~~.', { - extensions: [gfm()], - mdastExtensions: [gfmFromMarkdown()] - }).children[0] - ) + const other = fromMarkdown('Another ~~sentence~~.', { + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()] + }).children[0] const l = types.length + 5 // (p, text, delete, text, text) let n = 0 - visit(tree, visitor) - - assert.equal(n, l, 'should walk over all nodes') - - /** - * @param {Node} _1 - * @param {number|null} _2 - * @param {Parent|null} parent - */ - function visitor(_1, _2, parent) { + visit(tree, function (_1, _2, parent) { n++ if (parent && n === 2) { + assert(parent.type === 'root') parent.children.push(other) } - } + }) + + assert.equal(n, l, 'should walk over all nodes') }) }) From 07a2fbe13dfc3ce9f024c291cf499b0cbca32026 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:21:42 +0200 Subject: [PATCH 55/63] Refactor docs --- readme.md | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/readme.md b/readme.md index 2735cb7..36282d2 100644 --- a/readme.md +++ b/readme.md @@ -48,7 +48,7 @@ of parents. ## 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-visit @@ -57,41 +57,39 @@ npm install unist-util-visit In Deno with [`esm.sh`][esmsh]: ```js -import {visit} from 'https://esm.sh/unist-util-visit@4' +import {CONTINUE, EXIT, SKIP, visit} from 'https://esm.sh/unist-util-visit@4' ``` In browsers with [`esm.sh`][esmsh]: ```html ``` ## Use ```js -import {u} from 'unist-builder' +import {fromMarkdown} from 'mdast-util-from-markdown' import {visit} from 'unist-util-visit' -const tree = u('tree', [ - u('leaf', '1'), - u('node', [u('leaf', '2')]), - u('void'), - u('leaf', '3') -]) +const tree = fromMarkdown('Some *emphasis*, **strong**, and `code`.') -visit(tree, 'leaf', function (node) { - console.log(node) +visit(tree, 'text', function (node, index, parent) { + console.log([node.value, parent ? parent.type : index]) }) ``` Yields: ```js -{type: 'leaf', value: '1'} -{type: 'leaf', value: '2'} -{type: 'leaf', value: '3'} +[ 'Some ', 'paragraph' ] +[ 'emphasis', 'emphasis' ] +[ ', ', 'paragraph' ] +[ 'strong', 'strong' ] +[ ', and ', 'paragraph' ] +[ '.', 'paragraph' ] ``` ## API @@ -125,12 +123,12 @@ See [`Action` in `unist-util-visit-parents`][vp-action]. ### `ActionTuple` List with an action and an index (TypeScript type). -See [`ActionTuple` in `unist-util-visit-parents`][vp-actiontuple]. +See [`ActionTuple` in `unist-util-visit-parents`][vp-action-tuple]. ### `BuildVisitor` Build a typed `Visitor` function from a tree and a test (TypeScript type). -See [`BuildVisitor` in `unist-util-visit-parents`][vp-buildvisitor]. +See [`BuildVisitor` in `unist-util-visit-parents`][vp-build-visitor]. ### `Index` @@ -183,22 +181,25 @@ When the `Action` is `CONTINUE`, `Index` can be returned. ### `VisitorResult` Any value that can be returned from a visitor (TypeScript type). -See [`VisitorResult` in `unist-util-visit-parents`][vp-visitorresult]. +See [`VisitorResult` in `unist-util-visit-parents`][vp-visitor-result]. ## Types This package is fully typed with [TypeScript][]. It exports the additional types [`Action`][api-action], -[`ActionTuple`][api-actiontuple], [`BuildVisitor`][api-buildvisitor], +[`ActionTuple`][api-action-tuple], [`BuildVisitor`][api-build-visitor], [`Index`][api-index], [`Test`][api-test], [`Visitor`][api-visitor], and -[`VisitorResult`][api-visitorresult]. +[`VisitorResult`][api-visitor-result]. ## 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-visit@^4`, +compatible with Node.js 12. ## Related @@ -243,9 +244,9 @@ abide by its terms. [downloads]: https://www.npmjs.com/package/unist-util-visit -[size-badge]: https://img.shields.io/bundlephobia/minzip/unist-util-visit.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unist-util-visit -[size]: https://bundlephobia.com/result?p=unist-util-visit +[size]: https://bundlejs.com/?q=unist-util-visit [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg @@ -287,13 +288,13 @@ abide by its terms. [vp-action]: https://github.com/syntax-tree/unist-util-visit-parents#action -[vp-actiontuple]: https://github.com/syntax-tree/unist-util-visit-parents#actiontuple +[vp-action-tuple]: https://github.com/syntax-tree/unist-util-visit-parents#actiontuple -[vp-buildvisitor]: https://github.com/syntax-tree/unist-util-visit-parents#buildvisitor +[vp-build-visitor]: https://github.com/syntax-tree/unist-util-visit-parents#buildvisitor [vp-index]: https://github.com/syntax-tree/unist-util-visit-parents#index -[vp-visitorresult]: https://github.com/syntax-tree/unist-util-visit-parents#visitorresult +[vp-visitor-result]: https://github.com/syntax-tree/unist-util-visit-parents#visitorresult [api-visit]: #visittree-test-visitor-reverse @@ -305,9 +306,9 @@ abide by its terms. [api-action]: #action -[api-actiontuple]: #actiontuple +[api-action-tuple]: #actiontuple -[api-buildvisitor]: #buildvisitor +[api-build-visitor]: #buildvisitor [api-index]: #index @@ -315,4 +316,4 @@ abide by its terms. [api-visitor]: #visitor -[api-visitorresult]: #visitorresult +[api-visitor-result]: #visitorresult From 12c9ee907bc108eeb3a1d26dc6e147844d8b5f1f Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:25:20 +0200 Subject: [PATCH 56/63] Change to pass `undefined`, not `null` --- index.test-d.ts | 18 ++++++++++-------- lib/index.js | 6 +++--- readme.md | 4 ++-- test.js | 1 + 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 4ba8020..6de53d0 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -46,15 +46,15 @@ visit(sampleTree) // ## No test visit(sampleTree, function (node, index, parent) { expectType(node) - expectType(index) - expectType(parent) + expectType(index) + expectType(parent) }) visit(implicitTree, function (node, index, parent) { // Objects are too loose. expectAssignable(node) expectNotType(node) - expectType(index) + expectType(index) expectType(parent) }) @@ -63,8 +63,10 @@ visit(implicitTree, function (node, index, parent) { // Knows it’s a heading and its parents. visit(sampleTree, 'heading', function (node, index, parent) { expectType(node) - expectType(index) - expectType
(parent) + expectType(index) + expectType< + Blockquote | FootnoteDefinition | ListItem | Root | undefined + >(parent) }) // Not in tree. @@ -83,8 +85,8 @@ visit(implicitTree, 'heading', function (node, index, parent) { visit(sampleTree, 'tableCell', function (node, index, parent) { expectType(node) - expectType(index) - expectType(parent) + expectType(index) + expectType(parent) }) // ## Props test @@ -203,7 +205,7 @@ visit(sampleTree, 'tableCell', function (node) { | LinkReference | Strong | TableCell - | null + | undefined >(parent) }) }) diff --git a/lib/index.js b/lib/index.js index dfb451d..11ceef1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -43,9 +43,9 @@ * traversed. * @param {Visited} node * Found node. - * @param {Visited extends UnistNode ? number | null : never} index + * @param {Visited extends UnistNode ? number | undefined : never} index * Index of `node` in `parent`. - * @param {Ancestor extends UnistNode ? Ancestor | null : never} parent + * @param {Ancestor extends UnistNode ? Ancestor | undefined : never} parent * Parent of `node`. * @returns {VisitorResult} * What to do next. @@ -187,7 +187,7 @@ export function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) { */ function overload(node, parents) { const parent = parents[parents.length - 1] - const index = parent ? parent.children.indexOf(node) : null + const index = parent ? parent.children.indexOf(node) : undefined return visitor(node, index, parent) } } diff --git a/readme.md b/readme.md index 36282d2..2eab5ff 100644 --- a/readme.md +++ b/readme.md @@ -162,9 +162,9 @@ traversed. * `node` ([`Node`][node]) — found node -* `index` (`number` or `null`) +* `index` (`number` or `undefined`) — index of `node` in `parent` -* `parent` ([`Node`][node] or `null`) +* `parent` ([`Node`][node] or `undefined`) — parent of `node` ###### Returns diff --git a/test.js b/test.js index de571e3..8f5934b 100644 --- a/test.js +++ b/test.js @@ -145,6 +145,7 @@ test('visit', async function (t) { assert.equal(n, 3, 'should visit all passing nodes') + // To do: when `unist-util-is` updates, we can drop `null`. /** * @param {Node} _ * @param {number | null | undefined} index From 3cb2732d7e0697247f98b1780f9c1e1eb759aabb Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:32:36 +0200 Subject: [PATCH 57/63] Fix performance of `InclusiveDescendant` type --- index.test-d.ts | 5 +- lib/index.js | 144 ++++++++++++++++++++++++++++++++++++++++++------ package.json | 4 ++ 3 files changed, 134 insertions(+), 19 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 6de53d0..cf964e0 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -55,7 +55,7 @@ visit(implicitTree, function (node, index, parent) { expectAssignable(node) expectNotType(node) expectType(index) - expectType(parent) + expectAssignable(parent) }) // ## String test @@ -126,8 +126,7 @@ visit(sampleTree, isHeading, function (node) { }) // Function test (explicit assertion). visit(sampleTree, isHeading2, function (node) { - // To do: improving `InclusiveDescendant` should use `Heading & {depth: 2}`. - expectType(node) + expectType(node) }) // ## Combined tests diff --git a/lib/index.js b/lib/index.js index 11ceef1..aac4691 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,22 +5,134 @@ * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult */ +// To do: use types from `unist-util-visit-parents` when it’s released. + +/** + * @typedef {( + * Fn extends (value: any) => value is infer Thing + * ? Thing + * : Fallback + * )} Predicate + * Get the value of a type guard `Fn`. + * @template Fn + * Value; typically function that is a type guard (such as `(x): x is Y`). + * @template Fallback + * Value to yield if `Fn` is not a type guard. + */ + /** * @typedef {( - * Ancestor extends UnistParent - * ? Child extends Ancestor['children'][number] - * ? Ancestor - * : never + * Check extends null | undefined // No test. + * ? Value + * : Value extends {type: Check} // String (type) test. + * ? Value + * : Value extends Check // Partial test. + * ? Value + * : Check extends Function // Function test. + * ? Predicate extends Value + * ? Predicate * : never - * )} ParentsOf - * Check if `Child` can be a child of `Ancestor`. - * - * Returns the ancestor when `Child` can be a child of `Ancestor`, or returns - * `never`. - * @template {UnistNode} Ancestor - * Node type. + * : never // Some other test? + * )} MatchesOne + * Check whether a node matches a primitive check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test, but not arrays. + */ + +/** + * @typedef {( + * Check extends Array + * ? MatchesOne + * : MatchesOne + * )} Matches + * Check whether a node matches a check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test. + */ + +/** + * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint + * Number; capped reasonably. + */ + +/** + * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment + * Increment a number in the type system. + * @template {Uint} [I=0] + * Index. + */ + +/** + * @typedef {( + * Node extends UnistParent + * ? Node extends {children: Array} + * ? Child extends Children ? Node : never + * : never + * : never + * )} InternalParent + * Collect nodes that can be parents of `Child`. + * @template {UnistNode} Node + * All node types in a tree. * @template {UnistNode} Child - * Node type. + * Node to search for. + */ + +/** + * @typedef {InternalParent, Child>} Parent + * Collect nodes in `Tree` that can be parents of `Child`. + * @template {UnistNode} Tree + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {( + * Depth extends Max + * ? never + * : + * | InternalParent + * | InternalAncestor, Max, Increment> + * )} InternalAncestor + * Collect nodes in `Tree` that can be ancestors of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @typedef {( + * Tree extends UnistParent + * ? Depth extends Max + * ? Tree + * : Tree | InclusiveDescendant> + * : Tree + * )} InclusiveDescendant + * Collect all (inclusive) descendants of `Tree`. + * + * > 👉 **Note**: for performance reasons, this seems to be the fastest way to + * > recurse without actually running into an infinite loop, which the + * > previous version did. + * > + * > Practically, a max of `2` is typically enough assuming a `Root` is + * > passed, but it doesn’t improve performance. + * > It gets higher with `List > ListItem > Table > TableRow > TableCell`. + * > Using up to `10` doesn’t hurt or help either. + * @template {UnistNode} Tree + * Tree type. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. */ /** @@ -45,7 +157,7 @@ * Found node. * @param {Visited extends UnistNode ? number | undefined : never} index * Index of `node` in `parent`. - * @param {Ancestor extends UnistNode ? Ancestor | undefined : never} parent + * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent * Parent of `node`. * @returns {VisitorResult} * What to do next. @@ -63,7 +175,7 @@ */ /** - * @typedef {Visitor>} BuildVisitorFromMatch + * @typedef {Visitor>} BuildVisitorFromMatch * Build a typed `Visitor` function from a node and all possible parents. * * It will infer which values are passed as `node` and which as `parent`. @@ -76,7 +188,7 @@ /** * @typedef {( * BuildVisitorFromMatch< - * import('unist-util-visit-parents/complex-types.js').Matches, + * Matches, * Extract * > * )} BuildVisitorFromDescendants @@ -92,7 +204,7 @@ /** * @typedef {( * BuildVisitorFromDescendants< - * import('unist-util-visit-parents/complex-types.js').InclusiveDescendant, + * InclusiveDescendant, * Check * > * )} BuildVisitor diff --git a/package.json b/package.json index 5cb6d0e..e09ef83 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,10 @@ "typeCoverage": { "atLeast": 100, "detail": true, + "#": "needed `any`s", + "ignoreFiles": [ + "lib/index.d.ts" + ], "ignoreCatch": true, "strict": true }, From 89fc050045097fc0c8b7a4a839505bec89f02825 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:57:03 +0200 Subject: [PATCH 58/63] Change to remove `complex-types.d.ts` All types are now exposed from regular files (`index.js`). --- complex-types.d.ts | 2 -- package.json | 1 - tsconfig.json | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 complex-types.d.ts diff --git a/complex-types.d.ts b/complex-types.d.ts deleted file mode 100644 index 5085b4b..0000000 --- a/complex-types.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// To do: next major: remove this file. -export type {Visitor, BuildVisitor} from './index.js' diff --git a/package.json b/package.json index e09ef83..8704a2a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "types": "index.d.ts", "files": [ "lib/", - "complex-types.d.ts", "index.d.ts", "index.js" ], diff --git a/tsconfig.json b/tsconfig.json index f4022c7..ad1496e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "target": "es2020" }, "exclude": ["coverage/", "node_modules/"], - "include": ["**/*.js", "complex-types.d.ts", "index.d.ts"] + "include": ["**/*.js", "index.d.ts"] } From 77d6cb19583e06b7cc0b43892ec6fc72d9ebb7c5 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:57:54 +0200 Subject: [PATCH 59/63] Remove `xo` rules --- package.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/package.json b/package.json index 8704a2a..2d9c039 100644 --- a/package.json +++ b/package.json @@ -99,19 +99,6 @@ "strict": true }, "xo": { - "overrides": [ - { - "files": [ - "**/*.ts" - ], - "rules": { - "@typescript-eslint/array-type": "off", - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/consistent-type-definitions": "off", - "@typescript-eslint/no-empty-function": "off" - } - } - ], "prettier": true } } From b5f36de38759270558c08a4b65970cd8eedf0a45 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 13:15:37 +0200 Subject: [PATCH 60/63] Change to use `export` map --- .gitignore | 4 ++-- index.test-d.ts | 2 +- package.json | 13 +++++++++++-- test.js | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 07ec94c..d8239d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store -lib/index.d.ts -test.d.ts +*.d.ts *.log coverage/ node_modules/ yarn.lock +!/index.d.ts diff --git a/index.test-d.ts b/index.test-d.ts index cf964e0..a17a554 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -18,7 +18,7 @@ import type { TableRow } from 'mdast' import type {Node, Parent} from 'unist' -import {CONTINUE, EXIT, SKIP, visit} from './index.js' +import {CONTINUE, EXIT, SKIP, visit} from 'unist-util-visit' // To do: use `mdast` when released. type Nodes = Root | Content diff --git a/package.json b/package.json index 2d9c039..f482cb9 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,7 @@ ], "sideEffects": false, "type": "module", - "main": "index.js", - "types": "index.d.ts", + "exports": "./index.js", "files": [ "lib/", "index.d.ts", @@ -99,6 +98,16 @@ "strict": true }, "xo": { + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "rules": { + "import/no-extraneous-dependencies": "off" + } + } + ], "prettier": true } } diff --git a/test.js b/test.js index 8f5934b..e471347 100644 --- a/test.js +++ b/test.js @@ -9,7 +9,7 @@ import test from 'node:test' import {fromMarkdown} from 'mdast-util-from-markdown' import {gfmFromMarkdown} from 'mdast-util-gfm' import {gfm} from 'micromark-extension-gfm' -import {CONTINUE, EXIT, SKIP, visit} from './index.js' +import {CONTINUE, EXIT, SKIP, visit} from 'unist-util-visit' const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') @@ -50,7 +50,7 @@ const reverseTypes = [ test('visit', async function (t) { await t.test('should expose the public api', async function () { - assert.deepEqual(Object.keys(await import('./index.js')).sort(), [ + assert.deepEqual(Object.keys(await import('unist-util-visit')).sort(), [ 'CONTINUE', 'EXIT', 'SKIP', From befc0b3db4daf6b4ebde2088878235c023ea2a8c Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 13:16:24 +0200 Subject: [PATCH 61/63] 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 2eab5ff..7b4ac3e 100644 --- a/readme.md +++ b/readme.md @@ -198,8 +198,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-visit@^4`, -compatible with Node.js 12. +This means we try to keep the current release line, `unist-util-visit@^5`, +compatible with Node.js 16. ## Related diff --git a/tsconfig.json b/tsconfig.json index ad1496e..bed2bb4 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", "index.d.ts"] From 4dcff317b5fd0cce0a2d0ebf1e19a1f2e07fabf0 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 7 Jul 2023 11:38:55 +0200 Subject: [PATCH 62/63] Update `@types/unist` --- index.test-d.ts | 28 ++++++++-------------------- lib/index.js | 10 +++++++++- package.json | 8 ++++---- test.js | 9 +++++---- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index a17a554..ebc71fe 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,32 +1,27 @@ import {expectAssignable, expectNotType, expectType} from 'tsd' import type { Blockquote, - Content, Definition, Delete, Emphasis, - Footnote, FootnoteDefinition, Heading, Link, LinkReference, ListItem, + Nodes, + Parents, PhrasingContent, Root, + RootContent, Strong, TableCell, TableRow } from 'mdast' import type {Node, Parent} from 'unist' -import {CONTINUE, EXIT, SKIP, visit} from 'unist-util-visit' +import {CONTINUE, EXIT, SKIP, visit} from './index.js' -// To do: use `mdast` when released. -type Nodes = Root | Content - -// To do: use `mdast` when released. -type Parents = Extract - -/* Setup */ +// Setup. const implicitTree = { type: 'root', children: [{type: 'heading', depth: 1, children: []}] @@ -132,7 +127,7 @@ visit(sampleTree, isHeading2, function (node) { // ## Combined tests visit(sampleTree, ['heading', {depth: 1}, isHeading], function (node) { // Unfortunately TS casts things in arrays too vague. - expectType(node) + expectType(node) }) // To do: update to `unist-util-is` should make this work? @@ -141,7 +136,7 @@ visit(sampleTree, ['heading', {depth: 1}, isHeading], function (node) { // ['heading', {depth: 1}, isHeading] as const, // function (node) { // // Unfortunately TS casts things in arrays too vague. -// expectType(node) +// expectType(node) // } // ) @@ -197,14 +192,7 @@ visit(sampleTree, 'tableCell', function (node) { visit(node, function (node, _, parent) { expectType(node) expectType< - | Delete - | Emphasis - | Footnote - | Link - | LinkReference - | Strong - | TableCell - | undefined + Delete | Emphasis | Link | LinkReference | Strong | TableCell | undefined >(parent) }) }) diff --git a/lib/index.js b/lib/index.js index aac4691..0d521ed 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,10 +1,18 @@ /** * @typedef {import('unist').Node} UnistNode * @typedef {import('unist').Parent} UnistParent - * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult */ +/** + * @typedef {Exclude | undefined} Test + * Test from `unist-util-is`. + * + * Note: we have remove and add `undefined`, because otherwise when generating + * automatic `.d.ts` files, TS tries to flatten paths from a local perspective, + * which doesn’t work when publishing on npm. + */ + // To do: use types from `unist-util-visit-parents` when it’s released. /** diff --git a/package.json b/package.json index f482cb9..6ece99e 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,12 @@ "index.js" ], "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "devDependencies": { - "@types/mdast": "^3.0.0", + "@types/mdast": "^4.0.0", "@types/node": "^20.0.0", "c8": "^8.0.0", "mdast-util-from-markdown": "^1.0.0", diff --git a/test.js b/test.js index e471347..6ee0f22 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,6 @@ /** * @typedef {import('mdast').Root} Root * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent */ import assert from 'node:assert/strict' @@ -11,7 +10,10 @@ import {gfmFromMarkdown} from 'mdast-util-gfm' import {gfm} from 'micromark-extension-gfm' import {CONTINUE, EXIT, SKIP, visit} from 'unist-util-visit' -const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') +// To do: remove cast after update. +const tree = /** @type {Root} */ ( + fromMarkdown('Some _emphasis_, **importance**, and `code`.') +) const stopIndex = 5 const skipIndex = 7 @@ -145,10 +147,9 @@ test('visit', async function (t) { assert.equal(n, 3, 'should visit all passing nodes') - // To do: when `unist-util-is` updates, we can drop `null`. /** * @param {Node} _ - * @param {number | null | undefined} index + * @param {number | undefined} index */ function test(_, index) { return typeof index === 'number' && index > 3 From bc59f3bb488a71a1aab3da094306022eeecabda8 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Fri, 7 Jul 2023 11:39:36 +0200 Subject: [PATCH 63/63] 5.0.0 --- package.json | 2 +- readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6ece99e..425c516 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unist-util-visit", - "version": "4.1.2", + "version": "5.0.0", "description": "unist utility to visit nodes", "license": "MIT", "keywords": [ diff --git a/readme.md b/readme.md index 7b4ac3e..1829fe8 100644 --- a/readme.md +++ b/readme.md @@ -57,14 +57,14 @@ npm install unist-util-visit In Deno with [`esm.sh`][esmsh]: ```js -import {CONTINUE, EXIT, SKIP, visit} from 'https://esm.sh/unist-util-visit@4' +import {CONTINUE, EXIT, SKIP, visit} from 'https://esm.sh/unist-util-visit@5' ``` In browsers with [`esm.sh`][esmsh]: ```html ```