link.js 2.7 KB

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