Skip to content

Commit

Permalink
Change to use maps for definitions on state
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 16, 2023
1 parent b328aa9 commit 72b8a68
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 103 deletions.
6 changes: 4 additions & 2 deletions lib/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function footer(state) {
let index = -1

while (++index < state.footnoteOrder.length) {
const def = state.footnoteById[state.footnoteOrder[index]]
const def = state.footnoteById.get(state.footnoteOrder[index])

if (!def) {
continue
Expand All @@ -44,8 +44,10 @@ export function footer(state) {
let referenceIndex = 0
/** @type {Array<ElementContent>} */
const backReferences = []
const counts = state.footnoteCounts.get(id)

while (++referenceIndex <= state.footnoteCounts[id]) {
// eslint-disable-next-line no-unmodified-loop-condition
while (counts !== undefined && ++referenceIndex <= counts) {
/** @type {Element} */
const backReference = {
type: 'element',
Expand Down
10 changes: 6 additions & 4 deletions lib/handlers/footnote-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ export function footnoteReference(state, node) {
/** @type {number} */
let counter

if (index === -1) {
let reuseCounter = state.footnoteCounts.get(id)

if (reuseCounter === undefined) {
reuseCounter = 0
state.footnoteOrder.push(id)
state.footnoteCounts[id] = 1
counter = state.footnoteOrder.length
} else {
state.footnoteCounts[id]++
counter = index + 1
}

const reuseCounter = state.footnoteCounts[id]
reuseCounter += 1
state.footnoteCounts.set(id, reuseCounter)

/** @type {Element} */
const link = {
Expand Down
10 changes: 6 additions & 4 deletions lib/handlers/footnote.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* @typedef {import('hast').Element} Element
* @typedef {import('mdast').FootnoteDefinition} FootnoteDefinition
* @typedef {import('mdast').Node} Node
* @typedef {import('../state.js').State} State
*/
Expand All @@ -23,21 +24,22 @@ import {footnoteReference} from './footnote-reference.js'
* hast node.
*/
export function footnote(state, node) {
const footnoteById = state.footnoteById
let no = 1

while (no in footnoteById) no++
while (state.footnoteById.get(String(no))) no++

const identifier = String(no)

footnoteById[identifier] = {
/** @type {FootnoteDefinition} */
const definition = {
type: 'footnoteDefinition',
identifier,
// @ts-expect-error: to do: remove this.
children: [{type: 'paragraph', children: node.children}],
position: node.position
}

state.footnoteById.set(identifier, definition)

return footnoteReference(state, {
type: 'footnoteReference',
identifier,
Expand Down
3 changes: 2 additions & 1 deletion lib/handlers/image-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {revert} from '../revert.js'
* hast node.
*/
export function imageReference(state, node) {
const def = state.definition(node.identifier)
const id = String(node.identifier).toUpperCase()
const def = state.definitionById.get(id)

if (!def) {
return revert(state, node)
Expand Down
3 changes: 2 additions & 1 deletion lib/handlers/link-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {revert} from '../revert.js'
* hast node.
*/
export function linkReference(state, node) {
const def = state.definition(node.identifier)
const id = String(node.identifier).toUpperCase()
const def = state.definitionById.get(id)

if (!def) {
return revert(state, node)
Expand Down
117 changes: 30 additions & 87 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@
* Transform the children of an mdast parent to hast.
* @property {<Type extends HastNodes>(from: MdastNodes, to: Type) => HastElement | Type} applyData
* Honor the `data` of `from`, and generate an element instead of `node`.
* @property {Record<string, MdastFootnoteDefinition>} footnoteById
* @property {Map<string, MdastDefinition>} definitionById
* Definitions by their identifier.
* @property {Map<string, MdastFootnoteDefinition>} footnoteById
* Footnote definitions by their identifier.
* @property {Record<string, number>} footnoteCounts
* @property {Map<string, number>} footnoteCounts
* Counts for how often the same footnote was called.
* @property {Array<string>} footnoteOrder
* Identifiers of order when footnote calls first appear in tree order.
Expand All @@ -84,11 +86,6 @@
* @property {<Type extends HastRootContent>(nodes: Array<Type>, loose?: boolean | null | undefined) => Array<HastText | Type>} wrap
* Wrap `nodes` with line endings between each node, adds initial/final line endings when `loose`.
*
* @property {(identifier: string) => MdastDefinition | undefined} definition
* Definition cache.
*
* To do: expose map, document.
*
* @typedef Options
* Configuration (optional).
* @property {boolean | null | undefined} [allowDangerousHtml=false]
Expand Down Expand Up @@ -123,8 +120,7 @@
*/

import {visit} from 'unist-util-visit'
import {pointEnd, pointStart, position} from 'unist-util-position'
import {definitions} from 'mdast-util-definitions'
import {position} from 'unist-util-position'
import {handlers} from './handlers/index.js'

const own = {}.hasOwnProperty
Expand All @@ -141,100 +137,47 @@ const own = {}.hasOwnProperty
*/
export function createState(tree, options) {
const settings = options || {}
/** @type {Record<string, MdastFootnoteDefinition>} */
const footnoteById = {}
/** @type {Map<string, MdastDefinition>} */
const definitionById = new Map()
/** @type {Map<string, MdastFootnoteDefinition>} */
const footnoteById = new Map()
/** @type {Map<string, number>} */
const footnoteCounts = new Map()

/** @type {State} */
const state = {
options: settings,
// @ts-expect-error: fix `null` handling?
handlers: {...handlers, ...settings.handlers},

// To do: next major: replace utility with `definitionById` object, so we
// only walk once (as we need footnotes too).
definition: definitions(tree),
all: allBound,
applyData,
definitionById,
footnoteById,
/** @type {Array<string>} */
footnoteCounts,
footnoteOrder: [],
/** @type {Record<string, number>} */
footnoteCounts: {},

patch,
applyData,
// @ts-expect-error: fix `null` handling?
handlers: {...handlers, ...settings.handlers},
// @ts-expect-error: fix `null` handling.
one: oneBound,
all: allBound,
options: settings,
patch,
// @ts-expect-error: fix `null` handling.
wrap,
// To do: next major: remove `augment`.
augment
wrap
}

visit(tree, 'footnoteDefinition', function (definition) {
const id = String(definition.identifier).toUpperCase()
visit(tree, function (node) {
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
const map = node.type === 'definition' ? definitionById : footnoteById
const id = String(node.identifier).toUpperCase()

// Mimick CM behavior of link definitions.
// See: <https://github.com/syntax-tree/mdast-util-definitions/blob/8290999/index.js#L26>.
if (!own.call(footnoteById, id)) {
footnoteById[id] = definition
// Mimick CM behavior of link definitions.
// See: <https://github.com/syntax-tree/mdast-util-definitions/blob/9032189/lib/index.js#L20-L21>.
if (!map.has(id)) {
// @ts-expect-error: node type matches map.
map.set(id, node)
}
}
})

return state

/**
* Finalise the created `right`, a hast node, from `left`, an mdast node.
*
* @param {MdastNodeWithData | PositionLike | null | undefined} left
* @param {HastElementContent} right
* @returns {HastElementContent}
*/
/* c8 ignore start */
// To do: next major: remove.
function augment(left, right) {
// Handle `data.hName`, `data.hProperties, `data.hChildren`.
if (left && 'data' in left && left.data) {
/** @type {MdastData} */
const data = left.data

if (data.hName) {
if (right.type !== 'element') {
right = {
type: 'element',
tagName: '',
properties: {},
children: []
}
}

right.tagName = data.hName
}

if (right.type === 'element' && data.hProperties) {
right.properties = {...right.properties, ...data.hProperties}
}

if ('children' in right && right.children && data.hChildren) {
right.children = data.hChildren
}
}

if (left) {
const ctx = 'type' in left ? left : {position: left}
// @ts-expect-error: fine, can be removed when this function is removed.
const start = pointStart(ctx)
// @ts-expect-error: fine, can be removed when this function is removed.
const end = pointEnd(ctx)

if (start && end) {
right.position = {start, end}
}
}

return right
}
/* c8 ignore stop */

/**
* Transform an mdast node into a hast node.
*
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
"@types/mdast": "^4.0.0",
"@ungap/structured-clone": "^1.0.0",
"devlop": "^1.0.0",
"mdast-util-definitions": "^6.0.0",
"micromark-util-sanitize-uri": "^2.0.0",
"trim-lines": "^3.0.0",
"unist-util-position": "^5.0.0",
Expand Down
7 changes: 4 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,11 @@ Info passed around about the current state (TypeScript type).
— transform the children of an mdast parent to hast
* `applyData` (`<Type extends HastNode>(from: MdastNode, to: Type) => Type | HastElement`)
— honor the `data` of `from` and maybe generate an element instead of `to`
* `footnoteById` (`Record<string, MdastFootnoteDefinition>`)
* `definitionById` (`Map<string, Definition>`)
— definitions by their uppercased identifier
* `footnoteById` (`Map<string, FootnoteDefinition>`)
— footnote definitions by their uppercased identifier
* `footnoteCounts` (`Record<string, number>`)
* `footnoteCounts` (`Map<string, number>`)
— counts for how often the same footnote was called
* `footnoteOrder` (`Array<string>`)
— identifiers of order when footnote calls first appear in tree order
Expand All @@ -299,7 +301,6 @@ Info passed around about the current state (TypeScript type).
* `options` ([`Options`][api-options])
— configuration
* `patch` (`(from: MdastNode, to: HastNode) => undefined`)
— copy a node’s positional info
* `wrap` (`<Type extends HastNode>(nodes: Array<Type>, loose?: boolean) => Array<Type | HastText>`)
— wrap `nodes` with line endings between each node, adds initial/final line
endings when `loose`
Expand Down

0 comments on commit 72b8a68

Please sign in to comment.