Skip to content

Add directives to mdast node type registry #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
coverage/
node_modules/
.DS_Store
*.d.ts
index.d.ts
test.d.ts
*.log
yarn.lock
35 changes: 35 additions & 0 deletions complex-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type {Parent} from 'unist'
import type {PhrasingContent, BlockContent} from 'mdast'

type DirectiveAttributes = Record<string, string>

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
}
}
106 changes: 52 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -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<string, string>} Attributes
* @typedef {{name: string, attributes?: Attributes}} Directive
*
* @typedef {Parent & Directive & {type: 'textDirective', children: Array.<import('mdast').PhrasingContent>}} TextDirective
* @typedef {Parent & Directive & {type: 'leafDirective', children: Array.<import('mdast').PhrasingContent>}} LeafDirective
* @typedef {Parent & Directive & {type: 'containerDirective', children: Array.<import('mdast').BlockContent>}} 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'
Expand Down Expand Up @@ -112,21 +111,21 @@ function enterText(token) {
}

/**
* @this {ThisParameterType<FromMarkdownHandle>}
* @param {string} type
* @param {Parameters<FromMarkdownHandle>[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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really feels like magic and is awesome: because mdast-util-{to,from}-markdown use @types/mdast, and this package adds Directive nodes, this just starts working!

}

/**
* @this {ThisParameterType<FromMarkdownHandle>}
* @param {Parameters<FromMarkdownHandle>[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} */
Expand All @@ -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.
Expand All @@ -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.<string, string>} */
const cleaned = {}
let index = -1
Expand All @@ -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} */
Expand All @@ -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)
Expand All @@ -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')
Expand All @@ -264,7 +264,7 @@ function label(node, context) {
}

/**
* @param {TextDirective|LeafDirective|ContainerDirective} node
* @param {Directive} node
* @param {Context} context
* @returns {string}
*/
Expand Down Expand Up @@ -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
)
}

Expand All @@ -395,7 +393,7 @@ function decodeIfPossible($0, $1) {
}

/**
* @param {TextDirective|LeafDirective|ContainerDirective} node
* @param {Directive} node
* @returns {string}
*/
function fence(node) {
Expand All @@ -412,7 +410,7 @@ function fence(node) {

return ':'.repeat(size)

/** @type {import('unist-util-visit-parents').Visitor<TextDirective|LeafDirective|ContainerDirective>} */
/** @type {import('unist-util-visit-parents').Visitor<Directive>} */
function onvisit(_, parents) {
let index = parents.length
let nesting = 0
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"main": "index.js",
"types": "index.d.ts",
"files": [
"complex-types.d.ts",
"index.d.ts",
"index.js"
],
Expand All @@ -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",
Expand Down
23 changes: 18 additions & 5 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'}
]
Expand All @@ -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.'}
]
Expand Down Expand Up @@ -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: []
},
Expand Down Expand Up @@ -509,13 +512,15 @@ 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`'
)

t.deepEqual(
toMarkdown(
// @ts-expect-error: `children` missing.
{type: 'leafDirective', name: 'a'},
{extensions: [directiveToMarkdown]}
),
Expand Down Expand Up @@ -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]}
),
Expand All @@ -577,6 +583,7 @@ test('mdast -> markdown', (t) => {

t.deepEqual(
toMarkdown(
// @ts-expect-error: `children`, `name` missing.
{type: 'containerDirective'},
{extensions: [directiveToMarkdown]}
),
Expand All @@ -586,6 +593,7 @@ test('mdast -> markdown', (t) => {

t.deepEqual(
toMarkdown(
// @ts-expect-error: `children` missing.
{type: 'containerDirective', name: 'a'},
{extensions: [directiveToMarkdown]}
),
Expand All @@ -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]}
),
Expand All @@ -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]}
),
Expand All @@ -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]}
),
Expand Down Expand Up @@ -882,7 +895,7 @@ test('mdast -> markdown', (t) => {
{
type: 'paragraph',
children: [
{type: 'textDirective', name: 'red'},
{type: 'textDirective', name: 'red', children: []},
{type: 'text', value: ':'}
]
},
Expand Down