function inferAttributes(node, document) {
  /** @type {Array<Attribute>} */
  const attributes = []
  /** @type {Point | undefined} */
  let start

  // Children of leaf and text are *inside* `[` and `]`,
  // so before attributes.
  if (node.type === 'leafDirective' || node.type === 'textDirective') {
    const tail = node.children.at(-1)
    start = tail ? pointEnd(tail) : pointStart(node)
  }
  // Children of container can be both before and after attributes.
  else {
    const head = node.children[0]

    start =
      head && head.type === 'paragraph' && head.data && head.data.directiveLabel
        ? pointEnd(head)
        : pointStart(node)
  }

  if (!start) return attributes

  const brace = document.indexOf('{', start.offset)
  let lineFeed = document.indexOf('\n', start.offset)
  if (lineFeed === -1) lineFeed = document.length

  if (brace === -1 || brace > lineFeed) return attributes

  let index = brace + 1

  while (index < document.length) {
    // Skip whitespaces.
    while (markdownSpace(document.charCodeAt(index))) index++

    const code = document.charCodeAt(index)

    // Done.
    if (code === codes.rightCurlyBrace) break

    /** @type {Token} */
    let key
    /** @type {Token | undefined} */
    let value

    // Shortcut.
    if (code === codes.numberSign || code === codes.dot) {
      key = [document.charAt(index), index, index + 1]
      index++
      const valueStart = index
      while (shortcut(document.charCodeAt(index))) index++
      /* c8 ignore next -- exit if bogus, there should be a value, `##` or `#}` are not allowed to parse. */
      if (valueStart === index) break
      value = [document.slice(valueStart, index), valueStart, index]
    }
    // Key.
    else {
      const keyStart = index
      while (name(document.charCodeAt(index))) index++
      key = [document.slice(keyStart, index), keyStart, index]
      /* c8 ignore next -- exit if bogus, there should be a key. */
      if (keyStart === index) break

      while (markdownSpace(document.charCodeAt(index))) index++

      // Value.
      if (document.charCodeAt(index) === codes.equalsTo) {
        index++

        while (markdownSpace(document.charCodeAt(index))) index++

        const quoteCharacter = document.charCodeAt(index)
        const quote =
          quoteCharacter === codes.quotationMark
            ? codes.quotationMark
            : quoteCharacter === codes.apostrophe
              ? codes.apostrophe
              : undefined
        const valueStart = index

        if (quote) {
          index++
          while (document.charCodeAt(index) !== quote) index++
          index++
          value = [document.slice(valueStart + 1, index - 1), valueStart, index]
        }
        // Unquoted.
        else {
          while (unquoted(document.charCodeAt(index))) index++
          /* c8 ignore next -- exit if bogus, there should be a value. */
          if (valueStart === index) break
          value = [document.slice(valueStart, index), valueStart, index]
        }
      }
    }

    attributes.push({key, value})
  }

  return attributes
}
Internal API to get offsets of where attributes occur.
Shared with other lint rules to work with attributes.

@param {Directives} node
@param {string} document
@returns {Array<Attribute>}