textlint-util-to-string
Advanced tools
Comparing version 3.2.0 to 3.3.0
import type { TxtNode, TxtParentNode } from "@textlint/ast-node-types"; | ||
import { SourcePosition } from "structured-source"; | ||
import type { Node as UnistNode } from "unist"; | ||
import { emptyValue, maskValue, StringSourceReplacerCommand } from "./replacer"; | ||
export type StringSourceOptions = { | ||
replacer?: ({ node, parent }: { | ||
node: TxtNode | UnistNode; | ||
parent?: TxtParentNode; | ||
maskValue: typeof maskValue; | ||
emptyValue: typeof emptyValue; | ||
}) => StringSourceReplacerCommand | undefined; | ||
}; | ||
export default class StringSource { | ||
@@ -10,3 +19,3 @@ private rootNode; | ||
private tokenMaps; | ||
constructor(node: TxtParentNode); | ||
constructor(node: TxtParentNode, options?: StringSourceOptions); | ||
toString(): string; | ||
@@ -69,8 +78,9 @@ /** | ||
* does not expose plain-text fields, `toString` will | ||
* recursively mapping | ||
* recursively map | ||
* | ||
* @param {Node} node - Node to transform to a string. | ||
* @param {Node} [parent] - Parent Node of the `node`. | ||
* @param options | ||
*/ | ||
private _stringify; | ||
} |
{ | ||
"name": "textlint-util-to-string", | ||
"version": "3.2.0", | ||
"version": "3.3.0", | ||
"description": "textlint utility that convert Paragraph Node to text with SourceMap.", | ||
@@ -47,3 +47,3 @@ "homepage": "https://github.com/textlint/textlint-util-to-string", | ||
"lint-staged": "^13.1.0", | ||
"markdown-to-ast": "^6.0.3", | ||
"@textlint/markdown-to-ast": "^13.2.0", | ||
"microbundle": "^0.15.1", | ||
@@ -50,0 +50,0 @@ "mocha": "^10.2.0", |
129
README.md
# textlint-util-to-string [![Actions Status: test](https://github.com/textlint/textlint-util-to-string/workflows/test/badge.svg)](https://github.com/textlint/textlint-util-to-string/actions?query=workflow%3A"test") | ||
Convert `Paragraph` Node to plain text with SourceMap. | ||
It means that you can get original position from plain text. | ||
SourceMap mean that could revert `position` in plain text to `position` in Node. | ||
This library is for [textlint](https://github.com/textlint/textlint "textlint") and [textstat](https://github.com/azu/textstat "textstat"). | ||
@@ -15,61 +14,109 @@ | ||
The concepts `position` and `index` are the same as those explained in [Constellation/structured-source](https://github.com/Constellation/structured-source). | ||
The concepts `position` and `index` are the same with [TxtAST Interface](https://textlint.github.io/docs/txtnode.html) and [textlint/structured-source](https://github.com/textlint/structured-source). | ||
**Note**: the `column` property of `position` as it is **0-based** index. | ||
- `position` is a `{ line, column }` object. | ||
- The `column` property of `position` is **0-based**. | ||
- The `line` property of `position` is **1-based**. | ||
- `index` is an offset number. | ||
- The `index` property is **0-based**. | ||
## Usage | ||
## API | ||
### `Constructor(rootNode): source` | ||
### `new StringSource(node: TxtParentNode, options?: StringSourceOptions)` | ||
Return instance of Source. | ||
Create new `StringSource` instance for `paragraph` Node. | ||
## `originalIndexFromIndex(generatedIndex): number | undefined` | ||
### `toString(): string` | ||
Get plain text from `Paragraph` Node. | ||
This plain text is concatenated from `value` of all children nodes of `Paragraph` Node. | ||
```ts | ||
import { StringSource } from "textlint-util-to-string"; | ||
const report = function (context) { | ||
const { Syntax, report, RuleError } = context; | ||
return { | ||
// "This is **a** `code`." | ||
[Syntax.Paragraph](node) { | ||
const source = new StringSource(node); | ||
const text = source.toString(); // => "This is a code." | ||
} | ||
} | ||
}; | ||
``` | ||
In some cases, you may want to replace some characters in the plain text for avoiding false positives. | ||
You can replace `value` of children nodes by `options.replacer`. | ||
`options.replacer` is a function that takes a `node` and commands like `maskValue` or `emptyValue`. | ||
If you want to modify the `value` of the node, return command function calls. | ||
```ts | ||
// "This is a `code`." | ||
const source = new StringSource(paragraphNode, { | ||
replacer({ node, maskValue }) { | ||
if (node.type === Syntax.Code) { | ||
return maskValue("_"); // code => ____ | ||
} | ||
} | ||
}); | ||
console.log(source.toString()); // => "This is a ____." | ||
``` | ||
- `maskValue(character: string)`: mask the `value` of the node with the given `character`. | ||
- `emptyValue()`: replace the `value` of the node with an empty string. | ||
### `originalIndexFromIndex(generatedIndex): number | undefined` | ||
Get original index from generated index value | ||
## `originalPositionFromPosition(position): Position | undefined` | ||
### `originalPositionFromPosition(position): Position | undefined` | ||
Get original position from generated position | ||
## `originalIndexFromPosition(generatedPosition): number | undefined` | ||
### `originalIndexFromPosition(generatedPosition): number | undefined` | ||
Get original index from generated position | ||
## `originalPositionFromIndex(generatedIndex): Position | undefined` | ||
### `originalPositionFromIndex(generatedIndex): Position | undefined` | ||
Get original position from generated index | ||
```js | ||
import assert from "assert" | ||
import {parse} from "markdown-to-ast"; | ||
import {StringSource} from "textlint-util-to-string"; | ||
## Examples | ||
const originalText = "This is [Example!?](http://example.com/)"; | ||
const AST = parse(originalText); | ||
const source = new StringSource(AST); | ||
const result = source.toString(); | ||
Create plain text from `Paragraph` Node and get original position from plain text. | ||
// StringSource#toString returns a plain text | ||
assert.equal(result, "This is Example!?"); | ||
```js | ||
import assert from "assert"; | ||
import { StringSource } from "textlint-util-to-string"; | ||
const report = function (context) { | ||
const { Syntax, report, RuleError } = context; | ||
return { | ||
// "This is [Example!?](http://example.com/)" | ||
[Syntax.Paragraph](node) { | ||
const source = new StringSource(node); | ||
const text = source.toString(); // => "This is Example!?" | ||
// "Example" is located at the index 8 in the plain text | ||
// ^ | ||
const index1 = result.indexOf("Example"); | ||
assert.strictEqual(index1, 8); | ||
// The "Example" is located at the index 9 in the original text | ||
assert.strictEqual(source.originalIndexFromIndex(index1), 9); | ||
assert.deepStrictEqual(source.originalPositionFromPosition({ | ||
line: 1, | ||
column: 8 | ||
}), { | ||
line: 1, | ||
column: 9 | ||
}); | ||
// "Example" is located at the index 8 in the plain text | ||
// ^ | ||
let index1 = result.indexOf("Example"); | ||
assert.equal(index1, 8); | ||
// The same "E" is located at the index 9 in the original text | ||
assert.equal(source.originalIndexFromIndex(index1), 9); | ||
assert.deepEqual(source.originalPositionFromPosition({ | ||
line: 1, | ||
column: 8 | ||
}), { | ||
line: 1, | ||
column: 9 | ||
}); | ||
// Another example with "!", which is located at 15 in the plain text | ||
// and at 16 in the original text | ||
let index2 = result.indexOf("!?"); | ||
assert.equal(index2, 15); | ||
assert.equal(source.originalIndexFromIndex(index2), 16); | ||
// Another example with "!?", which is located at 15 in the plain text | ||
// and at 16 in the original text | ||
const index2 = result.indexOf("!?"); | ||
assert.strictEqual(index2, 15); | ||
assert.strictEqual(source.originalIndexFromIndex(index2), 16); | ||
} | ||
} | ||
}; | ||
``` | ||
@@ -76,0 +123,0 @@ |
@@ -7,2 +7,3 @@ import type { TxtNode, TxtParentNode } from "@textlint/ast-node-types"; | ||
import parse from "rehype-parse"; | ||
import { emptyValue, handleReplacerCommand, maskValue, StringSourceReplacerCommand } from "./replacer"; | ||
@@ -13,6 +14,11 @@ const isTxtNode = (node: unknown): node is TxtNode => { | ||
const htmlProcessor = unified().use(parse, { fragment: true }); | ||
const html2hast = (html: string) => { | ||
return unified().use(parse, { fragment: true }).parse(html); | ||
return htmlProcessor.parse(html); | ||
}; | ||
const isParentNode = (node: TxtNode | TxtParentNode): node is TxtParentNode => { | ||
return "children" in node; | ||
}; | ||
/* StringSourceIR example | ||
@@ -40,2 +46,14 @@ Example: **Str** | ||
}; | ||
export type StringSourceOptions = { | ||
replacer?: ({ | ||
node, | ||
parent | ||
}: { | ||
node: TxtNode | UnistNode; | ||
parent?: TxtParentNode; | ||
maskValue: typeof maskValue; | ||
emptyValue: typeof emptyValue; | ||
}) => StringSourceReplacerCommand | undefined; | ||
}; | ||
export default class StringSource { | ||
@@ -48,3 +66,3 @@ private rootNode: TxtParentNode; | ||
constructor(node: TxtParentNode) { | ||
constructor(node: TxtParentNode, options: StringSourceOptions = {}) { | ||
this.rootNode = node; | ||
@@ -54,3 +72,3 @@ this.tokenMaps = []; | ||
// pre calculate | ||
this._stringify(this.rootNode); | ||
this._stringify({ node: this.rootNode, options }); | ||
this.originalSource = new StructuredSource(this.rootNode.raw); | ||
@@ -233,11 +251,20 @@ this.generatedSource = new StructuredSource(this.generatedString); | ||
private _valueOf(node: TxtNode | UnistNode, parent?: TxtParentNode): StringSourceIR | undefined { | ||
private _valueOf({ | ||
node, | ||
parent, | ||
options | ||
}: { | ||
node: TxtNode | UnistNode; | ||
parent?: TxtParentNode; | ||
options: StringSourceOptions; | ||
}): StringSourceIR | undefined { | ||
if (!node) { | ||
return; | ||
} | ||
const replaceCommand = options?.replacer?.({ node, parent, maskValue, emptyValue }); | ||
const newNode = replaceCommand ? handleReplacerCommand(replaceCommand, node) : node; | ||
// [padding][value][padding] | ||
// => | ||
// [value][value][value] | ||
const value = this._getValue(node); | ||
const value = this._getValue(newNode); | ||
if (!value) { | ||
@@ -250,6 +277,6 @@ return; | ||
// <p><Str /></p> | ||
if (this.isParagraphNode(parent) && this.isStringNode(node)) { | ||
if (this.isParagraphNode(parent) && this.isStringNode(newNode)) { | ||
return { | ||
original: this._nodeRangeAsRelative(node), | ||
intermediate: this._nodeRangeAsRelative(node), | ||
original: this._nodeRangeAsRelative(newNode), | ||
intermediate: this._nodeRangeAsRelative(newNode), | ||
generatedValue: value | ||
@@ -262,3 +289,3 @@ }; | ||
// => container is <strong> | ||
const container = this.isParagraphNode(parent) ? node : parent; | ||
const container = this.isParagraphNode(parent) ? newNode : parent; | ||
const rawValue = container.raw as string | undefined; | ||
@@ -288,6 +315,6 @@ if (rawValue === undefined) { | ||
if (this.tokenMaps.length === 0) { | ||
let textLength = addedTokenMap.intermediate[1] - addedTokenMap.intermediate[0]; | ||
const textLength = addedTokenMap.intermediate[1] - addedTokenMap.intermediate[0]; | ||
addedTokenMap["generated"] = [0, textLength]; | ||
} else { | ||
let textLength = addedTokenMap.intermediate[1] - addedTokenMap.intermediate[0]; | ||
const textLength = addedTokenMap.intermediate[1] - addedTokenMap.intermediate[0]; | ||
addedTokenMap["generated"] = [this.generatedString.length, this.generatedString.length + textLength]; | ||
@@ -302,11 +329,20 @@ } | ||
* does not expose plain-text fields, `toString` will | ||
* recursively mapping | ||
* recursively map | ||
* | ||
* @param {Node} node - Node to transform to a string. | ||
* @param {Node} [parent] - Parent Node of the `node`. | ||
* @param options | ||
*/ | ||
private _stringify(node: TxtNode | TxtParentNode, parent?: TxtParentNode): void | StringSourceIR { | ||
private _stringify({ | ||
node, | ||
parent, | ||
options | ||
}: { | ||
node: TxtNode | TxtParentNode; | ||
parent?: TxtParentNode; | ||
options: StringSourceOptions; | ||
}): void | StringSourceIR { | ||
const isHTML = node.type === "Html"; | ||
const currentNode = isHTML ? html2hast(node.value) : node; | ||
const value = this._valueOf(currentNode, parent); | ||
const value = this._valueOf({ node: currentNode, parent: parent, options }); | ||
if (value) { | ||
@@ -322,3 +358,3 @@ return value; | ||
} | ||
const tokenMap = this._stringify(childNode, node); | ||
const tokenMap = this._stringify({ node: childNode, parent: node, options }); | ||
if (tokenMap) { | ||
@@ -330,5 +366,1 @@ this._addTokenMap(tokenMap); | ||
} | ||
const isParentNode = (node: TxtNode | TxtParentNode): node is TxtParentNode => { | ||
return "children" in node; | ||
}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
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
4989302
19
11870
160
0