Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Aug 4, 2021
1 parent e7062ec commit eb799e9
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 104 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
coverage/
node_modules/
.DS_Store
*.d.ts
*.log
yarn.lock
106 changes: 93 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
// Expose modifiers for available node types.
// Node types not listed here are not changed (but their children are).
/**
* @typedef {import('mdast').Content} Content
* @typedef {import('mdast').Root} Root
* @typedef {Root|Content} Node
* @typedef {Node['type']} Type
*
* @callback Handler
* @param {any} node
* @returns {Node|Node[]} node
*
* @typedef {Partial<Record<Type, Handler>>} Handlers
*
* @typedef Options
* Configuration.
* @property {Array.<Type>|undefined} [keep]
* List of node types to leave unchanged.
* @property {Array.<Type|[Type, Handler]>|undefined} [remove]
* List of additional node types to remove or replace.
*/

/**
* Expose modifiers for available node types.
* Node types not listed here are not changed (but their children are).
*
* @type {Handlers}
*/
const defaults = {
heading: paragraph,
text,
Expand All @@ -18,13 +42,14 @@ const defaults = {
linkReference: children,

code: empty,
horizontalRule: empty,
thematicBreak: empty,
html: empty,
table: empty,
tableCell: empty,
definition: empty,
yaml: empty,

// @ts-expect-error: custom frontmatter node.
toml: empty,

footnoteReference: empty,
Expand All @@ -33,6 +58,12 @@ const defaults = {

const own = {}.hasOwnProperty

/**
* Plugin to remove markdown formatting.
*
* @type {import('unified').Plugin<[Options?] | void[], Root>}
* @returns {import('unified').Transformer<Root>}
*/
export default function stripMarkdown(options = {}) {
const handlers = Object.assign({}, defaults)
const remove = options.remove || []
Expand All @@ -50,11 +81,13 @@ export default function stripMarkdown(options = {}) {
}
}

/** @type {Handlers} */
let map = {}

if (keep.length === 0) {
map = handlers
} else {
/** @type {Type} */
let key

for (key in handlers) {
Expand All @@ -79,32 +112,48 @@ export default function stripMarkdown(options = {}) {
}
}

// @ts-expect-error: assume content model (for root) matches.
return one

/**
* @param {Node} node
* @returns {Node|Node[]}
*/
function one(node) {
/** @type {Type} */
const type = node.type
/** @type {Node|Node[]} */
let result = node

if (type in map) {
const result = map[type](node)
node = Array.isArray(result) ? all(result) : result
const handler = map[type]
if (handler) result = handler(result)
}

if (node.children) {
node.children = all(node.children)
result = Array.isArray(result) ? all(result) : result

if ('children' in result) {
// @ts-expect-error: assume content models match.
result.children = all(result.children)
}

return node
return result
}

/**
* @param {Node[]} nodes
* @returns {Node[]}
*/
function all(nodes) {
const result = []
let index = -1
/** @type {Node[]} */
const result = []

while (++index < nodes.length) {
const value = one(nodes[index])

if (Array.isArray(value)) {
result.push(...value.map((d) => one(d)))
result.push(...value.flatMap((d) => one(d)))
} else {
result.push(value)
}
Expand All @@ -114,16 +163,24 @@ export default function stripMarkdown(options = {}) {
}
}

// Clean nodes: merges texts.
/**
* Clean nodes: merges literals.
*
* @param {Node[]} values
* @returns {Node[]}
*/
function clean(values) {
const result = []
let index = -1
/** @type {Node[]} */
const result = []
/** @type {Node|undefined} */
let previous

while (++index < values.length) {
const value = values[index]

if (previous && value.type === previous.type && 'value' in value) {
// @ts-expect-error: we just checked that they’re the same node.
previous.value += value.value
} else {
result.push(value)
Expand All @@ -134,26 +191,49 @@ function clean(values) {
return result
}

/**
* @type {Handler}
* @param {import('mdast').Image|import('mdast').ImageReference} node
*/
function image(node) {
return {type: 'text', value: node.alt || node.title || ''}
const title = 'title' in node ? node.title : ''
return {type: 'text', value: node.alt || title || ''}
}

/**
* @type {Handler}
* @param {import('mdast').Text} node
*/
function text(node) {
return {type: 'text', value: node.value}
}

/**
* @type {Handler}
* @param {import('mdast').Paragraph} node
*/
function paragraph(node) {
return {type: 'paragraph', children: node.children}
}

/**
* @type {Handler}
* @param {Extract<Node, import('unist').Parent>} node
*/
function children(node) {
return node.children || []
}

/**
* @type {Handler}
*/
function lineBreak() {
return {type: 'text', value: '\n'}
}

/**
* @type {Handler}
*/
function empty() {
return {type: 'text', value: ''}
}
27 changes: 24 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "strip-markdown",
"version": "4.2.0",
"description": "remark plugin to remove Markdown formatting",
"description": "remark plugin to remove markdown formatting",
"license": "MIT",
"keywords": [
"unified",
Expand All @@ -28,11 +28,18 @@
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.d.ts",
"index.js"
],
"dependencies": {},
"dependencies": {
"@types/mdast": "^3.0.0",
"@types/unist": "^2.0.6",
"unified": "^10.0.0"
},
"devDependencies": {
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"prettier": "^2.0.0",
"remark": "^14.0.0",
Expand All @@ -41,15 +48,19 @@
"remark-footnotes": "^3.0.0",
"remark-gfm": "^1.0.0",
"remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"unist-builder": "^3.0.0",
"xo": "^0.39.0"
},
"scripts": {
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
"test": "npm run format && npm run test-coverage"
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
Expand All @@ -69,5 +80,15 @@
"plugins": [
"preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true,
"#": "needed `any`s",
"ignoreFiles": [
"index.d.ts"
]
}
}
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]

[**remark**][remark] plugin remove Markdown formatting.
[**remark**][remark] plugin remove markdown formatting.
This essentially removes everything but paragraphs and text nodes.

> This is one of the first remark plugins, before prefixing with `remark-` got
Expand Down Expand Up @@ -58,7 +58,7 @@ The default export is `stripMarkdown`.

### `unified().use(stripMarkdown[, options])`

Plugin remove Markdown formatting.
Plugin remove markdown formatting.

* Removes `html` ([*note*][gh-19]), `code`, `horizontalRule`, `table`, `yaml`,
`toml`, and their content
Expand Down
24 changes: 23 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* @typedef {import('unist').Node} Node
* @typedef {import('mdast').Content} Content
* @typedef {import('./index.js').Options} Options
*/

import test from 'tape'
import {remark} from 'remark'
import remarkGfm from 'remark-gfm'
Expand All @@ -6,6 +12,11 @@ import remarkDirective from 'remark-directive'
import {u} from 'unist-builder'
import stripMarkdown from './index.js'

/**
* @param {string} value
* @param {Options|undefined} [options]
* @returns {string}
*/
function proc(value, options) {
return remark()
.use(remarkGfm)
Expand All @@ -22,6 +33,7 @@ test('stripMarkdown()', (t) => {
remark()
.use(stripMarkdown)
.runSync(
// @ts-expect-error: custom nodes.
u('root', [
u('unknown', [u('strong', [u('text', 'value')])]),
u('anotherUnknown', 'with value')
Expand All @@ -37,6 +49,7 @@ test('stripMarkdown()', (t) => {
t.deepEqual(
remark()
.use(stripMarkdown)
// @ts-expect-error: custom nodes.
.runSync(u('root', [u('paragraph', [u('link')])])),
u('root', [u('paragraph', [])]),
'should keep unknown nodes'
Expand Down Expand Up @@ -110,6 +123,7 @@ test('stripMarkdown()', (t) => {
)
t.throws(
() => {
// @ts-expect-error: custom node.
proc('- **Hello**\n\n- World!', {keep: ['typo']})
},
/Error: Invalid `keep` option/,
Expand All @@ -118,6 +132,9 @@ test('stripMarkdown()', (t) => {

// "remove" option
t.equal(
// To do: once `remark-directive` is typed and registered, this will
// magically be registered as a known node.
// @ts-expect-error: custom node.
proc('I read this :cite[smith04]!', {remove: ['textDirective']}),
'I read this !',
'remove directive'
Expand All @@ -127,9 +144,14 @@ test('stripMarkdown()', (t) => {
'A :i[lovely] language known as :abbr[HTML]{title="HyperText Markup Language"}.',
{
remove: [
// To do: once `remark-directive` is typed and registered, this will
// magically be registered as a known node.
// @ts-expect-error: custom node.
[
'textDirective',
(node) => {
(
/** @type {Node & {children: Content[], name: string, attributes: Record<string, string>}} */ node
) => {
if (node.name === 'abbr') {
return {type: 'text', value: node.attributes.title}
}
Expand Down
16 changes: 16 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"include": ["*.js"],
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ES2020",
"moduleResolution": "node",
"allowJs": true,
"checkJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"strict": true
}
}
Loading

0 comments on commit eb799e9

Please sign in to comment.