mdast-util-find-and-replace
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -1,60 +0,9 @@ | ||
/** | ||
* @param tree mdast tree | ||
* @param find Value to find and remove. When `string`, escaped and made into a global `RegExp` | ||
* @param [replace] Value to insert. | ||
* * When `string`, turned into a Text node. | ||
* * When `Function`, called with the results of calling `RegExp.exec` as | ||
* arguments, in which case it can return a single or a list of `Node`, | ||
* a `string` (which is wrapped in a `Text` node), or `false` to not replace | ||
* @param [options] Configuration. | ||
*/ | ||
export const findAndReplace: (( | ||
tree: Node, | ||
find: Find, | ||
replace?: Replace | undefined, | ||
options?: Options | undefined | ||
) => Node) & | ||
(( | ||
tree: Node, | ||
schema: FindAndReplaceSchema | FindAndReplaceList, | ||
options?: Options | undefined | ||
) => Node) | ||
/** | ||
* Configuration. | ||
*/ | ||
export type Options = { | ||
/** | ||
* `unist-util-is` test used to assert parents | ||
*/ | ||
ignore?: Test | ||
} | ||
export type Root = import('mdast').Root | ||
export type Content = import('mdast').Content | ||
export type PhrasingContent = import('mdast').PhrasingContent | ||
export type Text = import('mdast').Text | ||
export type Node = Content | Root | ||
export type Parent = Extract<Node, import('mdast').Parent> | ||
export type Test = import('unist-util-visit-parents').Test | ||
export type VisitorResult = import('unist-util-visit-parents').VisitorResult | ||
export type RegExpMatchObject = { | ||
index: number | ||
input: string | ||
} | ||
export type Find = string | RegExp | ||
export type Replace = string | ReplaceFunction | ||
export type FindAndReplaceTuple = [Find, Replace] | ||
export type FindAndReplaceSchema = { | ||
[x: string]: Replace | ||
} | ||
export type FindAndReplaceList = Array<[Find, Replace]> | ||
export type Pair = [RegExp, ReplaceFunction] | ||
export type Pairs = Array<[RegExp, ReplaceFunction]> | ||
export type ReplaceFunction = ( | ||
...parameters: any[] | ||
) => | ||
| Array<PhrasingContent> | ||
| PhrasingContent | ||
| string | ||
| false | ||
| undefined | ||
| null | ||
export {findAndReplace} from './lib/index.js' | ||
export type Options = import('./lib/index.js').Options | ||
export type RegExpMatchObject = import('./lib/index.js').RegExpMatchObject | ||
export type Find = import('./lib/index.js').Find | ||
export type Replace = import('./lib/index.js').Replace | ||
export type ReplaceFunction = import('./lib/index.js').ReplaceFunction | ||
export type FindAndReplaceTuple = import('./lib/index.js').FindAndReplaceTuple | ||
export type FindAndReplaceSchema = import('./lib/index.js').FindAndReplaceSchema | ||
export type FindAndReplaceList = import('./lib/index.js').FindAndReplaceList |
251
index.js
/** | ||
* @typedef Options Configuration. | ||
* @property {Test} [ignore] `unist-util-is` test used to assert parents | ||
* | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('mdast').Content} Content | ||
* @typedef {import('mdast').PhrasingContent} PhrasingContent | ||
* @typedef {import('mdast').Text} Text | ||
* @typedef {Content|Root} Node | ||
* @typedef {Extract<Node, import('mdast').Parent>} Parent | ||
* | ||
* @typedef {import('unist-util-visit-parents').Test} Test | ||
* @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult | ||
* | ||
* @typedef RegExpMatchObject | ||
* @property {number} index | ||
* @property {string} input | ||
* | ||
* @typedef {string|RegExp} Find | ||
* @typedef {string|ReplaceFunction} Replace | ||
* | ||
* @typedef {[Find, Replace]} FindAndReplaceTuple | ||
* @typedef {Object.<string, Replace>} FindAndReplaceSchema | ||
* @typedef {Array.<FindAndReplaceTuple>} FindAndReplaceList | ||
* | ||
* @typedef {[RegExp, ReplaceFunction]} Pair | ||
* @typedef {Array.<Pair>} Pairs | ||
* @typedef {import('./lib/index.js').Options} Options | ||
* @typedef {import('./lib/index.js').RegExpMatchObject} RegExpMatchObject | ||
* @typedef {import('./lib/index.js').Find} Find | ||
* @typedef {import('./lib/index.js').Replace} Replace | ||
* @typedef {import('./lib/index.js').ReplaceFunction} ReplaceFunction | ||
* @typedef {import('./lib/index.js').FindAndReplaceTuple} FindAndReplaceTuple | ||
* @typedef {import('./lib/index.js').FindAndReplaceSchema} FindAndReplaceSchema | ||
* @typedef {import('./lib/index.js').FindAndReplaceList} FindAndReplaceList | ||
*/ | ||
/** | ||
* @callback ReplaceFunction | ||
* @param {...any} parameters | ||
* @returns {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null} | ||
*/ | ||
import escape from 'escape-string-regexp' | ||
import {visitParents} from 'unist-util-visit-parents' | ||
import {convert} from 'unist-util-is' | ||
const own = {}.hasOwnProperty | ||
/** | ||
* @param tree mdast tree | ||
* @param find Value to find and remove. When `string`, escaped and made into a global `RegExp` | ||
* @param [replace] Value to insert. | ||
* * When `string`, turned into a Text node. | ||
* * When `Function`, called with the results of calling `RegExp.exec` as | ||
* arguments, in which case it can return a single or a list of `Node`, | ||
* a `string` (which is wrapped in a `Text` node), or `false` to not replace | ||
* @param [options] Configuration. | ||
*/ | ||
export const findAndReplace = | ||
/** | ||
* @type {( | ||
* ((tree: Node, find: Find, replace?: Replace, options?: Options) => Node) & | ||
* ((tree: Node, schema: FindAndReplaceSchema|FindAndReplaceList, options?: Options) => Node) | ||
* )} | ||
**/ | ||
( | ||
/** | ||
* @param {Node} tree | ||
* @param {Find|FindAndReplaceSchema|FindAndReplaceList} find | ||
* @param {Replace|Options} [replace] | ||
* @param {Options} [options] | ||
*/ | ||
function (tree, find, replace, options) { | ||
/** @type {Options|undefined} */ | ||
let settings | ||
/** @type {FindAndReplaceSchema|FindAndReplaceList} */ | ||
let schema | ||
if (typeof find === 'string' || find instanceof RegExp) { | ||
// @ts-expect-error don’t expect options twice. | ||
schema = [[find, replace]] | ||
settings = options | ||
} else { | ||
schema = find | ||
// @ts-expect-error don’t expect replace twice. | ||
settings = replace | ||
} | ||
if (!settings) { | ||
settings = {} | ||
} | ||
const ignored = convert(settings.ignore || []) | ||
const pairs = toPairs(schema) | ||
let pairIndex = -1 | ||
while (++pairIndex < pairs.length) { | ||
visitParents(tree, 'text', visitor) | ||
} | ||
return tree | ||
/** @type {import('unist-util-visit-parents').Visitor<Text>} */ | ||
function visitor(node, parents) { | ||
let index = -1 | ||
/** @type {Parent|undefined} */ | ||
let grandparent | ||
while (++index < parents.length) { | ||
const parent = /** @type {Parent} */ (parents[index]) | ||
if ( | ||
ignored( | ||
parent, | ||
// @ts-expect-error mdast vs. unist parent. | ||
grandparent ? grandparent.children.indexOf(parent) : undefined, | ||
grandparent | ||
) | ||
) { | ||
return | ||
} | ||
grandparent = parent | ||
} | ||
if (grandparent) { | ||
return handler(node, grandparent) | ||
} | ||
} | ||
/** | ||
* @param {Text} node | ||
* @param {Parent} parent | ||
* @returns {VisitorResult} | ||
*/ | ||
function handler(node, parent) { | ||
const find = pairs[pairIndex][0] | ||
const replace = pairs[pairIndex][1] | ||
let start = 0 | ||
// @ts-expect-error: TS is wrong, some of these children can be text. | ||
let index = parent.children.indexOf(node) | ||
/** @type {Array.<PhrasingContent>} */ | ||
let nodes = [] | ||
/** @type {number|undefined} */ | ||
let position | ||
find.lastIndex = 0 | ||
let match = find.exec(node.value) | ||
while (match) { | ||
position = match.index | ||
// @ts-expect-error this is perfectly fine, typescript. | ||
let value = replace(...match, { | ||
index: match.index, | ||
input: match.input | ||
}) | ||
if (typeof value === 'string') { | ||
value = value.length > 0 ? {type: 'text', value} : undefined | ||
} | ||
if (value !== false) { | ||
if (start !== position) { | ||
nodes.push({ | ||
type: 'text', | ||
value: node.value.slice(start, position) | ||
}) | ||
} | ||
if (Array.isArray(value)) { | ||
nodes.push(...value) | ||
} else if (value) { | ||
nodes.push(value) | ||
} | ||
start = position + match[0].length | ||
} | ||
if (!find.global) { | ||
break | ||
} | ||
match = find.exec(node.value) | ||
} | ||
if (position === undefined) { | ||
nodes = [node] | ||
index-- | ||
} else { | ||
if (start < node.value.length) { | ||
nodes.push({type: 'text', value: node.value.slice(start)}) | ||
} | ||
parent.children.splice(index, 1, ...nodes) | ||
} | ||
return index + nodes.length + 1 | ||
} | ||
} | ||
) | ||
/** | ||
* @param {FindAndReplaceSchema|FindAndReplaceList} schema | ||
* @returns {Pairs} | ||
*/ | ||
function toPairs(schema) { | ||
/** @type {Pairs} */ | ||
const result = [] | ||
if (typeof schema !== 'object') { | ||
throw new TypeError('Expected array or object as schema') | ||
} | ||
if (Array.isArray(schema)) { | ||
let index = -1 | ||
while (++index < schema.length) { | ||
result.push([ | ||
toExpression(schema[index][0]), | ||
toFunction(schema[index][1]) | ||
]) | ||
} | ||
} else { | ||
/** @type {string} */ | ||
let key | ||
for (key in schema) { | ||
if (own.call(schema, key)) { | ||
result.push([toExpression(key), toFunction(schema[key])]) | ||
} | ||
} | ||
} | ||
return result | ||
} | ||
/** | ||
* @param {Find} find | ||
* @returns {RegExp} | ||
*/ | ||
function toExpression(find) { | ||
return typeof find === 'string' ? new RegExp(escape(find), 'g') : find | ||
} | ||
/** | ||
* @param {Replace} replace | ||
* @returns {ReplaceFunction} | ||
*/ | ||
function toFunction(replace) { | ||
return typeof replace === 'function' ? replace : () => replace | ||
} | ||
export {findAndReplace} from './lib/index.js' |
{ | ||
"name": "mdast-util-find-and-replace", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "mdast utility to find and replace text in a tree", | ||
@@ -31,2 +31,3 @@ "license": "MIT", | ||
"files": [ | ||
"lib/", | ||
"index.d.ts", | ||
@@ -38,3 +39,3 @@ "index.js" | ||
"unist-util-is": "^5.0.0", | ||
"unist-util-visit-parents": "^4.0.0" | ||
"unist-util-visit-parents": "^5.0.0" | ||
}, | ||
@@ -45,4 +46,4 @@ "devDependencies": { | ||
"prettier": "^2.0.0", | ||
"remark-cli": "^9.0.0", | ||
"remark-preset-wooorm": "^8.0.0", | ||
"remark-cli": "^10.0.0", | ||
"remark-preset-wooorm": "^9.0.0", | ||
"rimraf": "^3.0.0", | ||
@@ -53,10 +54,10 @@ "tape": "^5.0.0", | ||
"unist-builder": "^3.0.0", | ||
"xo": "^0.42.0" | ||
"xo": "^0.49.0" | ||
}, | ||
"scripts": { | ||
"prepack": "npm run build && npm run format", | ||
"build": "rimraf \"*.d.ts\" && tsc && type-coverage", | ||
"build": "rimraf \"lib/**/*.d.ts\" \"*.d.ts\" && tsc && 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" | ||
@@ -85,5 +86,5 @@ }, | ||
"ignoreFiles": [ | ||
"index.d.ts" | ||
"lib/index.d.ts" | ||
] | ||
} | ||
} |
183
readme.md
@@ -11,11 +11,38 @@ # mdast-util-find-and-replace | ||
[**mdast**][mdast] utility to find and replace text in a [*tree*][tree]. | ||
[mdast][] utility to find and replace things. | ||
## Contents | ||
* [What is this?](#what-is-this) | ||
* [When should I use this?](#when-should-i-use-this) | ||
* [Install](#install) | ||
* [Use](#use) | ||
* [API](#api) | ||
* [`findAndReplace(tree, find, replace[, options])`](#findandreplacetree-find-replace-options) | ||
* [Types](#types) | ||
* [Compatibility](#compatibility) | ||
* [Security](#security) | ||
* [Related](#related) | ||
* [Contribute](#contribute) | ||
* [License](#license) | ||
## What is this? | ||
This package is a utility that lets you find patterns (`string`, `RegExp`) in | ||
text and replace them with nodes. | ||
## When should I use this? | ||
This utility is typically useful when you have regexes and want to modify mdast. | ||
One example is when you have some form of “mentions” (such as | ||
`/@([a-z][_a-z0-9])\b/gi`) and want to create links to persons from them. | ||
A similar package, [`hast-util-find-and-replace`][hast-util-find-and-replace] | ||
does the same but on [hast][]. | ||
## 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. | ||
This package is [ESM only][esm]. | ||
In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]: | ||
[npm][]: | ||
```sh | ||
@@ -25,2 +52,16 @@ npm install mdast-util-find-and-replace | ||
In Deno with [`esm.sh`][esmsh]: | ||
```js | ||
import {findAndReplace} from 'https://esm.sh/mdast-util-find-and-replace@2' | ||
``` | ||
In browsers with [`esm.sh`][esmsh]: | ||
```html | ||
<script type="module"> | ||
import {findAndReplace} from 'https://esm.sh/mdast-util-find-and-replace@2?bundle' | ||
</script> | ||
``` | ||
## Use | ||
@@ -41,12 +82,14 @@ | ||
findAndReplace(tree, 'and', 'or') | ||
findAndReplace(tree, [ | ||
[/and/gi, 'or'], | ||
[/emphasis/gi, 'em'], | ||
[/importance/gi, 'strong'], | ||
[ | ||
/Some/g, | ||
function ($0) { | ||
return u('link', {url: '//example.com#' + $0}, [u('text', $0)]) | ||
} | ||
] | ||
]) | ||
findAndReplace(tree, {emphasis: 'em', importance: 'strong'}) | ||
findAndReplace(tree, { | ||
Some: function ($0) { | ||
return u('link', {url: '//example.com#' + $0}, [u('text', $0)]) | ||
} | ||
}) | ||
console.log(inspect(tree)) | ||
@@ -59,13 +102,14 @@ ``` | ||
paragraph[8] | ||
├─ link[1] [url="//example.com#Some"] | ||
│ └─ text: "Some" | ||
├─ text: " " | ||
├─ emphasis[1] | ||
│ └─ text: "em" | ||
├─ text: " " | ||
├─ text: "or" | ||
├─ text: " " | ||
├─ strong[1] | ||
│ └─ text: "strong" | ||
└─ text: "." | ||
├─0 link[1] | ||
│ │ url: "//example.com#Some" | ||
│ └─0 text "Some" | ||
├─1 text " " | ||
├─2 emphasis[1] | ||
│ └─0 text "em" | ||
├─3 text " " | ||
├─4 text "or" | ||
├─5 text " " | ||
├─6 strong[1] | ||
│ └─0 text "strong" | ||
└─7 text "." | ||
``` | ||
@@ -75,10 +119,10 @@ | ||
This package exports the following identifiers: `findAndReplace`. | ||
This package exports the identifier `findAndReplace`. | ||
There is no default export. | ||
### `findAndReplace(tree, find[, replace][, options])` | ||
### `findAndReplace(tree, find, replace[, options])` | ||
Find and replace text in [**mdast**][mdast] [*tree*][tree]s. | ||
The algorithm searches the tree in [*preorder*][preorder] for complete values | ||
in [`Text`][text] nodes. | ||
Find patterns in a tree and replace them. | ||
The algorithm searches the tree in *[preorder][]* for complete values in | ||
[`Text`][text] nodes. | ||
Partial matches are not supported. | ||
@@ -88,3 +132,3 @@ | ||
* `findAndReplace(tree, find, replace?[, options])` | ||
* `findAndReplace(tree, find, replace[, options])` | ||
* `findAndReplace(tree, search[, options])` | ||
@@ -95,30 +139,41 @@ | ||
* `tree` ([`Node`][node]) | ||
— [**mdast**][mdast] [*tree*][tree] | ||
* `find` (`string` or `RegExp`) | ||
— Value to find and remove. | ||
When `string`, escaped and made into a global `RegExp` | ||
— value to find and remove (`string`s are escaped and turned into a global | ||
`RegExp`) | ||
* `replace` (`string` or `Function`) | ||
— Value to insert. | ||
When `string`, turned into a [`Text`][text] node. | ||
When `Function`, invoked with the results of calling `RegExp.exec` as | ||
arguments, in which case it can return a single or a list of [`Node`][node], | ||
a `string` (which is wrapped in a [`Text`][text] node), or `false` to not | ||
replace | ||
* `search` (`Object` or `Array`) | ||
— Perform multiple find-and-replaces. | ||
When `Array`, each entry is a tuple (`Array`) of a `find` (at `0`) and | ||
`replace` (at `1`). | ||
When `Object`, each key is a `find` (in string form) and each value is a | ||
`replace` | ||
— value to insert. | ||
`string`s are turned into a [`Text`][text] node, | ||
`Function`s are called with the results of calling `RegExp.exec` as | ||
arguments, and they can return a [`Node`][node], a `string` (which is | ||
wrapped in a [`Text`][text] node), or `false` to not replace | ||
* `search` (`Array` or `Object`) | ||
— perform multiple find-and-replaces. | ||
Either an `Array` of tuples (`Array`s) with `find` (at `0`) and `replace` | ||
(at `1`), or an `Object` where each key is `find` and each value is | ||
the corresponding `replace` | ||
* `options.ignore` (`Test`, default: `[]`) | ||
— Any [`unist-util-is`][test] compatible test. | ||
— any [`unist-util-is`][test] compatible test. | ||
###### Returns | ||
The given, modified, `tree`. | ||
The given `tree` ([`Node`][node]). | ||
## Types | ||
This package is fully typed with [TypeScript][]. | ||
It exports the types `Find`, `Replace`, `ReplaceFunction`, | ||
`FindAndReplaceTuple`, `FindAndReplaceSchema`, `FindAndReplaceList`, | ||
`RegExpMatchObject`, and `Options`. | ||
## 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+, and 16.0+. | ||
Our projects sometimes work with older versions, but this is not guaranteed. | ||
## Security | ||
Use of `mdast-util-find-and-replace` does not involve [**hast**][hast] or user | ||
content so there are no openings for [cross-site scripting (XSS)][xss] attacks. | ||
Use of `mdast-util-find-and-replace` does not involve [hast][] or user content | ||
so there are no openings for [cross-site scripting (XSS)][xss] attacks. | ||
@@ -128,3 +183,5 @@ ## Related | ||
* [`hast-util-find-and-replace`](https://github.com/syntax-tree/hast-util-find-and-replace) | ||
— hast utility to find and replace text | ||
— find and replace in hast | ||
* [`hast-util-select`](https://github.com/syntax-tree/hast-util-select) | ||
— `querySelector`, `querySelectorAll`, and `matches` | ||
* [`unist-util-select`](https://github.com/syntax-tree/unist-util-select) | ||
@@ -135,8 +192,8 @@ — select unist nodes with CSS-like selectors | ||
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]. | ||
By interacting with this repository, organization, or community you agree to | ||
By interacting with this repository, organisation, or community you agree to | ||
abide by its terms. | ||
@@ -178,2 +235,8 @@ | ||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[esmsh]: https://esm.sh | ||
[typescript]: https://www.typescriptlang.org | ||
[license]: license | ||
@@ -183,8 +246,10 @@ | ||
[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md | ||
[health]: https://github.com/syntax-tree/.github | ||
[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md | ||
[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md | ||
[support]: https://github.com/syntax-tree/.github/blob/main/support.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md | ||
[hast]: https://github.com/syntax-tree/hast | ||
@@ -196,4 +261,2 @@ | ||
[tree]: https://github.com/syntax-tree/unist#tree | ||
[preorder]: https://github.com/syntax-tree/unist#preorder | ||
@@ -206,1 +269,3 @@ | ||
[test]: https://github.com/syntax-tree/unist-util-is#api | ||
[hast-util-find-and-replace]: https://github.com/syntax-tree/hast-util-find-and-replace |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
20516
7
297
259
1
+ Addedunist-util-visit-parents@5.1.3(transitive)
- Removedunist-util-visit-parents@4.1.1(transitive)