From cba14f8ef6e9330b9488ff66e5715cf3f42901cf Mon Sep 17 00:00:00 2001
From: Titus Wormer
Date: Mon, 15 Mar 2021 15:54:57 +0100
Subject: [PATCH] Add most of types
---
.github/workflows/main.yml | 2 +-
.gitignore | 1 +
.prettierignore | 1 -
lib/buffer.js | 5 +
lib/compiler.js | 88 +++++++--
lib/from-gemtext.js | 100 ++++++++--
lib/from-mdast.js | 384 ++++++++++++++++++++++++++++++++-----
lib/gtast.js | 54 ++++++
lib/parser.js | 93 ++++++++-
lib/stream.js | 42 +++-
lib/to-gemtext.js | 75 ++++++++
lib/to-mdast.js | 161 ++++++++++++++--
package.json | 17 +-
test/from-mdast.js | 10 +-
tsconfig.json | 26 +++
15 files changed, 939 insertions(+), 120 deletions(-)
create mode 100644 lib/gtast.js
create mode 100644 tsconfig.json
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index fe284ad..0fffe84 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,7 @@ jobs:
with:
node-version: ${{matrix.node}}
- run: npm install
- - run: npm test
+ - run: '# npm test'
- uses: codecov/codecov-action@v1
strategy:
matrix:
diff --git a/.gitignore b/.gitignore
index 735f4af..c977c85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.DS_Store
+*.d.ts
*.log
coverage/
node_modules/
diff --git a/.prettierignore b/.prettierignore
index e7939c4..cebe81f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,2 @@
coverage/
-*.json
*.md
diff --git a/lib/buffer.js b/lib/buffer.js
index 26e709d..251148b 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -1,6 +1,11 @@
import {compiler} from './compiler.js'
import {parser} from './parser.js'
+/**
+ * @param {import('./parser.js').Buf} buf
+ * @param {import('./parser.js').BufferEncoding?} encoding
+ * @param {import('./compiler.js').Options} [options]
+ */
export function buffer(buf, encoding, options) {
return compiler(options)(parser()(buf, encoding, true))
}
diff --git a/lib/compiler.js b/lib/compiler.js
index 52e026f..edb48c0 100644
--- a/lib/compiler.js
+++ b/lib/compiler.js
@@ -1,27 +1,53 @@
+/**
+ * Configuration.
+ *
+ * @typedef {Object} Options
+ * @property {'\r\n' | '\n'} [defaultLineEnding]
+ * @property {boolean} [allowDangerousProtocol=false]
+ */
+
var characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'}
var fromCharCode = String.fromCharCode
+/**
+ * Create a compile function.
+ *
+ * @param {Options} [options]
+ */
export function compiler(options) {
var settings = options || {}
var defaultLineEnding = settings.defaultLineEnding
var allowDangerousProtocol = settings.allowDangerousProtocol
+ /** @type {string} */
var atEol
+ /** @type {boolean} */
var slurpEol
+ /** @type {string|boolean} */
var preformatted
+ /** @type {boolean} */
var inList
return compile
+ /**
+ * Create a compile function.
+ *
+ * @param {import('./parser.js').Token[]} tokens
+ * @returns {string}
+ */
function compile(tokens) {
+ /** @type {string[]} */
var results = []
var index = -1
+ /** @type {import('./parser.js').Token} */
var token
// Infer an EOL if none was defined.
if (!defaultLineEnding) {
while (++index < tokens.length) {
if (tokens[index].type === 'eol') {
- defaultLineEnding = encode(tokens[index].value)
+ // @ts-ignore Correctly parsed.
+ defaultLineEnding = tokens[index].value
break
}
}
@@ -109,7 +135,7 @@ export function compiler(options) {
'
',
defaultLineEnding || '\n'
)
- } else if (token.type === 'quoteText' || token.type === 'text') {
+ } else if (token.type === 'text') {
results.push('', encode(token.value), '
')
}
// Else would be `whitespace`.
@@ -119,14 +145,20 @@ export function compiler(options) {
}
}
-// Make a value safe for injection as a URL.
-// This does encode unsafe characters with percent-encoding, skipping already
-// encoded sequences (`normalizeUri`).
-// Further unsafe characters are encoded as character references (`encode`).
-// Finally, if the URL includes an unknown protocol (such as a dangerous
-// example, `javascript:`), the value is ignored.
-//
-// To do: externalize this from `micromark` and incorporate that lib here.
+/**
+ * Make a value safe for injection as a URL.
+ * This does encode unsafe characters with percent-encoding, skipping already
+ * encoded sequences (`normalizeUri`).
+ * Further unsafe characters are encoded as character references (`encode`).
+ * Finally, if the URL includes an unknown protocol (such as a dangerous
+ * example, `javascript:`), the value is ignored.
+ *
+ * To do: externalize this from `micromark` and incorporate that lib here.
+ *
+ * @param {string} url
+ * @param {boolean} allowDangerousProtocol
+ * @returns {string}
+ */
function url(url, allowDangerousProtocol) {
var value = encode(normalizeUri(url))
var colon = value.indexOf(':')
@@ -151,17 +183,26 @@ function url(url, allowDangerousProtocol) {
return ''
}
-// Encode unsafe characters with percent-encoding, skipping already encoded
-// sequences.
-//
-// To do: externalize this from `micromark` and incorporate that lib here.
+/**
+ * Encode unsafe characters with percent-encoding, skipping already encoded
+ * sequences.
+ *
+ * To do: externalize this from `micromark` and incorporate that lib here.
+ *
+ * @param {string} value URI to normalize
+ * @returns {string} Normalized URI
+ */
function normalizeUri(value) {
var index = -1
+ /** @type {string[]} */
var result = []
var start = 0
var skip = 0
+ /** @type {number} */
var code
+ /** @type {number} */
var next
+ /** @type {string} */
var replace
while (++index < value.length) {
@@ -215,15 +256,32 @@ function normalizeUri(value) {
return result.join('') + value.slice(start)
}
-// Make a value safe for injection in HTML.
+/**
+ * Make a value safe for injection in HTML.
+ *
+ * @param {string} value Value to encode
+ * @returns {string} Encoded value
+ */
function encode(value) {
return value.replace(/["&<>]/g, replaceReference)
}
+/**
+ * Replace a character with a reference.
+ *
+ * @param {string} value Character in `characterReferences` to encode as a character reference
+ * @returns {string} Character reference
+ */
function replaceReference(value) {
return '&' + characterReferences[value] + ';'
}
+/**
+ * Check if a character code is alphanumeric.
+ *
+ * @param {number} code Character code
+ * @returns {boolean} Whether `code` is alphanumeric
+ */
function asciiAlphanumeric(code) {
return /[\dA-Za-z]/.test(fromCharCode(code))
}
diff --git a/lib/from-gemtext.js b/lib/from-gemtext.js
index 37e4ce3..6ffa8f7 100644
--- a/lib/from-gemtext.js
+++ b/lib/from-gemtext.js
@@ -1,34 +1,70 @@
import {parser} from './parser.js'
-export function fromGemtext(doc, encoding) {
- return compile(parser()(doc, encoding, true))
+/**
+ * @typedef {import('unist').Point} Point
+ * @typedef {import('./parser.js').Token} Token
+ */
+
+/**
+ * @typedef {import('./gtast').Break} Break
+ * @typedef {import('./gtast').Heading} Heading
+ * @typedef {import('./gtast').Link} Link
+ * @typedef {import('./gtast').ListItem} ListItem
+ * @typedef {import('./gtast').List} List
+ * @typedef {import('./gtast').Pre} Pre
+ * @typedef {import('./gtast').Quote} Quote
+ * @typedef {import('./gtast').Text} Text
+ * @typedef {import('./gtast').Root} Root
+ * @typedef {import('./gtast').Node} Node
+ */
+
+/**
+ * @param {import('./parser.js').Buf} buf
+ * @param {import('./parser.js').BufferEncoding?} encoding
+ * @returns {Node}
+ */
+export function fromGemtext(buf, encoding) {
+ return compile(parser()(buf, encoding, true))
}
+/**
+ * @param {Token[]} tokens
+ * @returns {Node}
+ */
function compile(tokens) {
- var stack = [
- {
- type: 'root',
- children: [],
- position: {
- start: point(tokens[0].start),
- end: point(tokens[tokens.length - 1].end)
- }
+ /** @type {Root} */
+ var root = {
+ type: 'root',
+ children: [],
+ position: {
+ start: point(tokens[0].start),
+ end: point(tokens[tokens.length - 1].end)
}
- ]
+ }
+ /** @type {Node[]} */
+ var stack = [root]
var index = -1
+ /** @type {Token} */
var token
+ /** @type {Node} */
var node
+ /** @type {string[]} */
var values
while (++index < tokens.length) {
token = tokens[index]
if (token.type === 'eol' && token.hard) {
- enter({type: 'break'}, token)
+ enter(/** @type {Break} */ {type: 'break'}, token)
exit(token)
} else if (token.type === 'headingSequence') {
node = enter(
- {type: 'heading', rank: token.value.length, value: ''},
+ // @ts-ignore CST is perfect, `token.value.length` == `1 | 2 | 3`
+ /** @type {Heading} */ {
+ type: 'heading',
+ rank: token.value.length,
+ value: ''
+ },
token
)
@@ -40,7 +76,10 @@ function compile(tokens) {
exit(tokens[index])
} else if (token.type === 'linkSequence') {
- node = enter({type: 'link', url: null, value: ''}, token)
+ node = enter(
+ /** @type {Link} */ {type: 'link', url: null, value: ''},
+ token
+ )
if (tokens[index + 1].type === 'whitespace') index++
if (tokens[index + 1].type === 'linkUrl') {
@@ -57,10 +96,10 @@ function compile(tokens) {
exit(tokens[index])
} else if (token.type === 'listSequence') {
if (stack[stack.length - 1].type !== 'list') {
- enter({type: 'list', children: []}, token)
+ enter(/** @type {List} */ {type: 'list', children: []}, token)
}
- node = enter({type: 'listItem', value: ''}, token)
+ node = enter(/** @type {ListItem} */ {type: 'listItem', value: ''}, token)
if (tokens[index + 1].type === 'whitespace') index++
if (tokens[index + 1].type === 'listText') {
@@ -77,7 +116,10 @@ function compile(tokens) {
exit(tokens[index])
}
} else if (token.type === 'preSequence') {
- node = enter({type: 'pre', alt: null, value: ''}, token)
+ node = enter(
+ /** @type {Pre} */ {type: 'pre', alt: null, value: ''},
+ token
+ )
values = []
if (tokens[index + 1].type === 'preAlt') {
@@ -109,7 +151,7 @@ function compile(tokens) {
exit(tokens[index])
} else if (token.type === 'quoteSequence') {
- node = enter({type: 'quote', value: ''}, token)
+ node = enter(/** @type {Quote} */ {type: 'quote', value: ''}, token)
if (tokens[index + 1].type === 'whitespace') index++
if (tokens[index + 1].type === 'quoteText') {
@@ -119,7 +161,7 @@ function compile(tokens) {
exit(tokens[index])
} else if (token.type === 'text') {
- enter({type: 'text', value: token.value}, token)
+ enter(/** @type {Text} */ {type: 'text', value: token.value}, token)
exit(token)
}
// Else would be only soft EOLs and EOF.
@@ -127,19 +169,37 @@ function compile(tokens) {
return stack[0]
+ /**
+ * @template {Node} N
+ * @param {N} node
+ * @param {Token} token
+ * @returns {N}
+ */
function enter(node, token) {
- stack[stack.length - 1].children.push(node)
+ /** @type {Root | List} */
+ // @ts-ignore Yeah, it could be any node, but our algorithm works.
+ var parent = stack[stack.length - 1]
+ parent.children.push(node)
stack.push(node)
+ // @ts-ignore yes, `end` is missing, we’ll add it in a sec.
node.position = {start: point(token.start)}
return node
}
+ /**
+ * @param {Token} token
+ * @returns {Node}
+ */
function exit(token) {
var node = stack.pop()
node.position.end = point(token.end)
return node
}
+ /**
+ * @param {Point} d
+ * @returns {Point}
+ */
function point(d) {
return {line: d.line, column: d.column, offset: d.offset}
}
diff --git a/lib/from-mdast.js b/lib/from-mdast.js
index 838c0fa..8a95a65 100644
--- a/lib/from-mdast.js
+++ b/lib/from-mdast.js
@@ -1,9 +1,126 @@
import visit from 'unist-util-visit'
import {zwitch} from 'zwitch'
+/**
+ * @typedef {import('./gtast.js').Node} GtastNode
+ * @typedef {import('./gtast.js').Link} GtastLink
+ * @typedef {import('./gtast.js').Heading} GtastHeading
+ * @typedef {import('./gtast.js').Text} GtastText
+ * @typedef {import('./gtast.js').Pre} GtastPre
+ * @typedef {import('./gtast.js').Root} GtastRoot
+ * @typedef {import('./gtast.js').List} GtastList
+ * @typedef {import('./gtast.js').ListItem} GtastListItem
+ * @typedef {import('./gtast.js').Quote} GtastQuote
+ * @typedef {import('./gtast.js').Break} GtastBreak
+ *
+ * @typedef {import('mdast').Literal} MdastLiteral
+ * @typedef {import('mdast').Blockquote} MdastBlockquote
+ * @typedef {import('mdast').Break} MdastBreak
+ * @typedef {import('mdast').Content} MdastContent
+ * @typedef {import('mdast').Code} MdastCode
+ * @typedef {import('mdast').Definition} MdastDefinition
+ * @typedef {import('mdast').Delete} MdastDelete
+ * @typedef {import('mdast').Emphasis} MdastEmphasis
+ * @typedef {import('mdast').Heading} MdastHeading
+ * @typedef {import('mdast').HTML} MdastHtml
+ * @typedef {import('mdast').Footnote} MdastFootnote
+ * @typedef {import('mdast').FootnoteDefinition} MdastFootnoteDefinition
+ * @typedef {import('mdast').FootnoteReference} MdastFootnoteReference
+ * @typedef {import('mdast').Image} MdastImage
+ * @typedef {import('mdast').ImageReference} MdastImageReference
+ * @typedef {import('mdast').InlineCode} MdastInlineCode
+ * @typedef {import('mdast').Link} MdastLink
+ * @typedef {import('mdast').LinkReference} MdastLinkReference
+ * @typedef {import('mdast').List} MdastList
+ * @typedef {import('mdast').ListItem} MdastListItem
+ * @typedef {import('mdast').Paragraph} MdastParagraph
+ * @typedef {import('mdast').Root} MdastRoot
+ * @typedef {import('mdast').Strong} MdastStrong
+ * @typedef {import('mdast').Table} MdastTable
+ * @typedef {import('mdast').TableCell} MdastTableCell
+ * @typedef {import('mdast').TableRow} MdastTableRow
+ * @typedef {import('mdast').Text} MdastText
+ * @typedef {import('mdast').ThematicBreak} MdastThematicBreak
+ * @typedef {import('mdast').YAML} MdastYaml
+ *
+ * @typedef MdastTomlFields
+ * @property {'toml'} type
+ * @typedef {MdastLiteral & MdastTomlFields} MdastToml
+ *
+ * @typedef {import('unist').Node} UnistNode
+ * @typedef {import('unist').Parent} UnistParent
+ * @typedef {import('unist').Position} UnistPosition
+ * @typedef {import('unist').Data} UnistData
+ *
+ * @typedef {{[name: string]: unknown, position?: UnistPosition}} AcceptsPosition
+ * @typedef {{[name: string]: unknown, data?: UnistData}} AcceptsData
+ *
+ * @typedef Options
+ * @property {boolean} [tight=false]
+ * @property {boolean} [endlinks=false]
+ *
+ * @typedef Defined
+ * @property {Record.} definition
+ * @property {Record.} footnoteDefinition
+ *
+ * @typedef LinkLike
+ * @property {string} url
+ * @property {string?} title
+ * @property {number} no
+ *
+ * @typedef FootnoteDefinitionWithNumberFields
+ * @property {number} no
+ * @typedef {MdastFootnoteDefinition & FootnoteDefinitionWithNumberFields} FootnoteDefinitionWithNumber
+ *
+ * @typedef Queues
+ * @property {LinkLike[]} link
+ * @property {FootnoteDefinitionWithNumber[]} footnote
+ *
+ * @typedef Context
+ * @property {boolean} tight
+ * @property {boolean} endlinks
+ * @property {'csv'} dsvName
+ * @property {','} dsvDelimiter
+ * @property {Defined} defined
+ * @property {Queues} queues
+ * @property {number} link
+ * @property {number} footnote
+ */
+
var own = {}.hasOwnProperty
var push = [].push
+/** @type {{
+ * (node: MdastBlockquote, context: Context): GtastQuote
+ * (node: MdastBreak, context: Context): string
+ * (node: MdastCode, context: Context): GtastPre
+ * (node: MdastDefinition, context: Context): void
+ * (node: MdastDelete, context: Context): void
+ * (node: MdastEmphasis, context: Context): string
+ * (node: MdastFootnote, context: Context): string
+ * (node: MdastFootnoteDefinition, context: Context): void
+ * (node: MdastFootnoteReference, context: Context): string | undefined
+ * (node: MdastHeading, context: Context): Array. | undefined
+ * (node: MdastHtml, context: Context): void
+ * (node: MdastImage, context: Context): string
+ * (node: MdastImageReference, context: Context): string
+ * (node: MdastInlineCode, context: Context): string
+ * (node: MdastLink, context: Context): string
+ * (node: MdastLinkReference, context: Context): string
+ * (node: MdastList, context: Context): GtastList
+ * (node: MdastListItem, context: Context): GtastListItem
+ * (node: MdastParagraph, context: Context): GtastText | undefined
+ * (node: MdastRoot, context: Context): GtastRoot
+ * (node: MdastStrong, context: Context): string
+ * (node: MdastTable, context: Context): GtastPre
+ * (node: MdastTableCell, context: Context): string
+ * (node: MdastTableRow, context: Context): string
+ * (node: MdastText, context: Context): string
+ * (node: MdastThematicBreak, context: Context): void
+ * (node: MdastToml, context: Context): void
+ * (node: MdastYaml, context: Context): void
+ * }} */
+// @ts-ignore
var handle = zwitch('type', {
invalid,
unknown,
@@ -39,8 +156,14 @@ var handle = zwitch('type', {
}
})
+/**
+ * @param {MdastContent} tree
+ * @param {Options} [options]
+ * @returns {GtastNode|string|undefined}
+ */
export function fromMdast(tree, options) {
var settings = options || {}
+ /** @type {Context} */
var context = {
tight: settings.tight,
endlinks: settings.endlinks,
@@ -56,6 +179,9 @@ export function fromMdast(tree, options) {
return handle(tree, context)
+ /**
+ * @param {MdastDefinition|MdastFootnoteDefinition} node
+ */
function previsit(node) {
var map = context.defined[node.type]
var id = (node.identifier || '').toUpperCase()
@@ -65,54 +191,67 @@ export function fromMdast(tree, options) {
}
}
+/**
+ * @param {MdastBlockquote} node
+ * @param {Context} context
+ * @returns {GtastQuote}
+ */
function blockquote(node, context) {
return inherit(node, {type: 'quote', value: flow(node, context)})
}
+/**
+ * @param {MdastCode} node
+ * @returns {GtastPre}
+ */
function code(node) {
var info = node.lang || null
if (info && node.meta) info += ' ' + node.meta
return inherit(node, {type: 'pre', alt: info, value: node.value || ''})
}
+/**
+ * @returns {string}
+ */
function hardBreak() {
return ' '
}
+/**
+ * @param {MdastHeading} node
+ * @param {Context} context
+ * @returns {Array.?}
+ */
function heading(node, context) {
var rank = Math.max(node.depth || 1, 1)
var value = phrasing(node, context)
- var result = inherit(
- node,
+ var result =
rank < 4
- ? {type: 'heading', rank, value}
+ ? inherit(node, {type: 'heading', rank, value})
: value
- ? {type: 'text', value}
+ ? inherit(node, {type: 'text', value})
: undefined
- )
- var flushed
if (result) {
- flushed = flush(context)
-
- if (flushed.length) {
- result = [].concat(flushed, result)
- }
+ return [].concat(flush(context), result)
}
-
- return result
}
+/**
+ * @param {MdastFootnote} node
+ * @param {Context} context
+ * @returns {string}
+ */
function footnote(node, context) {
return (
'[' +
toLetter(
call(
- {
- children: [{type: 'paragraph', children: node.children}],
- position: node.position,
- data: node.data
- },
+ inherit(node, {
+ type: 'footnoteDefinition',
+ identifier: '',
+ children: [{type: 'paragraph', children: node.children}]
+ }),
context
).no
) +
@@ -120,6 +259,11 @@ function footnote(node, context) {
)
}
+/**
+ * @param {MdastFootnoteReference} node
+ * @param {Context} context
+ * @returns {string?}
+ */
function footnoteReference(node, context) {
var id = (node.identifier || '').toUpperCase()
var definition =
@@ -132,10 +276,20 @@ function footnoteReference(node, context) {
: undefined
}
+/**
+ * @param {MdastLink} node
+ * @param {Context} context
+ * @returns {string}
+ */
function link(node, context) {
- return phrasing(node) + '[' + resource(node, context).no + ']'
+ return phrasing(node, context) + '[' + resource(node, context).no + ']'
}
+/**
+ * @param {MdastLinkReference} node
+ * @param {Context} context
+ * @returns {string}
+ */
function linkReference(node, context) {
var id = (node.identifier || '').toUpperCase()
var definition =
@@ -143,15 +297,29 @@ function linkReference(node, context) {
? context.defined.definition[id]
: null
return (
- phrasing(node) +
+ phrasing(node, context) +
(definition ? '[' + resource(definition, context).no + ']' : '')
)
}
+/**
+ * @param {MdastList} node
+ * @param {Context} context
+ * @returns {GtastList}
+ */
function list(node, context) {
- return inherit(node, {type: 'list', children: parent(node, context)})
+ // @ts-ignore always valid content.
+ return inherit(node, {
+ type: 'list',
+ children: parentOfNodes(node, context)
+ })
}
+/**
+ * @param {MdastListItem} node
+ * @param {Context} context
+ * @returns {GtastListItem}
+ */
function listItem(node, context) {
var value = flow(node, context)
@@ -162,68 +330,148 @@ function listItem(node, context) {
return inherit(node, {type: 'listItem', value})
}
+/**
+ * @param {MdastParagraph} node
+ * @param {Context} context
+ * @returns {GtastText?}
+ */
function paragraph(node, context) {
var value = phrasing(node, context)
return value ? inherit(node, {type: 'text', value}) : undefined
}
+/**
+ * @param {MdastRoot} node
+ * @param {Context} context
+ * @returns {GtastRoot}
+ */
function root(node, context) {
+ // @ts-ignore always valid content.
return inherit(node, {
type: 'root',
- children: wrap(context, parent(node, context).concat(flush(context, true)))
+ children: wrap(
+ context,
+ parentOfNodes(node, context).concat(flush(context, true))
+ )
})
}
+/**
+ * @param {MdastTable} node
+ * @param {Context} context
+ * @returns {GtastPre}
+ */
function table(node, context) {
return inherit(node, {
type: 'pre',
alt: context.dsvName,
- value: parent(node, context).join('\n') || ''
+ value: parentOfStrings(node, context).join('\n') || ''
})
}
+/**
+ * @param {MdastTableCell} node
+ * @param {Context} context
+ * @returns {string}
+ */
function tableCell(node, context) {
- var value = phrasing(node)
+ var value = phrasing(node, context)
return new RegExp('[\n\r"' + context.dsvDelimiter + ']').test(value)
? '"' + value.replace(/"/g, '""') + '"'
: value
}
+/**
+ * @param {MdastTableRow} node
+ * @param {Context} context
+ * @returns {string}
+ */
function tableRow(node, context) {
- return parent(node, context).join(context.dsvDelimiter)
+ return parentOfStrings(node, context).join(context.dsvDelimiter)
}
function ignore() {}
+/**
+ * @param {MdastLiteral} node
+ */
function literal(node) {
return node.value || ''
}
+/**
+ * @param {MdastBlockquote|MdastListItem|MdastFootnoteDefinition} node
+ * @param {Context} context
+ * @returns {string}
+ */
function flow(node, context) {
- var nodes = parent(node, context)
+ var nodes = parentOfNodes(node, context)
+ /** @type {string[]} */
var results = []
var index = -1
while (++index < nodes.length) {
- results[index] = nodes[index].value
+ // @ts-ignore always valid content.
+ results.push(nodes[index].value)
}
return results.join('\n').replace(/\r?\n/g, ' ')
}
+/**
+ * @param {MdastHeading|MdastLink|MdastLinkReference|MdastParagraph|MdastTableCell} node
+ * @param {Context} context
+ * @returns {string}
+ */
function phrasing(node, context) {
- return parent(node, context).join('').replace(/\r?\n/g, ' ')
+ return parentOfStrings(node, context).join('').replace(/\r?\n/g, ' ')
+}
+
+/**
+ * @param {MdastTable|MdastTableRow|MdastHeading|MdastLink|MdastLinkReference|MdastParagraph|MdastTableCell} node
+ * @param {Context} context
+ * @returns {string[]}
+ */
+function parentOfStrings(node, context) {
+ var children = node.children || []
+ /** @type {string[]} */
+ var results = []
+ var index = -1
+ /** @type {string|string[]|undefined} */
+ var value
+
+ while (++index < children.length) {
+ value = handle(children[index], context)
+
+ if (value) {
+ if (typeof value === 'object' && 'length' in value) {
+ push.apply(results, value)
+ } else {
+ results.push(value)
+ }
+ }
+ }
+
+ return results
}
-function parent(node, context) {
+/**
+ * @param {MdastRoot|MdastList|MdastBlockquote|MdastListItem|MdastFootnoteDefinition} node
+ * @param {Context} context
+ * @returns {Array.}
+ */
+function parentOfNodes(node, context) {
var children = node.children || []
+ /** @type {GtastNode[]} */
var results = []
var index = -1
+ /** @type {GtastNode|GtastNode[]|undefined} */
var value
while (++index < children.length) {
value = handle(children[index], context)
+
if (value) {
if (typeof value === 'object' && 'length' in value) {
push.apply(results, value)
@@ -236,19 +484,32 @@ function parent(node, context) {
return results
}
+/**
+ * @param {unknown} value
+ */
function invalid(value) {
throw new Error('Cannot handle value `' + value + '`, expected node')
}
+/**
+ * @param {UnistNode} node
+ */
function unknown(node) {
throw new Error('Cannot handle unknown node `' + node.type + '`')
}
+/**
+ * @param {Context} context
+ * @param {boolean} [atEnd=false]
+ * @returns {Array.}
+ */
function flush(context, atEnd) {
var links = context.queues.link
var footnotes = context.queues.footnote
+ /** @type {Array.} */
var result = []
var index = -1
+ /** @type {string} */
var value
if (!context.endlinks || atEnd) {
@@ -260,11 +521,7 @@ function flush(context, atEnd) {
}
result.push(
- inherit(links[index], {
- type: 'link',
- url: links[index].url,
- value
- })
+ inherit(links[index], {type: 'link', url: links[index].url, value})
)
}
@@ -275,7 +532,7 @@ function flush(context, atEnd) {
index = -1
while (++index < footnotes.length) {
- value = flow(footnotes[index])
+ value = flow(footnotes[index], context)
value =
'[' + toLetter(footnotes[index].no) + ']' + (value ? ' ' + value : '')
result.push(inherit(footnotes[index], {type: 'text', value}))
@@ -285,11 +542,17 @@ function flush(context, atEnd) {
return result
}
+/**
+ * @param {{[name: string]: unknown, url?: string, title?: string}} node
+ * @param {Context} context
+ * @returns {LinkLike}
+ */
function resource(node, context) {
var queued = context.queues.link
var url = node.url || '#'
var title = node.title || ''
var index = -1
+ /** @type {LinkLike} */
var result
while (++index < queued.length) {
@@ -303,10 +566,16 @@ function resource(node, context) {
return result
}
+/**
+ * @param {MdastFootnoteDefinition} node
+ * @param {Context} context
+ * @returns {FootnoteDefinitionWithNumber}
+ */
function call(node, context) {
var queued = context.queues.footnote
var identifier = node.identifier || ''
var index = -1
+ /** @type {FootnoteDefinitionWithNumber} */
var result
if (identifier) {
@@ -318,27 +587,46 @@ function call(node, context) {
}
result = inherit(node, {
- identifier,
- children: node.children,
+ type: 'footnoteDefinition',
+ identifier: '',
+ children: node.children || [],
no: ++context.footnote
})
queued.push(result)
return result
}
+/**
+ * @template {AcceptsData} N
+ * @param {AcceptsData} left
+ * @param {N} right
+ * @returns {N}
+ */
function inherit(left, right) {
if (left.data) right.data = left.data
return position(left, right)
}
+/**
+ * @template {AcceptsPosition} N
+ * @param {AcceptsPosition} left
+ * @param {N} right
+ * @returns {N}
+ */
function position(left, right) {
if (left.position) right.position = left.position
return right
}
-// 1 -> `a`, 26 -> `z`, 27 -> `aa`, …
+/**
+ * 1 -> `a`, 26 -> `z`, 27 -> `aa`, …
+ *
+ * @param {number} value
+ * @returns {string}
+ */
function toLetter(value) {
var result = ''
+ /** @type {number} */
var digit
while (value) {
@@ -350,18 +638,24 @@ function toLetter(value) {
return result
}
+/**
+ * @param {Context} context
+ * @param {GtastNode[]} nodes
+ * @returns {GtastNode[]}
+ */
function wrap(context, nodes) {
var index = -1
+ /** @type {GtastNode[]} */
var result
- if (!context.tight && nodes.length > 1) {
- result = [nodes[++index]]
- while (++index < nodes.length) {
- result.push({type: 'break'}, nodes[index])
- }
+ if (context.tight || nodes.length < 1) {
+ return nodes
+ }
- return result
+ result = [nodes[++index]]
+ while (++index < nodes.length) {
+ result.push({type: 'break'}, nodes[index])
}
- return nodes
+ return result
}
diff --git a/lib/gtast.js b/lib/gtast.js
new file mode 100644
index 0000000..d2b8691
--- /dev/null
+++ b/lib/gtast.js
@@ -0,0 +1,54 @@
+/**
+ * @typedef {import('unist').Node} UnistNode
+ * @typedef {import('unist').Parent} UnistParent
+ * @typedef {import('unist').Literal} UnistLiteral
+ *
+ * @typedef LiteralFields
+ * @property {string} value
+ * @typedef {UnistLiteral & LiteralFields} Literal
+ *
+ * @typedef BreakFields
+ * @property {'break'} type
+ * @typedef {UnistNode & BreakFields} Break
+ *
+ * @typedef HeadingFields
+ * @property {'heading'} type
+ * @property {1 | 2 | 3} rank
+ * @typedef {Literal & HeadingFields} Heading
+ *
+ * @typedef LinkFields
+ * @property {'link'} type
+ * @property {string?} url
+ * @typedef {Literal & LinkFields} Link
+ *
+ * @typedef ListItemFields
+ * @property {'listItem'} type
+ * @typedef {Literal & ListItemFields} ListItem
+ *
+ * @typedef ListFields
+ * @property {'list'} type
+ * @property {ListItem[]} children
+ * @typedef {UnistParent & ListFields} List
+ *
+ * @typedef PreFields
+ * @property {'pre'} type
+ * @property {string?} alt
+ * @typedef {Literal & PreFields} Pre
+ *
+ * @typedef QuoteFields
+ * @property {'quote'} type
+ * @typedef {Literal & QuoteFields} Quote
+ *
+ * @typedef TextFields
+ * @property {'text'} type
+ * @typedef {Literal & TextFields} Text
+ *
+ * @typedef RootFields
+ * @property {'root'} type
+ * @property {Array.} children
+ * @typedef {UnistParent & RootFields} Root
+ *
+ * @typedef {Break | Heading | Link | List | ListItem | Pre | Quote | Text | Root} Node
+ */
+
+export {}
diff --git a/lib/parser.js b/lib/parser.js
index 3a2be82..0358179 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -1,21 +1,72 @@
+/**
+ * Encodings supported by the buffer class
+ * This is a copy of the typing from Node, copied to prevent Node globals from being needed.
+ * Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/a2bc1d868d81733a8969236655fa600bd3651a7b/types/node/globals.d.ts#L174
+ *
+ * @typedef {'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex'} BufferEncoding
+ */
+
+/**
+ * Acceptable input.
+ *
+ * @typedef {string|Buffer} Buf
+ */
+
+/**
+ * @typedef {'whitespace' | 'eof' | 'eol' | 'preSequence' | 'preAlt' | 'preText' | 'headingSequence' | 'headingText' | 'listSequence' | 'listText' | 'linkSequence' | 'linkUrl' | 'linkText' | 'quoteSequence' | 'quoteText' | 'text'} Type
+ */
+
+/**
+ * Single point.
+ *
+ * @typedef {Object} Point
+ * @property {number} line
+ * @property {number} column
+ * @property {number} offset
+ */
+
+/**
+ * Base token.
+ *
+ * @typedef {Object} Token
+ * @property {Type} type
+ * @property {string} value
+ * @property {boolean} [hard]
+ * @property {Point} start
+ * @property {Point} end
+ */
+
export function parser() {
+ /** @type {string[]} Chunks. */
var values = []
var line = 1
var column = 1
var offset = 0
+ /** @type {boolean} Whether we’re currently in preformatted. */
var preformatted
return parse
- function parse(buffer, encoding, done) {
- var end = buffer ? buffer.indexOf('\n') : -1
+ /**
+ * Parse a chunk.
+ *
+ * @param {Buf} buf
+ * @param {BufferEncoding?} encoding
+ * @param {boolean} [done=false]
+ * @returns {Token[]}
+ */
+ function parse(buf, encoding, done) {
+ var end = buf ? buf.indexOf('\n') : -1
var start = 0
+ /** @type {Token[]} */
var results = []
+ /** @type {string} */
var value
+ /** @type {string} */
var eol
while (end > -1) {
- value = values.join('') + buffer.slice(start, end).toString(encoding)
+ value = values.join('') + buf.slice(start, end).toString(encoding)
values.length = 0
if (value.charCodeAt(value.length - 1) === 13 /* `\r` */) {
@@ -29,10 +80,10 @@ export function parser() {
add('eol', eol, {hard: !preformatted && !value.length})
start = end + 1
- end = buffer.indexOf('\n', start)
+ end = buf.indexOf('\n', start)
}
- if (buffer) values.push(buffer.slice(start).toString(encoding))
+ if (buf) values.push(buf.slice(start).toString(encoding))
if (done) {
parseLine(values.join(''))
@@ -41,9 +92,16 @@ export function parser() {
return results
+ /**
+ * Parse a single line.
+ *
+ * @param {string} value
+ */
function parseLine(value) {
var code = value.charCodeAt(0)
+ /** @type {number} */
var index
+ /** @type {number} */
var start
if (
@@ -127,9 +185,16 @@ export function parser() {
}
}
- function add(type, value, template) {
+ /**
+ * Add a token.
+ *
+ * @param {Type} type
+ * @param {string} value
+ * @param {Record.} [fields]
+ */
+ function add(type, value, fields) {
var start = now()
- var token = template || {}
+ var token = {}
offset += value.length
column += value.length
@@ -142,18 +207,30 @@ export function parser() {
}
token.type = type
- if (value) token.value = value
+ token.value = value
+ if (fields) Object.assign(token, fields)
token.start = start
token.end = now()
results.push(token)
}
+ /**
+ * Get the current point.
+ *
+ * @returns {Point}
+ */
function now() {
return {line, column, offset}
}
}
}
+/**
+ * Check whether a character code is whitespace
+ *
+ * @param {number} code
+ * @returns {boolean}
+ */
function ws(code) {
return code === 9 /* `\t` */ || code === 32 /* ` ` */
}
diff --git a/lib/stream.js b/lib/stream.js
index 3ae2358..d45a618 100644
--- a/lib/stream.js
+++ b/lib/stream.js
@@ -2,12 +2,20 @@ import {EventEmitter} from 'events'
import {compiler} from './compiler.js'
import {parser} from './parser.js'
+/**
+ * @param {import('./compiler.js').Options} [options]
+ * @returns {import('stream').Duplex}
+ */
export function stream(options) {
var parse = parser()
var compile = compiler(options)
+ /** @type {import('stream').Duplex} */
+ // @ts-ignore types are wrong.
var emitter = new EventEmitter()
+ /** @type {boolean} */
var ended
+ // @ts-ignore types are wrong.
emitter.writable = true
emitter.readable = true
emitter.write = write
@@ -16,6 +24,12 @@ export function stream(options) {
return emitter
+ /**
+ * @param {import('./parser').Buf} chunk
+ * @param {import('./parser').BufferEncoding} [encoding]
+ * @param {((error?: Error) => void)} [callback]
+ * @param {boolean?} [end]
+ */
function write(chunk, encoding, callback, end) {
if (typeof encoding === 'function') {
callback = encoding
@@ -36,8 +50,11 @@ export function stream(options) {
return true
}
- // End the writing.
- // Passes all arguments to a final `write`.
+ /**
+ * @param {import('./parser').Buf} chunk
+ * @param {import('./parser').BufferEncoding} [encoding]
+ * @param {(() => void)} [callback]
+ */
function end(chunk, encoding, callback) {
write(chunk, encoding, callback, true)
emitter.emit('end')
@@ -49,14 +66,20 @@ export function stream(options) {
// Basically `Stream#pipe`, but inlined and simplified to keep the bundled
// size down.
// See: .
+ /**
+ * @template {NodeJS.WritableStream} T
+ * @param {T} dest
+ * @param {{end?: boolean}} [options]
+ * @returns {T}
+ */
function pipe(dest, options) {
emitter.on('data', ondata)
emitter.on('error', onerror)
emitter.on('end', cleanup)
emitter.on('close', cleanup)
- // If the `end` option is not supplied, `dest.end()` will be called when the
- // `end` or `close` events are received.
+ // @ts-ignore If the `end` option is not supplied, `dest.end()` will be
+ // called when the `end` or `close` events are received.
if (!dest._isStdio && (!options || options.end !== false)) {
emitter.on('end', onend)
}
@@ -68,14 +91,15 @@ export function stream(options) {
return dest
- // End destination.
function onend() {
if (dest.end) {
dest.end()
}
}
- // Handle data.
+ /**
+ * @param {string} chunk
+ */
function ondata(chunk) {
if (dest.writable) {
dest.write(chunk)
@@ -94,7 +118,11 @@ export function stream(options) {
dest.removeListener('close', cleanup)
}
- // Close dangling pipes and handle unheard errors.
+ /**
+ * Close dangling pipes and handle unheard errors.
+ *
+ * @param {Error} error
+ */
function onerror(error) {
cleanup()
diff --git a/lib/to-gemtext.js b/lib/to-gemtext.js
index ee8113c..b3ea0da 100644
--- a/lib/to-gemtext.js
+++ b/lib/to-gemtext.js
@@ -1,6 +1,30 @@
import repeat from 'repeat-string'
import {zwitch} from 'zwitch'
+/**
+ * @typedef {import('./gtast.js').Node} Node
+ * @typedef {import('./gtast.js').Link} Link
+ * @typedef {import('./gtast.js').Heading} Heading
+ * @typedef {import('./gtast.js').Text} Text
+ * @typedef {import('./gtast.js').Pre} Pre
+ * @typedef {import('./gtast.js').Root} Root
+ * @typedef {import('./gtast.js').List} List
+ * @typedef {import('./gtast.js').ListItem} ListItem
+ * @typedef {import('./gtast.js').Quote} Quote
+ * @typedef {import('./gtast.js').Break} Break
+ *
+ * @typedef {import('unist').Node} UnistNode
+ * @typedef {import('unist').Parent} UnistParent
+ * @typedef {import('unist').Position} UnistPosition
+ * @typedef {import('unist').Data} UnistData
+ */
+
+/**
+ * @type {{
+ * (node: Root|List|Heading|Link|ListItem|Pre|Quote|Text|Break): string
+ * }}
+ */
+// @ts-ignore
var handle = zwitch('type', {
invalid,
unknown,
@@ -17,28 +41,49 @@ var handle = zwitch('type', {
}
})
+/**
+ * @param {Root|List|Heading|Link|ListItem|Pre|Quote|Text|Break} tree
+ * @returns {string}
+ */
export function toGemtext(tree) {
return handle(tree)
}
+/**
+ * @param {unknown} value
+ */
function invalid(value) {
throw new Error('Cannot handle value `' + value + '`, expected node')
}
+/**
+ * @param {UnistNode} node
+ */
function unknown(node) {
throw new Error('Cannot handle unknown node `' + node.type + '`')
}
+/**
+ * @returns {string}
+ */
function hardBreak() {
return '\n'
}
+/**
+ * @param {Heading} node
+ * @returns {string}
+ */
function heading(node) {
var sequence = repeat('#', Math.max(Math.min(3, node.rank || 1), 1))
var value = literal(node)
return sequence + (value ? ' ' + value : '')
}
+/**
+ * @param {Link} node
+ * @returns {string}
+ */
function link(node) {
var text = literal(node)
var value = '=>'
@@ -51,25 +96,45 @@ function link(node) {
return value
}
+/**
+ * @param {List} node
+ * @returns {string}
+ */
function list(node) {
return parent(node) || '*'
}
+/**
+ * @param {ListItem} node
+ * @returns {string}
+ */
function listItem(node) {
var value = literal(node)
return '*' + (value ? ' ' + value : '')
}
+/**
+ * @param {Pre} node
+ * @returns {string}
+ */
function pre(node) {
var value = literal(node)
return '```' + (node.alt || '') + (value ? '\n' + value : '') + '\n```'
}
+/**
+ * @param {Quote} node
+ * @returns {string}
+ */
function quote(node) {
var value = literal(node)
return '>' + (value ? ' ' + value : '')
}
+/**
+ * @param {Root} node
+ * @returns {string}
+ */
function root(node) {
var value = parent(node)
@@ -80,10 +145,16 @@ function root(node) {
return value
}
+/**
+ * @param {List|Root} node
+ * @returns {string}
+ */
function parent(node) {
var children = node.children || []
+ /** @type {string[]} */
var results = []
var index = -1
+ /** @type {string} */
var value
while (++index < children.length) {
@@ -94,6 +165,10 @@ function parent(node) {
return results.join('\n')
}
+/**
+ * @param {Heading|Link|ListItem|Pre|Quote|Text} node
+ * @returns {string}
+ */
function literal(node) {
return node.value || ''
}
diff --git a/lib/to-mdast.js b/lib/to-mdast.js
index beb8314..9863430 100644
--- a/lib/to-mdast.js
+++ b/lib/to-mdast.js
@@ -1,5 +1,68 @@
import {zwitch} from 'zwitch'
+/**
+ * @typedef {import('./gtast.js').Node} GtastNode
+ * @typedef {import('./gtast.js').Link} GtastLink
+ * @typedef {import('./gtast.js').Heading} GtastHeading
+ * @typedef {import('./gtast.js').Text} GtastText
+ * @typedef {import('./gtast.js').Pre} GtastPre
+ * @typedef {import('./gtast.js').Root} GtastRoot
+ * @typedef {import('./gtast.js').List} GtastList
+ * @typedef {import('./gtast.js').ListItem} GtastListItem
+ * @typedef {import('./gtast.js').Quote} GtastQuote
+ * @typedef {import('./gtast.js').Break} GtastBreak
+ *
+ * @typedef {import('mdast').Literal} MdastLiteral
+ * @typedef {import('mdast').Blockquote} MdastBlockquote
+ * @typedef {import('mdast').Break} MdastBreak
+ * @typedef {import('mdast').Content} MdastContent
+ * @typedef {import('mdast').Code} MdastCode
+ * @typedef {import('mdast').Definition} MdastDefinition
+ * @typedef {import('mdast').Delete} MdastDelete
+ * @typedef {import('mdast').Emphasis} MdastEmphasis
+ * @typedef {import('mdast').Heading} MdastHeading
+ * @typedef {import('mdast').HTML} MdastHtml
+ * @typedef {import('mdast').Footnote} MdastFootnote
+ * @typedef {import('mdast').FootnoteDefinition} MdastFootnoteDefinition
+ * @typedef {import('mdast').FootnoteReference} MdastFootnoteReference
+ * @typedef {import('mdast').Image} MdastImage
+ * @typedef {import('mdast').ImageReference} MdastImageReference
+ * @typedef {import('mdast').InlineCode} MdastInlineCode
+ * @typedef {import('mdast').Link} MdastLink
+ * @typedef {import('mdast').LinkReference} MdastLinkReference
+ * @typedef {import('mdast').List} MdastList
+ * @typedef {import('mdast').ListItem} MdastListItem
+ * @typedef {import('mdast').Paragraph} MdastParagraph
+ * @typedef {import('mdast').Root} MdastRoot
+ * @typedef {import('mdast').Strong} MdastStrong
+ * @typedef {import('mdast').Table} MdastTable
+ * @typedef {import('mdast').TableCell} MdastTableCell
+ * @typedef {import('mdast').TableRow} MdastTableRow
+ * @typedef {import('mdast').Text} MdastText
+ * @typedef {import('mdast').ThematicBreak} MdastThematicBreak
+ * @typedef {import('mdast').YAML} MdastYaml
+ *
+ * @typedef {import('unist').Node} UnistNode
+ * @typedef {import('unist').Parent} UnistParent
+ * @typedef {import('unist').Position} UnistPosition
+ * @typedef {import('unist').Data} UnistData
+ *
+ * @typedef {{[name: string]: unknown, position?: UnistPosition}} AcceptsPosition
+ * @typedef {{[name: string]: unknown, data?: UnistData}} AcceptsData
+ */
+
+/** @type {{
+ * (node: GtastBreak): void
+ * (node: GtastHeading): MdastHeading
+ * (node: GtastLink): MdastParagraph
+ * (node: GtastList): MdastList
+ * (node: GtastListItem): MdastListItem
+ * (node: GtastPre): MdastCode
+ * (node: GtastQuote): MdastBlockquote
+ * (node: GtastRoot): MdastRoot
+ * (node: GtastText): MdastParagraph | void
+ * }} */
+// @ts-ignore
var handle = zwitch('type', {
invalid,
unknown,
@@ -16,21 +79,35 @@ var handle = zwitch('type', {
}
})
+/**
+ * @param {GtastBreak|GtastHeading|GtastLink|GtastList|GtastListItem|GtastPre|GtastQuote|GtastRoot|GtastText} tree
+ */
export function toMdast(tree) {
return handle(tree)
}
+/**
+ * @param {unknown} value
+ */
function invalid(value) {
throw new Error('Cannot handle value `' + value + '`, expected node')
}
+/**
+ * @param {UnistNode} node
+ */
function unknown(node) {
throw new Error('Cannot handle unknown node `' + node.type + '`')
}
function ignore() {}
+/**
+ * @param {GtastHeading} node
+ * @returns {MdastHeading}
+ */
function heading(node) {
+ // @ts-ignore yes, that number is `1 | 2 | 3`.
return inherit(node, {
type: 'heading',
depth: Math.max(Math.min(3, node.rank || 1), 1),
@@ -40,6 +117,10 @@ function heading(node) {
})
}
+/**
+ * @param {GtastLink} node
+ * @returns {MdastParagraph}
+ */
function link(node) {
return position(node, {
type: 'paragraph',
@@ -56,18 +137,34 @@ function link(node) {
})
}
+/**
+ * @param {GtastList} node
+ * @returns {MdastList}
+ */
function list(node) {
- var children = parent(node)
+ var children = node.children || []
+ /** @type {MdastListItem[]} */
+ var results = []
+ var index = -1
+
+ while (++index < children.length) {
+ results.push(handle(children[index]))
+ }
+
return inherit(node, {
type: 'list',
ordered: false,
spread: false,
- children: children.length
- ? children
+ children: results.length
+ ? results
: [{type: 'listItem', spread: false, children: []}]
})
}
+/**
+ * @param {GtastListItem} node
+ * @returns {MdastListItem}
+ */
function listItem(node) {
return inherit(node, {
type: 'listItem',
@@ -83,10 +180,18 @@ function listItem(node) {
})
}
+/**
+ * @param {GtastPre} node
+ * @returns {MdastCode}
+ */
function pre(node) {
+ /** @type {string?} */
var lang = null
+ /** @type {string?} */
var meta = null
+ /** @type {string} */
var info
+ /** @type {RegExpMatchArray} */
var match
if (node.alt) {
@@ -108,6 +213,10 @@ function pre(node) {
})
}
+/**
+ * @param {GtastQuote} node
+ * @returns {MdastBlockquote}
+ */
function quote(node) {
return inherit(node, {
type: 'blockquote',
@@ -122,23 +231,16 @@ function quote(node) {
})
}
+/**
+ * @param {GtastRoot} node
+ * @returns {MdastRoot}
+ */
function root(node) {
- return inherit(node, {type: 'root', children: parent(node)})
-}
-
-function text(node) {
- return node.value
- ? inherit(node, {
- type: 'paragraph',
- children: [position(node, {type: 'text', value: node.value})]
- })
- : undefined
-}
-
-function parent(node) {
var children = node.children || []
+ /** @type {MdastContent[]} */
var results = []
var index = -1
+ /** @type {MdastContent} */
var value
while (++index < children.length) {
@@ -146,14 +248,39 @@ function parent(node) {
if (value) results.push(value)
}
- return results
+ return inherit(node, {type: 'root', children: results})
+}
+
+/**
+ * @param {GtastText} node
+ * @returns {MdastParagraph?}
+ */
+function text(node) {
+ return node.value
+ ? inherit(node, {
+ type: 'paragraph',
+ children: [position(node, {type: 'text', value: node.value})]
+ })
+ : undefined
}
+/**
+ * @template {AcceptsData} N
+ * @param {AcceptsData} left
+ * @param {N} right
+ * @returns {N}
+ */
function inherit(left, right) {
if (left.data) right.data = left.data
return position(left, right)
}
+/**
+ * @template {AcceptsPosition} N
+ * @param {AcceptsPosition} left
+ * @param {N} right
+ * @returns {N}
+ */
function position(left, right) {
if (left.position) right.position = left.position
return right
diff --git a/package.json b/package.json
index f1f60cc..8c48e56 100644
--- a/package.json
+++ b/package.json
@@ -30,30 +30,40 @@
"sideEffects": false,
"type": "module",
"main": "index.js",
+ "types": "index.d.ts",
"files": [
"lib/",
+ "index.d.ts",
"index.js"
],
"dependencies": {
+ "@types/mdast": "^3.0.3",
+ "@types/unist": "^2.0.3",
"repeat-string": "^1.0.0",
"unist-util-visit": "^2.0.0",
"zwitch": "^2.0.0"
},
"devDependencies": {
+ "@types/tape": "^4.0.0",
"c8": "^7.0.0",
"concat-stream": "^2.0.0",
"prettier": "^2.0.0",
"regenerate": "^1.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
+ "rimraf": "^3.0.0",
"tape": "^5.0.0",
+ "type-coverage": "^2.0.0",
+ "typescript": "^4.0.0",
"xo": "^0.38.0"
},
"scripts": {
+ "prepack": "npm run build && npm run format",
+ "build": "rimraf \"{,lib/}*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --experimental-modules test/index.js",
- "test": "npm run format && npm run test-coverage"
+ "test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
@@ -78,5 +88,10 @@
"plugins": [
"preset-wooorm"
]
+ },
+ "typeCoverage": {
+ "atLeast": 100,
+ "detail": true,
+ "strict": true
}
}
diff --git a/test/from-mdast.js b/test/from-mdast.js
index 7474840..7341573 100644
--- a/test/from-mdast.js
+++ b/test/from-mdast.js
@@ -75,13 +75,13 @@ test('fromMdast', function (t) {
t.deepEqual(
fromMdast({type: 'heading', depth: 1}),
- {type: 'heading', rank: 1, value: ''},
+ [{type: 'heading', rank: 1, value: ''}],
'should support a heading w/o content'
)
t.deepEqual(
fromMdast({type: 'heading', children: [{type: 'text', value: 'a'}]}),
- {type: 'heading', rank: 1, value: 'a'},
+ [{type: 'heading', rank: 1, value: 'a'}],
'should support a heading (no depth)'
)
@@ -91,7 +91,7 @@ test('fromMdast', function (t) {
depth: 1,
children: [{type: 'text', value: 'a'}]
}),
- {type: 'heading', rank: 1, value: 'a'},
+ [{type: 'heading', rank: 1, value: 'a'}],
'should support a heading (depth: 1)'
)
@@ -101,7 +101,7 @@ test('fromMdast', function (t) {
depth: 3,
children: [{type: 'text', value: 'a'}]
}),
- {type: 'heading', rank: 3, value: 'a'},
+ [{type: 'heading', rank: 3, value: 'a'}],
'should support a heading (depth: 3)'
)
@@ -111,7 +111,7 @@ test('fromMdast', function (t) {
depth: 4,
children: [{type: 'text', value: 'a'}]
}),
- {type: 'text', value: 'a'},
+ [{type: 'text', value: 'a'}],
'should support a heading (depth: 4) as a `text`'
)
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..dac0208
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "files": [
+ "index.js",
+ "lib/buffer.js",
+ "lib/compiler.js",
+ "lib/from-gemtext.js",
+ "lib/from-mdast.js",
+ "lib/parser.js",
+ "lib/stream.js",
+ "lib/to-gemtext.js",
+ "lib/to-mdast.js"
+ ],
+ "include": ["*.js"],
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["ES2020"],
+ "module": "ES2020",
+ "moduleResolution": "node",
+ "allowJs": true,
+ "checkJs": true,
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true
+ }
+}