├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── index.js ├── lib └── index.js ├── license ├── package.json ├── readme.md ├── test ├── fixtures │ ├── mismatched │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── nodes │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── non-marker │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── range-children │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── remove-children │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── remove-subsequent │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── remove │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── replace-children │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── replace-subsequent │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ ├── replace │ │ ├── index.js │ │ ├── input.md │ │ └── output.md │ └── simple │ │ ├── index.js │ │ ├── input.md │ │ └── output.md └── index.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 | jobs: 2 | main: 3 | name: ${{matrix.node}} 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v4 7 | - uses: actions/setup-node@v4 8 | with: 9 | node-version: ${{matrix.node}} 10 | - run: npm install 11 | - run: npm test 12 | - uses: codecov/codecov-action@v3 13 | strategy: 14 | matrix: 15 | node: 16 | - lts/hydrogen 17 | - node 18 | name: main 19 | on: 20 | - pull_request 21 | - push 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.d.ts 3 | *.log 4 | coverage/ 5 | node_modules/ 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | ignore-scripts=true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./lib/index.js').Handler} Handler 3 | * @typedef {import('./lib/index.js').Info} Info 4 | */ 5 | 6 | export {zone} from './lib/index.js' 7 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Nodes, Parents} from 'mdast' 3 | */ 4 | 5 | /** 6 | * @typedef Info 7 | * Extra info. 8 | * @property {Parents} parent 9 | * Parent of the section. 10 | * @property {number} start 11 | * Index of `start` in `parent`. 12 | * @property {number} end 13 | * Index of `end` in `parent`. 14 | * 15 | * @callback Handler 16 | * Callback called when a section is found. 17 | * @param {Nodes} start 18 | * Start of section. 19 | * @param {Array} between 20 | * Nodes between `start` and `end`. 21 | * @param {Nodes} end 22 | * End of section. 23 | * @param {Info} info 24 | * Extra info. 25 | * @returns {Array | null | undefined | void} 26 | * Results. 27 | * 28 | * If nothing is returned, nothing will be changed. 29 | * If an array of nodes (can include `null` and `undefined`) is returned, the 30 | * original section will be replaced by those nodes. 31 | */ 32 | 33 | import {commentMarker} from 'mdast-comment-marker' 34 | import {visit} from 'unist-util-visit' 35 | 36 | /** 37 | * Search `tree` for a start and end comments matching `name` and change their 38 | * “section” with `handler`. 39 | * 40 | * @param {Nodes} node 41 | * Tree to search. 42 | * @param {string} name 43 | * Comment name to look for. 44 | * @param {Handler} handler 45 | * Handle a section. 46 | * @returns {undefined} 47 | * Nothing. 48 | */ 49 | export function zone(node, name, handler) { 50 | /** @type {number | undefined} */ 51 | let level 52 | /** @type {Nodes | undefined} */ 53 | let marker 54 | /** @type {Parents | undefined} */ 55 | let scope 56 | 57 | visit(node, function (node, index, parent) { 58 | const info = commentMarker(node) 59 | const match = 60 | info && info.name === name 61 | ? info.attributes.match(/(start|end)\b/) 62 | : undefined 63 | const type = match ? match[0] : undefined 64 | 65 | if (parent && index !== undefined && type) { 66 | if (!scope && type === 'start') { 67 | level = 0 68 | marker = node 69 | scope = parent 70 | } 71 | 72 | if (typeof level === 'number' && marker && scope && parent === scope) { 73 | if (type === 'start') { 74 | level++ 75 | } else { 76 | level-- 77 | } 78 | 79 | if (type === 'end' && !level) { 80 | // @ts-expect-error: Assume `scope` is a valid parent of `node`. 81 | const start = scope.children.indexOf(marker) 82 | 83 | const nodes = handler( 84 | marker, 85 | scope.children.slice(start + 1, index), 86 | node, 87 | {start, end: index, parent: scope} 88 | ) 89 | 90 | if (!nodes) { 91 | marker = undefined 92 | scope = undefined 93 | return 94 | } 95 | 96 | // Ensure no empty nodes are inserted. 97 | // This could be the case if `end` is in `nodes` but no `end` node exists. 98 | /** @type {Array} */ 99 | const result = [] 100 | let offset = -1 101 | 102 | while (++offset < nodes.length) { 103 | const node = nodes[offset] 104 | if (node) result.push(node) 105 | } 106 | 107 | const deleteCount = index - start + 1 108 | scope.children.splice( 109 | start, 110 | deleteCount, 111 | // @ts-expect-error: Assume the correct children are passed. 112 | ...result 113 | ) 114 | 115 | marker = undefined 116 | scope = undefined 117 | 118 | return index - deleteCount + result.length 119 | } 120 | } 121 | } 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /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": "mdast-zone", 3 | "version": "6.1.0", 4 | "description": "mdast utility to treat HTML comments as ranges or markers", 5 | "license": "MIT", 6 | "keywords": [ 7 | "unist", 8 | "mdast", 9 | "mdast-util", 10 | "util", 11 | "utility", 12 | "html", 13 | "comment", 14 | "comments", 15 | "zone", 16 | "range", 17 | "marker" 18 | ], 19 | "repository": "syntax-tree/mdast-zone", 20 | "bugs": "https://github.com/syntax-tree/mdast-zone/issues", 21 | "funding": { 22 | "type": "opencollective", 23 | "url": "https://opencollective.com/unified" 24 | }, 25 | "author": "Titus Wormer (https://wooorm.com)", 26 | "contributors": [ 27 | "Titus Wormer (https://wooorm.com)" 28 | ], 29 | "sideEffects": false, 30 | "type": "module", 31 | "exports": "./index.js", 32 | "files": [ 33 | "lib/", 34 | "index.d.ts", 35 | "index.js" 36 | ], 37 | "dependencies": { 38 | "@types/mdast": "^4.0.0", 39 | "@types/unist": "^3.0.0", 40 | "mdast-comment-marker": "^3.0.0", 41 | "unist-util-visit": "^5.0.0" 42 | }, 43 | "devDependencies": { 44 | "@types/node": "^22.0.0", 45 | "c8": "^10.0.0", 46 | "is-hidden": "^2.0.0", 47 | "mdast-util-from-markdown": "^2.0.0", 48 | "mdast-util-to-markdown": "^2.0.0", 49 | "prettier": "^3.0.0", 50 | "remark-cli": "^12.0.0", 51 | "remark-gfm": "^4.0.0", 52 | "remark-preset-wooorm": "^10.0.0", 53 | "type-coverage": "^2.0.0", 54 | "typescript": "^5.0.0", 55 | "xo": "^0.59.0" 56 | }, 57 | "scripts": { 58 | "prepack": "npm run build && npm run format", 59 | "build": "tsc --build --clean && tsc --build && type-coverage", 60 | "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", 61 | "test-api": "node --conditions development test/index.js", 62 | "test-coverage": "c8 --100 --reporter lcov npm run test-api", 63 | "test": "npm run build && npm run format && npm run test-coverage" 64 | }, 65 | "prettier": { 66 | "bracketSpacing": false, 67 | "semi": false, 68 | "singleQuote": true, 69 | "tabWidth": 2, 70 | "trailingComma": "none", 71 | "useTabs": false 72 | }, 73 | "remarkConfig": { 74 | "plugins": [ 75 | "remark-preset-wooorm" 76 | ] 77 | }, 78 | "typeCoverage": { 79 | "atLeast": 100, 80 | "detail": true, 81 | "ignoreCatch": true, 82 | "strict": true 83 | }, 84 | "xo": { 85 | "overrides": [ 86 | { 87 | "files": "test/**/*.js", 88 | "rules": { 89 | "no-await-in-loop": 0 90 | } 91 | } 92 | ], 93 | "prettier": true, 94 | "rules": { 95 | "max-depth": "off", 96 | "capitalized-comments": "off" 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # mdast-zone 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 | [mdast][] utility to find two comments and replace the content in them. 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 | * [`zone(tree, name, handler)`](#zonetree-name-handler) 21 | * [`Handler`](#handler) 22 | * [`Info`](#info) 23 | * [Types](#types) 24 | * [Compatibility](#compatibility) 25 | * [Security](#security) 26 | * [Related](#related) 27 | * [Contribute](#contribute) 28 | * [License](#license) 29 | 30 | ## What is this? 31 | 32 | This package is a utility that lets you find certain comments, then takes the 33 | content between them, and calls a given handler with the result, so that you can 34 | change or replace things. 35 | 36 | ## When should I use this? 37 | 38 | This utility is typically useful when you have certain sections that can be 39 | generated. 40 | Comments are a hidden part of markdown, so they can be used as processing 41 | instructions. 42 | You can use those comments to define what content to change or replace. 43 | 44 | A similar package, [`mdast-util-heading-range`][mdast-util-heading-range], does 45 | the same but uses a heading to mark the start and end of sections. 46 | 47 | ## Install 48 | 49 | This package is [ESM only][esm]. 50 | In Node.js (version 16+), install with [npm][]: 51 | 52 | ```sh 53 | npm install mdast-zone 54 | ``` 55 | 56 | In Deno with [`esm.sh`][esmsh]: 57 | 58 | ```js 59 | import {zone} from 'https://esm.sh/mdast-zone@6' 60 | ``` 61 | 62 | In browsers with [`esm.sh`][esmsh]: 63 | 64 | ```html 65 | 68 | ``` 69 | 70 | ## Use 71 | 72 | Say we have the following file, `example.md`: 73 | 74 | ```markdown 75 | 76 | 77 | Foo 78 | 79 | 80 | ``` 81 | 82 | …and a module `example.js`: 83 | 84 | ```js 85 | /** 86 | * @import {Plugin} from 'unified' 87 | * @import {Root} from 'mdast' 88 | */ 89 | 90 | import {zone} from 'mdast-zone' 91 | import {remark} from 'remark' 92 | import {read} from 'to-vfile' 93 | 94 | const file = await remark() 95 | .use(myPluginThatReplacesFoo) 96 | .process(await read('example.md')) 97 | 98 | console.log(String(file)) 99 | 100 | /** @type {Plugin<[], Root>} */ 101 | function myPluginThatReplacesFoo() { 102 | return function (tree) { 103 | zone(tree, 'foo', function (start, nodes, end) { 104 | return [ 105 | start, 106 | {type: 'paragraph', children: [{type: 'text', value: 'Bar.'}]}, 107 | end 108 | ] 109 | }) 110 | } 111 | } 112 | ``` 113 | 114 | …now running `node example.js` yields: 115 | 116 | ```markdown 117 | 118 | 119 | Bar. 120 | 121 | 122 | ``` 123 | 124 | ## API 125 | 126 | This package exports the identifier [`zone`][api-zone]. 127 | There is no default export. 128 | 129 | ### `zone(tree, name, handler)` 130 | 131 | Search `tree` for a start and end comments matching `name` and change their 132 | “section” with `handler`. 133 | 134 | ###### Parameters 135 | 136 | * `tree` ([`Node`][node]) 137 | — tree to change 138 | * `name` (`string`) 139 | — comment name to look for 140 | * `handler` ([`Handler`][api-handler]) 141 | — handle a section 142 | 143 | ###### Returns 144 | 145 | Nothing (`undefined`). 146 | 147 | ### `Handler` 148 | 149 | Callback called when a section is found (TypeScript type). 150 | 151 | ###### Parameters 152 | 153 | * `start` ([`Node`][node]) 154 | — start of section 155 | * `nodes` ([`Array`][node]) 156 | — nodes between `start` and `end` 157 | * `end` ([`Node`][node]) 158 | — end of section 159 | * `info` ([`Info`][api-info]) 160 | — extra info 161 | 162 | ###### Returns 163 | 164 | Results (`Array`, optional). 165 | 166 | If nothing is returned, nothing will be changed. 167 | If an array of nodes (can include `null` and `undefined`) is returned, the 168 | original section will be replaced by those nodes. 169 | 170 | ### `Info` 171 | 172 | Extra info (TypeScript type). 173 | 174 | ###### Fields 175 | 176 | * `parent` ([`Node`][node]) 177 | — parent of the section 178 | * `start` (`number`) 179 | — index of `start` in `parent` 180 | * `end` (`number`) 181 | — index of `end` in `parent` 182 | 183 | ## Types 184 | 185 | This package is fully typed with [TypeScript][]. 186 | It exports the additional types [`Handler`][api-handler] and 187 | [`Info`][api-info]. 188 | 189 | ## Compatibility 190 | 191 | Projects maintained by the unified collective are compatible with maintained 192 | versions of Node.js. 193 | 194 | When we cut a new major release, we drop support for unmaintained versions of 195 | Node. 196 | This means we try to keep the current release line, `mdast-zone@^6`, 197 | compatible with Node.js 16. 198 | 199 | ## Security 200 | 201 | Improper use of `handler` can open you up to a [cross-site scripting (XSS)][xss] 202 | attack as the value it returns is injected into the syntax tree. 203 | This can become a problem if the tree is later transformed to **[hast][]**. 204 | The following example shows how a script is injected that could run when loaded 205 | in a browser. 206 | 207 | ```js 208 | function handler(start, nodes, end) { 209 | return [start, {type: 'html', value: ''}, end] 210 | } 211 | ``` 212 | 213 | Yields: 214 | 215 | ```markdown 216 | 217 | 218 | 219 | 220 | 221 | ``` 222 | 223 | Either do not use user input or use [`hast-util-santize`][hast-util-sanitize]. 224 | 225 | ## Related 226 | 227 | * [`mdast-util-heading-range`](https://github.com/syntax-tree/mdast-util-heading-range) 228 | — similar but uses headings to mark sections 229 | 230 | ## Contribute 231 | 232 | See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for 233 | ways to get started. 234 | See [`support.md`][support] for ways to get help. 235 | 236 | This project has a [code of conduct][coc]. 237 | By interacting with this repository, organization, or community you agree to 238 | abide by its terms. 239 | 240 | ## License 241 | 242 | [MIT][license] © [Titus Wormer][author] 243 | 244 | 245 | 246 | [build-badge]: https://github.com/syntax-tree/mdast-zone/workflows/main/badge.svg 247 | 248 | [build]: https://github.com/syntax-tree/mdast-zone/actions 249 | 250 | [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/mdast-zone.svg 251 | 252 | [coverage]: https://codecov.io/github/syntax-tree/mdast-zone 253 | 254 | [downloads-badge]: https://img.shields.io/npm/dm/mdast-zone.svg 255 | 256 | [downloads]: https://www.npmjs.com/package/mdast-zone 257 | 258 | [size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=mdast-zone 259 | 260 | [size]: https://bundlejs.com/?q=mdast-zone 261 | 262 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 263 | 264 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 265 | 266 | [collective]: https://opencollective.com/unified 267 | 268 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 269 | 270 | [chat]: https://github.com/syntax-tree/unist/discussions 271 | 272 | [npm]: https://docs.npmjs.com/cli/install 273 | 274 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 275 | 276 | [esmsh]: https://esm.sh 277 | 278 | [typescript]: https://www.typescriptlang.org 279 | 280 | [license]: license 281 | 282 | [author]: https://wooorm.com 283 | 284 | [health]: https://github.com/syntax-tree/.github 285 | 286 | [contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md 287 | 288 | [support]: https://github.com/syntax-tree/.github/blob/main/support.md 289 | 290 | [coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md 291 | 292 | [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting 293 | 294 | [mdast]: https://github.com/syntax-tree/mdast 295 | 296 | [node]: https://github.com/syntax-tree/mdast#nodes 297 | 298 | [mdast-util-heading-range]: https://github.com/syntax-tree/mdast-util-heading-range 299 | 300 | [hast]: https://github.com/syntax-tree/hast 301 | 302 | [hast-util-sanitize]: https://github.com/syntax-tree/hast-util-sanitize 303 | 304 | [api-zone]: #zonetree-name-handler 305 | 306 | [api-handler]: #handler 307 | 308 | [api-info]: #info 309 | -------------------------------------------------------------------------------- /test/fixtures/mismatched/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', handle) 12 | 13 | function handle() { 14 | throw new Error('Should not be invoked') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/mismatched/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/mismatched/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/nodes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import assert from 'node:assert/strict' 6 | import {zone} from 'mdast-zone' 7 | 8 | /** 9 | * @param {Root} tree 10 | */ 11 | export default function assertion(tree) { 12 | let count = 0 13 | 14 | zone(tree, 'foo', function (_, nodes) { 15 | assert.equal(nodes.length, 1) 16 | const head = nodes[0] 17 | assert.equal(head.type, 'paragraph') 18 | assert(head.type === 'paragraph') 19 | assert.equal(head.children.length, 1) 20 | const headHead = head.children[0] 21 | assert.equal(headHead.type, 'text') 22 | assert(headHead.type === 'text') 23 | assert.equal(headHead.value, 'Foo.') 24 | assert.equal(count, 0, 'expected handle to be called once') 25 | count++ 26 | }) 27 | 28 | assert.equal(count, 1, 'expected handle to be called') 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/nodes/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/nodes/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/non-marker/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', handle) 12 | 13 | function handle() { 14 | throw new Error('Should not be invoked') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/non-marker/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/non-marker/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/range-children/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import assert from 'node:assert/strict' 6 | import {zone} from 'mdast-zone' 7 | 8 | /** 9 | * @param {Root} tree 10 | */ 11 | export default function assertion(tree) { 12 | let count = 0 13 | 14 | zone(tree, 'foo', function (_, nodes) { 15 | assert.equal(nodes.length, 1) 16 | assert.equal(nodes[0].type, 'blockquote') 17 | assert.equal(count, 0, 'expected handle to be called once') 18 | count++ 19 | }) 20 | 21 | assert.equal(count, 1, 'expected handle to be called') 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/range-children/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | > 6 | > 7 | > Foo. 8 | > 9 | > 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/range-children/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | > 6 | > 7 | > Foo. 8 | > 9 | > 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/remove-children/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function (start, _, end) { 12 | return [start, end] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/remove-children/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/remove-children/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/remove-subsequent/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function () { 12 | return [] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/remove-subsequent/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | Foo. 8 | 9 | 10 | 11 | Bar. 12 | 13 | 14 | 15 | Foo. 16 | 17 | Foo. 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/remove-subsequent/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | Bar. 4 | -------------------------------------------------------------------------------- /test/fixtures/remove/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function () { 12 | return [] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/remove/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/remove/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | -------------------------------------------------------------------------------- /test/fixtures/replace-children/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function (start, _, end) { 12 | return [ 13 | start, 14 | { 15 | type: 'heading', 16 | depth: 2, 17 | children: [{type: 'text', value: 'Bar'}] 18 | }, 19 | end 20 | ] 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/replace-children/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/replace-children/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | ## Bar 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/replace-subsequent/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function () { 12 | return [ 13 | { 14 | type: 'paragraph', 15 | children: [{type: 'text', value: 'Bar.'}] 16 | } 17 | ] 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/replace-subsequent/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | 9 | Bar. 10 | 11 | 12 | 13 | Foo. 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/fixtures/replace-subsequent/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | Bar. 4 | 5 | Bar. 6 | 7 | Bar. 8 | -------------------------------------------------------------------------------- /test/fixtures/replace/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import {zone} from 'mdast-zone' 6 | 7 | /** 8 | * @param {Root} tree 9 | */ 10 | export default function assertion(tree) { 11 | zone(tree, 'foo', function () { 12 | return [ 13 | { 14 | type: 'heading', 15 | depth: 2, 16 | children: [{type: 'text', value: 'Bar'}] 17 | } 18 | ] 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/replace/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | Foo. 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/replace/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | ## Bar 4 | -------------------------------------------------------------------------------- /test/fixtures/simple/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import assert from 'node:assert/strict' 6 | import {zone} from 'mdast-zone' 7 | 8 | /** 9 | * @param {Root} tree 10 | */ 11 | export default function assertion(tree) { 12 | let count = 0 13 | 14 | zone(tree, 'foo', function (start, nodes, end) { 15 | assert.equal(start.type, 'html') 16 | assert(start.type === 'html') 17 | assert.equal(start.value, '') 18 | assert.deepEqual(nodes, []) 19 | assert.equal(end.type, 'html') 20 | assert(end.type === 'html') 21 | assert.equal(end.value, '') 22 | count++ 23 | }) 24 | 25 | assert.equal(count, 1, 'expected handle to be called') 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/simple/input.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/simple/output.md: -------------------------------------------------------------------------------- 1 | # Foo 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {Root} from 'mdast' 3 | */ 4 | 5 | import assert from 'node:assert/strict' 6 | import fs from 'node:fs/promises' 7 | import test from 'node:test' 8 | import {isHidden} from 'is-hidden' 9 | import {fromMarkdown} from 'mdast-util-from-markdown' 10 | import {toMarkdown} from 'mdast-util-to-markdown' 11 | 12 | test('zone', async function (t) { 13 | await t.test('should expose the public api', async function () { 14 | assert.deepEqual(Object.keys(await import('mdast-zone')).sort(), ['zone']) 15 | }) 16 | 17 | const root = new URL('fixtures/', import.meta.url) 18 | const folders = await fs.readdir(root) 19 | let index = -1 20 | 21 | while (++index < folders.length) { 22 | const folder = folders[index] 23 | 24 | if (isHidden(folder)) continue 25 | 26 | await t.test('should work on `' + folder + '`', async function () { 27 | /** @type {string | undefined} */ 28 | let expected 29 | 30 | try { 31 | expected = String( 32 | await fs.readFile(new URL(folder + '/output.md', root)) 33 | ) 34 | } catch {} 35 | 36 | /** @type {{default: (tree: Root) => undefined}} */ 37 | const example = await import(new URL(folder + '/index.js', root).href) 38 | const check = example.default 39 | const tree = fromMarkdown( 40 | await fs.readFile(new URL(folder + '/input.md', root)) 41 | ) 42 | 43 | check(tree) 44 | 45 | const result = toMarkdown(tree) 46 | 47 | assert.equal(result, expected) 48 | }) 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /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"] 15 | } 16 | --------------------------------------------------------------------------------