Add JSDoc based types

This commit is contained in:
Titus Wormer 2021-06-08 17:11:30 +02:00
parent 472c259375
commit a0f521746f
No known key found for this signature in database
GPG key ID: E6E581152ED04E2E
14 changed files with 291 additions and 19 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
.DS_Store .DS_Store
*.d.ts
*.log *.log
coverage/ coverage/
node_modules/ node_modules/

View file

@ -1,2 +1,7 @@
/**
* @typedef {import('./lib/html.js').Handle} Handle
* @typedef {import('./lib/html.js').Options} Options
*/
export {directive} from './lib/syntax.js' export {directive} from './lib/syntax.js'
export {directiveHtml} from './lib/html.js' export {directiveHtml} from './lib/html.js'

View file

@ -1,3 +1,10 @@
/**
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Token} Token
*/
import assert from 'assert' import assert from 'assert'
import {factorySpace} from 'micromark-factory-space' import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character' import {markdownLineEnding} from 'micromark-util-character'
@ -8,6 +15,7 @@ import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js' import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js' import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveContainer = { export const directiveContainer = {
tokenize: tokenizeDirectiveContainer, tokenize: tokenizeDirectiveContainer,
concrete: true concrete: true
@ -16,6 +24,7 @@ export const directiveContainer = {
const label = {tokenize: tokenizeLabel, partial: true} const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true} const attributes = {tokenize: tokenizeAttributes, partial: true}
/** @type {Tokenizer} */
function tokenizeDirectiveContainer(effects, ok, nok) { function tokenizeDirectiveContainer(effects, ok, nok) {
const self = this const self = this
const tail = self.events[self.events.length - 1] const tail = self.events[self.events.length - 1]
@ -24,10 +33,12 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
? tail[2].sliceSerialize(tail[1], true).length ? tail[2].sliceSerialize(tail[1], true).length
: 0 : 0
let sizeOpen = 0 let sizeOpen = 0
/** @type {Token} */
let previous let previous
return start return start
/** @type {State} */
function start(code) { function start(code) {
assert(code === codes.colon, 'expected `:`') assert(code === codes.colon, 'expected `:`')
effects.enter('directiveContainer') effects.enter('directiveContainer')
@ -36,6 +47,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return sequenceOpen(code) return sequenceOpen(code)
} }
/** @type {State} */
function sequenceOpen(code) { function sequenceOpen(code) {
if (code === codes.colon) { if (code === codes.colon) {
effects.consume(code) effects.consume(code)
@ -57,22 +69,26 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
)(code) )(code)
} }
/** @type {State} */
function afterName(code) { function afterName(code) {
return code === codes.leftSquareBracket return code === codes.leftSquareBracket
? effects.attempt(label, afterLabel, afterLabel)(code) ? effects.attempt(label, afterLabel, afterLabel)(code)
: afterLabel(code) : afterLabel(code)
} }
/** @type {State} */
function afterLabel(code) { function afterLabel(code) {
return code === codes.leftCurlyBrace return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code) ? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code) : afterAttributes(code)
} }
/** @type {State} */
function afterAttributes(code) { function afterAttributes(code) {
return factorySpace(effects, openAfter, types.whitespace)(code) return factorySpace(effects, openAfter, types.whitespace)(code)
} }
/** @type {State} */
function openAfter(code) { function openAfter(code) {
effects.exit('directiveContainerFence') effects.exit('directiveContainerFence')
@ -91,6 +107,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return nok(code) return nok(code)
} }
/** @type {State} */
function contentStart(code) { function contentStart(code) {
if (code === codes.eof) { if (code === codes.eof) {
effects.exit('directiveContainer') effects.exit('directiveContainer')
@ -101,6 +118,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return lineStart(code) return lineStart(code)
} }
/** @type {State} */
function lineStart(code) { function lineStart(code) {
if (code === codes.eof) { if (code === codes.eof) {
return after(code) return after(code)
@ -115,6 +133,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
)(code) )(code)
} }
/** @type {State} */
function chunkStart(code) { function chunkStart(code) {
if (code === codes.eof) { if (code === codes.eof) {
return after(code) return after(code)
@ -129,6 +148,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return contentContinue(code) return contentContinue(code)
} }
/** @type {State} */
function contentContinue(code) { function contentContinue(code) {
if (code === codes.eof) { if (code === codes.eof) {
effects.exit('chunkDocument') effects.exit('chunkDocument')
@ -145,12 +165,14 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return contentContinue return contentContinue
} }
/** @type {State} */
function after(code) { function after(code) {
effects.exit('directiveContainerContent') effects.exit('directiveContainerContent')
effects.exit('directiveContainer') effects.exit('directiveContainer')
return ok(code) return ok(code)
} }
/** @type {Tokenizer} */
function tokenizeClosingFence(effects, ok, nok) { function tokenizeClosingFence(effects, ok, nok) {
let size = 0 let size = 0
@ -161,12 +183,14 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
constants.tabSize constants.tabSize
) )
/** @type {State} */
function closingPrefixAfter(code) { function closingPrefixAfter(code) {
effects.enter('directiveContainerFence') effects.enter('directiveContainerFence')
effects.enter('directiveContainerSequence') effects.enter('directiveContainerSequence')
return closingSequence(code) return closingSequence(code)
} }
/** @type {State} */
function closingSequence(code) { function closingSequence(code) {
if (code === codes.colon) { if (code === codes.colon) {
effects.consume(code) effects.consume(code)
@ -179,6 +203,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return factorySpace(effects, closingSequenceEnd, types.whitespace)(code) return factorySpace(effects, closingSequenceEnd, types.whitespace)(code)
} }
/** @type {State} */
function closingSequenceEnd(code) { function closingSequenceEnd(code) {
if (code === codes.eof || markdownLineEnding(code)) { if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('directiveContainerFence') effects.exit('directiveContainerFence')
@ -190,6 +215,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
} }
} }
/** @type {Tokenizer} */
function tokenizeLabel(effects, ok, nok) { function tokenizeLabel(effects, ok, nok) {
// Always a `[` // Always a `[`
return factoryLabel( return factoryLabel(
@ -203,6 +229,7 @@ function tokenizeLabel(effects, ok, nok) {
) )
} }
/** @type {Tokenizer} */
function tokenizeAttributes(effects, ok, nok) { function tokenizeAttributes(effects, ok, nok) {
// Always a `{` // Always a `{`
return factoryAttributes( return factoryAttributes(

View file

@ -1,3 +1,9 @@
/**
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
* @typedef {import('micromark-util-types').State} State
*/
import assert from 'assert' import assert from 'assert'
import {factorySpace} from 'micromark-factory-space' import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character' import {markdownLineEnding} from 'micromark-util-character'
@ -7,16 +13,19 @@ import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js' import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js' import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveLeaf = {tokenize: tokenizeDirectiveLeaf} export const directiveLeaf = {tokenize: tokenizeDirectiveLeaf}
const label = {tokenize: tokenizeLabel, partial: true} const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true} const attributes = {tokenize: tokenizeAttributes, partial: true}
/** @type {Tokenizer} */
function tokenizeDirectiveLeaf(effects, ok, nok) { function tokenizeDirectiveLeaf(effects, ok, nok) {
const self = this const self = this
return start return start
/** @type {State} */
function start(code) { function start(code) {
assert(code === codes.colon, 'expected `:`') assert(code === codes.colon, 'expected `:`')
effects.enter('directiveLeaf') effects.enter('directiveLeaf')
@ -25,6 +34,7 @@ function tokenizeDirectiveLeaf(effects, ok, nok) {
return inStart return inStart
} }
/** @type {State} */
function inStart(code) { function inStart(code) {
if (code === codes.colon) { if (code === codes.colon) {
effects.consume(code) effects.consume(code)
@ -41,22 +51,26 @@ function tokenizeDirectiveLeaf(effects, ok, nok) {
return nok(code) return nok(code)
} }
/** @type {State} */
function afterName(code) { function afterName(code) {
return code === codes.leftSquareBracket return code === codes.leftSquareBracket
? effects.attempt(label, afterLabel, afterLabel)(code) ? effects.attempt(label, afterLabel, afterLabel)(code)
: afterLabel(code) : afterLabel(code)
} }
/** @type {State} */
function afterLabel(code) { function afterLabel(code) {
return code === codes.leftCurlyBrace return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code) ? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code) : afterAttributes(code)
} }
/** @type {State} */
function afterAttributes(code) { function afterAttributes(code) {
return factorySpace(effects, end, types.whitespace)(code) return factorySpace(effects, end, types.whitespace)(code)
} }
/** @type {State} */
function end(code) { function end(code) {
if (code === codes.eof || markdownLineEnding(code)) { if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('directiveLeaf') effects.exit('directiveLeaf')
@ -67,6 +81,7 @@ function tokenizeDirectiveLeaf(effects, ok, nok) {
} }
} }
/** @type {Tokenizer} */
function tokenizeLabel(effects, ok, nok) { function tokenizeLabel(effects, ok, nok) {
// Always a `[` // Always a `[`
return factoryLabel( return factoryLabel(
@ -80,6 +95,7 @@ function tokenizeLabel(effects, ok, nok) {
) )
} }
/** @type {Tokenizer} */
function tokenizeAttributes(effects, ok, nok) { function tokenizeAttributes(effects, ok, nok) {
// Always a `{` // Always a `{`
return factoryAttributes( return factoryAttributes(

View file

@ -1,3 +1,10 @@
/**
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
* @typedef {import('micromark-util-types').Previous} Previous
* @typedef {import('micromark-util-types').State} State
*/
import assert from 'assert' import assert from 'assert'
import {codes} from 'micromark-util-symbol/codes.js' import {codes} from 'micromark-util-symbol/codes.js'
import {types} from 'micromark-util-symbol/types.js' import {types} from 'micromark-util-symbol/types.js'
@ -5,6 +12,7 @@ import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js' import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js' import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveText = { export const directiveText = {
tokenize: tokenizeDirectiveText, tokenize: tokenizeDirectiveText,
previous previous
@ -13,6 +21,7 @@ export const directiveText = {
const label = {tokenize: tokenizeLabel, partial: true} const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true} const attributes = {tokenize: tokenizeAttributes, partial: true}
/** @type {Previous} */
function previous(code) { function previous(code) {
// If there is a previous code, there will always be a tail. // If there is a previous code, there will always be a tail.
return ( return (
@ -21,11 +30,13 @@ function previous(code) {
) )
} }
/** @type {Tokenizer} */
function tokenizeDirectiveText(effects, ok, nok) { function tokenizeDirectiveText(effects, ok, nok) {
const self = this const self = this
return start return start
/** @type {State} */
function start(code) { function start(code) {
assert(code === codes.colon, 'expected `:`') assert(code === codes.colon, 'expected `:`')
assert(previous.call(self, self.previous), 'expected correct previous') assert(previous.call(self, self.previous), 'expected correct previous')
@ -36,6 +47,7 @@ function tokenizeDirectiveText(effects, ok, nok) {
return factoryName.call(self, effects, afterName, nok, 'directiveTextName') return factoryName.call(self, effects, afterName, nok, 'directiveTextName')
} }
/** @type {State} */
function afterName(code) { function afterName(code) {
return code === codes.colon return code === codes.colon
? nok(code) ? nok(code)
@ -44,18 +56,21 @@ function tokenizeDirectiveText(effects, ok, nok) {
: afterLabel(code) : afterLabel(code)
} }
/** @type {State} */
function afterLabel(code) { function afterLabel(code) {
return code === codes.leftCurlyBrace return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code) ? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code) : afterAttributes(code)
} }
/** @type {State} */
function afterAttributes(code) { function afterAttributes(code) {
effects.exit('directiveText') effects.exit('directiveText')
return ok(code) return ok(code)
} }
} }
/** @type {Tokenizer} */
function tokenizeLabel(effects, ok, nok) { function tokenizeLabel(effects, ok, nok) {
// Always a `[` // Always a `[`
return factoryLabel( return factoryLabel(
@ -68,6 +83,7 @@ function tokenizeLabel(effects, ok, nok) {
) )
} }
/** @type {Tokenizer} */
function tokenizeAttributes(effects, ok, nok) { function tokenizeAttributes(effects, ok, nok) {
// Always a `{` // Always a `{`
return factoryAttributes( return factoryAttributes(

View file

@ -1,3 +1,9 @@
/**
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Code} Code
*/
import assert from 'assert' import assert from 'assert'
import {factorySpace} from 'micromark-factory-space' import {factorySpace} from 'micromark-factory-space'
import {factoryWhitespace} from 'micromark-factory-whitespace' import {factoryWhitespace} from 'micromark-factory-whitespace'
@ -11,6 +17,23 @@ import {
import {codes} from 'micromark-util-symbol/codes.js' import {codes} from 'micromark-util-symbol/codes.js'
import {types} from 'micromark-util-symbol/types.js' import {types} from 'micromark-util-symbol/types.js'
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {string} attributesType
* @param {string} attributesMarkerType
* @param {string} attributeType
* @param {string} attributeIdType
* @param {string} attributeClassType
* @param {string} attributeNameType
* @param {string} attributeInitializerType
* @param {string} attributeValueLiteralType
* @param {string} attributeValueType
* @param {string} attributeValueMarker
* @param {string} attributeValueData
* @param {boolean} [disallowEol=false]
*/
/* eslint-disable-next-line max-params */ /* eslint-disable-next-line max-params */
export function factoryAttributes( export function factoryAttributes(
effects, effects,
@ -29,11 +52,14 @@ export function factoryAttributes(
attributeValueData, attributeValueData,
disallowEol disallowEol
) { ) {
/** @type {string} */
let type let type
/** @type {Code|undefined} */
let marker let marker
return start return start
/** @type {State} */
function start(code) { function start(code) {
assert(code === codes.leftCurlyBrace, 'expected `{`') assert(code === codes.leftCurlyBrace, 'expected `{`')
effects.enter(attributesType) effects.enter(attributesType)
@ -43,6 +69,7 @@ export function factoryAttributes(
return between return between
} }
/** @type {State} */
function between(code) { function between(code) {
if (code === codes.numberSign) { if (code === codes.numberSign) {
type = attributeIdType type = attributeIdType
@ -72,6 +99,7 @@ export function factoryAttributes(
return end(code) return end(code)
} }
/** @type {State} */
function shortcutStart(code) { function shortcutStart(code) {
effects.enter(attributeType) effects.enter(attributeType)
effects.enter(type) effects.enter(type)
@ -81,6 +109,7 @@ export function factoryAttributes(
return shortcutStartAfter return shortcutStartAfter
} }
/** @type {State} */
function shortcutStartAfter(code) { function shortcutStartAfter(code) {
if ( if (
code === codes.eof || code === codes.eof ||
@ -103,6 +132,7 @@ export function factoryAttributes(
return shortcut return shortcut
} }
/** @type {State} */
function shortcut(code) { function shortcut(code) {
if ( if (
code === codes.eof || code === codes.eof ||
@ -132,6 +162,7 @@ export function factoryAttributes(
return shortcut return shortcut
} }
/** @type {State} */
function name(code) { function name(code) {
if ( if (
code === codes.dash || code === codes.dash ||
@ -157,6 +188,7 @@ export function factoryAttributes(
return nameAfter(code) return nameAfter(code)
} }
/** @type {State} */
function nameAfter(code) { function nameAfter(code) {
if (code === codes.equalsTo) { if (code === codes.equalsTo) {
effects.enter(attributeInitializerType) effects.enter(attributeInitializerType)
@ -170,6 +202,7 @@ export function factoryAttributes(
return between(code) return between(code)
} }
/** @type {State} */
function valueBefore(code) { function valueBefore(code) {
if ( if (
code === codes.eof || code === codes.eof ||
@ -207,6 +240,7 @@ export function factoryAttributes(
return valueUnquoted return valueUnquoted
} }
/** @type {State} */
function valueUnquoted(code) { function valueUnquoted(code) {
if ( if (
code === codes.eof || code === codes.eof ||
@ -231,6 +265,7 @@ export function factoryAttributes(
return valueUnquoted return valueUnquoted
} }
/** @type {State} */
function valueQuotedStart(code) { function valueQuotedStart(code) {
if (code === marker) { if (code === marker) {
effects.enter(attributeValueMarker) effects.enter(attributeValueMarker)
@ -245,6 +280,7 @@ export function factoryAttributes(
return valueQuotedBetween(code) return valueQuotedBetween(code)
} }
/** @type {State} */
function valueQuotedBetween(code) { function valueQuotedBetween(code) {
if (code === marker) { if (code === marker) {
effects.exit(attributeValueType) effects.exit(attributeValueType)
@ -267,6 +303,7 @@ export function factoryAttributes(
return valueQuoted return valueQuoted
} }
/** @type {State} */
function valueQuoted(code) { function valueQuoted(code) {
if (code === marker || code === codes.eof || markdownLineEnding(code)) { if (code === marker || code === codes.eof || markdownLineEnding(code)) {
effects.exit(attributeValueData) effects.exit(attributeValueData)
@ -277,12 +314,14 @@ export function factoryAttributes(
return valueQuoted return valueQuoted
} }
/** @type {State} */
function valueQuotedAfter(code) { function valueQuotedAfter(code) {
return code === codes.rightCurlyBrace || markdownLineEndingOrSpace(code) return code === codes.rightCurlyBrace || markdownLineEndingOrSpace(code)
? between(code) ? between(code)
: end(code) : end(code)
} }
/** @type {State} */
function end(code) { function end(code) {
if (code === codes.rightCurlyBrace) { if (code === codes.rightCurlyBrace) {
effects.enter(attributesMarkerType) effects.enter(attributesMarkerType)

View file

@ -1,3 +1,8 @@
/**
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').State} State
*/
import assert from 'assert' import assert from 'assert'
import {markdownLineEnding} from 'micromark-util-character' import {markdownLineEnding} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol/codes.js' import {codes} from 'micromark-util-symbol/codes.js'
@ -9,6 +14,15 @@ import {types} from 'micromark-util-symbol/types.js'
// to allow empty labels, balanced brackets (such as for nested directives), // to allow empty labels, balanced brackets (such as for nested directives),
// text instead of strings, and optionally disallows EOLs. // text instead of strings, and optionally disallows EOLs.
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {string} type
* @param {string} markerType
* @param {string} stringType
* @param {boolean} [disallowEol=false]
*/
// eslint-disable-next-line max-params // eslint-disable-next-line max-params
export function factoryLabel( export function factoryLabel(
effects, effects,
@ -24,6 +38,7 @@ export function factoryLabel(
return start return start
/** @type {State} */
function start(code) { function start(code) {
assert(code === codes.leftSquareBracket, 'expected `[`') assert(code === codes.leftSquareBracket, 'expected `[`')
effects.enter(type) effects.enter(type)
@ -33,6 +48,7 @@ export function factoryLabel(
return afterStart return afterStart
} }
/** @type {State} */
function afterStart(code) { function afterStart(code) {
if (code === codes.rightSquareBracket) { if (code === codes.rightSquareBracket) {
effects.enter(markerType) effects.enter(markerType)
@ -46,6 +62,7 @@ export function factoryLabel(
return atBreak(code) return atBreak(code)
} }
/** @type {State} */
function atBreak(code) { function atBreak(code) {
if (code === codes.eof || size > constants.linkReferenceSizeMax) { if (code === codes.eof || size > constants.linkReferenceSizeMax) {
return nok(code) return nok(code)
@ -70,6 +87,7 @@ export function factoryLabel(
return label(code) return label(code)
} }
/** @type {State} */
function label(code) { function label(code) {
if ( if (
code === codes.eof || code === codes.eof ||
@ -96,6 +114,7 @@ export function factoryLabel(
return code === codes.backslash ? labelEscape : label return code === codes.backslash ? labelEscape : label
} }
/** @type {State} */
function atClosingBrace(code) { function atClosingBrace(code) {
effects.exit(stringType) effects.exit(stringType)
effects.enter(markerType) effects.enter(markerType)
@ -105,6 +124,7 @@ export function factoryLabel(
return ok return ok
} }
/** @type {State} */
function labelEscape(code) { function labelEscape(code) {
if ( if (
code === codes.leftSquareBracket || code === codes.leftSquareBracket ||

View file

@ -1,14 +1,28 @@
/**
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').State} State
*/
import {asciiAlpha, asciiAlphanumeric} from 'micromark-util-character' import {asciiAlpha, asciiAlphanumeric} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol/codes.js' import {codes} from 'micromark-util-symbol/codes.js'
export function factoryName(effects, ok, nok, nameType) { /**
* @this {TokenizeContext}
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {string} type
*/
export function factoryName(effects, ok, nok, type) {
const self = this const self = this
return start return start
/** @type {State} */
function start(code) { function start(code) {
if (asciiAlpha(code)) { if (asciiAlpha(code)) {
effects.enter(nameType) effects.enter(type)
effects.consume(code) effects.consume(code)
return name return name
} }
@ -16,6 +30,7 @@ export function factoryName(effects, ok, nok, nameType) {
return nok(code) return nok(code)
} }
/** @type {State} */
function name(code) { function name(code) {
if ( if (
code === codes.dash || code === codes.dash ||
@ -26,7 +41,7 @@ export function factoryName(effects, ok, nok, nameType) {
return name return name
} }
effects.exit(nameType) effects.exit(type)
return self.previous === codes.dash || self.previous === codes.underscore return self.previous === codes.dash || self.previous === codes.underscore
? nok(code) ? nok(code)
: ok(code) : ok(code)

View file

@ -1,7 +1,35 @@
/**
* @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
* @typedef {import('micromark-util-types').Handle} _Handle
* @typedef {import('micromark-util-types').CompileContext} CompileContext
*/
/**
* @typedef {[string, string]} Attribute
* @typedef {'containerDirective'|'leafDirective'|'textDirective'} DirectiveType
*
* @typedef Directive
* @property {DirectiveType} type
* @property {string} name
* @property {string} [label]
* @property {Record<string, string>} [attributes]
* @property {string} [content]
* @property {number} [_fenceCount]
*
* @typedef {(this: CompileContext, directive: Directive) => boolean|void} Handle
*
* @typedef {Record<string, Handle>} Options
*/
import assert from 'assert'
import {decodeEntity} from 'parse-entities/decode-entity.js' import {decodeEntity} from 'parse-entities/decode-entity.js'
const own = {}.hasOwnProperty const own = {}.hasOwnProperty
/**
* @param {Options} [options]
* @returns {HtmlExtension}
*/
export function directiveHtml(options = {}) { export function directiveHtml(options = {}) {
return { return {
enter: { enter: {
@ -58,65 +86,97 @@ export function directiveHtml(options = {}) {
} }
} }
/**
* @this {CompileContext}
* @param {DirectiveType} type
*/
function enter(type) { function enter(type) {
/** @type {Directive[]} */
// @ts-expect-error
let stack = this.getData('directiveStack') let stack = this.getData('directiveStack')
if (!stack) this.setData('directiveStack', (stack = [])) if (!stack) this.setData('directiveStack', (stack = []))
stack.push({type}) stack.push({type, name: ''})
} }
/** @type {_Handle} */
function exitName(token) { function exitName(token) {
/** @type {Directive[]} */
// @ts-expect-error
const stack = this.getData('directiveStack') const stack = this.getData('directiveStack')
stack[stack.length - 1].name = this.sliceSerialize(token) stack[stack.length - 1].name = this.sliceSerialize(token)
} }
/** @type {_Handle} */
function enterLabel() { function enterLabel() {
this.buffer() this.buffer()
} }
/** @type {_Handle} */
function exitLabel() { function exitLabel() {
const data = this.resume() const data = this.resume()
/** @type {Directive[]} */
// @ts-expect-error
const stack = this.getData('directiveStack') const stack = this.getData('directiveStack')
stack[stack.length - 1].label = data stack[stack.length - 1].label = data
} }
/** @type {_Handle} */
function enterAttributes() { function enterAttributes() {
this.buffer() this.buffer()
this.setData('directiveAttributes', []) this.setData('directiveAttributes', [])
} }
/** @type {_Handle} */
function exitAttributeIdValue(token) { function exitAttributeIdValue(token) {
this.getData('directiveAttributes').push([ /** @type {Attribute[]} */
'id', // @ts-expect-error
decodeLight(this.sliceSerialize(token)) const attributes = this.getData('directiveAttributes')
]) attributes.push(['id', decodeLight(this.sliceSerialize(token))])
} }
/** @type {_Handle} */
function exitAttributeClassValue(token) { function exitAttributeClassValue(token) {
this.getData('directiveAttributes').push([ /** @type {Attribute[]} */
'class', // @ts-expect-error
decodeLight(this.sliceSerialize(token)) const attributes = this.getData('directiveAttributes')
])
attributes.push(['class', decodeLight(this.sliceSerialize(token))])
} }
/** @type {_Handle} */
function exitAttributeName(token) { function exitAttributeName(token) {
// Attribute names in CommonMark are significantly limited, so character // Attribute names in CommonMark are significantly limited, so character
// references cant exist. // references cant exist.
this.getData('directiveAttributes').push([this.sliceSerialize(token), '']) /** @type {Attribute[]} */
// @ts-expect-error
const attributes = this.getData('directiveAttributes')
attributes.push([this.sliceSerialize(token), ''])
} }
/** @type {_Handle} */
function exitAttributeValue(token) { function exitAttributeValue(token) {
/** @type {Attribute[]} */
// @ts-expect-error
const attributes = this.getData('directiveAttributes') const attributes = this.getData('directiveAttributes')
attributes[attributes.length - 1][1] = decodeLight( attributes[attributes.length - 1][1] = decodeLight(
this.sliceSerialize(token) this.sliceSerialize(token)
) )
} }
/** @type {_Handle} */
function exitAttributes() { function exitAttributes() {
/** @type {Directive[]} */
// @ts-expect-error
const stack = this.getData('directiveStack') const stack = this.getData('directiveStack')
/** @type {Attribute[]} */
// @ts-expect-error
const attributes = this.getData('directiveAttributes') const attributes = this.getData('directiveAttributes')
/** @type {Directive['attributes']} */
const cleaned = {} const cleaned = {}
let index = -1 /** @type {Attribute} */
let attribute let attribute
let index = -1
while (++index < attributes.length) { while (++index < attributes.length) {
attribute = attributes[index] attribute = attributes[index]
@ -133,25 +193,38 @@ export function directiveHtml(options = {}) {
stack[stack.length - 1].attributes = cleaned stack[stack.length - 1].attributes = cleaned
} }
/** @type {_Handle} */
function exitContainerContent() { function exitContainerContent() {
const data = this.resume() const data = this.resume()
/** @type {Directive[]} */
// @ts-expect-error
const stack = this.getData('directiveStack') const stack = this.getData('directiveStack')
stack[stack.length - 1].content = data stack[stack.length - 1].content = data
} }
/** @type {_Handle} */
function exitContainerFence() { function exitContainerFence() {
/** @type {Directive[]} */
// @ts-expect-error
const stack = this.getData('directiveStack') const stack = this.getData('directiveStack')
const directive = stack[stack.length - 1] const directive = stack[stack.length - 1]
if (!directive.fenceCount) directive.fenceCount = 0 if (!directive._fenceCount) directive._fenceCount = 0
directive.fenceCount++ directive._fenceCount++
if (directive.fenceCount === 1) this.setData('slurpOneLineEnding', true) if (directive._fenceCount === 1) this.setData('slurpOneLineEnding', true)
} }
/** @type {_Handle} */
function exit() { function exit() {
/** @type {Directive} */
// @ts-expect-error
const directive = this.getData('directiveStack').pop() const directive = this.getData('directiveStack').pop()
/** @type {boolean|undefined} */
let found let found
/** @type {boolean|void} */
let result let result
assert(directive.name, 'expected `name`')
if (own.call(options, directive.name)) { if (own.call(options, directive.name)) {
result = options[directive.name].call(this, directive) result = options[directive.name].call(this, directive)
found = result !== false found = result !== false
@ -168,6 +241,10 @@ export function directiveHtml(options = {}) {
} }
} }
/**
* @param {string} value
* @returns {string}
*/
function decodeLight(value) { function decodeLight(value) {
return value.replace( return value.replace(
/&(#(\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi, /&(#(\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi,
@ -175,6 +252,11 @@ function decodeLight(value) {
) )
} }
/**
* @param {string} $0
* @param {string} $1
* @returns {string}
*/
function decodeIfPossible($0, $1) { function decodeIfPossible($0, $1) {
return decodeEntity($1) || $0 return decodeEntity($1) || $0
} }

View file

@ -1,8 +1,15 @@
/**
* @typedef {import('micromark-util-types').Extension} Extension
*/
import {codes} from 'micromark-util-symbol/codes.js' import {codes} from 'micromark-util-symbol/codes.js'
import {directiveContainer} from './directive-container.js' import {directiveContainer} from './directive-container.js'
import {directiveLeaf} from './directive-leaf.js' import {directiveLeaf} from './directive-leaf.js'
import {directiveText} from './directive-text.js' import {directiveText} from './directive-text.js'
/**
* @returns {Extension}
*/
export function directive() { export function directive() {
return { return {
text: {[codes.colon]: directiveText}, text: {[codes.colon]: directiveText},

View file

@ -26,9 +26,11 @@
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",
"types": "index.d.ts",
"files": [ "files": [
"dev/", "dev/",
"lib/", "lib/",
"index.d.ts",
"index.js" "index.js"
], ],
"exports": { "exports": {
@ -40,9 +42,11 @@
"micromark-factory-whitespace": "^1.0.0-alpha.2", "micromark-factory-whitespace": "^1.0.0-alpha.2",
"micromark-util-character": "^1.0.0-alpha.2", "micromark-util-character": "^1.0.0-alpha.2",
"micromark-util-symbol": "^1.0.0-alpha.2", "micromark-util-symbol": "^1.0.0-alpha.2",
"micromark-util-types": "^1.0.0-alpha.2",
"parse-entities": "^3.0.0" "parse-entities": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/tape": "^4.0.0",
"c8": "^7.0.0", "c8": "^7.0.0",
"html-void-elements": "^2.0.0", "html-void-elements": "^2.0.0",
"micromark": "^3.0.0-alpha.2", "micromark": "^3.0.0-alpha.2",
@ -50,11 +54,14 @@
"prettier": "^2.0.0", "prettier": "^2.0.0",
"remark-cli": "^9.0.0", "remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0", "remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0", "tape": "^5.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"xo": "^0.39.0" "xo": "^0.39.0"
}, },
"scripts": { "scripts": {
"build": "micromark-build", "build": "rimraf \"dev/**/*.d.ts\" \"test/**/*.d.ts\" && tsc && type-coverage && micromark-build",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test/index.js", "test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js", "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js",
@ -78,5 +85,11 @@
"plugins": [ "plugins": [
"preset-wooorm" "preset-wooorm"
] ]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
} }
} }

View file

@ -103,7 +103,8 @@ None yet, but might be added in the future.
###### `htmlOptions` ###### `htmlOptions`
An object mapping names of directives to handlers ([`Object.<Handle>`][handle]). An object mapping names of directives to handlers
([`Record<string, Handle>`][handle]).
The special name `'*'` is the fallback to handle all unhandled directives. The special name `'*'` is the fallback to handle all unhandled directives.
### `function handle(directive)` ### `function handle(directive)`

View file

@ -1,3 +1,8 @@
/**
* @typedef {import('../dev/index.js').Options} Options
* @typedef {import('../dev/index.js').Handle} Handle
*/
import test from 'tape' import test from 'tape'
import {micromark} from 'micromark' import {micromark} from 'micromark'
import {htmlVoidElements} from 'html-void-elements' import {htmlVoidElements} from 'html-void-elements'
@ -1369,6 +1374,7 @@ test('content', (t) => {
t.end() t.end()
}) })
/** @type {Handle} */
function abbr(d) { function abbr(d) {
if (d.type !== 'textDirective') return false if (d.type !== 'textDirective') return false
@ -1383,9 +1389,11 @@ function abbr(d) {
this.tag('</abbr>') this.tag('</abbr>')
} }
/** @type {Handle} */
function youtube(d) { function youtube(d) {
const attrs = d.attributes || {} const attrs = d.attributes || {}
const v = attrs.v const v = attrs.v
/** @type {string} */
let prop let prop
if (!v) return false if (!v) return false
@ -1416,10 +1424,13 @@ function youtube(d) {
this.tag('</iframe>') this.tag('</iframe>')
} }
/** @type {Handle} */
function h(d) { function h(d) {
const content = d.content || d.label const content = d.content || d.label
const attrs = d.attributes || {} const attrs = d.attributes || {}
/** @type {Array.<string>} */
const list = [] const list = []
/** @type {string} */
let prop let prop
for (prop in attrs) { for (prop in attrs) {
@ -1441,6 +1452,9 @@ function h(d) {
if (!htmlVoidElements.includes(d.name)) this.tag('</' + d.name + '>') if (!htmlVoidElements.includes(d.name)) this.tag('</' + d.name + '>')
} }
/**
* @param {Options} [options]
*/
function options(options) { function options(options) {
return { return {
allowDangerousHtml: true, allowDangerousHtml: true,

16
tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"include": ["dev/**/*.js", "test/**/*.js"],
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ES2020",
"moduleResolution": "node",
"allowJs": true,
"checkJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"strict": true
}
}