diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml index 291ab09..0198fc3 100644 --- a/.github/workflows/bb.yml +++ b/.github/workflows/bb.yml @@ -2,7 +2,7 @@ name: bb on: issues: types: [opened, reopened, edited, closed, labeled, unlabeled] - pull_request: + pull_request_target: types: [opened, reopened, edited, closed, labeled, unlabeled] jobs: main: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe284ad..fb63387 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,15 +7,15 @@ jobs: name: ${{matrix.node}} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: dcodeIO/setup-node-nvm@master + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{matrix.node}} - run: npm install - run: npm test - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 strategy: matrix: node: - - lts/erbium + - lts/gallium - node diff --git a/.npmrc b/.npmrc index 43c97e7..9951b11 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +ignore-scripts=true diff --git a/index.js b/index.js index 138193a..8674f30 100644 --- a/index.js +++ b/index.js @@ -1,55 +1,5 @@ /** - * @typedef Options - * @property {boolean} [includeImageAlt=true] + * @typedef {import('./lib/index.js').Options} Options */ -/** - * Get the text content of a node. - * Prefer the node’s plain-text fields, otherwise serialize its children, - * and if the given value is an array, serialize the nodes in it. - * - * @param {unknown} node - * @param {Options} [options] - * @returns {string} - */ -export function toString(node, options) { - var {includeImageAlt = true} = options || {} - return one(node, includeImageAlt) -} - -/** - * @param {unknown} node - * @param {boolean} includeImageAlt - * @returns {string} - */ -function one(node, includeImageAlt) { - return ( - (node && - typeof node === 'object' && - // @ts-ignore looks like a literal. - (node.value || - // @ts-ignore looks like an image. - (includeImageAlt ? node.alt : '') || - // @ts-ignore looks like a parent. - ('children' in node && all(node.children, includeImageAlt)) || - (Array.isArray(node) && all(node, includeImageAlt)))) || - '' - ) -} - -/** - * @param {Array.} values - * @param {boolean} includeImageAlt - * @returns {string} - */ -function all(values, includeImageAlt) { - /** @type {Array.} */ - var result = [] - var index = -1 - - while (++index < values.length) { - result[index] = one(values[index], includeImageAlt) - } - - return result.join('') -} +export {toString} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..d0e7314 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,84 @@ +/** + * @typedef {import('mdast').Root|import('mdast').Content} Node + * + * @typedef Options + * Configuration (optional). + * @property {boolean | null | undefined} [includeImageAlt=true] + * Whether to use `alt` for `image`s. + */ + +/** + * Get the text content of a node or list of nodes. + * + * Prefers the node’s plain-text fields, otherwise serializes its children, + * and if the given value is an array, serialize the nodes in it. + * + * @param {unknown} value + * Thing to serialize, typically `Node`. + * @param {Options | null | undefined} [options] + * Configuration (optional). + * @returns {string} + * Serialized `value`. + */ +export function toString(value, options) { + const includeImageAlt = (options || {}).includeImageAlt + return one( + value, + typeof includeImageAlt === 'boolean' ? includeImageAlt : true + ) +} + +/** + * One node or several nodes. + * + * @param {unknown} value + * Thing to serialize. + * @param {boolean} includeImageAlt + * Include image `alt`s. + * @returns {string} + * Serialized node. + */ +function one(value, includeImageAlt) { + return ( + (node(value) && + (('value' in value && value.value) || + (includeImageAlt && 'alt' in value && value.alt) || + ('children' in value && all(value.children, includeImageAlt)))) || + (Array.isArray(value) && all(value, includeImageAlt)) || + '' + ) +} + +/** + * Serialize a list of nodes. + * + * @param {Array} values + * Thing to serialize. + * @param {boolean} includeImageAlt + * Include image `alt`s. + * @returns {string} + * Serialized nodes. + */ +function all(values, includeImageAlt) { + /** @type {Array} */ + const result = [] + let index = -1 + + while (++index < values.length) { + result[index] = one(values[index], includeImageAlt) + } + + return result.join('') +} + +/** + * Check if `value` looks like a node. + * + * @param {unknown} value + * Thing. + * @returns {value is Node} + * Whether `value` is a node. + */ +function node(value) { + return Boolean(value && typeof value === 'object') +} diff --git a/package.json b/package.json index 03ef400..10edf20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mdast-util-to-string", - "version": "3.1.0", + "version": "3.1.1", "description": "mdast utility to get the plain text content of a node", "license": "MIT", "keywords": [ @@ -29,27 +29,30 @@ "main": "index.js", "types": "index.d.ts", "files": [ + "lib/", "index.d.ts", "index.js" ], + "dependencies": { + "@types/mdast": "^3.0.0" + }, "devDependencies": { - "@types/tape": "^4.0.0", + "@types/node": "^18.0.0", "c8": "^7.0.0", "prettier": "^2.0.0", - "remark-cli": "^9.0.0", - "remark-preset-wooorm": "^8.0.0", - "rimraf": "^3.0.0", + "remark-cli": "^11.0.0", + "remark-preset-wooorm": "^9.0.0", "tape": "^5.0.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", - "xo": "^0.39.0" + "xo": "^0.53.0" }, "scripts": { "prepack": "npm run build && npm run format", - "build": "rimraf \"*.d.ts\" && tsc && type-coverage", + "build": "tsc --build --clean && tsc --build && type-coverage", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", - "test-api": "node test.js", - "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", + "test-api": "node --conditions development test.js", + "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { @@ -61,11 +64,7 @@ "trailingComma": "none" }, "xo": { - "prettier": true, - "rules": { - "no-var": "off", - "prefer-arrow-callback": "off" - } + "prettier": true }, "remarkConfig": { "plugins": [ diff --git a/readme.md b/readme.md index 4a77fb9..94dac6e 100644 --- a/readme.md +++ b/readme.md @@ -8,52 +8,117 @@ [![Backers][backers-badge]][collective] [![Chat][chat-badge]][chat] -**[mdast][]** utility to get the plain text content of a node. +[mdast][] utility to get the text content of a node. -## Install +## Contents + +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`toString(value[, options])`](#tostringvalue-options) + * [`Options`](#options) +* [Types](#types) +* [Compatibility](#compatibility) +* [Security](#security) +* [Related](#related) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This package is a tiny utility that gets the textual content of a node. + +## When should I use this? -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 utility is useful when you have a node, say a heading, and want to get the +text inside it. -[npm][]: +This package does not serialize markdown, that’s what +[`mdast-util-to-markdown`][mdast-util-to-markdown] does. + +Similar packages, [`hast-util-to-string`][hast-util-to-string] and +[`hast-util-to-text`][hast-util-to-text], do the same but on [hast][]. + +## Install + +This package is [ESM only][esm]. +In Node.js (version 14.14+ and 16.0+), install with [npm][]: ```sh npm install mdast-util-to-string ``` +In Deno with [`esm.sh`][esmsh]: + +```js +import {toString} from 'https://esm.sh/mdast-util-to-string@3' +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + ## Use ```js -import unified from 'unified' -import remarkParse from 'remark-parse' +import {unified} from 'unified' +import {fromMarkdown} from 'mdast-util-from-markdown' import {toString} from 'mdast-util-to-string' -var tree = unified() - .use(remarkParse) - .parse('Some _emphasis_, **importance**, and `code`.') +const tree = fromMarkdown('Some _emphasis_, **importance**, and `code`.') console.log(toString(tree)) // => 'Some emphasis, importance, and code.' ``` ## API -This package exports the following identifiers: `toString`. +This package exports the identifier [`toString`][api-tostring]. There is no default export. -### `toString(node[, options])` +### `toString(value[, options])` + +Get the text content of a node or list of nodes. + +Prefers the node’s plain-text fields, otherwise serializes its children, +and if the given value is an array, serialize the nodes in it. + +###### Parameters -Get the text content of a [node][] or list of nodes. +* `value` (`unknown`) + — thing to serialize, typically [`Node`][node] +* `options` ([`Options`][api-options], optional) + — configuration -The algorithm checks `value` of `node` and then `alt`. -If no value is found, the algorithm checks the children of `node` and joins them -(without spaces or newlines). +###### Returns -> This is not a markdown to plain-text library. -> Use [`strip-markdown`][strip-markdown] for that. +Serialized `value` (`string`). -###### `options.includeImageAlt` +### `Options` -Whether to use `alt` (`boolean`, default: `true`) +Configuration (TypeScript type). + +###### Fields + +* `includeImageAlt` (`boolean`, default: `true`) + — whether to use `alt` for `image`s + +## Types + +This package is fully typed with [TypeScript][]. +It exports the additional type [`Options`][api-options]. + +## Compatibility + +Projects maintained by the unified collective are compatible with all maintained +versions of Node.js. +As of now, that is Node.js 14.14+ and 16.0+. +Our projects sometimes work with older versions, but this is not guaranteed. ## Security @@ -63,19 +128,15 @@ attacks. ## Related -* [`nlcst-to-string`](https://github.com/syntax-tree/nlcst-to-string) - — Get text content in nlcst -* [`hast-util-to-string`](https://github.com/wooorm/rehype-minify/tree/HEAD/packages/hast-util-to-string) - — Get text content in hast +* [`hast-util-to-string`](https://github.com/wooorm/rehype-minify/tree/main/packages/hast-util-to-string) + — get text content in hast * [`hast-util-to-text`](https://github.com/syntax-tree/hast-util-to-text) - — Get text content in hast according to the `innerText` algorithm -* [`hast-util-from-string`](https://github.com/wooorm/rehype-minify/tree/HEAD/packages/hast-util-from-string) - — Set text content in hast + — get text content in hast according to the `innerText` algorithm ## Contribute -See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get -started. +See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for +ways to get started. See [`support.md`][support] for ways to get help. This project has a [code of conduct][coc]. @@ -116,22 +177,38 @@ abide by its terms. [npm]: https://docs.npmjs.com/cli/install +[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +[esmsh]: https://esm.sh + +[typescript]: https://www.typescriptlang.org + [license]: license [author]: https://wooorm.com -[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md +[health]: https://github.com/syntax-tree/.github -[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 [mdast]: https://github.com/syntax-tree/mdast -[node]: https://github.com/syntax-tree/mdast#nodes +[mdast-util-to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown + +[hast]: https://github.com/syntax-tree/hast + +[hast-util-to-string]: https://github.com/rehypejs/rehype-minify/tree/main/packages/hast-util-to-string -[strip-markdown]: https://github.com/remarkjs/strip-markdown +[hast-util-to-text]: https://github.com/syntax-tree/hast-util-to-text + +[node]: https://github.com/syntax-tree/mdast#nodes [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting -[hast]: https://github.com/syntax-tree/hast +[api-tostring]: #tostringvalue-options + +[api-options]: #options diff --git a/test.js b/test.js index 3a4c6a3..1096c35 100644 --- a/test.js +++ b/test.js @@ -1,13 +1,26 @@ -import test from 'tape' +import assert from 'node:assert/strict' +import test from 'node:test' import {toString} from './index.js' +import * as mod from './index.js' -test('toString', function (t) { - t.equal(toString(), '', 'should not fail on a missing node') - t.equal(toString(null), '', 'should not fail on `null` missing node') +test('toString', () => { + assert.deepEqual( + Object.keys(mod).sort(), + ['toString'], + 'should expose the public api' + ) - t.equal(toString({value: 'foo'}), 'foo', 'should not fail on nodes w/o type') + // @ts-expect-error: runtime. + assert.equal(toString(), '', 'should not fail on a missing node') + assert.equal(toString(null), '', 'should not fail on `null` missing node') - t.equal( + assert.equal( + toString({value: 'foo'}), + 'foo', + 'should not fail on nodes w/o type' + ) + + assert.equal( toString({ value: 'foo', alt: 'bar', @@ -18,37 +31,35 @@ test('toString', function (t) { 'should prefer `value` over all others' ) - t.equal( + assert.equal( toString({alt: 'bar', title: 'baz', children: [{value: 'qux'}]}), 'bar', 'should prefer `alt` over all others' ) - t.equal( + assert.equal( toString({title: 'baz', children: [{value: 'qux'}]}), 'qux', 'should *not* prefer `title` over all others' ) - t.equal( + assert.equal( toString({alt: 'bar'}, {includeImageAlt: false}), '', 'should *not* include `alt` w/ `includeImageAlt: false`' ) - t.equal( + assert.equal( toString({children: [{value: 'foo'}, {alt: 'bar'}, {title: 'baz'}]}), 'foobar', 'should serialize children' ) - t.equal( + assert.equal( toString([{value: 'foo'}, {alt: 'bar'}, {title: 'baz'}]), 'foobar', 'should serialize a list of nodes' ) - t.equal(toString({}), '', 'should produce an empty string otherwise') - - t.end() + assert.equal(toString({}), '', 'should produce an empty string otherwise') }) diff --git a/tsconfig.json b/tsconfig.json index be08abe..ebe8889 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,17 @@ { - "include": ["*.js"], + "include": ["**/*.js"], + "exclude": ["coverage/", "node_modules/"], "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020"], - "module": "ES2020", - "moduleResolution": "node", - "allowJs": true, "checkJs": true, "declaration": true, "emitDeclarationOnly": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "node16", + "newLine": "lf", + "skipLibCheck": true, + "strict": true, + "target": "es2020" } }