Skip to content

Commit 52c18b4

Browse files
committed
Add support for output position tracking
This adds support for tracking line and column numbers (not offsets) when generating output. This information is particularly useful to print information based on a future line wrap. Related to: syntax-tree/mdast-util-mdx-jsx#3.
1 parent 407af18 commit 52c18b4

20 files changed

+487
-164
lines changed

lib/handle/blockquote.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@
66

77
import {containerFlow} from '../util/container-flow.js'
88
import {indentLines} from '../util/indent-lines.js'
9+
import {track} from '../util/track.js'
910

1011
/**
1112
* @type {Handle}
1213
* @param {Blockquote} node
1314
*/
14-
export function blockquote(node, _, context) {
15+
export function blockquote(node, _, context, safeOptions) {
1516
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+
)
1724
exit()
1825
return value
1926
}

lib/handle/code.js

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,63 @@ import {formatCodeAsIndented} from '../util/format-code-as-indented.js'
1010
import {checkFence} from '../util/check-fence.js'
1111
import {indentLines} from '../util/indent-lines.js'
1212
import {safe} from '../util/safe.js'
13+
import {track} from '../util/track.js'
1314

1415
/**
1516
* @type {Handle}
1617
* @param {Code} node
1718
*/
18-
export function code(node, _, context) {
19+
export function code(node, _, context, safeOptions) {
1920
const marker = checkFence(context)
2021
const raw = node.value || ''
2122
const suffix = marker === '`' ? 'GraveAccent' : 'Tilde'
22-
/** @type {string} */
23-
let value
24-
/** @type {Exit} */
25-
let exit
2623

2724
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)
3635

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,
4141
after: ' ',
42-
encode: ['`']
42+
encode: ['`'],
43+
...tracker.current()
4344
})
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+
}
5848

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+
}
6062

61-
if (raw) {
62-
value += raw + '\n'
63-
}
63+
value += tracker.move('\n')
6464

65-
value += sequence
65+
if (raw) {
66+
value += tracker.move(raw + '\n')
6667
}
6768

69+
value += tracker.move(sequence)
6870
exit()
6971
return value
7072
}

lib/handle/definition.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,27 @@
66
import {association} from '../util/association.js'
77
import {checkQuote} from '../util/check-quote.js'
88
import {safe} from '../util/safe.js'
9+
import {track} from '../util/track.js'
910

1011
/**
1112
* @type {Handle}
1213
* @param {Definition} node
1314
*/
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'
1718
const exit = context.enter('definition')
1819
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(']: ')
2130

2231
subexit()
2332

@@ -28,22 +37,36 @@ export function definition(node, _, context) {
2837
/[\0- \u007F]/.test(node.url)
2938
) {
3039
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('>')
3245
} else {
3346
// No whitespace, raw is prettier.
3447
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+
)
3655
}
3756

3857
subexit()
3958

4059
if (node.title) {
4160
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)
4770
subexit()
4871
}
4972

lib/handle/emphasis.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import {checkEmphasis} from '../util/check-emphasis.js'
77
import {containerPhrasing} from '../util/container-phrasing.js'
8+
import {track} from '../util/track.js'
89

910
emphasis.peek = emphasisPeek
1011

@@ -16,15 +17,21 @@ emphasis.peek = emphasisPeek
1617
* @type {Handle}
1718
* @param {Emphasis} node
1819
*/
19-
export function emphasis(node, _, context) {
20+
export function emphasis(node, _, context, safeOptions) {
2021
const marker = checkEmphasis(context)
2122
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)
2633
exit()
27-
return marker + value + marker
34+
return value
2835
}
2936

3037
/**

lib/handle/heading.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@
66

77
import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js'
88
import {containerPhrasing} from '../util/container-phrasing.js'
9+
import {track} from '../util/track.js'
910

1011
/**
1112
* @type {Handle}
1213
* @param {Heading} node
1314
*/
14-
export function heading(node, _, context) {
15+
export function heading(node, _, context, safeOptions) {
1516
const rank = Math.max(Math.min(6, node.depth || 1), 1)
17+
const tracker = track(safeOptions)
1618

1719
if (formatHeadingAsSetext(node, context)) {
1820
const exit = context.enter('headingSetext')
1921
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+
})
2127
subexit()
2228
exit()
2329

@@ -37,9 +43,21 @@ export function heading(node, _, context) {
3743
const sequence = '#'.repeat(rank)
3844
const exit = context.enter('headingAtx')
3945
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+
})
4158

4259
if (/^[\t ]/.test(value)) {
60+
// To do: what effect has the character reference on tracking?
4361
value =
4462
'&#x' +
4563
value.charCodeAt(0).toString(16).toUpperCase() +

lib/handle/image-reference.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,52 @@
55

66
import {association} from '../util/association.js'
77
import {safe} from '../util/safe.js'
8+
import {track} from '../util/track.js'
89

910
imageReference.peek = imageReferencePeek
1011

1112
/**
1213
* @type {Handle}
1314
* @param {ImageReference} node
1415
*/
15-
export function imageReference(node, _, context) {
16+
export function imageReference(node, _, context, safeOptions) {
1617
const type = node.referenceType
1718
const exit = context.enter('imageReference')
1819
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 + '][')
2128

2229
subexit()
2330
// Hide the fact that we’re in phrasing, because escapes don’t work.
2431
const stack = context.stack
2532
context.stack = []
2633
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+
})
2843
subexit()
2944
context.stack = stack
3045
exit()
3146

3247
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(']')
3654
}
3755

3856
return value

0 commit comments

Comments
 (0)