link.js 2.6 KB

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