diff --git a/.gitignore b/.gitignore index 53a29e3..4fe7736 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ coverage/ node_modules/ .DS_Store -*.d.ts +index.d.ts +test.d.ts *.log yarn.lock diff --git a/complex-types.d.ts b/complex-types.d.ts new file mode 100644 index 0000000..5f3a4b9 --- /dev/null +++ b/complex-types.d.ts @@ -0,0 +1,35 @@ +import type {Parent} from 'unist' +import type {PhrasingContent, BlockContent} from 'mdast' + +type DirectiveAttributes = Record + +interface DirectiveFields { + name: string + attributes?: DirectiveAttributes +} + +export interface TextDirective extends Parent, DirectiveFields { + type: 'textDirective' + children: PhrasingContent[] +} + +export interface LeafDirective extends Parent, DirectiveFields { + type: 'leafDirective' + children: PhrasingContent[] +} + +export interface ContainerDirective extends Parent, DirectiveFields { + type: 'containerDirective' + children: BlockContent[] +} + +declare module 'mdast' { + interface StaticPhrasingContentMap { + textDirective: TextDirective + } + + interface BlockContentMap { + containerDirective: ContainerDirective + leafDirective: LeafDirective + } +} diff --git a/index.js b/index.js index 67787c6..1dfc5cd 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,17 @@ /** + * @typedef {import('mdast').BlockContent} BlockContent + * @typedef {import('mdast').Paragraph} Paragraph * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-to-markdown/lib/types.js').Node} Node - * @typedef {import('mdast-util-to-markdown/lib/types.js').Parent} Parent + * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext + * @typedef {import('mdast-util-from-markdown').Token} Token * @typedef {import('mdast-util-to-markdown/lib/types.js').Handle} ToMarkdownHandle * @typedef {import('mdast-util-to-markdown/lib/types.js').Context} Context * @typedef {import('mdast-util-to-markdown/lib/types.js').Options} ToMarkdownExtension - * - * @typedef {Record} Attributes - * @typedef {{name: string, attributes?: Attributes}} Directive - * - * @typedef {Parent & Directive & {type: 'textDirective', children: Array.}} TextDirective - * @typedef {Parent & Directive & {type: 'leafDirective', children: Array.}} LeafDirective - * @typedef {Parent & Directive & {type: 'containerDirective', children: Array.}} ContainerDirective + * @typedef {import('./complex-types').ContainerDirective} ContainerDirective + * @typedef {import('./complex-types').LeafDirective} LeafDirective + * @typedef {import('./complex-types').TextDirective} TextDirective + * @typedef {ContainerDirective|LeafDirective|TextDirective} Directive */ import {decodeEntity} from 'parse-entities/decode-entity.js' @@ -112,21 +111,21 @@ function enterText(token) { } /** - * @this {ThisParameterType} - * @param {string} type - * @param {Parameters[0]} token + * @this {CompileContext} + * @param {Directive['type']} type + * @param {Token} token */ function enter(type, token) { - // @ts-expect-error: custom node. this.enter({type, name: '', attributes: {}, children: []}, token) } /** - * @this {ThisParameterType} - * @param {Parameters[0]} token + * @this {CompileContext} + * @param {Token} token */ function exitName(token) { - this.stack[this.stack.length - 1].name = this.sliceSerialize(token) + const node = /** @type {Directive} */ (this.stack[this.stack.length - 1]) + node.name = this.sliceSerialize(token) } /** @type {FromMarkdownHandle} */ @@ -150,33 +149,33 @@ function enterAttributes() { /** @type {FromMarkdownHandle} */ function exitAttributeIdValue(token) { - /** @type {Array.<[string, string]>} */ - // @ts-expect-error: custom. - const list = this.getData('directiveAttributes') + const list = /** @type {Array.<[string, string]>} */ ( + this.getData('directiveAttributes') + ) list.push(['id', decodeLight(this.sliceSerialize(token))]) } /** @type {FromMarkdownHandle} */ function exitAttributeClassValue(token) { - /** @type {Array.<[string, string]>} */ - // @ts-expect-error: custom. - const list = this.getData('directiveAttributes') + const list = /** @type {Array.<[string, string]>} */ ( + this.getData('directiveAttributes') + ) list.push(['class', decodeLight(this.sliceSerialize(token))]) } /** @type {FromMarkdownHandle} */ function exitAttributeValue(token) { - /** @type {Array.<[string, string]>} */ - // @ts-expect-error: custom. - const list = this.getData('directiveAttributes') + const list = /** @type {Array.<[string, string]>} */ ( + this.getData('directiveAttributes') + ) list[list.length - 1][1] = decodeLight(this.sliceSerialize(token)) } /** @type {FromMarkdownHandle} */ function exitAttributeName(token) { - /** @type {Array.<[string, string]>} */ - // @ts-expect-error: custom. - const list = this.getData('directiveAttributes') + const list = /** @type {Array.<[string, string]>} */ ( + this.getData('directiveAttributes') + ) // Attribute names in CommonMark are significantly limited, so character // references can’t exist. @@ -185,9 +184,9 @@ function exitAttributeName(token) { /** @type {FromMarkdownHandle} */ function exitAttributes() { - /** @type {Array.<[string, string]>} */ - // @ts-expect-error: custom. - const list = this.getData('directiveAttributes') + const list = /** @type {Array.<[string, string]>} */ ( + this.getData('directiveAttributes') + ) /** @type {Record.} */ const cleaned = {} let index = -1 @@ -204,7 +203,8 @@ function exitAttributes() { this.setData('directiveAttributes') this.resume() // Drop EOLs - this.stack[this.stack.length - 1].attributes = cleaned + const node = /** @type {Directive} */ (this.stack[this.stack.length - 1]) + node.attributes = cleaned } /** @type {FromMarkdownHandle} */ @@ -214,7 +214,7 @@ function exit(token) { /** * @type {ToMarkdownHandle} - * @param {TextDirective|LeafDirective|ContainerDirective} node + * @param {Directive} node */ function handleDirective(node, _, context) { const prefix = fence(node) @@ -241,18 +241,18 @@ function peekDirective() { } /** - * @param {TextDirective|LeafDirective|ContainerDirective} node + * @param {Directive} node * @param {Context} context * @returns {string} */ function label(node, context) { - /** @type {Parent} */ + /** @type {Directive|Paragraph} */ let label = node if (node.type === 'containerDirective') { - if (!inlineDirectiveLabel(node)) return '' - // @ts-expect-error: we just asserted it’s a parent. - label = node.children[0] + const head = (node.children || [])[0] + if (!inlineDirectiveLabel(head)) return '' + label = head } const exit = context.enter('label') @@ -264,7 +264,7 @@ function label(node, context) { } /** - * @param {TextDirective|LeafDirective|ContainerDirective} node + * @param {Directive} node * @param {Context} context * @returns {string} */ @@ -348,29 +348,27 @@ function attributes(node, context) { } /** - * @param {TextDirective|LeafDirective|ContainerDirective} node + * @param {ContainerDirective} node * @param {Context} context * @returns {string} */ function content(node, context) { - return containerFlow( - inlineDirectiveLabel(node) - ? Object.assign({}, node, {children: node.children.slice(1)}) - : node, - context - ) + const head = (node.children || [])[0] + + if (inlineDirectiveLabel(head)) { + node = Object.assign({}, node, {children: node.children.slice(1)}) + } + + return containerFlow(node, context) } /** - * @param {TextDirective|LeafDirective|ContainerDirective} node - * @returns {boolean} + * @param {BlockContent} node + * @returns {node is Paragraph & {data: {directiveLabel: boolean}}} */ function inlineDirectiveLabel(node) { return Boolean( - node.children && - node.children[0] && - node.children[0].data && - node.children[0].data.directiveLabel + node && node.type === 'paragraph' && node.data && node.data.directiveLabel ) } @@ -395,7 +393,7 @@ function decodeIfPossible($0, $1) { } /** - * @param {TextDirective|LeafDirective|ContainerDirective} node + * @param {Directive} node * @returns {string} */ function fence(node) { @@ -412,7 +410,7 @@ function fence(node) { return ':'.repeat(size) - /** @type {import('unist-util-visit-parents').Visitor} */ + /** @type {import('unist-util-visit-parents').Visitor} */ function onvisit(_, parents) { let index = parents.length let nesting = 0 diff --git a/package.json b/package.json index eec60b4..78343fc 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "main": "index.js", "types": "index.d.ts", "files": [ + "complex-types.d.ts", "index.d.ts", "index.js" ], @@ -57,7 +58,7 @@ "xo": "^0.39.0" }, "scripts": { - "build": "rimraf \"*.d.ts\" && tsc && type-coverage", + "build": "rimraf \"{index,test}.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 node --conditions development test.js", diff --git a/test.js b/test.js index 2be02c9..44621bc 100644 --- a/test.js +++ b/test.js @@ -275,6 +275,7 @@ test('mdast -> markdown', (t) => { type: 'paragraph', children: [ {type: 'text', value: 'a '}, + // @ts-expect-error: `children`, `name` missing. {type: 'textDirective'}, {type: 'text', value: ' b.'} ] @@ -291,6 +292,7 @@ test('mdast -> markdown', (t) => { type: 'paragraph', children: [ {type: 'text', value: 'a '}, + // @ts-expect-error: `children` missing. {type: 'textDirective', name: 'b'}, {type: 'text', value: ' c.'} ] @@ -370,6 +372,7 @@ test('mdast -> markdown', (t) => { { type: 'textDirective', name: 'b', + // @ts-expect-error: should contain only `string`s attributes: {c: 'd', e: 'f', g: '', h: null, i: undefined, j: 2}, children: [] }, @@ -509,6 +512,7 @@ test('mdast -> markdown', (t) => { ) t.deepEqual( + // @ts-expect-error: `children`, `name` missing. toMarkdown({type: 'leafDirective'}, {extensions: [directiveToMarkdown]}), '::\n', 'should try to serialize a directive (leaf) w/o `name`' @@ -516,6 +520,7 @@ test('mdast -> markdown', (t) => { t.deepEqual( toMarkdown( + // @ts-expect-error: `children` missing. {type: 'leafDirective', name: 'a'}, {extensions: [directiveToMarkdown]} ), @@ -567,7 +572,8 @@ test('mdast -> markdown', (t) => { { type: 'leafDirective', name: 'a', - attributes: {id: 'b', class: 'c d', key: 'e\nf'} + attributes: {id: 'b', class: 'c d', key: 'e\nf'}, + children: [] }, {extensions: [directiveToMarkdown]} ), @@ -577,6 +583,7 @@ test('mdast -> markdown', (t) => { t.deepEqual( toMarkdown( + // @ts-expect-error: `children`, `name` missing. {type: 'containerDirective'}, {extensions: [directiveToMarkdown]} ), @@ -586,6 +593,7 @@ test('mdast -> markdown', (t) => { t.deepEqual( toMarkdown( + // @ts-expect-error: `children` missing. {type: 'containerDirective', name: 'a'}, {extensions: [directiveToMarkdown]} ), @@ -611,7 +619,9 @@ test('mdast -> markdown', (t) => { { type: 'containerDirective', name: 'a', - children: [{type: 'heading', children: [{type: 'text', value: 'b'}]}] + children: [ + {type: 'heading', depth: 1, children: [{type: 'text', value: 'b'}]} + ] }, {extensions: [directiveToMarkdown]} ), @@ -624,7 +634,9 @@ test('mdast -> markdown', (t) => { { type: 'containerDirective', name: 'a', - children: [{type: 'text', value: 'b\nc'}] + children: [ + {type: 'paragraph', children: [{type: 'text', value: 'b\nc'}]} + ] }, {extensions: [directiveToMarkdown]} ), @@ -637,7 +649,8 @@ test('mdast -> markdown', (t) => { { type: 'containerDirective', name: 'a', - attributes: {id: 'b', class: 'c d', key: 'e\nf'} + attributes: {id: 'b', class: 'c d', key: 'e\nf'}, + children: [] }, {extensions: [directiveToMarkdown]} ), @@ -882,7 +895,7 @@ test('mdast -> markdown', (t) => { { type: 'paragraph', children: [ - {type: 'textDirective', name: 'red'}, + {type: 'textDirective', name: 'red', children: []}, {type: 'text', value: ':'} ] },