├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── index.d.ts ├── index.js ├── index.test-d.ts ├── lib └── index.js ├── license ├── package.json ├── readme.md ├── test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v4 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.d.ts 3 | *.log 4 | coverage/ 5 | node_modules/ 6 | yarn.lock 7 | !/index.d.ts 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export type {Test} from 'unist-util-is' 2 | export type { 3 | Action, 4 | ActionTuple, 5 | Index, 6 | VisitorResult 7 | } from 'unist-util-visit-parents' 8 | export type {Visitor, BuildVisitor} from './lib/index.js' 9 | export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js' 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Note: types exported from `index.d.ts` 2 | export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js' 3 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable, expectNotType, expectType} from 'tsd' 2 | import type { 3 | Blockquote, 4 | Definition, 5 | Delete, 6 | Emphasis, 7 | FootnoteDefinition, 8 | Heading, 9 | Link, 10 | LinkReference, 11 | ListItem, 12 | Nodes, 13 | Parents, 14 | PhrasingContent, 15 | Root, 16 | RootContent, 17 | Strong, 18 | TableCell, 19 | TableRow 20 | } from 'mdast' 21 | import type {Node, Parent} from 'unist' 22 | import {CONTINUE, EXIT, SKIP, visit} from './index.js' 23 | 24 | // Setup. 25 | const implicitTree = { 26 | type: 'root', 27 | children: [{type: 'heading', depth: 1, children: []}] 28 | } 29 | 30 | const sampleTree: Root = { 31 | type: 'root', 32 | children: [{type: 'heading', depth: 1, children: []}] 33 | } 34 | 35 | // ## Missing parameters 36 | // @ts-expect-error: check that `node` is passed. 37 | visit() 38 | // @ts-expect-error: check that `visitor` is passed. 39 | visit(sampleTree) 40 | 41 | // ## No test 42 | visit(sampleTree, function (node, index, parent) { 43 | expectType(node) 44 | expectType(index) 45 | expectType(parent) 46 | }) 47 | 48 | visit(implicitTree, function (node, index, parent) { 49 | // Objects are too loose. 50 | expectAssignable(node) 51 | expectNotType(node) 52 | expectType(index) 53 | expectAssignable(parent) 54 | }) 55 | 56 | // ## String test 57 | 58 | // Knows it’s a heading and its parents. 59 | visit(sampleTree, 'heading', function (node, index, parent) { 60 | expectType(node) 61 | expectType(index) 62 | expectType
( 63 | parent 64 | ) 65 | }) 66 | 67 | // Not in tree. 68 | visit(sampleTree, 'element', function (node, index, parent) { 69 | expectType(node) 70 | expectType(index) 71 | expectType(parent) 72 | }) 73 | 74 | // Implicit nodes are too loose. 75 | visit(implicitTree, 'heading', function (node, index, parent) { 76 | expectType(node) 77 | expectType(index) 78 | expectType(parent) 79 | }) 80 | 81 | visit(sampleTree, 'tableCell', function (node, index, parent) { 82 | expectType(node) 83 | expectType(index) 84 | expectType(parent) 85 | }) 86 | 87 | // ## Props test 88 | 89 | // Knows that headings have depth, but TS doesn’t infer the depth normally. 90 | visit(sampleTree, {depth: 1}, function (node) { 91 | expectType(node) 92 | expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) 93 | }) 94 | 95 | // This goes fine. 96 | visit(sampleTree, {type: 'heading'} as const, function (node) { 97 | expectType(node) 98 | expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) 99 | }) 100 | 101 | // For some reason the const goes wrong. 102 | visit(sampleTree, {depth: 1} as const, function (node) { 103 | // Note: something going wrong here, to do: investigate. 104 | expectType(node) 105 | }) 106 | 107 | // For some reason the const goes wrong. 108 | visit(sampleTree, {type: 'heading', depth: 1} as const, function (node) { 109 | // Note: something going wrong here, to do: investigate. 110 | expectType(node) 111 | }) 112 | 113 | // Function test (implicit assertion). 114 | visit(sampleTree, isHeadingLoose, function (node) { 115 | expectType(node) 116 | }) 117 | // Function test (explicit assertion). 118 | visit(sampleTree, isHeading, function (node) { 119 | expectType(node) 120 | expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth) 121 | }) 122 | // Function test (explicit assertion). 123 | visit(sampleTree, isHeading2, function (node) { 124 | expectType(node) 125 | }) 126 | 127 | // ## Combined tests 128 | visit(sampleTree, ['heading', {depth: 1}, isHeading], function (node) { 129 | // Unfortunately TS casts things in arrays too vague. 130 | expectType(node) 131 | }) 132 | 133 | // To do: update to `unist-util-is` should make this work? 134 | // visit( 135 | // sampleTree, 136 | // ['heading', {depth: 1}, isHeading] as const, 137 | // function (node) { 138 | // // Unfortunately TS casts things in arrays too vague. 139 | // expectType(node) 140 | // } 141 | // ) 142 | 143 | // ## Return type: incorrect. 144 | // @ts-expect-error: not an action. 145 | visit(sampleTree, function () { 146 | return 'random' 147 | }) 148 | // @ts-expect-error: not a tuple: missing action. 149 | visit(sampleTree, function () { 150 | return [1] 151 | }) 152 | // @ts-expect-error: not a tuple: incorrect action. 153 | visit(sampleTree, function () { 154 | return ['random', 1] 155 | }) 156 | 157 | // ## Return type: action. 158 | visit(sampleTree, function () { 159 | return CONTINUE 160 | }) 161 | visit(sampleTree, function () { 162 | return EXIT 163 | }) 164 | visit(sampleTree, function () { 165 | return SKIP 166 | }) 167 | 168 | // ## Return type: index. 169 | visit(sampleTree, function () { 170 | return 0 171 | }) 172 | visit(sampleTree, function () { 173 | return 1 174 | }) 175 | 176 | // ## Return type: tuple. 177 | visit(sampleTree, function () { 178 | return [CONTINUE, 1] 179 | }) 180 | visit(sampleTree, function () { 181 | return [EXIT, 1] 182 | }) 183 | visit(sampleTree, function () { 184 | return [SKIP, 1] 185 | }) 186 | visit(sampleTree, function () { 187 | return [SKIP] 188 | }) 189 | 190 | // ## Infer on tree 191 | visit(sampleTree, 'tableCell', function (node) { 192 | visit(node, function (node, _, parent) { 193 | expectType(node) 194 | expectType< 195 | Delete | Emphasis | Link | LinkReference | Strong | TableCell | undefined 196 | >(parent) 197 | }) 198 | }) 199 | 200 | visit(sampleTree, 'definition', function (node) { 201 | visit(node, function (node, _, parent) { 202 | expectType(node) 203 | expectType(parent) 204 | }) 205 | }) 206 | 207 | function isHeading(node: Node): node is Heading { 208 | return node ? node.type === 'heading' : false 209 | } 210 | 211 | function isHeading2(node: Node): node is Heading & {depth: 2} { 212 | return isHeading(node) && node.depth === 2 213 | } 214 | 215 | function isHeadingLoose(node: Node) { 216 | return node ? node.type === 'heading' : false 217 | } 218 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('unist').Node} UnistNode 3 | * @typedef {import('unist').Parent} UnistParent 4 | * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult 5 | */ 6 | 7 | /** 8 | * @typedef {Exclude | undefined} Test 9 | * Test from `unist-util-is`. 10 | * 11 | * Note: we have remove and add `undefined`, because otherwise when generating 12 | * automatic `.d.ts` files, TS tries to flatten paths from a local perspective, 13 | * which doesn’t work when publishing on npm. 14 | */ 15 | 16 | // To do: use types from `unist-util-visit-parents` when it’s released. 17 | 18 | /** 19 | * @typedef {( 20 | * Fn extends (value: any) => value is infer Thing 21 | * ? Thing 22 | * : Fallback 23 | * )} Predicate 24 | * Get the value of a type guard `Fn`. 25 | * @template Fn 26 | * Value; typically function that is a type guard (such as `(x): x is Y`). 27 | * @template Fallback 28 | * Value to yield if `Fn` is not a type guard. 29 | */ 30 | 31 | /** 32 | * @typedef {( 33 | * Check extends null | undefined // No test. 34 | * ? Value 35 | * : Value extends {type: Check} // String (type) test. 36 | * ? Value 37 | * : Value extends Check // Partial test. 38 | * ? Value 39 | * : Check extends Function // Function test. 40 | * ? Predicate extends Value 41 | * ? Predicate 42 | * : never 43 | * : never // Some other test? 44 | * )} MatchesOne 45 | * Check whether a node matches a primitive check in the type system. 46 | * @template Value 47 | * Value; typically unist `Node`. 48 | * @template Check 49 | * Value; typically `unist-util-is`-compatible test, but not arrays. 50 | */ 51 | 52 | /** 53 | * @typedef {( 54 | * Check extends Array 55 | * ? MatchesOne 56 | * : MatchesOne 57 | * )} Matches 58 | * Check whether a node matches a check in the type system. 59 | * @template Value 60 | * Value; typically unist `Node`. 61 | * @template Check 62 | * Value; typically `unist-util-is`-compatible test. 63 | */ 64 | 65 | /** 66 | * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint 67 | * Number; capped reasonably. 68 | */ 69 | 70 | /** 71 | * @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 72 | * Increment a number in the type system. 73 | * @template {Uint} [I=0] 74 | * Index. 75 | */ 76 | 77 | /** 78 | * @typedef {( 79 | * Node extends UnistParent 80 | * ? Node extends {children: Array} 81 | * ? Child extends Children ? Node : never 82 | * : never 83 | * : never 84 | * )} InternalParent 85 | * Collect nodes that can be parents of `Child`. 86 | * @template {UnistNode} Node 87 | * All node types in a tree. 88 | * @template {UnistNode} Child 89 | * Node to search for. 90 | */ 91 | 92 | /** 93 | * @typedef {InternalParent, Child>} Parent 94 | * Collect nodes in `Tree` that can be parents of `Child`. 95 | * @template {UnistNode} Tree 96 | * All node types in a tree. 97 | * @template {UnistNode} Child 98 | * Node to search for. 99 | */ 100 | 101 | /** 102 | * @typedef {( 103 | * Depth extends Max 104 | * ? never 105 | * : 106 | * | InternalParent 107 | * | InternalAncestor, Max, Increment> 108 | * )} InternalAncestor 109 | * Collect nodes in `Tree` that can be ancestors of `Child`. 110 | * @template {UnistNode} Node 111 | * All node types in a tree. 112 | * @template {UnistNode} Child 113 | * Node to search for. 114 | * @template {Uint} [Max=10] 115 | * Max; searches up to this depth. 116 | * @template {Uint} [Depth=0] 117 | * Current depth. 118 | */ 119 | 120 | /** 121 | * @typedef {( 122 | * Tree extends UnistParent 123 | * ? Depth extends Max 124 | * ? Tree 125 | * : Tree | InclusiveDescendant> 126 | * : Tree 127 | * )} InclusiveDescendant 128 | * Collect all (inclusive) descendants of `Tree`. 129 | * 130 | * > 👉 **Note**: for performance reasons, this seems to be the fastest way to 131 | * > recurse without actually running into an infinite loop, which the 132 | * > previous version did. 133 | * > 134 | * > Practically, a max of `2` is typically enough assuming a `Root` is 135 | * > passed, but it doesn’t improve performance. 136 | * > It gets higher with `List > ListItem > Table > TableRow > TableCell`. 137 | * > Using up to `10` doesn’t hurt or help either. 138 | * @template {UnistNode} Tree 139 | * Tree type. 140 | * @template {Uint} [Max=10] 141 | * Max; searches up to this depth. 142 | * @template {Uint} [Depth=0] 143 | * Current depth. 144 | */ 145 | 146 | /** 147 | * @callback Visitor 148 | * Handle a node (matching `test`, if given). 149 | * 150 | * Visitors are free to transform `node`. 151 | * They can also transform `parent`. 152 | * 153 | * Replacing `node` itself, if `SKIP` is not returned, still causes its 154 | * descendants to be walked (which is a bug). 155 | * 156 | * When adding or removing previous siblings of `node` (or next siblings, in 157 | * case of reverse), the `Visitor` should return a new `Index` to specify the 158 | * sibling to traverse after `node` is traversed. 159 | * Adding or removing next siblings of `node` (or previous siblings, in case 160 | * of reverse) is handled as expected without needing to return a new `Index`. 161 | * 162 | * Removing the children property of `parent` still results in them being 163 | * traversed. 164 | * @param {Visited} node 165 | * Found node. 166 | * @param {Visited extends UnistNode ? number | undefined : never} index 167 | * Index of `node` in `parent`. 168 | * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent 169 | * Parent of `node`. 170 | * @returns {VisitorResult} 171 | * What to do next. 172 | * 173 | * An `Index` is treated as a tuple of `[CONTINUE, Index]`. 174 | * An `Action` is treated as a tuple of `[Action]`. 175 | * 176 | * Passing a tuple back only makes sense if the `Action` is `SKIP`. 177 | * When the `Action` is `EXIT`, that action can be returned. 178 | * When the `Action` is `CONTINUE`, `Index` can be returned. 179 | * @template {UnistNode} [Visited=UnistNode] 180 | * Visited node type. 181 | * @template {UnistParent} [Ancestor=UnistParent] 182 | * Ancestor type. 183 | */ 184 | 185 | /** 186 | * @typedef {Visitor>} BuildVisitorFromMatch 187 | * Build a typed `Visitor` function from a node and all possible parents. 188 | * 189 | * It will infer which values are passed as `node` and which as `parent`. 190 | * @template {UnistNode} Visited 191 | * Node type. 192 | * @template {UnistParent} Ancestor 193 | * Parent type. 194 | */ 195 | 196 | /** 197 | * @typedef {( 198 | * BuildVisitorFromMatch< 199 | * Matches, 200 | * Extract 201 | * > 202 | * )} BuildVisitorFromDescendants 203 | * Build a typed `Visitor` function from a list of descendants and a test. 204 | * 205 | * It will infer which values are passed as `node` and which as `parent`. 206 | * @template {UnistNode} Descendant 207 | * Node type. 208 | * @template {Test} Check 209 | * Test type. 210 | */ 211 | 212 | /** 213 | * @typedef {( 214 | * BuildVisitorFromDescendants< 215 | * InclusiveDescendant, 216 | * Check 217 | * > 218 | * )} BuildVisitor 219 | * Build a typed `Visitor` function from a tree and a test. 220 | * 221 | * It will infer which values are passed as `node` and which as `parent`. 222 | * @template {UnistNode} [Tree=UnistNode] 223 | * Node type. 224 | * @template {Test} [Check=Test] 225 | * Test type. 226 | */ 227 | 228 | import {visitParents} from 'unist-util-visit-parents' 229 | 230 | export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents' 231 | 232 | /** 233 | * Visit nodes. 234 | * 235 | * This algorithm performs *depth-first* *tree traversal* in *preorder* 236 | * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**). 237 | * 238 | * You can choose for which nodes `visitor` is called by passing a `test`. 239 | * For complex tests, you should test yourself in `visitor`, as it will be 240 | * faster and will have improved type information. 241 | * 242 | * Walking the tree is an intensive task. 243 | * Make use of the return values of the visitor when possible. 244 | * Instead of walking a tree multiple times, walk it once, use `unist-util-is` 245 | * to check if a node matches, and then perform different operations. 246 | * 247 | * You can change the tree. 248 | * See `Visitor` for more info. 249 | * 250 | * @overload 251 | * @param {Tree} tree 252 | * @param {Check} check 253 | * @param {BuildVisitor} visitor 254 | * @param {boolean | null | undefined} [reverse] 255 | * @returns {undefined} 256 | * 257 | * @overload 258 | * @param {Tree} tree 259 | * @param {BuildVisitor} visitor 260 | * @param {boolean | null | undefined} [reverse] 261 | * @returns {undefined} 262 | * 263 | * @param {UnistNode} tree 264 | * Tree to traverse. 265 | * @param {Visitor | Test} testOrVisitor 266 | * `unist-util-is`-compatible test (optional, omit to pass a visitor). 267 | * @param {Visitor | boolean | null | undefined} [visitorOrReverse] 268 | * Handle each node (when test is omitted, pass `reverse`). 269 | * @param {boolean | null | undefined} [maybeReverse=false] 270 | * Traverse in reverse preorder (NRL) instead of the default preorder (NLR). 271 | * @returns {undefined} 272 | * Nothing. 273 | * 274 | * @template {UnistNode} Tree 275 | * Node type. 276 | * @template {Test} Check 277 | * `unist-util-is`-compatible test. 278 | */ 279 | export function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) { 280 | /** @type {boolean | null | undefined} */ 281 | let reverse 282 | /** @type {Test} */ 283 | let test 284 | /** @type {Visitor} */ 285 | let visitor 286 | 287 | if ( 288 | typeof testOrVisitor === 'function' && 289 | typeof visitorOrReverse !== 'function' 290 | ) { 291 | test = undefined 292 | visitor = testOrVisitor 293 | reverse = visitorOrReverse 294 | } else { 295 | // @ts-expect-error: assume the overload with test was given. 296 | test = testOrVisitor 297 | // @ts-expect-error: assume the overload with test was given. 298 | visitor = visitorOrReverse 299 | reverse = maybeReverse 300 | } 301 | 302 | visitParents(tree, test, overload, reverse) 303 | 304 | /** 305 | * @param {UnistNode} node 306 | * @param {Array} parents 307 | */ 308 | function overload(node, parents) { 309 | const parent = parents[parents.length - 1] 310 | const index = parent ? parent.children.indexOf(node) : undefined 311 | return visitor(node, index, parent) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unist-util-visit", 3 | "version": "5.0.0", 4 | "description": "unist utility to visit nodes", 5 | "license": "MIT", 6 | "keywords": [ 7 | "unist", 8 | "unist-util", 9 | "util", 10 | "utility", 11 | "remark", 12 | "retext", 13 | "rehype", 14 | "mdast", 15 | "hast", 16 | "xast", 17 | "nlcst", 18 | "natural", 19 | "language", 20 | "markdown", 21 | "html", 22 | "xml", 23 | "tree", 24 | "ast", 25 | "node", 26 | "visit", 27 | "walk" 28 | ], 29 | "repository": "syntax-tree/unist-util-visit", 30 | "bugs": "https://github.com/syntax-tree/unist-util-visit/issues", 31 | "funding": { 32 | "type": "opencollective", 33 | "url": "https://opencollective.com/unified" 34 | }, 35 | "author": "Titus Wormer (https://wooorm.com)", 36 | "contributors": [ 37 | "Titus Wormer (https://wooorm.com)", 38 | "Eugene Sharygin ", 39 | "Richard Gibson " 40 | ], 41 | "sideEffects": false, 42 | "type": "module", 43 | "exports": "./index.js", 44 | "files": [ 45 | "lib/", 46 | "index.d.ts", 47 | "index.js" 48 | ], 49 | "dependencies": { 50 | "@types/unist": "^3.0.0", 51 | "unist-util-is": "^6.0.0", 52 | "unist-util-visit-parents": "^6.0.0" 53 | }, 54 | "devDependencies": { 55 | "@types/mdast": "^4.0.0", 56 | "@types/node": "^20.0.0", 57 | "c8": "^9.0.0", 58 | "mdast-util-from-markdown": "^2.0.0", 59 | "mdast-util-gfm": "^3.0.0", 60 | "micromark-extension-gfm": "^3.0.0", 61 | "prettier": "^3.0.0", 62 | "remark-cli": "^12.0.0", 63 | "remark-preset-wooorm": "^10.0.0", 64 | "tsd": "^0.31.0", 65 | "type-coverage": "^2.0.0", 66 | "typescript": "^5.0.0", 67 | "xo": "^0.58.0" 68 | }, 69 | "scripts": { 70 | "prepack": "npm run build && npm run format", 71 | "build": "tsc --build --clean && tsc --build && tsd && type-coverage", 72 | "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", 73 | "test-api": "node --conditions development test.js", 74 | "test-coverage": "c8 --100 --reporter lcov npm run test-api", 75 | "test": "npm run build && npm run format && npm run test-coverage" 76 | }, 77 | "prettier": { 78 | "bracketSpacing": false, 79 | "semi": false, 80 | "singleQuote": true, 81 | "tabWidth": 2, 82 | "trailingComma": "none", 83 | "useTabs": false 84 | }, 85 | "remarkConfig": { 86 | "plugins": [ 87 | "remark-preset-wooorm" 88 | ] 89 | }, 90 | "typeCoverage": { 91 | "atLeast": 100, 92 | "detail": true, 93 | "#": "needed `any`s", 94 | "ignoreFiles": [ 95 | "lib/index.d.ts" 96 | ], 97 | "ignoreCatch": true, 98 | "strict": true 99 | }, 100 | "xo": { 101 | "overrides": [ 102 | { 103 | "files": [ 104 | "**/*.ts" 105 | ], 106 | "rules": { 107 | "import/no-extraneous-dependencies": "off" 108 | } 109 | } 110 | ], 111 | "prettier": true, 112 | "rules": { 113 | "unicorn/prefer-at": "off" 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # unist-util-visit 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | [unist][] utility to walk the tree. 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When should I use this?](#when-should-i-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`visit(tree[, test], visitor[, reverse])`](#visittree-test-visitor-reverse) 21 | * [`CONTINUE`](#continue) 22 | * [`EXIT`](#exit) 23 | * [`SKIP`](#skip) 24 | * [`Action`](#action) 25 | * [`ActionTuple`](#actiontuple) 26 | * [`BuildVisitor`](#buildvisitor) 27 | * [`Index`](#index) 28 | * [`Test`](#test) 29 | * [`Visitor`](#visitor) 30 | * [`VisitorResult`](#visitorresult) 31 | * [Types](#types) 32 | * [Compatibility](#compatibility) 33 | * [Related](#related) 34 | * [Contribute](#contribute) 35 | * [License](#license) 36 | 37 | ## What is this? 38 | 39 | This is a very important utility for working with unist as it lets you walk the 40 | tree. 41 | 42 | ## When should I use this? 43 | 44 | You can use this utility when you want to walk the tree. 45 | You can use [`unist-util-visit-parents`][vp] if you care about the entire stack 46 | of parents. 47 | 48 | ## Install 49 | 50 | This package is [ESM only][esm]. 51 | In Node.js (version 16+), install with [npm][]: 52 | 53 | ```sh 54 | npm install unist-util-visit 55 | ``` 56 | 57 | In Deno with [`esm.sh`][esmsh]: 58 | 59 | ```js 60 | import {CONTINUE, EXIT, SKIP, visit} from 'https://esm.sh/unist-util-visit@5' 61 | ``` 62 | 63 | In browsers with [`esm.sh`][esmsh]: 64 | 65 | ```html 66 | 69 | ``` 70 | 71 | ## Use 72 | 73 | ```js 74 | import {fromMarkdown} from 'mdast-util-from-markdown' 75 | import {visit} from 'unist-util-visit' 76 | 77 | const tree = fromMarkdown('Some *emphasis*, **strong**, and `code`.') 78 | 79 | visit(tree, 'text', function (node, index, parent) { 80 | console.log([node.value, parent ? parent.type : index]) 81 | }) 82 | ``` 83 | 84 | Yields: 85 | 86 | ```js 87 | [ 'Some ', 'paragraph' ] 88 | [ 'emphasis', 'emphasis' ] 89 | [ ', ', 'paragraph' ] 90 | [ 'strong', 'strong' ] 91 | [ ', and ', 'paragraph' ] 92 | [ '.', 'paragraph' ] 93 | ``` 94 | 95 | ## API 96 | 97 | This package exports the identifiers [`CONTINUE`][api-continue], 98 | [`EXIT`][api-exit], [`SKIP`][api-skip], and [`visit`][api-visit]. 99 | There is no default export. 100 | 101 | ### `visit(tree[, test], visitor[, reverse])` 102 | 103 | This function works exactly the same as [`unist-util-visit-parents`][vp], 104 | but [`Visitor`][api-visitor] has a different signature. 105 | 106 | ### `CONTINUE` 107 | 108 | Continue traversing as normal (`true`). 109 | 110 | ### `EXIT` 111 | 112 | Stop traversing immediately (`false`). 113 | 114 | ### `SKIP` 115 | 116 | Do not traverse this node’s children (`'skip'`). 117 | 118 | ### `Action` 119 | 120 | Union of the action types (TypeScript type). 121 | See [`Action` in `unist-util-visit-parents`][vp-action]. 122 | 123 | ### `ActionTuple` 124 | 125 | List with an action and an index (TypeScript type). 126 | See [`ActionTuple` in `unist-util-visit-parents`][vp-action-tuple]. 127 | 128 | ### `BuildVisitor` 129 | 130 | Build a typed `Visitor` function from a tree and a test (TypeScript type). 131 | See [`BuildVisitor` in `unist-util-visit-parents`][vp-build-visitor]. 132 | 133 | ### `Index` 134 | 135 | Move to the sibling at `index` next (TypeScript type). 136 | See [`Index` in `unist-util-visit-parents`][vp-index]. 137 | 138 | ### `Test` 139 | 140 | [`unist-util-is`][unist-util-is] compatible test (TypeScript type). 141 | 142 | ### `Visitor` 143 | 144 | Handle a node (matching `test`, if given) (TypeScript type). 145 | 146 | Visitors are free to transform `node`. 147 | They can also transform `parent`. 148 | 149 | Replacing `node` itself, if `SKIP` is not returned, still causes its 150 | descendants to be walked (which is a bug). 151 | 152 | When adding or removing previous siblings of `node` (or next siblings, in 153 | case of reverse), the `Visitor` should return a new `Index` to specify the 154 | sibling to traverse after `node` is traversed. 155 | Adding or removing next siblings of `node` (or previous siblings, in case 156 | of reverse) is handled as expected without needing to return a new `Index`. 157 | 158 | Removing the children property of `parent` still results in them being 159 | traversed. 160 | 161 | ###### Parameters 162 | 163 | * `node` ([`Node`][node]) 164 | — found node 165 | * `index` (`number` or `undefined`) 166 | — index of `node` in `parent` 167 | * `parent` ([`Node`][node] or `undefined`) 168 | — parent of `node` 169 | 170 | ###### Returns 171 | 172 | What to do next. 173 | 174 | An `Index` is treated as a tuple of `[CONTINUE, Index]`. 175 | An `Action` is treated as a tuple of `[Action]`. 176 | 177 | Passing a tuple back only makes sense if the `Action` is `SKIP`. 178 | When the `Action` is `EXIT`, that action can be returned. 179 | When the `Action` is `CONTINUE`, `Index` can be returned. 180 | 181 | ### `VisitorResult` 182 | 183 | Any value that can be returned from a visitor (TypeScript type). 184 | See [`VisitorResult` in `unist-util-visit-parents`][vp-visitor-result]. 185 | 186 | ## Types 187 | 188 | This package is fully typed with [TypeScript][]. 189 | It exports the additional types [`Action`][api-action], 190 | [`ActionTuple`][api-action-tuple], [`BuildVisitor`][api-build-visitor], 191 | [`Index`][api-index], [`Test`][api-test], [`Visitor`][api-visitor], and 192 | [`VisitorResult`][api-visitor-result]. 193 | 194 | ## Compatibility 195 | 196 | Projects maintained by the unified collective are compatible with maintained 197 | versions of Node.js. 198 | 199 | When we cut a new major release, we drop support for unmaintained versions of 200 | Node. 201 | This means we try to keep the current release line, `unist-util-visit@^5`, 202 | compatible with Node.js 16. 203 | 204 | ## Related 205 | 206 | * [`unist-util-visit-parents`][vp] 207 | — walk the tree with a stack of parents 208 | * [`unist-util-filter`](https://github.com/syntax-tree/unist-util-filter) 209 | — create a new tree with all nodes that pass a test 210 | * [`unist-util-map`](https://github.com/syntax-tree/unist-util-map) 211 | — create a new tree with all nodes mapped by a given function 212 | * [`unist-util-flatmap`](https://gitlab.com/staltz/unist-util-flatmap) 213 | — create a new tree by mapping (to an array) with the given function 214 | * [`unist-util-remove`](https://github.com/syntax-tree/unist-util-remove) 215 | — remove nodes from a tree that pass a test 216 | * [`unist-util-select`](https://github.com/syntax-tree/unist-util-select) 217 | — select nodes with CSS-like selectors 218 | 219 | ## Contribute 220 | 221 | See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for 222 | ways to get started. 223 | See [`support.md`][support] for ways to get help. 224 | 225 | This project has a [code of conduct][coc]. 226 | By interacting with this repository, organization, or community you agree to 227 | abide by its terms. 228 | 229 | ## License 230 | 231 | [MIT][license] © [Titus Wormer][author] 232 | 233 | 234 | 235 | [build-badge]: https://github.com/syntax-tree/unist-util-visit/workflows/main/badge.svg 236 | 237 | [build]: https://github.com/syntax-tree/unist-util-visit/actions 238 | 239 | [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/unist-util-visit.svg 240 | 241 | [coverage]: https://codecov.io/github/syntax-tree/unist-util-visit 242 | 243 | [downloads-badge]: https://img.shields.io/npm/dm/unist-util-visit.svg 244 | 245 | [downloads]: https://www.npmjs.com/package/unist-util-visit 246 | 247 | [size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unist-util-visit 248 | 249 | [size]: https://bundlejs.com/?q=unist-util-visit 250 | 251 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 252 | 253 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 254 | 255 | [collective]: https://opencollective.com/unified 256 | 257 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 258 | 259 | [chat]: https://github.com/syntax-tree/unist/discussions 260 | 261 | [npm]: https://docs.npmjs.com/cli/install 262 | 263 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 264 | 265 | [esmsh]: https://esm.sh 266 | 267 | [typescript]: https://www.typescriptlang.org 268 | 269 | [license]: license 270 | 271 | [author]: https://wooorm.com 272 | 273 | [health]: https://github.com/syntax-tree/.github 274 | 275 | [contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md 276 | 277 | [support]: https://github.com/syntax-tree/.github/blob/main/support.md 278 | 279 | [coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md 280 | 281 | [unist]: https://github.com/syntax-tree/unist 282 | 283 | [node]: https://github.com/syntax-tree/unist#nodes 284 | 285 | [unist-util-is]: https://github.com/syntax-tree/unist-util-is 286 | 287 | [vp]: https://github.com/syntax-tree/unist-util-visit-parents 288 | 289 | [vp-action]: https://github.com/syntax-tree/unist-util-visit-parents#action 290 | 291 | [vp-action-tuple]: https://github.com/syntax-tree/unist-util-visit-parents#actiontuple 292 | 293 | [vp-build-visitor]: https://github.com/syntax-tree/unist-util-visit-parents#buildvisitor 294 | 295 | [vp-index]: https://github.com/syntax-tree/unist-util-visit-parents#index 296 | 297 | [vp-visitor-result]: https://github.com/syntax-tree/unist-util-visit-parents#visitorresult 298 | 299 | [api-visit]: #visittree-test-visitor-reverse 300 | 301 | [api-continue]: #continue 302 | 303 | [api-exit]: #exit 304 | 305 | [api-skip]: #skip 306 | 307 | [api-action]: #action 308 | 309 | [api-action-tuple]: #actiontuple 310 | 311 | [api-build-visitor]: #buildvisitor 312 | 313 | [api-index]: #index 314 | 315 | [api-test]: #test 316 | 317 | [api-visitor]: #visitor 318 | 319 | [api-visitor-result]: #visitorresult 320 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Root} Root 3 | * @typedef {import('unist').Node} Node 4 | */ 5 | 6 | import assert from 'node:assert/strict' 7 | import test from 'node:test' 8 | import {fromMarkdown} from 'mdast-util-from-markdown' 9 | import {gfmFromMarkdown} from 'mdast-util-gfm' 10 | import {gfm} from 'micromark-extension-gfm' 11 | import {CONTINUE, EXIT, SKIP, visit} from 'unist-util-visit' 12 | 13 | // To do: remove cast after update. 14 | const tree = /** @type {Root} */ ( 15 | fromMarkdown('Some _emphasis_, **importance**, and `code`.') 16 | ) 17 | 18 | const stopIndex = 5 19 | const skipIndex = 7 20 | const skipReverseIndex = 6 21 | 22 | const texts = 6 23 | const codes = 1 24 | 25 | const types = [ 26 | 'root', 27 | 'paragraph', 28 | 'text', 29 | 'emphasis', 30 | 'text', 31 | 'text', 32 | 'strong', 33 | 'text', 34 | 'text', 35 | 'inlineCode', 36 | 'text' 37 | ] 38 | 39 | const reverseTypes = [ 40 | 'root', 41 | 'paragraph', 42 | 'text', 43 | 'inlineCode', 44 | 'text', 45 | 'strong', 46 | 'text', 47 | 'text', 48 | 'emphasis', 49 | 'text', 50 | 'text' 51 | ] 52 | 53 | test('visit', async function (t) { 54 | await t.test('should expose the public api', async function () { 55 | assert.deepEqual(Object.keys(await import('unist-util-visit')).sort(), [ 56 | 'CONTINUE', 57 | 'EXIT', 58 | 'SKIP', 59 | 'visit' 60 | ]) 61 | }) 62 | 63 | await t.test('should fail without tree', async function () { 64 | assert.throws(function () { 65 | // @ts-expect-error: check that the runtime throws an error. 66 | visit() 67 | }, /TypeError: visitor is not a function/) 68 | }) 69 | 70 | await t.test('should fail without visitor', async function () { 71 | assert.throws(function () { 72 | // @ts-expect-error: check that the runtime throws an error. 73 | visit(tree) 74 | }, /TypeError: visitor is not a function/) 75 | }) 76 | 77 | await t.test('should iterate over all nodes', async function () { 78 | let n = 0 79 | 80 | visit(tree, function (node) { 81 | assert.strictEqual(node.type, types[n], 'should be the expected type') 82 | n++ 83 | }) 84 | 85 | assert.equal(n, types.length, 'should visit all nodes') 86 | }) 87 | 88 | await t.test('should iterate over all nodes, backwards', async function () { 89 | let n = 0 90 | 91 | visit( 92 | tree, 93 | function (node) { 94 | assert.strictEqual( 95 | node.type, 96 | reverseTypes[n], 97 | 'should be the expected type' 98 | ) 99 | n++ 100 | }, 101 | true 102 | ) 103 | 104 | assert.equal(n, reverseTypes.length, 'should visit all nodes in reverse') 105 | }) 106 | 107 | await t.test('should only visit a given `type`', async function () { 108 | let n = 0 109 | 110 | visit(tree, 'text', function (node) { 111 | assert.strictEqual(node.type, 'text', 'should be the expected type') 112 | n++ 113 | }) 114 | 115 | assert.equal(n, texts, 'should visit all matching nodes') 116 | }) 117 | 118 | await t.test('should only visit given `type`s', async function () { 119 | const types = ['text', 'inlineCode'] 120 | let n = 0 121 | 122 | visit(tree, types, function (node) { 123 | n++ 124 | assert.notStrictEqual(types.indexOf(node.type), -1, 'should match') 125 | }) 126 | 127 | assert.equal(n, texts + codes, 'should visit all matching nodes') 128 | }) 129 | 130 | await t.test( 131 | 'should accept any `is`-compatible test function', 132 | async function () { 133 | let n = 0 134 | 135 | visit( 136 | tree, 137 | test, 138 | /** 139 | * @returns {undefined} 140 | */ 141 | function (node, index, parent) { 142 | const info = '(' + (parent && parent.type) + ':' + index + ')' 143 | assert.ok(test(node, index), 'should be a requested node ' + info) 144 | n++ 145 | } 146 | ) 147 | 148 | assert.equal(n, 3, 'should visit all passing nodes') 149 | 150 | /** 151 | * @param {Node} _ 152 | * @param {number | undefined} index 153 | */ 154 | function test(_, index) { 155 | return typeof index === 'number' && index > 3 156 | } 157 | } 158 | ) 159 | 160 | await t.test( 161 | 'should accept an array of `is`-compatible tests', 162 | async function () { 163 | const expected = new Set(['root', 'paragraph', 'emphasis', 'strong']) 164 | let n = 0 165 | 166 | visit( 167 | tree, 168 | [ 169 | function (node) { 170 | return node.type === 'root' 171 | }, 172 | 'paragraph', 173 | {value: '.'}, 174 | 'emphasis', 175 | 'strong' 176 | ], 177 | function (node) { 178 | const ok = 179 | expected.has(node.type) || ('value' in node && node.value === '.') 180 | assert.ok(ok, 'should be a requested type: ' + node.type) 181 | n++ 182 | } 183 | ) 184 | 185 | assert.equal(n, 5, 'should visit all passing nodes') 186 | } 187 | ) 188 | 189 | await t.test('should stop if `visitor` stops', async function () { 190 | let n = 0 191 | 192 | visit(tree, function (node) { 193 | assert.strictEqual(node.type, types[n++], 'should be the expected type') 194 | return n === stopIndex ? EXIT : CONTINUE 195 | }) 196 | 197 | assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') 198 | }) 199 | 200 | await t.test('should stop if `visitor` stops, backwards', async function () { 201 | let n = 0 202 | 203 | visit( 204 | tree, 205 | function (node) { 206 | assert.strictEqual( 207 | node.type, 208 | reverseTypes[n++], 209 | 'should be the expected type' 210 | ) 211 | 212 | return n === stopIndex ? EXIT : CONTINUE 213 | }, 214 | true 215 | ) 216 | 217 | assert.equal(n, stopIndex, 'should visit nodes until `EXIT` is given') 218 | }) 219 | 220 | await t.test('should skip if `visitor` skips', async function () { 221 | let n = 0 222 | let count = 0 223 | 224 | visit(tree, function (node) { 225 | assert.strictEqual(node.type, types[n++], 'should be the expected type') 226 | count++ 227 | 228 | if (n === skipIndex) { 229 | n++ // The one node inside it. 230 | return SKIP 231 | } 232 | }) 233 | 234 | assert.equal( 235 | count, 236 | types.length - 1, 237 | 'should visit nodes except when `SKIP` is given' 238 | ) 239 | }) 240 | 241 | await t.test('should skip if `visitor` skips, backwards', async function () { 242 | let n = 0 243 | let count = 0 244 | 245 | visit( 246 | tree, 247 | function (node) { 248 | assert.strictEqual( 249 | node.type, 250 | reverseTypes[n++], 251 | 'should be the expected type' 252 | ) 253 | count++ 254 | 255 | if (n === skipReverseIndex) { 256 | n++ // The one node inside it. 257 | return SKIP 258 | } 259 | }, 260 | true 261 | ) 262 | 263 | assert.equal( 264 | count, 265 | reverseTypes.length - 1, 266 | 'should visit nodes except when `SKIP` is given' 267 | ) 268 | }) 269 | 270 | await t.test( 271 | 'should support a given `index` to iterate over next (`0` to reiterate)', 272 | async function () { 273 | let n = 0 274 | let again = false 275 | const expected = [ 276 | 'root', 277 | 'paragraph', 278 | 'text', 279 | 'emphasis', 280 | 'text', 281 | 'text', 282 | 'strong', 283 | 'text', 284 | 'text', // Again. 285 | 'emphasis', 286 | 'text', 287 | 'text', 288 | 'strong', 289 | 'text', 290 | 'text', 291 | 'inlineCode', 292 | 'text' 293 | ] 294 | 295 | visit(tree, function (node) { 296 | assert.strictEqual( 297 | node.type, 298 | expected[n++], 299 | 'should be the expected type' 300 | ) 301 | 302 | if (again === false && node.type === 'strong') { 303 | again = true 304 | return 0 // Start over. 305 | } 306 | }) 307 | 308 | assert.equal(n, expected.length, 'should visit nodes again') 309 | } 310 | ) 311 | 312 | await t.test( 313 | 'should support a given `index` to iterate over next (`children.length` to skip further children)', 314 | async function () { 315 | let n = 0 316 | let again = false 317 | const expected = [ 318 | 'root', 319 | 'paragraph', 320 | 'text', 321 | 'emphasis', 322 | 'text', 323 | 'text', 324 | 'strong', // Skip here 325 | 'text' 326 | ] 327 | 328 | visit(tree, function (node, _, parent) { 329 | assert.strictEqual( 330 | node.type, 331 | expected[n++], 332 | 'should be the expected type' 333 | ) 334 | 335 | if (parent && again === false && node.type === 'strong') { 336 | again = true 337 | return parent.children.length // Skip siblings. 338 | } 339 | }) 340 | 341 | assert.equal(n, expected.length, 'should skip nodes') 342 | } 343 | ) 344 | 345 | await t.test( 346 | 'should support any other given `index` to iterate over next', 347 | async function () { 348 | let n = 0 349 | let again = false 350 | const expected = [ 351 | 'root', 352 | 'paragraph', 353 | 'text', 354 | 'emphasis', 355 | 'text', 356 | 'text', 357 | 'strong', 358 | 'text', 359 | 'inlineCode', // Skip to here. 360 | 'text' 361 | ] 362 | 363 | visit(tree, function (node, index) { 364 | assert.strictEqual( 365 | node.type, 366 | expected[n++], 367 | 'should be the expected type' 368 | ) 369 | 370 | if ( 371 | typeof index === 'number' && 372 | again === false && 373 | node.type === 'strong' 374 | ) { 375 | again = true 376 | return index + 2 // Skip to `inlineCode`. 377 | } 378 | }) 379 | 380 | assert.equal(n, expected.length, 'should skip nodes') 381 | } 382 | ) 383 | 384 | await t.test('should visit added nodes', async function () { 385 | const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') 386 | const other = fromMarkdown('Another ~~sentence~~.', { 387 | extensions: [gfm()], 388 | mdastExtensions: [gfmFromMarkdown()] 389 | }).children[0] 390 | 391 | const l = types.length + 5 // (p, text, delete, text, text) 392 | let n = 0 393 | 394 | visit(tree, function (_1, _2, parent) { 395 | n++ 396 | 397 | if (parent && n === 2) { 398 | assert(parent.type === 'root') 399 | parent.children.push(other) 400 | } 401 | }) 402 | 403 | assert.equal(n, l, 'should walk over all nodes') 404 | }) 405 | }) 406 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js", "index.d.ts"] 15 | } 16 | --------------------------------------------------------------------------------