Skip to content

Commit ba997ce

Browse files
authored
Add directives to mdast node type registry
When using this utility, the directive types will automatically be added to mdast in the correct places. See DefinitelyTyped/DefinitelyTyped#54421 for more information. Closes GH-2.
1 parent b4be0da commit ba997ce

File tree

5 files changed

+109
-61
lines changed

5 files changed

+109
-61
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
coverage/
22
node_modules/
33
.DS_Store
4-
*.d.ts
4+
index.d.ts
5+
test.d.ts
56
*.log
67
yarn.lock

complex-types.d.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type {Parent} from 'unist'
2+
import type {PhrasingContent, BlockContent} from 'mdast'
3+
4+
type DirectiveAttributes = Record<string, string>
5+
6+
interface DirectiveFields {
7+
name: string
8+
attributes?: DirectiveAttributes
9+
}
10+
11+
export interface TextDirective extends Parent, DirectiveFields {
12+
type: 'textDirective'
13+
children: PhrasingContent[]
14+
}
15+
16+
export interface LeafDirective extends Parent, DirectiveFields {
17+
type: 'leafDirective'
18+
children: PhrasingContent[]
19+
}
20+
21+
export interface ContainerDirective extends Parent, DirectiveFields {
22+
type: 'containerDirective'
23+
children: BlockContent[]
24+
}
25+
26+
declare module 'mdast' {
27+
interface StaticPhrasingContentMap {
28+
textDirective: TextDirective
29+
}
30+
31+
interface BlockContentMap {
32+
containerDirective: ContainerDirective
33+
leafDirective: LeafDirective
34+
}
35+
}

index.js

+52-54
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
/**
2+
* @typedef {import('mdast').BlockContent} BlockContent
3+
* @typedef {import('mdast').Paragraph} Paragraph
24
* @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
35
* @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
4-
* @typedef {import('mdast-util-to-markdown/lib/types.js').Node} Node
5-
* @typedef {import('mdast-util-to-markdown/lib/types.js').Parent} Parent
6+
* @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
7+
* @typedef {import('mdast-util-from-markdown').Token} Token
68
* @typedef {import('mdast-util-to-markdown/lib/types.js').Handle} ToMarkdownHandle
79
* @typedef {import('mdast-util-to-markdown/lib/types.js').Context} Context
810
* @typedef {import('mdast-util-to-markdown/lib/types.js').Options} ToMarkdownExtension
9-
*
10-
* @typedef {Record<string, string>} Attributes
11-
* @typedef {{name: string, attributes?: Attributes}} Directive
12-
*
13-
* @typedef {Parent & Directive & {type: 'textDirective', children: Array.<import('mdast').PhrasingContent>}} TextDirective
14-
* @typedef {Parent & Directive & {type: 'leafDirective', children: Array.<import('mdast').PhrasingContent>}} LeafDirective
15-
* @typedef {Parent & Directive & {type: 'containerDirective', children: Array.<import('mdast').BlockContent>}} ContainerDirective
11+
* @typedef {import('./complex-types').ContainerDirective} ContainerDirective
12+
* @typedef {import('./complex-types').LeafDirective} LeafDirective
13+
* @typedef {import('./complex-types').TextDirective} TextDirective
14+
* @typedef {ContainerDirective|LeafDirective|TextDirective} Directive
1615
*/
1716

1817
import {decodeEntity} from 'parse-entities/decode-entity.js'
@@ -112,21 +111,21 @@ function enterText(token) {
112111
}
113112

114113
/**
115-
* @this {ThisParameterType<FromMarkdownHandle>}
116-
* @param {string} type
117-
* @param {Parameters<FromMarkdownHandle>[0]} token
114+
* @this {CompileContext}
115+
* @param {Directive['type']} type
116+
* @param {Token} token
118117
*/
119118
function enter(type, token) {
120-
// @ts-expect-error: custom node.
121119
this.enter({type, name: '', attributes: {}, children: []}, token)
122120
}
123121

124122
/**
125-
* @this {ThisParameterType<FromMarkdownHandle>}
126-
* @param {Parameters<FromMarkdownHandle>[0]} token
123+
* @this {CompileContext}
124+
* @param {Token} token
127125
*/
128126
function exitName(token) {
129-
this.stack[this.stack.length - 1].name = this.sliceSerialize(token)
127+
const node = /** @type {Directive} */ (this.stack[this.stack.length - 1])
128+
node.name = this.sliceSerialize(token)
130129
}
131130

132131
/** @type {FromMarkdownHandle} */
@@ -150,33 +149,33 @@ function enterAttributes() {
150149

151150
/** @type {FromMarkdownHandle} */
152151
function exitAttributeIdValue(token) {
153-
/** @type {Array.<[string, string]>} */
154-
// @ts-expect-error: custom.
155-
const list = this.getData('directiveAttributes')
152+
const list = /** @type {Array.<[string, string]>} */ (
153+
this.getData('directiveAttributes')
154+
)
156155
list.push(['id', decodeLight(this.sliceSerialize(token))])
157156
}
158157

159158
/** @type {FromMarkdownHandle} */
160159
function exitAttributeClassValue(token) {
161-
/** @type {Array.<[string, string]>} */
162-
// @ts-expect-error: custom.
163-
const list = this.getData('directiveAttributes')
160+
const list = /** @type {Array.<[string, string]>} */ (
161+
this.getData('directiveAttributes')
162+
)
164163
list.push(['class', decodeLight(this.sliceSerialize(token))])
165164
}
166165

167166
/** @type {FromMarkdownHandle} */
168167
function exitAttributeValue(token) {
169-
/** @type {Array.<[string, string]>} */
170-
// @ts-expect-error: custom.
171-
const list = this.getData('directiveAttributes')
168+
const list = /** @type {Array.<[string, string]>} */ (
169+
this.getData('directiveAttributes')
170+
)
172171
list[list.length - 1][1] = decodeLight(this.sliceSerialize(token))
173172
}
174173

175174
/** @type {FromMarkdownHandle} */
176175
function exitAttributeName(token) {
177-
/** @type {Array.<[string, string]>} */
178-
// @ts-expect-error: custom.
179-
const list = this.getData('directiveAttributes')
176+
const list = /** @type {Array.<[string, string]>} */ (
177+
this.getData('directiveAttributes')
178+
)
180179

181180
// Attribute names in CommonMark are significantly limited, so character
182181
// references can’t exist.
@@ -185,9 +184,9 @@ function exitAttributeName(token) {
185184

186185
/** @type {FromMarkdownHandle} */
187186
function exitAttributes() {
188-
/** @type {Array.<[string, string]>} */
189-
// @ts-expect-error: custom.
190-
const list = this.getData('directiveAttributes')
187+
const list = /** @type {Array.<[string, string]>} */ (
188+
this.getData('directiveAttributes')
189+
)
191190
/** @type {Record.<string, string>} */
192191
const cleaned = {}
193192
let index = -1
@@ -204,7 +203,8 @@ function exitAttributes() {
204203

205204
this.setData('directiveAttributes')
206205
this.resume() // Drop EOLs
207-
this.stack[this.stack.length - 1].attributes = cleaned
206+
const node = /** @type {Directive} */ (this.stack[this.stack.length - 1])
207+
node.attributes = cleaned
208208
}
209209

210210
/** @type {FromMarkdownHandle} */
@@ -214,7 +214,7 @@ function exit(token) {
214214

215215
/**
216216
* @type {ToMarkdownHandle}
217-
* @param {TextDirective|LeafDirective|ContainerDirective} node
217+
* @param {Directive} node
218218
*/
219219
function handleDirective(node, _, context) {
220220
const prefix = fence(node)
@@ -241,18 +241,18 @@ function peekDirective() {
241241
}
242242

243243
/**
244-
* @param {TextDirective|LeafDirective|ContainerDirective} node
244+
* @param {Directive} node
245245
* @param {Context} context
246246
* @returns {string}
247247
*/
248248
function label(node, context) {
249-
/** @type {Parent} */
249+
/** @type {Directive|Paragraph} */
250250
let label = node
251251

252252
if (node.type === 'containerDirective') {
253-
if (!inlineDirectiveLabel(node)) return ''
254-
// @ts-expect-error: we just asserted it’s a parent.
255-
label = node.children[0]
253+
const head = (node.children || [])[0]
254+
if (!inlineDirectiveLabel(head)) return ''
255+
label = head
256256
}
257257

258258
const exit = context.enter('label')
@@ -264,7 +264,7 @@ function label(node, context) {
264264
}
265265

266266
/**
267-
* @param {TextDirective|LeafDirective|ContainerDirective} node
267+
* @param {Directive} node
268268
* @param {Context} context
269269
* @returns {string}
270270
*/
@@ -348,29 +348,27 @@ function attributes(node, context) {
348348
}
349349

350350
/**
351-
* @param {TextDirective|LeafDirective|ContainerDirective} node
351+
* @param {ContainerDirective} node
352352
* @param {Context} context
353353
* @returns {string}
354354
*/
355355
function content(node, context) {
356-
return containerFlow(
357-
inlineDirectiveLabel(node)
358-
? Object.assign({}, node, {children: node.children.slice(1)})
359-
: node,
360-
context
361-
)
356+
const head = (node.children || [])[0]
357+
358+
if (inlineDirectiveLabel(head)) {
359+
node = Object.assign({}, node, {children: node.children.slice(1)})
360+
}
361+
362+
return containerFlow(node, context)
362363
}
363364

364365
/**
365-
* @param {TextDirective|LeafDirective|ContainerDirective} node
366-
* @returns {boolean}
366+
* @param {BlockContent} node
367+
* @returns {node is Paragraph & {data: {directiveLabel: boolean}}}
367368
*/
368369
function inlineDirectiveLabel(node) {
369370
return Boolean(
370-
node.children &&
371-
node.children[0] &&
372-
node.children[0].data &&
373-
node.children[0].data.directiveLabel
371+
node && node.type === 'paragraph' && node.data && node.data.directiveLabel
374372
)
375373
}
376374

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

397395
/**
398-
* @param {TextDirective|LeafDirective|ContainerDirective} node
396+
* @param {Directive} node
399397
* @returns {string}
400398
*/
401399
function fence(node) {
@@ -412,7 +410,7 @@ function fence(node) {
412410

413411
return ':'.repeat(size)
414412

415-
/** @type {import('unist-util-visit-parents').Visitor<TextDirective|LeafDirective|ContainerDirective>} */
413+
/** @type {import('unist-util-visit-parents').Visitor<Directive>} */
416414
function onvisit(_, parents) {
417415
let index = parents.length
418416
let nesting = 0

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"main": "index.js",
3232
"types": "index.d.ts",
3333
"files": [
34+
"complex-types.d.ts",
3435
"index.d.ts",
3536
"index.js"
3637
],
@@ -57,7 +58,7 @@
5758
"xo": "^0.39.0"
5859
},
5960
"scripts": {
60-
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
61+
"build": "rimraf \"{index,test}.d.ts\" && tsc && type-coverage",
6162
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
6263
"test-api": "node --conditions development test.js",
6364
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test.js",

test.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ test('mdast -> markdown', (t) => {
275275
type: 'paragraph',
276276
children: [
277277
{type: 'text', value: 'a '},
278+
// @ts-expect-error: `children`, `name` missing.
278279
{type: 'textDirective'},
279280
{type: 'text', value: ' b.'}
280281
]
@@ -291,6 +292,7 @@ test('mdast -> markdown', (t) => {
291292
type: 'paragraph',
292293
children: [
293294
{type: 'text', value: 'a '},
295+
// @ts-expect-error: `children` missing.
294296
{type: 'textDirective', name: 'b'},
295297
{type: 'text', value: ' c.'}
296298
]
@@ -370,6 +372,7 @@ test('mdast -> markdown', (t) => {
370372
{
371373
type: 'textDirective',
372374
name: 'b',
375+
// @ts-expect-error: should contain only `string`s
373376
attributes: {c: 'd', e: 'f', g: '', h: null, i: undefined, j: 2},
374377
children: []
375378
},
@@ -509,13 +512,15 @@ test('mdast -> markdown', (t) => {
509512
)
510513

511514
t.deepEqual(
515+
// @ts-expect-error: `children`, `name` missing.
512516
toMarkdown({type: 'leafDirective'}, {extensions: [directiveToMarkdown]}),
513517
'::\n',
514518
'should try to serialize a directive (leaf) w/o `name`'
515519
)
516520

517521
t.deepEqual(
518522
toMarkdown(
523+
// @ts-expect-error: `children` missing.
519524
{type: 'leafDirective', name: 'a'},
520525
{extensions: [directiveToMarkdown]}
521526
),
@@ -567,7 +572,8 @@ test('mdast -> markdown', (t) => {
567572
{
568573
type: 'leafDirective',
569574
name: 'a',
570-
attributes: {id: 'b', class: 'c d', key: 'e\nf'}
575+
attributes: {id: 'b', class: 'c d', key: 'e\nf'},
576+
children: []
571577
},
572578
{extensions: [directiveToMarkdown]}
573579
),
@@ -577,6 +583,7 @@ test('mdast -> markdown', (t) => {
577583

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

587594
t.deepEqual(
588595
toMarkdown(
596+
// @ts-expect-error: `children` missing.
589597
{type: 'containerDirective', name: 'a'},
590598
{extensions: [directiveToMarkdown]}
591599
),
@@ -611,7 +619,9 @@ test('mdast -> markdown', (t) => {
611619
{
612620
type: 'containerDirective',
613621
name: 'a',
614-
children: [{type: 'heading', children: [{type: 'text', value: 'b'}]}]
622+
children: [
623+
{type: 'heading', depth: 1, children: [{type: 'text', value: 'b'}]}
624+
]
615625
},
616626
{extensions: [directiveToMarkdown]}
617627
),
@@ -624,7 +634,9 @@ test('mdast -> markdown', (t) => {
624634
{
625635
type: 'containerDirective',
626636
name: 'a',
627-
children: [{type: 'text', value: 'b\nc'}]
637+
children: [
638+
{type: 'paragraph', children: [{type: 'text', value: 'b\nc'}]}
639+
]
628640
},
629641
{extensions: [directiveToMarkdown]}
630642
),
@@ -637,7 +649,8 @@ test('mdast -> markdown', (t) => {
637649
{
638650
type: 'containerDirective',
639651
name: 'a',
640-
attributes: {id: 'b', class: 'c d', key: 'e\nf'}
652+
attributes: {id: 'b', class: 'c d', key: 'e\nf'},
653+
children: []
641654
},
642655
{extensions: [directiveToMarkdown]}
643656
),
@@ -882,7 +895,7 @@ test('mdast -> markdown', (t) => {
882895
{
883896
type: 'paragraph',
884897
children: [
885-
{type: 'textDirective', name: 'red'},
898+
{type: 'textDirective', name: 'red', children: []},
886899
{type: 'text', value: ':'}
887900
]
888901
},

0 commit comments

Comments
 (0)