preprocess.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * @import {Chunk, Code, Encoding, Value} from 'micromark-util-types'
  3. */
  4. /**
  5. * @callback Preprocessor
  6. * Preprocess a value.
  7. * @param {Value} value
  8. * Value.
  9. * @param {Encoding | null | undefined} [encoding]
  10. * Encoding when `value` is a typed array (optional).
  11. * @param {boolean | null | undefined} [end=false]
  12. * Whether this is the last chunk (default: `false`).
  13. * @returns {Array<Chunk>}
  14. * Chunks.
  15. */
  16. import {codes, constants} from 'micromark-util-symbol'
  17. const search = /[\0\t\n\r]/g
  18. /**
  19. * @returns {Preprocessor}
  20. * Preprocess a value.
  21. */
  22. export function preprocess() {
  23. let column = 1
  24. let buffer = ''
  25. /** @type {boolean | undefined} */
  26. let start = true
  27. /** @type {boolean | undefined} */
  28. let atCarriageReturn
  29. return preprocessor
  30. /** @type {Preprocessor} */
  31. // eslint-disable-next-line complexity
  32. function preprocessor(value, encoding, end) {
  33. /** @type {Array<Chunk>} */
  34. const chunks = []
  35. /** @type {RegExpMatchArray | null} */
  36. let match
  37. /** @type {number} */
  38. let next
  39. /** @type {number} */
  40. let startPosition
  41. /** @type {number} */
  42. let endPosition
  43. /** @type {Code} */
  44. let code
  45. value =
  46. buffer +
  47. (typeof value === 'string'
  48. ? value.toString()
  49. : new TextDecoder(encoding || undefined).decode(value))
  50. startPosition = 0
  51. buffer = ''
  52. if (start) {
  53. // To do: `markdown-rs` actually parses BOMs (byte order mark).
  54. if (value.charCodeAt(0) === codes.byteOrderMarker) {
  55. startPosition++
  56. }
  57. start = undefined
  58. }
  59. while (startPosition < value.length) {
  60. search.lastIndex = startPosition
  61. match = search.exec(value)
  62. endPosition =
  63. match && match.index !== undefined ? match.index : value.length
  64. code = value.charCodeAt(endPosition)
  65. if (!match) {
  66. buffer = value.slice(startPosition)
  67. break
  68. }
  69. if (
  70. code === codes.lf &&
  71. startPosition === endPosition &&
  72. atCarriageReturn
  73. ) {
  74. chunks.push(codes.carriageReturnLineFeed)
  75. atCarriageReturn = undefined
  76. } else {
  77. if (atCarriageReturn) {
  78. chunks.push(codes.carriageReturn)
  79. atCarriageReturn = undefined
  80. }
  81. if (startPosition < endPosition) {
  82. chunks.push(value.slice(startPosition, endPosition))
  83. column += endPosition - startPosition
  84. }
  85. switch (code) {
  86. case codes.nul: {
  87. chunks.push(codes.replacementCharacter)
  88. column++
  89. break
  90. }
  91. case codes.ht: {
  92. next = Math.ceil(column / constants.tabSize) * constants.tabSize
  93. chunks.push(codes.horizontalTab)
  94. while (column++ < next) chunks.push(codes.virtualSpace)
  95. break
  96. }
  97. case codes.lf: {
  98. chunks.push(codes.lineFeed)
  99. column = 1
  100. break
  101. }
  102. default: {
  103. atCarriageReturn = true
  104. column = 1
  105. }
  106. }
  107. }
  108. startPosition = endPosition + 1
  109. }
  110. if (end) {
  111. if (atCarriageReturn) chunks.push(codes.carriageReturn)
  112. if (buffer) chunks.push(buffer)
  113. chunks.push(codes.eof)
  114. }
  115. return chunks
  116. }
  117. }