TransformableString.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. "use strict"
  2. const lineEndingsRe = /\r\n|\r|\n/g
  3. function lineStarts(str) {
  4. const result = [0]
  5. lineEndingsRe.lastIndex = 0
  6. while (true) {
  7. const match = lineEndingsRe.exec(str)
  8. if (!match) break
  9. result.push(lineEndingsRe.lastIndex)
  10. }
  11. return result
  12. }
  13. function locationToIndex(location, lineStarts) {
  14. if (
  15. !location.line ||
  16. location.line < 0 ||
  17. !location.column ||
  18. location.column < 0
  19. ) {
  20. throw new Error("Invalid location")
  21. }
  22. return lineStarts[location.line - 1] + location.column - 1
  23. }
  24. function indexToLocation(index, lineStarts) {
  25. if (index < 0) throw new Error("Invalid index")
  26. let line = 0
  27. while (line + 1 < lineStarts.length && lineStarts[line + 1] <= index) {
  28. line += 1
  29. }
  30. return {
  31. line: line + 1,
  32. column: index - lineStarts[line] + 1,
  33. }
  34. }
  35. module.exports = class TransformableString {
  36. constructor(original) {
  37. this._original = original
  38. this._blocks = []
  39. this._lineStarts = lineStarts(original)
  40. this._cache = null
  41. }
  42. _compute() {
  43. if (!this._cache) {
  44. let result = ""
  45. let index = 0
  46. for (const block of this._blocks) {
  47. result += this._original.slice(index, block.from) + block.str
  48. index = block.to
  49. }
  50. result += this._original.slice(index)
  51. this._cache = {
  52. lineStarts: lineStarts(result),
  53. result,
  54. }
  55. }
  56. return this._cache
  57. }
  58. getOriginalLine(n) {
  59. if (n < 1 || n > this._lineStarts.length) {
  60. throw new Error("Invalid line number")
  61. }
  62. return this._original
  63. .slice(this._lineStarts[n - 1], this._lineStarts[n])
  64. .replace(lineEndingsRe, "")
  65. }
  66. toString() {
  67. return this._compute().result
  68. }
  69. replace(from, to, str) {
  70. this._cache = null
  71. if (from > to || from < 0 || to > this._original.length) {
  72. throw new Error("Invalid slice indexes")
  73. }
  74. const newBlock = { from, to, str }
  75. if (
  76. !this._blocks.length ||
  77. this._blocks[this._blocks.length - 1].to <= from
  78. ) {
  79. this._blocks.push(newBlock)
  80. } else {
  81. const index = this._blocks.findIndex((other) => other.to > from)
  82. if (this._blocks[index].from < to) throw new Error("Can't replace slice")
  83. this._blocks.splice(index, 0, newBlock)
  84. }
  85. }
  86. originalIndex(index) {
  87. let block
  88. for (block of this._blocks) {
  89. if (index < block.from) break
  90. if (index < block.from + block.str.length) {
  91. return
  92. } else {
  93. index += block.to - block.from - block.str.length
  94. }
  95. }
  96. if (index < 0 || index > this._original.length) {
  97. throw new Error("Invalid index")
  98. }
  99. if (index == this._original.length) {
  100. if (block.to && block.to === this._original.length) {
  101. return block.from + block.str.length
  102. }
  103. return this._original.length
  104. }
  105. return index
  106. }
  107. originalLocation(location) {
  108. const index = locationToIndex(location, this._compute().lineStarts)
  109. const originalIndex = this.originalIndex(index)
  110. if (originalIndex !== undefined) {
  111. return indexToLocation(originalIndex, this._lineStarts)
  112. }
  113. }
  114. }