mapping.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. var assert = require("assert");
  2. var types = require("./types");
  3. var isString = types.builtInTypes.string;
  4. var isNumber = types.builtInTypes.number;
  5. var SourceLocation = types.namedTypes.SourceLocation;
  6. var Position = types.namedTypes.Position;
  7. var linesModule = require("./lines");
  8. var comparePos = require("./util").comparePos;
  9. function Mapping(sourceLines, sourceLoc, targetLoc) {
  10. assert.ok(this instanceof Mapping);
  11. assert.ok(sourceLines instanceof linesModule.Lines);
  12. SourceLocation.assert(sourceLoc);
  13. if (targetLoc) {
  14. // In certain cases it's possible for targetLoc.{start,end}.column
  15. // values to be negative, which technically makes them no longer
  16. // valid SourceLocation nodes, so we need to be more forgiving.
  17. assert.ok(
  18. isNumber.check(targetLoc.start.line) &&
  19. isNumber.check(targetLoc.start.column) &&
  20. isNumber.check(targetLoc.end.line) &&
  21. isNumber.check(targetLoc.end.column)
  22. );
  23. } else {
  24. // Assume identity mapping if no targetLoc specified.
  25. targetLoc = sourceLoc;
  26. }
  27. Object.defineProperties(this, {
  28. sourceLines: { value: sourceLines },
  29. sourceLoc: { value: sourceLoc },
  30. targetLoc: { value: targetLoc }
  31. });
  32. }
  33. var Mp = Mapping.prototype;
  34. module.exports = Mapping;
  35. Mp.slice = function(lines, start, end) {
  36. assert.ok(lines instanceof linesModule.Lines);
  37. Position.assert(start);
  38. if (end) {
  39. Position.assert(end);
  40. } else {
  41. end = lines.lastPos();
  42. }
  43. var sourceLines = this.sourceLines;
  44. var sourceLoc = this.sourceLoc;
  45. var targetLoc = this.targetLoc;
  46. function skip(name) {
  47. var sourceFromPos = sourceLoc[name];
  48. var targetFromPos = targetLoc[name];
  49. var targetToPos = start;
  50. if (name === "end") {
  51. targetToPos = end;
  52. } else {
  53. assert.strictEqual(name, "start");
  54. }
  55. return skipChars(
  56. sourceLines, sourceFromPos,
  57. lines, targetFromPos, targetToPos
  58. );
  59. }
  60. if (comparePos(start, targetLoc.start) <= 0) {
  61. if (comparePos(targetLoc.end, end) <= 0) {
  62. targetLoc = {
  63. start: subtractPos(targetLoc.start, start.line, start.column),
  64. end: subtractPos(targetLoc.end, start.line, start.column)
  65. };
  66. // The sourceLoc can stay the same because the contents of the
  67. // targetLoc have not changed.
  68. } else if (comparePos(end, targetLoc.start) <= 0) {
  69. return null;
  70. } else {
  71. sourceLoc = {
  72. start: sourceLoc.start,
  73. end: skip("end")
  74. };
  75. targetLoc = {
  76. start: subtractPos(targetLoc.start, start.line, start.column),
  77. end: subtractPos(end, start.line, start.column)
  78. };
  79. }
  80. } else {
  81. if (comparePos(targetLoc.end, start) <= 0) {
  82. return null;
  83. }
  84. if (comparePos(targetLoc.end, end) <= 0) {
  85. sourceLoc = {
  86. start: skip("start"),
  87. end: sourceLoc.end
  88. };
  89. targetLoc = {
  90. // Same as subtractPos(start, start.line, start.column):
  91. start: { line: 1, column: 0 },
  92. end: subtractPos(targetLoc.end, start.line, start.column)
  93. };
  94. } else {
  95. sourceLoc = {
  96. start: skip("start"),
  97. end: skip("end")
  98. };
  99. targetLoc = {
  100. // Same as subtractPos(start, start.line, start.column):
  101. start: { line: 1, column: 0 },
  102. end: subtractPos(end, start.line, start.column)
  103. };
  104. }
  105. }
  106. return new Mapping(this.sourceLines, sourceLoc, targetLoc);
  107. };
  108. Mp.add = function(line, column) {
  109. return new Mapping(this.sourceLines, this.sourceLoc, {
  110. start: addPos(this.targetLoc.start, line, column),
  111. end: addPos(this.targetLoc.end, line, column)
  112. });
  113. };
  114. function addPos(toPos, line, column) {
  115. return {
  116. line: toPos.line + line - 1,
  117. column: (toPos.line === 1)
  118. ? toPos.column + column
  119. : toPos.column
  120. };
  121. }
  122. Mp.subtract = function(line, column) {
  123. return new Mapping(this.sourceLines, this.sourceLoc, {
  124. start: subtractPos(this.targetLoc.start, line, column),
  125. end: subtractPos(this.targetLoc.end, line, column)
  126. });
  127. };
  128. function subtractPos(fromPos, line, column) {
  129. return {
  130. line: fromPos.line - line + 1,
  131. column: (fromPos.line === line)
  132. ? fromPos.column - column
  133. : fromPos.column
  134. };
  135. }
  136. Mp.indent = function(by, skipFirstLine, noNegativeColumns) {
  137. if (by === 0) {
  138. return this;
  139. }
  140. var targetLoc = this.targetLoc;
  141. var startLine = targetLoc.start.line;
  142. var endLine = targetLoc.end.line;
  143. if (skipFirstLine && startLine === 1 && endLine === 1) {
  144. return this;
  145. }
  146. targetLoc = {
  147. start: targetLoc.start,
  148. end: targetLoc.end
  149. };
  150. if (!skipFirstLine || startLine > 1) {
  151. var startColumn = targetLoc.start.column + by;
  152. targetLoc.start = {
  153. line: startLine,
  154. column: noNegativeColumns
  155. ? Math.max(0, startColumn)
  156. : startColumn
  157. };
  158. }
  159. if (!skipFirstLine || endLine > 1) {
  160. var endColumn = targetLoc.end.column + by;
  161. targetLoc.end = {
  162. line: endLine,
  163. column: noNegativeColumns
  164. ? Math.max(0, endColumn)
  165. : endColumn
  166. };
  167. }
  168. return new Mapping(this.sourceLines, this.sourceLoc, targetLoc);
  169. };
  170. function skipChars(
  171. sourceLines, sourceFromPos,
  172. targetLines, targetFromPos, targetToPos
  173. ) {
  174. assert.ok(sourceLines instanceof linesModule.Lines);
  175. assert.ok(targetLines instanceof linesModule.Lines);
  176. Position.assert(sourceFromPos);
  177. Position.assert(targetFromPos);
  178. Position.assert(targetToPos);
  179. var targetComparison = comparePos(targetFromPos, targetToPos);
  180. if (targetComparison === 0) {
  181. // Trivial case: no characters to skip.
  182. return sourceFromPos;
  183. }
  184. if (targetComparison < 0) {
  185. // Skipping forward.
  186. var sourceCursor = sourceLines.skipSpaces(sourceFromPos);
  187. var targetCursor = targetLines.skipSpaces(targetFromPos);
  188. var lineDiff = targetToPos.line - targetCursor.line;
  189. sourceCursor.line += lineDiff;
  190. targetCursor.line += lineDiff;
  191. if (lineDiff > 0) {
  192. // If jumping to later lines, reset columns to the beginnings
  193. // of those lines.
  194. sourceCursor.column = 0;
  195. targetCursor.column = 0;
  196. } else {
  197. assert.strictEqual(lineDiff, 0);
  198. }
  199. while (comparePos(targetCursor, targetToPos) < 0 &&
  200. targetLines.nextPos(targetCursor, true)) {
  201. assert.ok(sourceLines.nextPos(sourceCursor, true));
  202. assert.strictEqual(
  203. sourceLines.charAt(sourceCursor),
  204. targetLines.charAt(targetCursor)
  205. );
  206. }
  207. } else {
  208. // Skipping backward.
  209. var sourceCursor = sourceLines.skipSpaces(sourceFromPos, true);
  210. var targetCursor = targetLines.skipSpaces(targetFromPos, true);
  211. var lineDiff = targetToPos.line - targetCursor.line;
  212. sourceCursor.line += lineDiff;
  213. targetCursor.line += lineDiff;
  214. if (lineDiff < 0) {
  215. // If jumping to earlier lines, reset columns to the ends of
  216. // those lines.
  217. sourceCursor.column = sourceLines.getLineLength(sourceCursor.line);
  218. targetCursor.column = targetLines.getLineLength(targetCursor.line);
  219. } else {
  220. assert.strictEqual(lineDiff, 0);
  221. }
  222. while (comparePos(targetToPos, targetCursor) < 0 &&
  223. targetLines.prevPos(targetCursor, true)) {
  224. assert.ok(sourceLines.prevPos(sourceCursor, true));
  225. assert.strictEqual(
  226. sourceLines.charAt(sourceCursor),
  227. targetLines.charAt(targetCursor)
  228. );
  229. }
  230. }
  231. return sourceCursor;
  232. }