syntax-tree / mdast-util-to-markdown

@@ -5,6 +5,7 @@
Loading
5 5
6 6
import {checkQuote} from '../util/check-quote.js'
7 7
import {safe} from '../util/safe.js'
8 +
import {track} from '../util/track.js'
8 9
9 10
image.peek = imagePeek
10 11
@@ -12,12 +13,17 @@
Loading
12 13
 * @type {Handle}
13 14
 * @param {Image} node
14 15
 */
15 -
export function image(node, _, context) {
16 +
export function image(node, _, context, safeOptions) {
16 17
  const quote = checkQuote(context)
17 18
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
18 19
  const exit = context.enter('image')
19 20
  let subexit = context.enter('label')
20 -
  let value = '![' + safe(context, node.alt, {before: '[', after: ']'}) + ']('
21 +
  const tracker = track(safeOptions)
22 +
  let value = tracker.move('![')
23 +
  value += tracker.move(
24 +
    safe(context, node.alt, {before: value, after: ']', ...tracker.current()})
25 +
  )
26 +
  value += tracker.move('](')
21 27
22 28
  subexit()
23 29
@@ -28,29 +34,40 @@
Loading
28 34
    /[\0- \u007F]/.test(node.url)
29 35
  ) {
30 36
    subexit = context.enter('destinationLiteral')
31 -
    value += '<' + safe(context, node.url, {before: '<', after: '>'}) + '>'
37 +
    value += tracker.move('<')
38 +
    value += tracker.move(
39 +
      safe(context, node.url, {before: value, after: '>', ...tracker.current()})
40 +
    )
41 +
    value += tracker.move('>')
32 42
  } else {
33 43
    // No whitespace, raw is prettier.
34 44
    subexit = context.enter('destinationRaw')
35 -
    value += safe(context, node.url, {
36 -
      before: '(',
37 -
      after: node.title ? ' ' : ')'
38 -
    })
45 +
    value += tracker.move(
46 +
      safe(context, node.url, {
47 +
        before: value,
48 +
        after: node.title ? ' ' : ')',
49 +
        ...tracker.current()
50 +
      })
51 +
    )
39 52
  }
40 53
41 54
  subexit()
42 55
43 56
  if (node.title) {
44 57
    subexit = context.enter('title' + suffix)
45 -
    value +=
46 -
      ' ' +
47 -
      quote +
48 -
      safe(context, node.title, {before: quote, after: quote}) +
49 -
      quote
58 +
    value += tracker.move(' ' + quote)
59 +
    value += tracker.move(
60 +
      safe(context, node.title, {
61 +
        before: value,
62 +
        after: quote,
63 +
        ...tracker.current()
64 +
      })
65 +
    )
66 +
    value += tracker.move(quote)
50 67
    subexit()
51 68
  }
52 69
53 -
  value += ')'
70 +
  value += tracker.move(')')
54 71
  exit()
55 72
56 73
  return value

@@ -5,6 +5,8 @@
Loading
5 5
 * @typedef {import('../types.js').Context} Context
6 6
 */
7 7
8 +
import {track} from './track.js'
9 +
8 10
/**
9 11
 * @param {Parent} parent
10 12
 * @param {Context} context
@@ -20,6 +22,7 @@
Loading
20 22
  let before = safeOptions.before
21 23
22 24
  indexStack.push(-1)
25 +
  let tracker = track(safeOptions)
23 26
24 27
  while (++index < children.length) {
25 28
    const child = children[index]
@@ -35,7 +38,8 @@
Loading
35 38
      after = handle
36 39
        ? handle(children[index + 1], parent, context, {
37 40
            before: '',
38 -
            after: ''
41 +
            after: '',
42 +
            ...tracker.current()
39 43
          }).charAt(0)
40 44
        : ''
41 45
    } else {
@@ -58,9 +62,21 @@
Loading
58 62
        ' '
59 63
      )
60 64
      before = ' '
65 +
66 +
      // To do: does this work to reset tracker?
67 +
      tracker = track(safeOptions)
68 +
      tracker.move(results.join(''))
61 69
    }
62 70
63 -
    results.push(context.handle(child, parent, context, {before, after}))
71 +
    results.push(
72 +
      tracker.move(
73 +
        context.handle(child, parent, context, {
74 +
          ...tracker.current(),
75 +
          before,
76 +
          after
77 +
        })
78 +
      )
79 +
    )
64 80
65 81
    before = results[results.length - 1].slice(-1)
66 82
  }

@@ -5,6 +5,7 @@
Loading
5 5
6 6
import {association} from '../util/association.js'
7 7
import {safe} from '../util/safe.js'
8 +
import {track} from '../util/track.js'
8 9
9 10
imageReference.peek = imageReferencePeek
10 11
@@ -12,27 +13,44 @@
Loading
12 13
 * @type {Handle}
13 14
 * @param {ImageReference} node
14 15
 */
15 -
export function imageReference(node, _, context) {
16 +
export function imageReference(node, _, context, safeOptions) {
16 17
  const type = node.referenceType
17 18
  const exit = context.enter('imageReference')
18 19
  let subexit = context.enter('label')
19 -
  const alt = safe(context, node.alt, {before: '[', after: ']'})
20 -
  let value = '![' + alt + ']'
20 +
  const tracker = track(safeOptions)
21 +
  let value = tracker.move('![')
22 +
  const alt = safe(context, node.alt, {
23 +
    before: value,
24 +
    after: ']',
25 +
    ...tracker.current()
26 +
  })
27 +
  value += tracker.move(alt + '][')
21 28
22 29
  subexit()
23 30
  // Hide the fact that we’re in phrasing, because escapes don’t work.
24 31
  const stack = context.stack
25 32
  context.stack = []
26 33
  subexit = context.enter('reference')
27 -
  const reference = safe(context, association(node), {before: '[', after: ']'})
34 +
  // Note: for proper tracking, we should reset the output positions when we end
35 +
  // up making a `shortcut` reference, because then there is no brace output.
36 +
  // Practically, in that case, there is no content, so it doesn’t matter that
37 +
  // we’ve tracked one too many characters.
38 +
  const reference = safe(context, association(node), {
39 +
    before: value,
40 +
    after: ']',
41 +
    ...tracker.current()
42 +
  })
28 43
  subexit()
29 44
  context.stack = stack
30 45
  exit()
31 46
32 47
  if (type === 'full' || !alt || alt !== reference) {
33 -
    value += '[' + reference + ']'
34 -
  } else if (type !== 'shortcut') {
35 -
    value += '[]'
48 +
    value += tracker.move(reference + ']')
49 +
  } else if (type === 'shortcut') {
50 +
    // Remove the unwanted `[`.
51 +
    value = value.slice(0, -1)
52 +
  } else {
53 +
    value += tracker.move(']')
36 54
  }
37 55
38 56
  return value

@@ -5,6 +5,7 @@
Loading
5 5
6 6
import {checkStrong} from '../util/check-strong.js'
7 7
import {containerPhrasing} from '../util/container-phrasing.js'
8 +
import {track} from '../util/track.js'
8 9
9 10
strong.peek = strongPeek
10 11
@@ -16,15 +17,21 @@
Loading
16 17
 * @type {Handle}
17 18
 * @param {Strong} node
18 19
 */
19 -
export function strong(node, _, context) {
20 +
export function strong(node, _, context, safeOptions) {
20 21
  const marker = checkStrong(context)
21 22
  const exit = context.enter('strong')
22 -
  const value = containerPhrasing(node, context, {
23 -
    before: marker,
24 -
    after: marker
25 -
  })
23 +
  const tracker = track(safeOptions)
24 +
  let value = tracker.move(marker + marker)
25 +
  value += tracker.move(
26 +
    containerPhrasing(node, context, {
27 +
      before: value,
28 +
      after: marker,
29 +
      ...tracker.current()
30 +
    })
31 +
  )
32 +
  value += tracker.move(marker + marker)
26 33
  exit()
27 -
  return marker + marker + value + marker + marker
34 +
  return value
28 35
}
29 36
30 37
/**

@@ -47,7 +47,12 @@
Loading
47 47
    handlers: context.handlers
48 48
  })
49 49
50 -
  let result = context.handle(tree, null, context, {before: '\n', after: '\n'})
50 +
  let result = context.handle(tree, null, context, {
51 +
    before: '\n',
52 +
    after: '\n',
53 +
    now: {line: 1, column: 1},
54 +
    lineShift: 0
55 +
  })
51 56
52 57
  if (
53 58
    result &&

@@ -9,10 +9,10 @@
Loading
9 9
 * @type {Handle}
10 10
 * @param {Paragraph} node
11 11
 */
12 -
export function paragraph(node, _, context) {
12 +
export function paragraph(node, _, context, safeOptions) {
13 13
  const exit = context.enter('paragraph')
14 14
  const subexit = context.enter('phrasing')
15 -
  const value = containerPhrasing(node, context, {before: '\n', after: '\n'})
15 +
  const value = containerPhrasing(node, context, safeOptions)
16 16
  subexit()
17 17
  exit()
18 18
  return value

@@ -8,6 +8,7 @@
Loading
8 8
import {formatLinkAsAutolink} from '../util/format-link-as-autolink.js'
9 9
import {containerPhrasing} from '../util/container-phrasing.js'
10 10
import {safe} from '../util/safe.js'
11 +
import {track} from '../util/track.js'
11 12
12 13
link.peek = linkPeek
13 14
@@ -15,23 +16,29 @@
Loading
15 16
 * @type {Handle}
16 17
 * @param {Link} node
17 18
 */
18 -
export function link(node, _, context) {
19 +
export function link(node, _, context, safeOptions) {
19 20
  const quote = checkQuote(context)
20 21
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
22 +
  const tracker = track(safeOptions)
21 23
  /** @type {Exit} */
22 24
  let exit
23 25
  /** @type {Exit} */
24 26
  let subexit
25 -
  /** @type {string} */
26 -
  let value
27 27
28 28
  if (formatLinkAsAutolink(node, context)) {
29 29
    // Hide the fact that we’re in phrasing, because escapes don’t work.
30 30
    const stack = context.stack
31 31
    context.stack = []
32 32
    exit = context.enter('autolink')
33 -
    value =
34 -
      '<' + containerPhrasing(node, context, {before: '<', after: '>'}) + '>'
33 +
    let value = tracker.move('<')
34 +
    value += tracker.move(
35 +
      containerPhrasing(node, context, {
36 +
        before: value,
37 +
        after: '>',
38 +
        ...tracker.current()
39 +
      })
40 +
    )
41 +
    value += tracker.move('>')
35 42
    exit()
36 43
    context.stack = stack
37 44
    return value
@@ -39,8 +46,15 @@
Loading
39 46
40 47
  exit = context.enter('link')
41 48
  subexit = context.enter('label')
42 -
  value =
43 -
    '[' + containerPhrasing(node, context, {before: '[', after: ']'}) + ']('
49 +
  let value = tracker.move('[')
50 +
  value += tracker.move(
51 +
    containerPhrasing(node, context, {
52 +
      before: value,
53 +
      after: '](',
54 +
      ...tracker.current()
55 +
    })
56 +
  )
57 +
  value += tracker.move('](')
44 58
  subexit()
45 59
46 60
  if (
@@ -50,29 +64,40 @@
Loading
50 64
    /[\0- \u007F]/.test(node.url)
51 65
  ) {
52 66
    subexit = context.enter('destinationLiteral')
53 -
    value += '<' + safe(context, node.url, {before: '<', after: '>'}) + '>'
67 +
    value += tracker.move('<')
68 +
    value += tracker.move(
69 +
      safe(context, node.url, {before: value, after: '>', ...tracker.current()})
70 +
    )
71 +
    value += tracker.move('>')
54 72
  } else {
55 73
    // No whitespace, raw is prettier.
56 74
    subexit = context.enter('destinationRaw')
57 -
    value += safe(context, node.url, {
58 -
      before: '(',
59 -
      after: node.title ? ' ' : ')'
60 -
    })
75 +
    value += tracker.move(
76 +
      safe(context, node.url, {
77 +
        before: value,
78 +
        after: node.title ? ' ' : ')',
79 +
        ...tracker.current()
80 +
      })
81 +
    )
61 82
  }
62 83
63 84
  subexit()
64 85
65 86
  if (node.title) {
66 87
    subexit = context.enter('title' + suffix)
67 -
    value +=
68 -
      ' ' +
69 -
      quote +
70 -
      safe(context, node.title, {before: quote, after: quote}) +
71 -
      quote
88 +
    value += tracker.move(' ' + quote)
89 +
    value += tracker.move(
90 +
      safe(context, node.title, {
91 +
        before: value,
92 +
        after: quote,
93 +
        ...tracker.current()
94 +
      })
95 +
    )
96 +
    value += tracker.move(quote)
72 97
    subexit()
73 98
  }
74 99
75 -
  value += ')'
100 +
  value += tracker.move(')')
76 101
77 102
  exit()
78 103
  return value

@@ -10,61 +10,63 @@
Loading
10 10
import {checkFence} from '../util/check-fence.js'
11 11
import {indentLines} from '../util/indent-lines.js'
12 12
import {safe} from '../util/safe.js'
13 +
import {track} from '../util/track.js'
13 14
14 15
/**
15 16
 * @type {Handle}
16 17
 * @param {Code} node
17 18
 */
18 -
export function code(node, _, context) {
19 +
export function code(node, _, context, safeOptions) {
19 20
  const marker = checkFence(context)
20 21
  const raw = node.value || ''
21 22
  const suffix = marker === '`' ? 'GraveAccent' : 'Tilde'
22 -
  /** @type {string} */
23 -
  let value
24 -
  /** @type {Exit} */
25 -
  let exit
26 23
27 24
  if (formatCodeAsIndented(node, context)) {
28 -
    exit = context.enter('codeIndented')
29 -
    value = indentLines(raw, map)
30 -
  } else {
31 -
    const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
32 -
    /** @type {Exit} */
33 -
    let subexit
34 -
    exit = context.enter('codeFenced')
35 -
    value = sequence
25 +
    const exit = context.enter('codeIndented')
26 +
    const value = indentLines(raw, map)
27 +
    exit()
28 +
    return value
29 +
  }
30 +
31 +
  const tracker = track(safeOptions)
32 +
  const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
33 +
  const exit = context.enter('codeFenced')
34 +
  let value = tracker.move(sequence)
36 35
37 -
    if (node.lang) {
38 -
      subexit = context.enter('codeFencedLang' + suffix)
39 -
      value += safe(context, node.lang, {
40 -
        before: '`',
36 +
  if (node.lang) {
37 +
    const subexit = context.enter('codeFencedLang' + suffix)
38 +
    value += tracker.move(
39 +
      safe(context, node.lang, {
40 +
        before: value,
41 41
        after: ' ',
42 -
        encode: ['`']
42 +
        encode: ['`'],
43 +
        ...tracker.current()
43 44
      })
44 -
      subexit()
45 -
    }
46 -
47 -
    if (node.lang && node.meta) {
48 -
      subexit = context.enter('codeFencedMeta' + suffix)
49 -
      value +=
50 -
        ' ' +
51 -
        safe(context, node.meta, {
52 -
          before: ' ',
53 -
          after: '\n',
54 -
          encode: ['`']
55 -
        })
56 -
      subexit()
57 -
    }
45 +
    )
46 +
    subexit()
47 +
  }
58 48
59 -
    value += '\n'
49 +
  if (node.lang && node.meta) {
50 +
    const subexit = context.enter('codeFencedMeta' + suffix)
51 +
    value += tracker.move(' ')
52 +
    value += tracker.move(
53 +
      safe(context, node.meta, {
54 +
        before: value,
55 +
        after: '\n',
56 +
        encode: ['`'],
57 +
        ...tracker.current()
58 +
      })
59 +
    )
60 +
    subexit()
61 +
  }
60 62
61 -
    if (raw) {
62 -
      value += raw + '\n'
63 -
    }
63 +
  value += tracker.move('\n')
64 64
65 -
    value += sequence
65 +
  if (raw) {
66 +
    value += tracker.move(raw + '\n')
66 67
  }
67 68
69 +
  value += tracker.move(sequence)
68 70
  exit()
69 71
  return value
70 72
}

@@ -5,6 +5,7 @@
Loading
5 5
6 6
import {checkEmphasis} from '../util/check-emphasis.js'
7 7
import {containerPhrasing} from '../util/container-phrasing.js'
8 +
import {track} from '../util/track.js'
8 9
9 10
emphasis.peek = emphasisPeek
10 11
@@ -16,15 +17,21 @@
Loading
16 17
 * @type {Handle}
17 18
 * @param {Emphasis} node
18 19
 */
19 -
export function emphasis(node, _, context) {
20 +
export function emphasis(node, _, context, safeOptions) {
20 21
  const marker = checkEmphasis(context)
21 22
  const exit = context.enter('emphasis')
22 -
  const value = containerPhrasing(node, context, {
23 -
    before: marker,
24 -
    after: marker
25 -
  })
23 +
  const tracker = track(safeOptions)
24 +
  let value = tracker.move(marker)
25 +
  value += tracker.move(
26 +
    containerPhrasing(node, context, {
27 +
      before: value,
28 +
      after: marker,
29 +
      ...tracker.current()
30 +
    })
31 +
  )
32 +
  value += tracker.move(marker)
26 33
  exit()
27 -
  return marker + value + marker
34 +
  return value
28 35
}
29 36
30 37
/**

@@ -6,18 +6,24 @@
Loading
6 6
7 7
import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js'
8 8
import {containerPhrasing} from '../util/container-phrasing.js'
9 +
import {track} from '../util/track.js'
9 10
10 11
/**
11 12
 * @type {Handle}
12 13
 * @param {Heading} node
13 14
 */
14 -
export function heading(node, _, context) {
15 +
export function heading(node, _, context, safeOptions) {
15 16
  const rank = Math.max(Math.min(6, node.depth || 1), 1)
17 +
  const tracker = track(safeOptions)
16 18
17 19
  if (formatHeadingAsSetext(node, context)) {
18 20
    const exit = context.enter('headingSetext')
19 21
    const subexit = context.enter('phrasing')
20 -
    const value = containerPhrasing(node, context, {before: '\n', after: '\n'})
22 +
    const value = containerPhrasing(node, context, {
23 +
      ...tracker.current(),
24 +
      before: '\n',
25 +
      after: '\n'
26 +
    })
21 27
    subexit()
22 28
    exit()
23 29
@@ -37,9 +43,21 @@
Loading
37 43
  const sequence = '#'.repeat(rank)
38 44
  const exit = context.enter('headingAtx')
39 45
  const subexit = context.enter('phrasing')
40 -
  let value = containerPhrasing(node, context, {before: '# ', after: '\n'})
46 +
47 +
  // Note: for proper tracking, we should reset the output positions when there
48 +
  // is no content returned, because then the space is not output.
49 +
  // Practically, in that case, there is no content, so it doesn’t matter that
50 +
  // we’ve tracked one too many characters.
51 +
  tracker.move(sequence + ' ')
52 +
53 +
  let value = containerPhrasing(node, context, {
54 +
    before: '# ',
55 +
    after: '\n',
56 +
    ...tracker.current()
57 +
  })
41 58
42 59
  if (/^[\t ]/.test(value)) {
60 +
    // To do: what effect has the character reference on tracking?
43 61
    value =
44 62
      '&#x' +
45 63
      value.charCodeAt(0).toString(16).toUpperCase() +

@@ -6,18 +6,27 @@
Loading
6 6
import {association} from '../util/association.js'
7 7
import {checkQuote} from '../util/check-quote.js'
8 8
import {safe} from '../util/safe.js'
9 +
import {track} from '../util/track.js'
9 10
10 11
/**
11 12
 * @type {Handle}
12 13
 * @param {Definition} node
13 14
 */
14 -
export function definition(node, _, context) {
15 -
  const marker = checkQuote(context)
16 -
  const suffix = marker === '"' ? 'Quote' : 'Apostrophe'
15 +
export function definition(node, _, context, safeOptions) {
16 +
  const quote = checkQuote(context)
17 +
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
17 18
  const exit = context.enter('definition')
18 19
  let subexit = context.enter('label')
19 -
  let value =
20 -
    '[' + safe(context, association(node), {before: '[', after: ']'}) + ']: '
20 +
  const tracker = track(safeOptions)
21 +
  let value = tracker.move('[')
22 +
  value += tracker.move(
23 +
    safe(context, association(node), {
24 +
      before: value,
25 +
      after: ']',
26 +
      ...tracker.current()
27 +
    })
28 +
  )
29 +
  value += tracker.move(']: ')
21 30
22 31
  subexit()
23 32
@@ -28,22 +37,36 @@
Loading
28 37
    /[\0- \u007F]/.test(node.url)
29 38
  ) {
30 39
    subexit = context.enter('destinationLiteral')
31 -
    value += '<' + safe(context, node.url, {before: '<', after: '>'}) + '>'
40 +
    value += tracker.move('<')
41 +
    value += tracker.move(
42 +
      safe(context, node.url, {before: value, after: '>', ...tracker.current()})
43 +
    )
44 +
    value += tracker.move('>')
32 45
  } else {
33 46
    // No whitespace, raw is prettier.
34 47
    subexit = context.enter('destinationRaw')
35 -
    value += safe(context, node.url, {before: ' ', after: ' '})
48 +
    value += tracker.move(
49 +
      safe(context, node.url, {
50 +
        before: value,
51 +
        after: node.title ? ' ' : '\n',
52 +
        ...tracker.current()
53 +
      })
54 +
    )
36 55
  }
37 56
38 57
  subexit()
39 58
40 59
  if (node.title) {
41 60
    subexit = context.enter('title' + suffix)
42 -
    value +=
43 -
      ' ' +
44 -
      marker +
45 -
      safe(context, node.title, {before: marker, after: marker}) +
46 -
      marker
61 +
    value += tracker.move(' ' + quote)
62 +
    value += tracker.move(
63 +
      safe(context, node.title, {
64 +
        before: value,
65 +
        after: quote,
66 +
        ...tracker.current()
67 +
      })
68 +
    )
69 +
    value += tracker.move(quote)
47 70
    subexit()
48 71
  }
49 72

@@ -10,12 +10,13 @@
Loading
10 10
import {checkListItemIndent} from '../util/check-list-item-indent.js'
11 11
import {containerFlow} from '../util/container-flow.js'
12 12
import {indentLines} from '../util/indent-lines.js'
13 +
import {track} from '../util/track.js'
13 14
14 15
/**
15 16
 * @type {Handle}
16 17
 * @param {ListItem} node
17 18
 */
18 -
export function listItem(node, parent, context) {
19 +
export function listItem(node, parent, context, safeOptions) {
19 20
  const listItemIndent = checkListItemIndent(context)
20 21
  let bullet = context.bulletCurrent || checkBullet(context)
21 22
@@ -41,8 +42,14 @@
Loading
41 42
    size = Math.ceil(size / 4) * 4
42 43
  }
43 44
45 +
  const tracker = track(safeOptions)
46 +
  tracker.move(bullet + ' '.repeat(size - bullet.length))
47 +
  tracker.shift(size)
44 48
  const exit = context.enter('listItem')
45 -
  const value = indentLines(containerFlow(node, context), map)
49 +
  const value = indentLines(
50 +
    containerFlow(node, context, tracker.current()),
51 +
    map
52 +
  )
46 53
  exit()
47 54
48 55
  return value

@@ -9,6 +9,6 @@
Loading
9 9
 * @type {Handle}
10 10
 * @param {Root} node
11 11
 */
12 -
export function root(node, _, context) {
13 -
  return containerFlow(node, context)
12 +
export function root(node, _, context, safeOptions) {
13 +
  return containerFlow(node, context, safeOptions)
14 14
}

@@ -14,7 +14,7 @@
Loading
14 14
 * @type {Handle}
15 15
 * @param {List} node
16 16
 */
17 -
export function list(node, parent, context) {
17 +
export function list(node, parent, context, safeOptions) {
18 18
  const exit = context.enter('list')
19 19
  const bulletCurrent = context.bulletCurrent
20 20
  /** @type {string} */
@@ -101,7 +101,7 @@
Loading
101 101
  }
102 102
103 103
  context.bulletCurrent = bullet
104 -
  const value = containerFlow(node, context)
104 +
  const value = containerFlow(node, context, safeOptions)
105 105
  context.bulletLastUsed = bullet
106 106
  context.bulletCurrent = bulletCurrent
107 107
  exit()

@@ -6,6 +6,7 @@
Loading
6 6
import {association} from '../util/association.js'
7 7
import {containerPhrasing} from '../util/container-phrasing.js'
8 8
import {safe} from '../util/safe.js'
9 +
import {track} from '../util/track.js'
9 10
10 11
linkReference.peek = linkReferencePeek
11 12
@@ -13,27 +14,44 @@
Loading
13 14
 * @type {Handle}
14 15
 * @param {LinkReference} node
15 16
 */
16 -
export function linkReference(node, _, context) {
17 +
export function linkReference(node, _, context, safeOptions) {
17 18
  const type = node.referenceType
18 19
  const exit = context.enter('linkReference')
19 20
  let subexit = context.enter('label')
20 -
  const text = containerPhrasing(node, context, {before: '[', after: ']'})
21 -
  let value = '[' + text + ']'
21 +
  const tracker = track(safeOptions)
22 +
  let value = tracker.move('[')
23 +
  const text = containerPhrasing(node, context, {
24 +
    before: value,
25 +
    after: ']',
26 +
    ...tracker.current()
27 +
  })
28 +
  value += tracker.move(text + '][')
22 29
23 30
  subexit()
24 31
  // Hide the fact that we’re in phrasing, because escapes don’t work.
25 32
  const stack = context.stack
26 33
  context.stack = []
27 34
  subexit = context.enter('reference')
28 -
  const reference = safe(context, association(node), {before: '[', after: ']'})
35 +
  // Note: for proper tracking, we should reset the output positions when we end
36 +
  // up making a `shortcut` reference, because then there is no brace output.
37 +
  // Practically, in that case, there is no content, so it doesn’t matter that
38 +
  // we’ve tracked one too many characters.
39 +
  const reference = safe(context, association(node), {
40 +
    before: value,
41 +
    after: ']',
42 +
    ...tracker.current()
43 +
  })
29 44
  subexit()
30 45
  context.stack = stack
31 46
  exit()
32 47
33 48
  if (type === 'full' || !text || text !== reference) {
34 -
    value += '[' + reference + ']'
35 -
  } else if (type !== 'shortcut') {
36 -
    value += '[]'
49 +
    value += tracker.move(reference + ']')
50 +
  } else if (type === 'shortcut') {
51 +
    // Remove the unwanted `[`.
52 +
    value = value.slice(0, -1)
53 +
  } else {
54 +
    value += tracker.move(']')
37 55
  }
38 56
39 57
  return value

@@ -6,14 +6,21 @@
Loading
6 6
7 7
import {containerFlow} from '../util/container-flow.js'
8 8
import {indentLines} from '../util/indent-lines.js'
9 +
import {track} from '../util/track.js'
9 10
10 11
/**
11 12
 * @type {Handle}
12 13
 * @param {Blockquote} node
13 14
 */
14 -
export function blockquote(node, _, context) {
15 +
export function blockquote(node, _, context, safeOptions) {
15 16
  const exit = context.enter('blockquote')
16 -
  const value = indentLines(containerFlow(node, context), map)
17 +
  const tracker = track(safeOptions)
18 +
  tracker.move('> ')
19 +
  tracker.shift(2)
20 +
  const value = indentLines(
21 +
    containerFlow(node, context, tracker.current()),
22 +
    map
23 +
  )
17 24
  exit()
18 25
  return value
19 26
}

@@ -3,16 +3,21 @@
Loading
3 3
 * @typedef {import('../types.js').Parent} Parent
4 4
 * @typedef {import('../types.js').Join} Join
5 5
 * @typedef {import('../types.js').Context} Context
6 +
 * @typedef {import('../types.js').TrackFields} TrackFields
6 7
 */
7 8
9 +
import {track} from './track.js'
10 +
8 11
/**
9 12
 * @param {Parent} parent
10 13
 * @param {Context} context
14 +
 * @param {TrackFields} safeOptions
11 15
 * @returns {string}
12 16
 */
13 -
export function containerFlow(parent, context) {
17 +
export function containerFlow(parent, context, safeOptions) {
14 18
  const indexStack = context.indexStack
15 19
  const children = parent.children || []
20 +
  const tracker = track(safeOptions)
16 21
  /** @type {Array<string>} */
17 22
  const results = []
18 23
  let index = -1
@@ -25,7 +30,13 @@
Loading
25 30
    indexStack[indexStack.length - 1] = index
26 31
27 32
    results.push(
28 -
      context.handle(child, parent, context, {before: '\n', after: '\n'})
33 +
      tracker.move(
34 +
        context.handle(child, parent, context, {
35 +
          before: '\n',
36 +
          after: '\n',
37 +
          ...tracker.current()
38 +
        })
39 +
      )
29 40
    )
30 41
31 42
    if (child.type !== 'list') {
@@ -33,7 +44,7 @@
Loading
33 44
    }
34 45
35 46
    if (index < children.length - 1) {
36 -
      results.push(between(child, children[index + 1]))
47 +
      results.push(tracker.move(between(child, children[index + 1])))
37 48
    }
38 49
  }
39 50

@@ -0,0 +1,53 @@
Loading
1 +
/**
2 +
 * @typedef {import('unist').Point} Point
3 +
 * @typedef {import('../types.js').TrackFields} TrackFields
4 +
 */
5 +
6 +
/**
7 +
 * Functions to track output positions.
8 +
 * This info isn’t used yet but suchs functionality allows line wrapping,
9 +
 * and theoretically source maps (though, is there practical use in that?).
10 +
 *
11 +
 * @param {TrackFields} options
12 +
 */
13 +
export function track(options) {
14 +
  const now = options.now
15 +
  let lineShift = options.lineShift
16 +
  let line = now.line
17 +
  let column = now.column
18 +
19 +
  return {move, current, shift}
20 +
21 +
  /**
22 +
   * Get the current tracked info.
23 +
   *
24 +
   * @returns {{now: Point, lineShift: number}}
25 +
   */
26 +
  function current() {
27 +
    return {now: {line, column}, lineShift}
28 +
  }
29 +
30 +
  /**
31 +
   * Define an increased line shift (the typical indent for lines).
32 +
   *
33 +
   * @param {number} value
34 +
   */
35 +
  function shift(value) {
36 +
    lineShift += value
37 +
  }
38 +
39 +
  /**
40 +
   * Move past a string.
41 +
   *
42 +
   * @param {string} value
43 +
   * @returns {string}
44 +
   */
45 +
  function move(value = '') {
46 +
    const chunks = value.split(/\r?\n|\r/g)
47 +
    const tail = chunks[chunks.length - 1]
48 +
    line += chunks.length - 1
49 +
    column =
50 +
      chunks.length === 1 ? column + tail.length : 1 + tail.length + lineShift
51 +
    return value
52 +
  }
53 +
}
Files Coverage
lib 100.00%
index.js 100.00%
Project Totals (47 files) 100.00%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading