From 63e6ef9baf46f37b5d72cb0b656bfd1eb4fdf862 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 12 Sep 2023 17:24:01 +0200 Subject: [PATCH] Refactor code-style --- .gitignore | 10 +- packages/remark-cli/cli.js | 17 +- packages/remark-parse/index.d.ts | 25 +- packages/remark-parse/index.js | 1 + packages/remark-parse/lib/index.js | 59 ++-- packages/remark-parse/package.json | 15 +- packages/remark-stringify/index.d.ts | 25 +- packages/remark-stringify/index.js | 1 + packages/remark-stringify/lib/index.js | 58 ++-- packages/remark-stringify/package.json | 15 +- packages/remark/index.js | 6 +- packages/remark/readme.md | 2 +- readme.md | 4 +- test.js | 422 ++++++++++++++----------- 14 files changed, 412 insertions(+), 248 deletions(-) diff --git a/.gitignore b/.gitignore index 47d8f5c78..9723afad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,8 @@ coverage/ node_modules/ -packages/remark/*.d.ts -packages/remark-cli/*.d.ts -packages/remark-parse/lib/*.d.ts -packages/remark-parse/test.d.ts -packages/remark-stringify/lib/*.d.ts -packages/remark-stringify/test.d.ts -test.d.ts .DS_Store +*.d.ts *.log yarn.lock +!/packages/remark-parse/index.d.ts +!/packages/remark-stringify/index.d.ts diff --git a/packages/remark-cli/cli.js b/packages/remark-cli/cli.js index 96a2fa0d8..c95f9bcd5 100755 --- a/packages/remark-cli/cli.js +++ b/packages/remark-cli/cli.js @@ -1,4 +1,5 @@ #!/usr/bin/env node + /** * @typedef Pack * @property {string} name @@ -25,16 +26,16 @@ const cli = JSON.parse( ) args({ - processor: remark, - name: proc.name, description: cli.description, + extensions: markdownExtensions, + ignoreName: '.' + proc.name + 'ignore', + name: proc.name, + packageField: proc.name + 'Config', + pluginPrefix: proc.name, + processor: remark, + rcName: '.' + proc.name + 'rc', version: [ proc.name + ': ' + proc.version, cli.name + ': ' + cli.version - ].join(', '), - pluginPrefix: proc.name, - packageField: proc.name + 'Config', - rcName: '.' + proc.name + 'rc', - ignoreName: '.' + proc.name + 'ignore', - extensions: markdownExtensions + ].join(', ') }) diff --git a/packages/remark-parse/index.d.ts b/packages/remark-parse/index.d.ts index 6d5c1031a..ff969de7a 100644 --- a/packages/remark-parse/index.d.ts +++ b/packages/remark-parse/index.d.ts @@ -1,9 +1,28 @@ -// This wrapper exists because JS in TS can’t export a `@type` of a function. import type {Root} from 'mdast' import type {Plugin} from 'unified' import type {Options} from './lib/index.js' -declare const remarkParse: Plugin<[(Options | undefined)?], string, Root> +export type {Options} from './lib/index.js' + +/** + * Add support for parsing from markdown. + * + * @this + * Unified processor. + * @param + * Configuration (optional). + * @returns + * Nothing. + */ +declare const remarkParse: Plugin< + [(Readonly | null | undefined)?], + string, + Root +> export default remarkParse -export type {Options} from './lib/index.js' +// To do: register types. +// // Add custom settings supported when `remark-parse` is added. +// declare module 'unified' { +// interface Settings extends Options {} +// } diff --git a/packages/remark-parse/index.js b/packages/remark-parse/index.js index dab322b23..6a4c25d60 100644 --- a/packages/remark-parse/index.js +++ b/packages/remark-parse/index.js @@ -1 +1,2 @@ +// Note: types exposed from `index.d.ts`. export {default} from './lib/index.js' diff --git a/packages/remark-parse/lib/index.js b/packages/remark-parse/lib/index.js index 264cc9423..837a7ba0f 100644 --- a/packages/remark-parse/lib/index.js +++ b/packages/remark-parse/lib/index.js @@ -1,33 +1,52 @@ /** * @typedef {import('mdast').Root} Root - * @typedef {import('mdast-util-from-markdown').Options} Options + * @typedef {import('mdast-util-from-markdown').Options} FromMarkdownOptions + * @typedef {import('unified').Parser} Parser + * @typedef {import('unified').Processor} Processor + */ + +/** + * @typedef {Omit} Options */ import {fromMarkdown} from 'mdast-util-from-markdown' /** - * @this {import('unified').Processor} - * @type {import('unified').Plugin<[Options?] | void[], string, Root>} + * Aadd support for parsing from markdown. + * + * @param {Readonly | null | undefined} [options] + * Configuration (optional). + * @returns {undefined} + * Nothing. */ export default function remarkParse(options) { - /** @type {import('unified').Parser} */ - const parser = (doc) => { + /** @type {Processor} */ + // @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly. + const self = this + + self.parser = parser + + /** + * @type {Parser} + */ + function parser(doc) { + // To do: remove cast when typed. // Assume options. - const settings = /** @type {Options} */ (this.data('settings')) + const settings = /** @type {Options} */ (self.data('settings')) - return fromMarkdown( - doc, - Object.assign({}, settings, options, { - // Note: these options are not in the readme. - // The goal is for them to be set by plugins on `data` instead of being - // passed by users. - // @ts-expect-error: to do: type. - extensions: this.data('micromarkExtensions') || [], - // @ts-expect-error: to do: type. - mdastExtensions: this.data('fromMarkdownExtensions') || [] - }) - ) - } + /** @type {FromMarkdownOptions} */ + const resolvedOptions = { + ...settings, + ...options, + // Note: these options are not in the readme. + // The goal is for them to be set by plugins on `data` instead of being + // passed by users. + // @ts-expect-error: to do: type. + extensions: self.data('micromarkExtensions') || [], + // @ts-expect-error: to do: type. + mdastExtensions: self.data('fromMarkdownExtensions') || [] + } - Object.assign(this, {Parser: parser}) + return fromMarkdown(doc, resolvedOptions) + } } diff --git a/packages/remark-parse/package.json b/packages/remark-parse/package.json index a3161cebc..6d400606d 100644 --- a/packages/remark-parse/package.json +++ b/packages/remark-parse/package.json @@ -53,6 +53,19 @@ "strict": true }, "xo": { - "prettier": true + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "rules": { + "@typescript-eslint/ban-types": "off" + } + } + ], + "prettier": true, + "rules": { + "unicorn/no-this-assignment": "off" + } } } diff --git a/packages/remark-stringify/index.d.ts b/packages/remark-stringify/index.d.ts index ac355a855..5db000009 100644 --- a/packages/remark-stringify/index.d.ts +++ b/packages/remark-stringify/index.d.ts @@ -1,9 +1,28 @@ -// This wrapper exists because JS in TS can’t export a `@type` of a function. import type {Root} from 'mdast' import type {Plugin} from 'unified' import type {Options} from './lib/index.js' -declare const remarkStringify: Plugin<[(Options | undefined)?], Root, string> +export type {Options} from './lib/index.js' + +/** + * Add support for serializing as HTML. + * + * @this + * Unified processor. + * @param + * Configuration (optional). + * @returns + * Nothing. + */ +declare const remarkStringify: Plugin< + [(Readonly | null | undefined)?], + Root, + string +> export default remarkStringify -export type {Options} from './lib/index.js' +// To do: register types. +// // Add custom settings supported when `remark-stringify` is added. +// declare module 'unified' { +// interface Settings extends Options {} +// } diff --git a/packages/remark-stringify/index.js b/packages/remark-stringify/index.js index dab322b23..6a4c25d60 100644 --- a/packages/remark-stringify/index.js +++ b/packages/remark-stringify/index.js @@ -1 +1,2 @@ +// Note: types exposed from `index.d.ts`. export {default} from './lib/index.js' diff --git a/packages/remark-stringify/lib/index.js b/packages/remark-stringify/lib/index.js index a0027bafa..da045d440 100644 --- a/packages/remark-stringify/lib/index.js +++ b/packages/remark-stringify/lib/index.js @@ -1,36 +1,50 @@ /** - * @typedef {import('mdast').Root|import('mdast').Content} Node + * @typedef {import('mdast').Root} Root * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownOptions + * @typedef {import('unified').Compiler} Compiler + * @typedef {import('unified').Processor} Processor + */ + +/** * @typedef {Omit} Options */ import {toMarkdown} from 'mdast-util-to-markdown' /** - * @this {import('unified').Processor} - * @type {import('unified').Plugin<[Options?]|void[], Node, string>} + * Add support for serializing as markdown. + * + * @param {Readonly | null | undefined} [options] + * Configuration (optional). + * @returns {undefined} + * Nothing. */ export default function remarkStringify(options) { - /** @type {import('unified').Compiler} */ - const compiler = (tree) => { + /** @type {Processor} */ + // @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly. + const self = this + + self.compiler = compiler + + /** + * @type {Compiler} + */ + function compiler(tree) { + // To do: remove cast when typed. // Assume options. - const settings = /** @type {Options} */ (this.data('settings')) + const settings = /** @type {Options} */ (self.data('settings')) - return toMarkdown( - tree, - Object.assign({}, settings, options, { - // Note: this option is not in the readme. - // The goal is for it to be set by plugins on `data` instead of being - // passed by users. - extensions: - // @ts-expect-error: to do: type. - /** @type {ToMarkdownOptions['extensions']} */ ( - // @ts-expect-error: to do: type. - this.data('toMarkdownExtensions') - ) || [] - }) - ) - } + /** @type {ToMarkdownOptions} */ + const resolvedOptions = { + ...settings, + ...options, + // Note: this option is not in the readme. + // The goal is for it to be set by plugins on `data` instead of being + // passed by users. + // @ts-expect-error: to do: type. + extensions: self.data('toMarkdownExtensions') || [] + } - Object.assign(this, {Compiler: compiler}) + return toMarkdown(tree, resolvedOptions) + } } diff --git a/packages/remark-stringify/package.json b/packages/remark-stringify/package.json index 971a0e762..ed59acc26 100644 --- a/packages/remark-stringify/package.json +++ b/packages/remark-stringify/package.json @@ -53,6 +53,19 @@ "strict": true }, "xo": { - "prettier": true + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "rules": { + "@typescript-eslint/ban-types": "off" + } + } + ], + "prettier": true, + "rules": { + "unicorn/no-this-assignment": "off" + } } } diff --git a/packages/remark/index.js b/packages/remark/index.js index 1cb80ce75..d403eeb30 100644 --- a/packages/remark/index.js +++ b/packages/remark/index.js @@ -1,5 +1,9 @@ -import {unified} from 'unified' import remarkParse from 'remark-parse' import remarkStringify from 'remark-stringify' +import {unified} from 'unified' +/** + * Create a new unified processor that already uses `remark-parse` and + * `remark-stringify`. + */ export const remark = unified().use(remarkParse).use(remarkStringify).freeze() diff --git a/packages/remark/readme.md b/packages/remark/readme.md index b1b8d7a7e..54667069a 100644 --- a/packages/remark/readme.md +++ b/packages/remark/readme.md @@ -175,7 +175,7 @@ main() async function main() { const file = await remark() - .data('settings', {bullet: '*', setext: true, listItemIndent: 'one'}) + .data('settings', {bullet: '*', listItemIndent: 'one', setext: true}) .process('# Moons of Neptune\n\n- Naiad\n- Thalassa\n- Despine\n- …') console.log(String(file)) diff --git a/readme.md b/readme.md index ade2def97..10f8f8e5e 100644 --- a/readme.md +++ b/readme.md @@ -114,8 +114,8 @@ console.log(String(file)) // => '## Hi, Saturn!' /** @type {import('unified').Plugin<[], import('mdast').Root>} */ function myRemarkPluginToIncreaseHeadings() { - return (tree) => { - visit(tree, (node) => { + return function (tree) { + visit(tree, function (node) { if (node.type === 'heading') { node.depth++ } diff --git a/test.js b/test.js index c99f64360..af159f04d 100644 --- a/test.js +++ b/test.js @@ -13,30 +13,33 @@ import {removePosition} from 'unist-util-remove-position' const exec = promisify(execCb) -test('remark', () => { - assert.equal( - remark().processSync('*foo*').toString(), - '*foo*\n', - 'should parse and stringify a file' - ) +test('remark', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('remark')).sort(), ['remark']) + }) - assert.equal( - remark() - // @ts-expect-error: to do: type settings. - .data('settings', {closeAtx: true}) - .processSync('# foo') - .toString(), - '# foo #\n', - 'should accept stringify options' - ) + await t.test('should process a file', async function () { + assert.equal(remark().processSync('*foo*').toString(), '*foo*\n') + }) + + await t.test('should accept settings', async function () { + assert.equal( + remark() + // @ts-expect-error: to do: type settings. + .data('settings', {closeAtx: true}) + .processSync('# foo') + .toString(), + '# foo #\n' + ) + }) }) -test('remark-cli', async (t) => { +test('remark-cli', async function (t) { const bin = fileURLToPath( new URL('packages/remark-cli/cli.js', import.meta.url) ) - await t.test('should show help on `--help`', async () => { + await t.test('should show help on `--help`', async function () { const result = await exec(bin + ' --help') assert.equal( @@ -90,7 +93,7 @@ test('remark-cli', async (t) => { ) }) - await t.test('should show version on `--version`', async () => { + await t.test('should show version on `--version`', async function () { const result = await exec(bin + ' --version') assert.match(result.stdout, /remark: \d+\.\d+\.\d+/) @@ -98,14 +101,43 @@ test('remark-cli', async (t) => { }) }) -test('remark-parse', async (t) => { - assert.equal( - unified().use(remarkParse).parse('Alfred').children.length, - 1, - 'should accept a `string`' - ) +test('remark-parse', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('remark-parse')).sort(), [ + 'default' + ]) + }) - await t.test('extensions', () => { + await t.test('should parse', async function () { + assert.deepEqual(unified().use(remarkParse).parse('Alfred'), { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'Alfred', + 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} + } + }) + }) + + await t.test('should support extensions', function () { const tree = unified() // @ts-expect-error: to do: type settings. .data('micromarkExtensions', [gfm()]) @@ -116,17 +148,172 @@ test('remark-parse', async (t) => { removePosition(tree, {force: true}) - assert.deepEqual( - tree, - { + assert.deepEqual(tree, { + type: 'root', + children: [ + { + type: 'list', + ordered: false, + start: null, + spread: false, + children: [ + { + type: 'listItem', + spread: false, + checked: true, + children: [ + { + type: 'paragraph', + children: [ + { + type: 'link', + title: null, + url: 'mailto:contact@example.com', + children: [{type: 'text', value: 'contact@example.com'}] + }, + {type: 'text', value: ' '}, + { + type: 'delete', + children: [{type: 'text', value: 'strikethrough'}] + } + ] + } + ] + } + ] + } + ] + }) + }) +}) + +test('remark-stringify', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('remark-stringify')).sort(), [ + 'default' + ]) + }) + + await t.test('should serialize', async function () { + assert.equal( + unified() + .use(remarkStringify) + .stringify({ + type: 'root', + children: [ + {type: 'paragraph', children: [{type: 'text', value: 'Alfred'}]} + ] + }), + 'Alfred\n' + ) + }) + + await t.test('should support extensions', async function () { + const result = unified() + // @ts-expect-error: to do: type settings. + .data('toMarkdownExtensions', [gfmToMarkdown()]) + .use(remarkStringify) + .stringify({ type: 'root', children: [ + { + type: 'heading', + depth: 1, + children: [{type: 'text', value: 'GFM'}] + }, + { + type: 'heading', + depth: 2, + children: [{type: 'text', value: 'Autolink literals'}] + }, + { + type: 'paragraph', + children: [ + { + type: 'link', + title: null, + url: 'http://www.example.com', + children: [{type: 'text', value: 'www.example.com'}] + }, + {type: 'text', value: ', '}, + { + type: 'link', + title: null, + url: 'https://example.com', + children: [{type: 'text', value: 'https://example.com'}] + }, + {type: 'text', value: ', and '}, + { + type: 'link', + title: null, + url: 'mailto:contact@example.com', + children: [{type: 'text', value: 'contact@example.com'}] + }, + {type: 'text', value: '.'} + ] + }, + { + type: 'heading', + depth: 2, + children: [{type: 'text', value: 'Strikethrough'}] + }, + { + type: 'paragraph', + children: [ + { + type: 'delete', + children: [{type: 'text', value: 'one'}] + }, + {type: 'text', value: ' or '}, + { + type: 'delete', + children: [{type: 'text', value: 'two'}] + }, + {type: 'text', value: ' tildes.'} + ] + }, + { + type: 'heading', + depth: 2, + children: [{type: 'text', value: 'Table'}] + }, + { + type: 'table', + align: [null, 'left', 'right', 'center'], + children: [ + { + type: 'tableRow', + children: [ + {type: 'tableCell', children: [{type: 'text', value: 'a'}]}, + {type: 'tableCell', children: [{type: 'text', value: 'b'}]}, + {type: 'tableCell', children: [{type: 'text', value: 'c'}]}, + {type: 'tableCell', children: [{type: 'text', value: 'd'}]} + ] + } + ] + }, + { + type: 'heading', + depth: 2, + children: [{type: 'text', value: 'Tasklist'}] + }, { type: 'list', ordered: false, - start: null, + start: undefined, spread: false, children: [ + { + type: 'listItem', + spread: false, + checked: false, + children: [ + { + type: 'paragraph', + children: [{type: 'text', value: 'to do'}] + } + ] + }, { type: 'listItem', spread: false, @@ -134,162 +321,39 @@ test('remark-parse', async (t) => { children: [ { type: 'paragraph', - children: [ - { - type: 'link', - title: null, - url: 'mailto:contact@example.com', - children: [{type: 'text', value: 'contact@example.com'}] - }, - {type: 'text', value: ' '}, - { - type: 'delete', - children: [{type: 'text', value: 'strikethrough'}] - } - ] + children: [{type: 'text', value: 'done'}] } ] } ] } ] - }, - 'should work' + }) + + assert.equal( + result, + [ + '# GFM', + '', + '## Autolink literals', + '', + '[www.example.com](http://www.example.com), , and .', + '', + '## Strikethrough', + '', + '~~one~~ or ~~two~~ tildes.', + '', + '## Table', + '', + '| a | b | c | d |', + '| - | :- | -: | :-: |', + '', + '## Tasklist', + '', + '* [ ] to do', + '* [x] done', + '' + ].join('\n') ) }) }) - -test('remark-stringify', async () => { - const doc = unified() - // @ts-expect-error: to do: type settings. - .data('toMarkdownExtensions', [gfmToMarkdown()]) - .use(remarkStringify) - .stringify({ - type: 'root', - children: [ - {type: 'heading', depth: 1, children: [{type: 'text', value: 'GFM'}]}, - { - type: 'heading', - depth: 2, - children: [{type: 'text', value: 'Autolink literals'}] - }, - { - type: 'paragraph', - children: [ - { - type: 'link', - title: null, - url: 'http://www.example.com', - children: [{type: 'text', value: 'www.example.com'}] - }, - {type: 'text', value: ', '}, - { - type: 'link', - title: null, - url: 'https://example.com', - children: [{type: 'text', value: 'https://example.com'}] - }, - {type: 'text', value: ', and '}, - { - type: 'link', - title: null, - url: 'mailto:contact@example.com', - children: [{type: 'text', value: 'contact@example.com'}] - }, - {type: 'text', value: '.'} - ] - }, - { - type: 'heading', - depth: 2, - children: [{type: 'text', value: 'Strikethrough'}] - }, - { - type: 'paragraph', - children: [ - { - type: 'delete', - children: [{type: 'text', value: 'one'}] - }, - {type: 'text', value: ' or '}, - { - type: 'delete', - children: [{type: 'text', value: 'two'}] - }, - {type: 'text', value: ' tildes.'} - ] - }, - {type: 'heading', depth: 2, children: [{type: 'text', value: 'Table'}]}, - { - type: 'table', - align: [null, 'left', 'right', 'center'], - children: [ - { - type: 'tableRow', - children: [ - {type: 'tableCell', children: [{type: 'text', value: 'a'}]}, - {type: 'tableCell', children: [{type: 'text', value: 'b'}]}, - {type: 'tableCell', children: [{type: 'text', value: 'c'}]}, - {type: 'tableCell', children: [{type: 'text', value: 'd'}]} - ] - } - ] - }, - { - type: 'heading', - depth: 2, - children: [{type: 'text', value: 'Tasklist'}] - }, - { - type: 'list', - ordered: false, - start: undefined, - spread: false, - children: [ - { - type: 'listItem', - spread: false, - checked: false, - children: [ - {type: 'paragraph', children: [{type: 'text', value: 'to do'}]} - ] - }, - { - type: 'listItem', - spread: false, - checked: true, - children: [ - {type: 'paragraph', children: [{type: 'text', value: 'done'}]} - ] - } - ] - } - ] - }) - - assert.equal( - doc, - [ - '# GFM', - '', - '## Autolink literals', - '', - '[www.example.com](http://www.example.com), , and .', - '', - '## Strikethrough', - '', - '~~one~~ or ~~two~~ tildes.', - '', - '## Table', - '', - '| a | b | c | d |', - '| - | :- | -: | :-: |', - '', - '## Tasklist', - '', - '* [ ] to do', - '* [x] done', - '' - ].join('\n') - ) -})