From d9eaf7fcf3a65e8f696e939a9a55846497a6ff67 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 8 Jul 2023 19:52:59 +0200 Subject: [PATCH] Refactor code-style --- dev/index.d.ts | 3 - dev/lib/index.js | 228 ++++++------ package.json | 11 +- readme.md | 10 +- test/index.js | 889 ++++++++++++++++++++++------------------------- 5 files changed, 557 insertions(+), 584 deletions(-) diff --git a/dev/index.d.ts b/dev/index.d.ts index 2082b9c..82bb86d 100644 --- a/dev/index.d.ts +++ b/dev/index.d.ts @@ -34,7 +34,6 @@ export type OnError = OnEnterError * } * ``` */ -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions export interface CompileData { /** * Whether we’re inside a hard break. @@ -76,12 +75,10 @@ export interface CompileData { } declare module 'micromark-util-types' { - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface TokenTypeMap { listItem: 'listItem' } - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Token { _spread?: boolean } diff --git a/dev/lib/index.js b/dev/lib/index.js index 75b0281..e682f40 100644 --- a/dev/lib/index.js +++ b/dev/lib/index.js @@ -1,16 +1,4 @@ /** - * @typedef {import('micromark-util-types').Encoding} Encoding - * @typedef {import('micromark-util-types').Event} Event - * @typedef {import('micromark-util-types').ParseOptions} ParseOptions - * @typedef {import('micromark-util-types').Token} Token - * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext - * @typedef {import('micromark-util-types').Value} Value - * - * @typedef {import('unist').Point} Point - * - * @typedef {import('mdast').Parent} Parent - * @typedef {import('mdast').PhrasingContent} PhrasingContent - * @typedef {import('mdast').Nodes} Nodes * @typedef {import('mdast').Break} Break * @typedef {import('mdast').Blockquote} Blockquote * @typedef {import('mdast').Code} Code @@ -23,18 +11,30 @@ * @typedef {import('mdast').Link} Link * @typedef {import('mdast').List} List * @typedef {import('mdast').ListItem} ListItem + * @typedef {import('mdast').Nodes} Nodes * @typedef {import('mdast').Paragraph} Paragraph + * @typedef {import('mdast').Parent} Parent + * @typedef {import('mdast').PhrasingContent} PhrasingContent + * @typedef {import('mdast').ReferenceType} ReferenceType * @typedef {import('mdast').Root} Root * @typedef {import('mdast').Strong} Strong * @typedef {import('mdast').Text} Text * @typedef {import('mdast').ThematicBreak} ThematicBreak - * @typedef {import('mdast').ReferenceType} ReferenceType + * + * @typedef {import('micromark-util-types').Encoding} Encoding + * @typedef {import('micromark-util-types').Event} Event + * @typedef {import('micromark-util-types').ParseOptions} ParseOptions + * @typedef {import('micromark-util-types').Token} Token + * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext + * @typedef {import('micromark-util-types').Value} Value + * + * @typedef {import('unist').Point} Point + * * @typedef {import('../index.js').CompileData} CompileData */ /** - * - * @typedef {Omit & {type: 'fragment', children: Array}} Fragment + * @typedef {Omit & {type: 'fragment', children: Array}} Fragment */ /** @@ -42,7 +42,7 @@ * Extra transform, to change the AST afterwards. * @param {Root} tree * Tree to transform. - * @returns {Root | undefined | null | void} + * @returns {Root | null | undefined | void} * New tree or nothing (in which case the current tree is used). * * @callback Handle @@ -51,7 +51,7 @@ * Context. * @param {Token} token * Current token. - * @returns {void} + * @returns {undefined | void} * Nothing. * * @typedef {Record} Handles @@ -66,7 +66,7 @@ * Left token. * @param {Token} right * Right token. - * @returns {void} + * @returns {undefined} * Nothing. * * @callback OnExitError @@ -78,7 +78,7 @@ * Left token. * @param {Token} right * Right token. - * @returns {void} + * @returns {undefined} * Nothing. * * @typedef {[Token, OnEnterError | undefined]} TokenTuple @@ -105,15 +105,15 @@ * * @typedef CompileContext * mdast compiler context. - * @property {Array} stack + * @property {Array} stack * Stack of nodes. * @property {Array} tokenStack * Stack of tokens. * @property {(key: Key) => CompileData[Key]} getData * Get data from the key/value store. - * @property {(key: Key, value?: CompileData[Key]) => void} setData + * @property {(key: Key, value?: CompileData[Key]) => undefined} setData * Set data into the key/value store. - * @property {(this: CompileContext) => void} buffer + * @property {(this: CompileContext) => undefined} buffer * Capture some of the output data. * @property {(this: CompileContext) => string} resume * Stop capturing and access the output data. @@ -152,42 +152,40 @@ import {stringifyPosition} from 'unist-util-stringify-position' const own = {}.hasOwnProperty /** - * @param value + * Turn markdown into a syntax tree. + * + * @overload + * @param {Value} value + * @param {Encoding | null | undefined} [encoding] + * @param {Options | null | undefined} [options] + * @returns {Root} + * + * @overload + * @param {Value} value + * @param {Options | null | undefined} [options] + * @returns {Root} + * + * @param {Value} value * Markdown to parse. - * @param encoding + * @param {Encoding | Options | null | undefined} [encoding] * Character encoding for when `value` is `Buffer`. - * @param options + * @param {Options | null | undefined} [options] * Configuration. - * @returns + * @returns {Root} * mdast tree. */ -export const fromMarkdown = - /** - * @type {( - * ((value: Value, encoding: Encoding, options?: Options | null | undefined) => Root) & - * ((value: Value, options?: Options | null | undefined) => Root) - * )} - */ - ( - /** - * @param {Value} value - * @param {Encoding | Options | null | undefined} [encoding] - * @param {Options | null | undefined} [options] - * @returns {Root} - */ - function (value, encoding, options) { - if (typeof encoding !== 'string') { - options = encoding - encoding = undefined - } +export function fromMarkdown(value, encoding, options) { + if (typeof encoding !== 'string') { + options = encoding + encoding = undefined + } - return compiler(options)( - postprocess( - parse(options).document().write(preprocess()(value, encoding, true)) - ) - ) - } + return compiler(options)( + postprocess( + parse(options).document().write(preprocess()(value, encoding, true)) + ) ) +} /** * Note this compiler only understand complete buffering, not streaming. @@ -411,41 +409,51 @@ function compiler(options) { while (++index <= length) { const event = events[index] - if ( - event[1].type === types.listUnordered || - event[1].type === types.listOrdered || - event[1].type === types.blockQuote - ) { - if (event[0] === 'enter') { - containerBalance++ - } else { - containerBalance-- + switch (event[1].type) { + case types.listUnordered: + case types.listOrdered: + case types.blockQuote: { + if (event[0] === 'enter') { + containerBalance++ + } else { + containerBalance-- + } + + atMarker = undefined + + break } - atMarker = undefined - } else if (event[1].type === types.lineEndingBlank) { - if (event[0] === 'enter') { - if ( - listItem && - !atMarker && - !containerBalance && - !firstBlankLineIndex - ) { - firstBlankLineIndex = index + case types.lineEndingBlank: { + if (event[0] === 'enter') { + if ( + listItem && + !atMarker && + !containerBalance && + !firstBlankLineIndex + ) { + firstBlankLineIndex = index + } + + atMarker = undefined } + break + } + + case types.linePrefix: + case types.listItemValue: + case types.listItemMarker: + case types.listItemPrefix: + case types.listItemPrefixWhitespace: { + // Empty. + + break + } + + default: { atMarker = undefined } - } else if ( - event[1].type === types.linePrefix || - event[1].type === types.listItemValue || - event[1].type === types.listItemMarker || - event[1].type === types.listItemPrefix || - event[1].type === types.listItemPrefixWhitespace - ) { - // Empty. - } else { - atMarker = undefined } if ( @@ -538,9 +546,9 @@ function compiler(options) { * Field type. * @param {Key} key * Key of field. - * @param {CompileData[Key]} [value] + * @param {CompileData[Key] | undefined} [value] * New value. - * @returns {void} + * @returns {undefined} * Nothing. */ function setData(key, value) { @@ -566,7 +574,7 @@ function compiler(options) { * * @param {(token: Token) => Nodes} create * Create a node. - * @param {Handle} [and] + * @param {Handle | undefined} [and] * Optional function to also run. * @returns {Handle} * Handle. @@ -577,7 +585,7 @@ function compiler(options) { /** * @this {CompileContext} * @param {Token} token - * @returns {void} + * @returns {undefined} */ function open(token) { enter.call(this, create(token), token) @@ -587,7 +595,7 @@ function compiler(options) { /** * @this {CompileContext} - * @returns {void} + * @returns {undefined} */ function buffer() { this.stack.push({type: 'fragment', children: []}) @@ -623,7 +631,7 @@ function compiler(options) { /** * Create a closer handle. * - * @param {Handle} [and] + * @param {Handle | undefined} [and] * Optional function to also run. * @returns {Handle} * Handle. @@ -634,7 +642,7 @@ function compiler(options) { /** * @this {CompileContext} * @param {Token} token - * @returns {void} + * @returns {undefined} */ function close(token) { if (and) and.call(this, token) @@ -865,7 +873,7 @@ function compiler(options) { assert(node.type === 'heading', 'expected heading on stack') node.depth = - this.sliceSerialize(token).charCodeAt(0) === codes.equalsTo ? 1 : 2 + this.sliceSerialize(token).codePointAt(0) === codes.equalsTo ? 1 : 2 } /** @@ -1380,8 +1388,8 @@ function point(d) { /** * @param {Config} combined - * @param {Array>} extensions - * @returns {void} + * @param {Array | Extension>} extensions + * @returns {undefined} */ function configure(combined, extensions) { let index = -1 @@ -1400,7 +1408,7 @@ function configure(combined, extensions) { /** * @param {Config} combined * @param {Extension} extension - * @returns {void} + * @returns {undefined} */ function extension(combined, extension) { /** @type {keyof Extension} */ @@ -1408,21 +1416,35 @@ function extension(combined, extension) { for (key in extension) { if (own.call(extension, key)) { - if (key === 'canContainEols') { - const right = extension[key] - if (right) { - combined[key].push(...right) + switch (key) { + case 'canContainEols': { + const right = extension[key] + if (right) { + combined[key].push(...right) + } + + break } - } else if (key === 'transforms') { - const right = extension[key] - if (right) { - combined[key].push(...right) + + case 'transforms': { + const right = extension[key] + if (right) { + combined[key].push(...right) + } + + break } - } else if (key === 'enter' || key === 'exit') { - const right = extension[key] - if (right) { - Object.assign(combined[key], right) + + case 'enter': + case 'exit': { + const right = extension[key] + if (right) { + Object.assign(combined[key], right) + } + + break } + // No default } } } diff --git a/package.json b/package.json index a858bae..6b14c02 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,12 @@ }, "xo": { "overrides": [ + { + "files": "**/*.ts", + "rules": { + "@typescript-eslint/consistent-type-definitions": "off" + } + }, { "files": "test/**/*.js", "rules": { @@ -111,10 +117,7 @@ "prettier": true, "rules": { "complexity": "off", - "n/file-extension-in-import": "off", - "unicorn/prefer-code-point": "off", - "unicorn/prefer-switch": "off", - "unicorn/prefer-node-protocol": "off" + "max-depth": "off" } } } diff --git a/readme.md b/readme.md index 9be48d1..a6fd74a 100644 --- a/readme.md +++ b/readme.md @@ -171,9 +171,9 @@ mdast compiler context (TypeScript type). — stack of tokens * `getData` (`(key: string) => unknown`) — get data from the key/value store (see [`CompileData`][api-compiledata]) -* `setData` (`(key: string, value?: unknown) => void`) +* `setData` (`(key: string, value?: unknown) => undefined`) — set data into the key/value store (see [`CompileData`][api-compiledata]) -* `buffer` (`() => void`) +* `buffer` (`() => undefined`) — capture some of the output data * `resume` (`() => string`) — stop capturing and access the output data @@ -251,7 +251,7 @@ Handle a token (TypeScript type). ###### Returns -Nothing (`void`). +Nothing (`undefined`). ### `OnEnterError` @@ -269,7 +269,7 @@ Handle the case where the `right` token is open, but it is closed (by the ###### Returns -Nothing (`void`). +Nothing (`undefined`). ### `OnExitError` @@ -287,7 +287,7 @@ exiting the `left` token (TypeScript type). ###### Returns -Nothing (`void`). +Nothing (`undefined`). ### `Options` diff --git a/test/index.js b/test/index.js index 0704d65..3a16fcc 100644 --- a/test/index.js +++ b/test/index.js @@ -5,37 +5,33 @@ import assert from 'node:assert/strict' import fs from 'node:fs/promises' import test from 'node:test' -import {toHast} from 'mdast-util-to-hast' -import {toString} from 'mdast-util-to-string' +import {commonmark} from 'commonmark.json' import {fromHtml} from 'hast-util-from-html' import {toHtml} from 'hast-util-to-html' -import {commonmark} from 'commonmark.json' +import {toHast} from 'mdast-util-to-hast' +import {toString} from 'mdast-util-to-string' import {fromMarkdown} from '../dev/index.js' -import * as mod from '../dev/index.js' -test('fromMarkdown', () => { - assert.deepEqual( - Object.keys(mod).sort(), - ['fromMarkdown'], - 'should expose the public api' - ) +test('fromMarkdown', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('../dev/index.js')).sort(), [ + 'fromMarkdown' + ]) + }) - assert.deepEqual( - fromMarkdown(''), - { + await t.test('should parse an empty document', async function () { + assert.deepEqual(fromMarkdown(''), { type: 'root', children: [], position: { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 1, offset: 0} } - }, - 'should parse an empty document' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a\nb'), - { + await t.test('should parse a paragraph', async function () { + assert.deepEqual(fromMarkdown('a\nb'), { type: 'root', children: [ { @@ -60,106 +56,51 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 2, column: 2, offset: 3} } - }, - 'should parse a paragraph' - ) + }) + }) - assert.deepEqual( - fromMarkdown(new Uint8Array()), - { + await t.test('should support empty typed arrays', async function () { + assert.deepEqual(fromMarkdown(new Uint8Array()), { type: 'root', children: [], position: { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 1, offset: 0} } - }, - 'should support empty typed arrays' - ) - - assert.equal( - toString(fromMarkdown(new TextEncoder().encode(''))), - 'admin@example.com', - 'should support types arrays' - ) + }) + }) - assert.equal( - toString( - fromMarkdown( - new Uint8Array([0xff, 0xfe, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00]), - 'utf-16le' - ) - ), - 'abc', - 'should support encoding' - ) + await t.test('should support types arrays', async function () { + assert.equal( + toString(fromMarkdown(new TextEncoder().encode(''))), + 'admin@example.com' + ) + }) - assert.deepEqual( - fromMarkdown('a\nb', { - mdastExtensions: [ - { - // `canContainEols` is an array. - canContainEols: ['someType'], - enter: { - lineEnding(token) { - this.enter({type: 'break'}, token) - } - }, - exit: { - lineEnding(token) { - this.exit(token) - } - } - } - ] - }).children[0], - { - type: 'paragraph', - children: [ - { - type: 'text', - value: 'a', - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 2, offset: 1} - } - }, - { - type: 'break', - position: { - start: {line: 1, column: 2, offset: 1}, - end: {line: 2, column: 1, offset: 2} - } - }, - { - type: 'text', - value: 'b', - position: { - start: {line: 2, column: 1, offset: 2}, - end: {line: 2, column: 2, offset: 3} - } - } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 2, column: 2, offset: 3} - } - }, - 'should support extensions' - ) + await t.test('should support encoding', async function () { + assert.equal( + toString( + fromMarkdown( + new Uint8Array([0xff, 0xfe, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00]), + 'utf-16le' + ) + ), + 'abc' + ) + }) - assert.deepEqual( - fromMarkdown('a\nb', { - mdastExtensions: [ - [ + await t.test('should support extensions', async function () { + assert.deepEqual( + fromMarkdown('a\nb', { + mdastExtensions: [ { + // `canContainEols` is an array. + canContainEols: ['someType'], enter: { lineEnding(token) { this.enter({type: 'break'}, token) } - } - }, - { + }, exit: { lineEnding(token) { this.exit(token) @@ -167,168 +108,228 @@ test('fromMarkdown', () => { } } ] - ] - }).children[0], - { - type: 'paragraph', - children: [ - { - type: 'text', - value: 'a', - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 2, offset: 1} - } - }, - { - type: 'break', - position: { - start: {line: 1, column: 2, offset: 1}, - end: {line: 2, column: 1, offset: 2} - } - }, - { - type: 'text', - value: 'b', - position: { - start: {line: 2, column: 1, offset: 2}, - end: {line: 2, column: 2, offset: 3} + }).children[0], + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'a', + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 2, offset: 1} + } + }, + { + type: 'break', + position: { + start: {line: 1, column: 2, offset: 1}, + end: {line: 2, column: 1, offset: 2} + } + }, + { + type: 'text', + value: 'b', + position: { + start: {line: 2, column: 1, offset: 2}, + end: {line: 2, column: 2, offset: 3} + } } + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 2, column: 2, offset: 3} } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 2, column: 2, offset: 3} } - }, - 'should support multiple extensions' - ) + ) + }) - assert.deepEqual( - fromMarkdown('*a*', { - mdastExtensions: [ - { - transforms: [ - function (tree) { - assert(tree.children[0].type === 'paragraph') - tree.children[0].children[0].type = 'strong' - } - ] - } - ] - }).children[0], - { - type: 'paragraph', - children: [ - { - type: 'strong', - children: [ + await t.test('should support multiple extensions', async function () { + assert.deepEqual( + fromMarkdown('a\nb', { + mdastExtensions: [ + [ { - type: 'text', - value: 'a', - position: { - start: {line: 1, column: 2, offset: 1}, - end: {line: 1, column: 3, offset: 2} + enter: { + lineEnding(token) { + this.enter({type: 'break'}, token) + } + } + }, + { + exit: { + lineEnding(token) { + this.exit(token) + } } } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 4, offset: 3} + ] + ] + }).children[0], + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'a', + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 2, offset: 1} + } + }, + { + type: 'break', + position: { + start: {line: 1, column: 2, offset: 1}, + end: {line: 2, column: 1, offset: 2} + } + }, + { + type: 'text', + value: 'b', + position: { + start: {line: 2, column: 1, offset: 2}, + end: {line: 2, column: 2, offset: 3} + } } + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 2, column: 2, offset: 3} } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 4, offset: 3} } - }, - 'should support `transforms` in extensions' - ) + ) + }) - assert.throws( - () => { - fromMarkdown('a', { + await t.test('should support `transforms` in extensions', async function () { + assert.deepEqual( + fromMarkdown('*a*', { mdastExtensions: [ { - enter: { - paragraph(token) { - this.enter({type: 'paragraph', children: []}, token) + transforms: [ + function (tree) { + assert(tree.children[0].type === 'paragraph') + tree.children[0].children[0].type = 'strong' } - }, - exit: {paragraph() {}} + ] } ] - }) - }, - /Cannot close document, a token \(`paragraph`, 1:1-1:2\) is still open/, - 'should crash if a token is opened but not closed' - ) - - assert.throws( - () => { - fromMarkdown('a', { - mdastExtensions: [ + }).children[0], + { + type: 'paragraph', + children: [ { - enter: { - paragraph(token) { - this.exit(token) + type: 'strong', + children: [ + { + type: 'text', + value: 'a', + position: { + start: {line: 1, column: 2, offset: 1}, + end: {line: 1, column: 3, offset: 2} + } } + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 4, offset: 3} } } - ] - }) - }, - /Cannot close `paragraph` \(1:1-1:2\): it’s not open/, - 'should crash when closing a token that isn’t open' + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 4, offset: 3} + } + } + ) + }) + + await t.test( + 'should crash if a token is opened but not closed', + async function () { + assert.throws(function () { + fromMarkdown('a', { + mdastExtensions: [ + { + enter: { + paragraph(token) { + this.enter({type: 'paragraph', children: []}, token) + } + }, + exit: {paragraph() {}} + } + ] + }) + }, /Cannot close document, a token \(`paragraph`, 1:1-1:2\) is still open/) + } ) - assert.throws( - () => { - fromMarkdown('a', { - mdastExtensions: [ - { - exit: { - paragraph(token) { - this.exit(Object.assign({}, token, {type: 'lol'})) + await t.test( + 'should crash when closing a token that isn’t open', + async function () { + assert.throws(function () { + fromMarkdown('a', { + mdastExtensions: [ + { + enter: { + paragraph(token) { + this.exit(token) + } } } - } - ] - }) - }, - /Cannot close `lol` \(1:1-1:2\): a different token \(`paragraph`, 1:1-1:2\) is open/, - 'should crash when closing a token when a different one is open' + ] + }) + }, /Cannot close `paragraph` \(1:1-1:2\): it’s not open/) + } ) - assert.throws( - () => { - fromMarkdown('a', { - mdastExtensions: [ - { - exit: { - paragraph(token) { - this.exit( - Object.assign({}, token, {type: 'lol'}), - function (a, b) { - assert.equal(a.type, 'lol') - assert.equal(b.type, 'paragraph') - throw new Error('problem') - } - ) + await t.test( + 'should crash when closing a token when a different one is open', + async function () { + assert.throws(function () { + fromMarkdown('a', { + mdastExtensions: [ + { + exit: { + paragraph(token) { + this.exit(Object.assign({}, token, {type: 'lol'})) + } } } - } - ] - }) - }, - /problem/, - 'should crash when closing a token when a different one is open with a custom handler' + ] + }) + }, /Cannot close `lol` \(1:1-1:2\): a different token \(`paragraph`, 1:1-1:2\) is open/) + } + ) + + await t.test( + 'should crash when closing a token when a different one is open with a custom handler', + async function () { + assert.throws(function () { + fromMarkdown('a', { + mdastExtensions: [ + { + exit: { + paragraph(token) { + this.exit( + Object.assign({}, token, {type: 'lol'}), + function (a, b) { + assert.equal(a.type, 'lol') + assert.equal(b.type, 'paragraph') + throw new Error('problem') + } + ) + } + } + } + ] + }) + }, /problem/) + } ) - assert.deepEqual( - fromMarkdown('').children[0], - { + await t.test('should parse an autolink (protocol)', async function () { + assert.deepEqual(fromMarkdown('').children[0], { type: 'paragraph', children: [ { @@ -355,13 +356,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 10, offset: 9} } - }, - 'should parse an autolink (protocol)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('').children[0], - { + await t.test('should parse an autolink (email)', async function () { + assert.deepEqual(fromMarkdown('').children[0], { type: 'paragraph', children: [ { @@ -388,13 +387,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 11, offset: 10} } - }, - 'should parse an autolink (email)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('> a').children[0], - { + await t.test('should parse a block quote', async function () { + assert.deepEqual(fromMarkdown('> a').children[0], { type: 'blockquote', children: [ { @@ -419,13 +416,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 4, offset: 3} } - }, - 'should parse a block quote' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a\\*b').children[0], - { + await t.test('should parse a character escape', async function () { + assert.deepEqual(fromMarkdown('a\\*b').children[0], { type: 'paragraph', children: [ { @@ -441,13 +436,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 5, offset: 4} } - }, - 'should parse a character escape' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a&b').children[0], - { + await t.test('should parse a character reference', async function () { + assert.deepEqual(fromMarkdown('a&b').children[0], { type: 'paragraph', children: [ { @@ -463,13 +456,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 8, offset: 7} } - }, - 'should parse a character reference' - ) + }) + }) - assert.deepEqual( - fromMarkdown('```a b\nc\n```').children[0], - { + await t.test('should parse code (fenced)', async function () { + assert.deepEqual(fromMarkdown('```a b\nc\n```').children[0], { type: 'code', lang: 'a', meta: 'b', @@ -478,13 +469,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 3, column: 4, offset: 12} } - }, - 'should parse code (fenced)' - ) + }) + }) - assert.deepEqual( - fromMarkdown(' a').children[0], - { + await t.test('should parse code (indented)', async function () { + assert.deepEqual(fromMarkdown(' a').children[0], { type: 'code', lang: null, meta: null, @@ -493,13 +482,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 6, offset: 5} } - }, - 'should parse code (indented)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('`a`').children[0], - { + await t.test('should parse code (text)', async function () { + assert.deepEqual(fromMarkdown('`a`').children[0], { type: 'paragraph', children: [ { @@ -515,13 +502,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 4, offset: 3} } - }, - 'should parse code (text)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('[a]: b "c"').children[0], - { + await t.test('should parse a definition', async function () { + assert.deepEqual(fromMarkdown('[a]: b "c"').children[0], { type: 'definition', identifier: 'a', label: 'a', @@ -531,13 +516,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 11, offset: 10} } - }, - 'should parse a definition' - ) + }) + }) - assert.deepEqual( - fromMarkdown('*a*').children[0], - { + await t.test('should parse emphasis', async function () { + assert.deepEqual(fromMarkdown('*a*').children[0], { type: 'paragraph', children: [ { @@ -562,13 +545,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 4, offset: 3} } - }, - 'should parse emphasis' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a\\\nb').children[0], - { + await t.test('should parse a hard break (escape)', async function () { + assert.deepEqual(fromMarkdown('a\\\nb').children[0], { type: 'paragraph', children: [ { @@ -599,13 +580,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 2, column: 2, offset: 4} } - }, - 'should parse a hard break (escape)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a \nb').children[0], - { + await t.test('should parse a hard break (prefix)', async function () { + assert.deepEqual(fromMarkdown('a \nb').children[0], { type: 'paragraph', children: [ { @@ -636,13 +615,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 2, column: 2, offset: 5} } - }, - 'should parse a hard break (prefix)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('## a').children[0], - { + await t.test('should parse a heading (atx)', async function () { + assert.deepEqual(fromMarkdown('## a').children[0], { type: 'heading', depth: 2, children: [ @@ -659,13 +636,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 5, offset: 4} } - }, - 'should parse a heading (atx)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('a\n=').children[0], - { + await t.test('should parse a heading (setext)', async function () { + assert.deepEqual(fromMarkdown('a\n=').children[0], { type: 'heading', depth: 1, children: [ @@ -682,26 +657,22 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 2, column: 2, offset: 3} } - }, - 'should parse a heading (setext)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('\nb\n').children[0], - { + await t.test('should parse html (flow)', async function () { + assert.deepEqual(fromMarkdown('\nb\n').children[0], { type: 'html', value: '\nb\n', position: { start: {line: 1, column: 1, offset: 0}, end: {line: 3, column: 5, offset: 10} } - }, - 'should parse html (flow)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('b').children[0], - { + await t.test('should parse html (text)', async function () { + assert.deepEqual(fromMarkdown('b').children[0], { type: 'paragraph', children: [ { @@ -733,13 +704,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 9, offset: 8} } - }, - 'should parse html (text)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('![a]\n\n[a]: b').children[0], - { + await t.test('should parse an image (shortcut reference)', async function () { + assert.deepEqual(fromMarkdown('![a]\n\n[a]: b').children[0], { type: 'paragraph', children: [ { @@ -758,38 +727,37 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 5, offset: 4} } - }, - 'should parse an image (shortcut reference)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('![a][]\n\n[a]: b').children[0], - { - type: 'paragraph', - children: [ - { - type: 'imageReference', - identifier: 'a', - label: 'a', - referenceType: 'collapsed', - alt: 'a', - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 7, offset: 6} + await t.test( + 'should parse an image (collapsed reference)', + async function () { + assert.deepEqual(fromMarkdown('![a][]\n\n[a]: b').children[0], { + type: 'paragraph', + children: [ + { + type: 'imageReference', + identifier: 'a', + label: 'a', + referenceType: 'collapsed', + alt: 'a', + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 7, offset: 6} + } } + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 7, offset: 6} } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 7, offset: 6} - } - }, - 'should parse an image (collapsed reference)' + }) + } ) - assert.deepEqual( - fromMarkdown('![a][b]\n\n[b]: c').children[0], - { + await t.test('should parse an image (full reference)', async function () { + assert.deepEqual(fromMarkdown('![a][b]\n\n[b]: c').children[0], { type: 'paragraph', children: [ { @@ -808,13 +776,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 8, offset: 7} } - }, - 'should parse an image (full reference)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('![a](b "c")').children[0], - { + await t.test('should parse an image (resource)', async function () { + assert.deepEqual(fromMarkdown('![a](b "c")').children[0], { type: 'paragraph', children: [ { @@ -832,13 +798,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 12, offset: 11} } - }, - 'should parse an image (resource)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('[a]\n\n[a]: b').children[0], - { + await t.test('should parse a link (shortcut reference)', async function () { + assert.deepEqual(fromMarkdown('[a]\n\n[a]: b').children[0], { type: 'paragraph', children: [ { @@ -866,13 +830,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 4, offset: 3} } - }, - 'should parse a link (shortcut reference)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('[a][]\n\n[a]: b').children[0], - { + await t.test('should parse a link (collapsed reference)', async function () { + assert.deepEqual(fromMarkdown('[a][]\n\n[a]: b').children[0], { type: 'paragraph', children: [ { @@ -900,47 +862,46 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 6, offset: 5} } - }, - 'should parse a link (collapsed reference)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('[`a`][]\n\n[`a`]: b').children[0], - { - type: 'paragraph', - children: [ - { - type: 'linkReference', - children: [ - { - type: 'inlineCode', - value: 'a', - position: { - start: {line: 1, column: 2, offset: 1}, - end: {line: 1, column: 5, offset: 4} + await t.test( + 'should parse a link (collapsed reference) with inline code in the label', + async function () { + assert.deepEqual(fromMarkdown('[`a`][]\n\n[`a`]: b').children[0], { + type: 'paragraph', + children: [ + { + type: 'linkReference', + children: [ + { + type: 'inlineCode', + value: 'a', + position: { + start: {line: 1, column: 2, offset: 1}, + end: {line: 1, column: 5, offset: 4} + } } - } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 8, offset: 7} - }, - identifier: '`a`', - label: '`a`', - referenceType: 'collapsed' + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 8, offset: 7} + }, + identifier: '`a`', + label: '`a`', + referenceType: 'collapsed' + } + ], + position: { + start: {line: 1, column: 1, offset: 0}, + end: {line: 1, column: 8, offset: 7} } - ], - position: { - start: {line: 1, column: 1, offset: 0}, - end: {line: 1, column: 8, offset: 7} - } - }, - 'should parse a link (collapsed reference) with inline code in the label' + }) + } ) - assert.deepEqual( - fromMarkdown('[a][b]\n\n[b]: c').children[0], - { + await t.test('should parse a link (full reference)', async function () { + assert.deepEqual(fromMarkdown('[a][b]\n\n[b]: c').children[0], { type: 'paragraph', children: [ { @@ -968,13 +929,11 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 7, offset: 6} } - }, - 'should parse a link (full reference)' - ) + }) + }) - assert.deepEqual( - fromMarkdown('[a](b "c")').children[0], - { + await t.test('should parse a link (resource)', async function () { + assert.deepEqual(fromMarkdown('[a](b "c")').children[0], { type: 'paragraph', children: [ { @@ -1001,15 +960,13 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 11, offset: 10} } - }, - 'should parse a link (resource)' - ) + }) + }) - // List. + await t.test('should parse strong', async function () { + // List. - assert.deepEqual( - fromMarkdown('**a**').children[0], - { + assert.deepEqual(fromMarkdown('**a**').children[0], { type: 'paragraph', children: [ { @@ -1034,24 +991,21 @@ test('fromMarkdown', () => { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 6, offset: 5} } - }, - 'should parse strong' - ) + }) + }) - assert.deepEqual( - fromMarkdown('***').children[0], - { + await t.test('should parse a thematic break', async function () { + assert.deepEqual(fromMarkdown('***').children[0], { type: 'thematicBreak', position: { start: {line: 1, column: 1, offset: 0}, end: {line: 1, column: 4, offset: 3} } - }, - 'should parse a thematic break' - ) + }) + }) }) -test('fixtures', async () => { +test('fixtures', async function (t) { const base = new URL('fixtures/', import.meta.url) const files = await fs.readdir(base) @@ -1065,49 +1019,46 @@ test('fixtures', async () => { } const stem = file.split('.').slice(0, -1).join('.') - const fp = new URL(stem + '.json', base) - const doc = await fs.readFile(new URL(file, base)) - const actual = fromMarkdown(doc) - /** @type {Root} */ - let expected - try { - expected = JSON.parse(String(await fs.readFile(fp))) - } catch { - // New fixture. - expected = actual - await fs.writeFile(fp, JSON.stringify(actual, null, 2) + '\n') - } + await t.test(stem, async function () { + const fp = new URL(stem + '.json', base) + const doc = await fs.readFile(new URL(file, base)) + const actual = fromMarkdown(doc) + /** @type {Root} */ + let expected + + try { + expected = JSON.parse(String(await fs.readFile(fp))) + } catch { + // New fixture. + expected = actual + await fs.writeFile(fp, JSON.stringify(actual, undefined, 2) + '\n') + } - assert.deepEqual(actual, expected, stem) + assert.deepEqual(actual, expected, stem) + }) } }) -test('commonmark', () => { +test('commonmark', async function (t) { let index = -1 - // To do: update micromark. - // Changes in living version of CommonMark. - const skip = new Set([623, 624]) - while (++index < commonmark.length) { - if (skip.has(index)) { - continue - } - const example = commonmark[index] - const input = example.markdown.slice(0, -1) - const output = example.html.slice(0, -1) - const mdast = fromMarkdown(input) - const hast = toHast(mdast, {allowDangerousHtml: true}) - assert(hast && hast.type === 'root', 'expected `root`') - const actual = toHtml(hast, {allowDangerousHtml: true}) + await t.test(example.section + ' (' + index + ')', async function () { + const input = example.markdown.slice(0, -1) + const output = example.html.slice(0, -1) - assert.equal( - toHtml(fromHtml(actual, {fragment: true})), - toHtml(fromHtml(output, {fragment: true})), - example.section + ' (' + index + ')' - ) + const mdast = fromMarkdown(input) + const hast = toHast(mdast, {allowDangerousHtml: true}) + assert(hast && hast.type === 'root', 'expected `root`') + const actual = toHtml(hast, {allowDangerousHtml: true}) + + assert.equal( + toHtml(fromHtml(actual, {fragment: true})), + toHtml(fromHtml(output, {fragment: true})) + ) + }) } })