source-map-consumer.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /* -*- Mode: js; js-indent-level: 2; -*- */
  2. /*
  3. * Copyright 2011 Mozilla Foundation and contributors
  4. * Licensed under the New BSD license. See LICENSE or:
  5. * http://opensource.org/licenses/BSD-3-Clause
  6. */
  7. var util = require('./util');
  8. var binarySearch = require('./binary-search');
  9. var ArraySet = require('./array-set').ArraySet;
  10. var base64VLQ = require('./base64-vlq');
  11. var quickSort = require('./quick-sort').quickSort;
  12. function SourceMapConsumer(aSourceMap, aSourceMapURL) {
  13. var sourceMap = aSourceMap;
  14. if (typeof aSourceMap === 'string') {
  15. sourceMap = util.parseSourceMapInput(aSourceMap);
  16. }
  17. return sourceMap.sections != null
  18. ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
  19. : new BasicSourceMapConsumer(sourceMap, aSourceMapURL);
  20. }
  21. SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) {
  22. return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL);
  23. }
  24. /**
  25. * The version of the source mapping spec that we are consuming.
  26. */
  27. SourceMapConsumer.prototype._version = 3;
  28. // `__generatedMappings` and `__originalMappings` are arrays that hold the
  29. // parsed mapping coordinates from the source map's "mappings" attribute. They
  30. // are lazily instantiated, accessed via the `_generatedMappings` and
  31. // `_originalMappings` getters respectively, and we only parse the mappings
  32. // and create these arrays once queried for a source location. We jump through
  33. // these hoops because there can be many thousands of mappings, and parsing
  34. // them is expensive, so we only want to do it if we must.
  35. //
  36. // Each object in the arrays is of the form:
  37. //
  38. // {
  39. // generatedLine: The line number in the generated code,
  40. // generatedColumn: The column number in the generated code,
  41. // source: The path to the original source file that generated this
  42. // chunk of code,
  43. // originalLine: The line number in the original source that
  44. // corresponds to this chunk of generated code,
  45. // originalColumn: The column number in the original source that
  46. // corresponds to this chunk of generated code,
  47. // name: The name of the original symbol which generated this chunk of
  48. // code.
  49. // }
  50. //
  51. // All properties except for `generatedLine` and `generatedColumn` can be
  52. // `null`.
  53. //
  54. // `_generatedMappings` is ordered by the generated positions.
  55. //
  56. // `_originalMappings` is ordered by the original positions.
  57. SourceMapConsumer.prototype.__generatedMappings = null;
  58. Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
  59. configurable: true,
  60. enumerable: true,
  61. get: function () {
  62. if (!this.__generatedMappings) {
  63. this._parseMappings(this._mappings, this.sourceRoot);
  64. }
  65. return this.__generatedMappings;
  66. }
  67. });
  68. SourceMapConsumer.prototype.__originalMappings = null;
  69. Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
  70. configurable: true,
  71. enumerable: true,
  72. get: function () {
  73. if (!this.__originalMappings) {
  74. this._parseMappings(this._mappings, this.sourceRoot);
  75. }
  76. return this.__originalMappings;
  77. }
  78. });
  79. SourceMapConsumer.prototype._charIsMappingSeparator =
  80. function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
  81. var c = aStr.charAt(index);
  82. return c === ";" || c === ",";
  83. };
  84. /**
  85. * Parse the mappings in a string in to a data structure which we can easily
  86. * query (the ordered arrays in the `this.__generatedMappings` and
  87. * `this.__originalMappings` properties).
  88. */
  89. SourceMapConsumer.prototype._parseMappings =
  90. function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  91. throw new Error("Subclasses must implement _parseMappings");
  92. };
  93. SourceMapConsumer.GENERATED_ORDER = 1;
  94. SourceMapConsumer.ORIGINAL_ORDER = 2;
  95. SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
  96. SourceMapConsumer.LEAST_UPPER_BOUND = 2;
  97. /**
  98. * Iterate over each mapping between an original source/line/column and a
  99. * generated line/column in this source map.
  100. *
  101. * @param Function aCallback
  102. * The function that is called with each mapping.
  103. * @param Object aContext
  104. * Optional. If specified, this object will be the value of `this` every
  105. * time that `aCallback` is called.
  106. * @param aOrder
  107. * Either `SourceMapConsumer.GENERATED_ORDER` or
  108. * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
  109. * iterate over the mappings sorted by the generated file's line/column
  110. * order or the original's source/line/column order, respectively. Defaults to
  111. * `SourceMapConsumer.GENERATED_ORDER`.
  112. */
  113. SourceMapConsumer.prototype.eachMapping =
  114. function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
  115. var context = aContext || null;
  116. var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
  117. var mappings;
  118. switch (order) {
  119. case SourceMapConsumer.GENERATED_ORDER:
  120. mappings = this._generatedMappings;
  121. break;
  122. case SourceMapConsumer.ORIGINAL_ORDER:
  123. mappings = this._originalMappings;
  124. break;
  125. default:
  126. throw new Error("Unknown order of iteration.");
  127. }
  128. var sourceRoot = this.sourceRoot;
  129. var boundCallback = aCallback.bind(context);
  130. var names = this._names;
  131. var sources = this._sources;
  132. var sourceMapURL = this._sourceMapURL;
  133. for (var i = 0, n = mappings.length; i < n; i++) {
  134. var mapping = mappings[i];
  135. var source = mapping.source === null ? null : sources.at(mapping.source);
  136. if(source !== null) {
  137. source = util.computeSourceURL(sourceRoot, source, sourceMapURL);
  138. }
  139. boundCallback({
  140. source: source,
  141. generatedLine: mapping.generatedLine,
  142. generatedColumn: mapping.generatedColumn,
  143. originalLine: mapping.originalLine,
  144. originalColumn: mapping.originalColumn,
  145. name: mapping.name === null ? null : names.at(mapping.name)
  146. });
  147. }
  148. };
  149. /**
  150. * Returns all generated line and column information for the original source,
  151. * line, and column provided. If no column is provided, returns all mappings
  152. * corresponding to a either the line we are searching for or the next
  153. * closest line that has any mappings. Otherwise, returns all mappings
  154. * corresponding to the given line and either the column we are searching for
  155. * or the next closest column that has any offsets.
  156. *
  157. * The only argument is an object with the following properties:
  158. *
  159. * - source: The filename of the original source.
  160. * - line: The line number in the original source. The line number is 1-based.
  161. * - column: Optional. the column number in the original source.
  162. * The column number is 0-based.
  163. *
  164. * and an array of objects is returned, each with the following properties:
  165. *
  166. * - line: The line number in the generated source, or null. The
  167. * line number is 1-based.
  168. * - column: The column number in the generated source, or null.
  169. * The column number is 0-based.
  170. */
  171. SourceMapConsumer.prototype.allGeneratedPositionsFor =
  172. function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
  173. var line = util.getArg(aArgs, 'line');
  174. // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
  175. // returns the index of the closest mapping less than the needle. By
  176. // setting needle.originalColumn to 0, we thus find the last mapping for
  177. // the given line, provided such a mapping exists.
  178. var needle = {
  179. source: util.getArg(aArgs, 'source'),
  180. originalLine: line,
  181. originalColumn: util.getArg(aArgs, 'column', 0)
  182. };
  183. needle.source = this._findSourceIndex(needle.source);
  184. if (needle.source < 0) {
  185. return [];
  186. }
  187. var mappings = [];
  188. var index = this._findMapping(needle,
  189. this._originalMappings,
  190. "originalLine",
  191. "originalColumn",
  192. util.compareByOriginalPositions,
  193. binarySearch.LEAST_UPPER_BOUND);
  194. if (index >= 0) {
  195. var mapping = this._originalMappings[index];
  196. if (aArgs.column === undefined) {
  197. var originalLine = mapping.originalLine;
  198. // Iterate until either we run out of mappings, or we run into
  199. // a mapping for a different line than the one we found. Since
  200. // mappings are sorted, this is guaranteed to find all mappings for
  201. // the line we found.
  202. while (mapping && mapping.originalLine === originalLine) {
  203. mappings.push({
  204. line: util.getArg(mapping, 'generatedLine', null),
  205. column: util.getArg(mapping, 'generatedColumn', null),
  206. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  207. });
  208. mapping = this._originalMappings[++index];
  209. }
  210. } else {
  211. var originalColumn = mapping.originalColumn;
  212. // Iterate until either we run out of mappings, or we run into
  213. // a mapping for a different line than the one we were searching for.
  214. // Since mappings are sorted, this is guaranteed to find all mappings for
  215. // the line we are searching for.
  216. while (mapping &&
  217. mapping.originalLine === line &&
  218. mapping.originalColumn == originalColumn) {
  219. mappings.push({
  220. line: util.getArg(mapping, 'generatedLine', null),
  221. column: util.getArg(mapping, 'generatedColumn', null),
  222. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  223. });
  224. mapping = this._originalMappings[++index];
  225. }
  226. }
  227. }
  228. return mappings;
  229. };
  230. exports.SourceMapConsumer = SourceMapConsumer;
  231. /**
  232. * A BasicSourceMapConsumer instance represents a parsed source map which we can
  233. * query for information about the original file positions by giving it a file
  234. * position in the generated source.
  235. *
  236. * The first parameter is the raw source map (either as a JSON string, or
  237. * already parsed to an object). According to the spec, source maps have the
  238. * following attributes:
  239. *
  240. * - version: Which version of the source map spec this map is following.
  241. * - sources: An array of URLs to the original source files.
  242. * - names: An array of identifiers which can be referrenced by individual mappings.
  243. * - sourceRoot: Optional. The URL root from which all sources are relative.
  244. * - sourcesContent: Optional. An array of contents of the original source files.
  245. * - mappings: A string of base64 VLQs which contain the actual mappings.
  246. * - file: Optional. The generated file this source map is associated with.
  247. *
  248. * Here is an example source map, taken from the source map spec[0]:
  249. *
  250. * {
  251. * version : 3,
  252. * file: "out.js",
  253. * sourceRoot : "",
  254. * sources: ["foo.js", "bar.js"],
  255. * names: ["src", "maps", "are", "fun"],
  256. * mappings: "AA,AB;;ABCDE;"
  257. * }
  258. *
  259. * The second parameter, if given, is a string whose value is the URL
  260. * at which the source map was found. This URL is used to compute the
  261. * sources array.
  262. *
  263. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
  264. */
  265. function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) {
  266. var sourceMap = aSourceMap;
  267. if (typeof aSourceMap === 'string') {
  268. sourceMap = util.parseSourceMapInput(aSourceMap);
  269. }
  270. var version = util.getArg(sourceMap, 'version');
  271. var sources = util.getArg(sourceMap, 'sources');
  272. // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
  273. // requires the array) to play nice here.
  274. var names = util.getArg(sourceMap, 'names', []);
  275. var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
  276. var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
  277. var mappings = util.getArg(sourceMap, 'mappings');
  278. var file = util.getArg(sourceMap, 'file', null);
  279. // Once again, Sass deviates from the spec and supplies the version as a
  280. // string rather than a number, so we use loose equality checking here.
  281. if (version != this._version) {
  282. throw new Error('Unsupported version: ' + version);
  283. }
  284. if (sourceRoot) {
  285. sourceRoot = util.normalize(sourceRoot);
  286. }
  287. sources = sources
  288. .map(String)
  289. // Some source maps produce relative source paths like "./foo.js" instead of
  290. // "foo.js". Normalize these first so that future comparisons will succeed.
  291. // See bugzil.la/1090768.
  292. .map(util.normalize)
  293. // Always ensure that absolute sources are internally stored relative to
  294. // the source root, if the source root is absolute. Not doing this would
  295. // be particularly problematic when the source root is a prefix of the
  296. // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
  297. .map(function (source) {
  298. return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
  299. ? util.relative(sourceRoot, source)
  300. : source;
  301. });
  302. // Pass `true` below to allow duplicate names and sources. While source maps
  303. // are intended to be compressed and deduplicated, the TypeScript compiler
  304. // sometimes generates source maps with duplicates in them. See Github issue
  305. // #72 and bugzil.la/889492.
  306. this._names = ArraySet.fromArray(names.map(String), true);
  307. this._sources = ArraySet.fromArray(sources, true);
  308. this._absoluteSources = this._sources.toArray().map(function (s) {
  309. return util.computeSourceURL(sourceRoot, s, aSourceMapURL);
  310. });
  311. this.sourceRoot = sourceRoot;
  312. this.sourcesContent = sourcesContent;
  313. this._mappings = mappings;
  314. this._sourceMapURL = aSourceMapURL;
  315. this.file = file;
  316. }
  317. BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
  318. BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
  319. /**
  320. * Utility function to find the index of a source. Returns -1 if not
  321. * found.
  322. */
  323. BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) {
  324. var relativeSource = aSource;
  325. if (this.sourceRoot != null) {
  326. relativeSource = util.relative(this.sourceRoot, relativeSource);
  327. }
  328. if (this._sources.has(relativeSource)) {
  329. return this._sources.indexOf(relativeSource);
  330. }
  331. // Maybe aSource is an absolute URL as returned by |sources|. In
  332. // this case we can't simply undo the transform.
  333. var i;
  334. for (i = 0; i < this._absoluteSources.length; ++i) {
  335. if (this._absoluteSources[i] == aSource) {
  336. return i;
  337. }
  338. }
  339. return -1;
  340. };
  341. /**
  342. * Create a BasicSourceMapConsumer from a SourceMapGenerator.
  343. *
  344. * @param SourceMapGenerator aSourceMap
  345. * The source map that will be consumed.
  346. * @param String aSourceMapURL
  347. * The URL at which the source map can be found (optional)
  348. * @returns BasicSourceMapConsumer
  349. */
  350. BasicSourceMapConsumer.fromSourceMap =
  351. function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) {
  352. var smc = Object.create(BasicSourceMapConsumer.prototype);
  353. var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
  354. var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
  355. smc.sourceRoot = aSourceMap._sourceRoot;
  356. smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
  357. smc.sourceRoot);
  358. smc.file = aSourceMap._file;
  359. smc._sourceMapURL = aSourceMapURL;
  360. smc._absoluteSources = smc._sources.toArray().map(function (s) {
  361. return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL);
  362. });
  363. // Because we are modifying the entries (by converting string sources and
  364. // names to indices into the sources and names ArraySets), we have to make
  365. // a copy of the entry or else bad things happen. Shared mutable state
  366. // strikes again! See github issue #191.
  367. var generatedMappings = aSourceMap._mappings.toArray().slice();
  368. var destGeneratedMappings = smc.__generatedMappings = [];
  369. var destOriginalMappings = smc.__originalMappings = [];
  370. for (var i = 0, length = generatedMappings.length; i < length; i++) {
  371. var srcMapping = generatedMappings[i];
  372. var destMapping = new Mapping;
  373. destMapping.generatedLine = srcMapping.generatedLine;
  374. destMapping.generatedColumn = srcMapping.generatedColumn;
  375. if (srcMapping.source) {
  376. destMapping.source = sources.indexOf(srcMapping.source);
  377. destMapping.originalLine = srcMapping.originalLine;
  378. destMapping.originalColumn = srcMapping.originalColumn;
  379. if (srcMapping.name) {
  380. destMapping.name = names.indexOf(srcMapping.name);
  381. }
  382. destOriginalMappings.push(destMapping);
  383. }
  384. destGeneratedMappings.push(destMapping);
  385. }
  386. quickSort(smc.__originalMappings, util.compareByOriginalPositions);
  387. return smc;
  388. };
  389. /**
  390. * The version of the source mapping spec that we are consuming.
  391. */
  392. BasicSourceMapConsumer.prototype._version = 3;
  393. /**
  394. * The list of original sources.
  395. */
  396. Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
  397. get: function () {
  398. return this._absoluteSources.slice();
  399. }
  400. });
  401. /**
  402. * Provide the JIT with a nice shape / hidden class.
  403. */
  404. function Mapping() {
  405. this.generatedLine = 0;
  406. this.generatedColumn = 0;
  407. this.source = null;
  408. this.originalLine = null;
  409. this.originalColumn = null;
  410. this.name = null;
  411. }
  412. /**
  413. * Parse the mappings in a string in to a data structure which we can easily
  414. * query (the ordered arrays in the `this.__generatedMappings` and
  415. * `this.__originalMappings` properties).
  416. */
  417. const compareGenerated = util.compareByGeneratedPositionsDeflatedNoLine;
  418. function sortGenerated(array, start) {
  419. let l = array.length;
  420. let n = array.length - start;
  421. if (n <= 1) {
  422. return;
  423. } else if (n == 2) {
  424. let a = array[start];
  425. let b = array[start + 1];
  426. if (compareGenerated(a, b) > 0) {
  427. array[start] = b;
  428. array[start + 1] = a;
  429. }
  430. } else if (n < 20) {
  431. for (let i = start; i < l; i++) {
  432. for (let j = i; j > start; j--) {
  433. let a = array[j - 1];
  434. let b = array[j];
  435. if (compareGenerated(a, b) <= 0) {
  436. break;
  437. }
  438. array[j - 1] = b;
  439. array[j] = a;
  440. }
  441. }
  442. } else {
  443. quickSort(array, compareGenerated, start);
  444. }
  445. }
  446. BasicSourceMapConsumer.prototype._parseMappings =
  447. function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  448. var generatedLine = 1;
  449. var previousGeneratedColumn = 0;
  450. var previousOriginalLine = 0;
  451. var previousOriginalColumn = 0;
  452. var previousSource = 0;
  453. var previousName = 0;
  454. var length = aStr.length;
  455. var index = 0;
  456. var cachedSegments = {};
  457. var temp = {};
  458. var originalMappings = [];
  459. var generatedMappings = [];
  460. var mapping, str, segment, end, value;
  461. let subarrayStart = 0;
  462. while (index < length) {
  463. if (aStr.charAt(index) === ';') {
  464. generatedLine++;
  465. index++;
  466. previousGeneratedColumn = 0;
  467. sortGenerated(generatedMappings, subarrayStart);
  468. subarrayStart = generatedMappings.length;
  469. }
  470. else if (aStr.charAt(index) === ',') {
  471. index++;
  472. }
  473. else {
  474. mapping = new Mapping();
  475. mapping.generatedLine = generatedLine;
  476. for (end = index; end < length; end++) {
  477. if (this._charIsMappingSeparator(aStr, end)) {
  478. break;
  479. }
  480. }
  481. str = aStr.slice(index, end);
  482. segment = [];
  483. while (index < end) {
  484. base64VLQ.decode(aStr, index, temp);
  485. value = temp.value;
  486. index = temp.rest;
  487. segment.push(value);
  488. }
  489. if (segment.length === 2) {
  490. throw new Error('Found a source, but no line and column');
  491. }
  492. if (segment.length === 3) {
  493. throw new Error('Found a source and line, but no column');
  494. }
  495. // Generated column.
  496. mapping.generatedColumn = previousGeneratedColumn + segment[0];
  497. previousGeneratedColumn = mapping.generatedColumn;
  498. if (segment.length > 1) {
  499. // Original source.
  500. mapping.source = previousSource + segment[1];
  501. previousSource += segment[1];
  502. // Original line.
  503. mapping.originalLine = previousOriginalLine + segment[2];
  504. previousOriginalLine = mapping.originalLine;
  505. // Lines are stored 0-based
  506. mapping.originalLine += 1;
  507. // Original column.
  508. mapping.originalColumn = previousOriginalColumn + segment[3];
  509. previousOriginalColumn = mapping.originalColumn;
  510. if (segment.length > 4) {
  511. // Original name.
  512. mapping.name = previousName + segment[4];
  513. previousName += segment[4];
  514. }
  515. }
  516. generatedMappings.push(mapping);
  517. if (typeof mapping.originalLine === 'number') {
  518. let currentSource = mapping.source;
  519. while (originalMappings.length <= currentSource) {
  520. originalMappings.push(null);
  521. }
  522. if (originalMappings[currentSource] === null) {
  523. originalMappings[currentSource] = [];
  524. }
  525. originalMappings[currentSource].push(mapping);
  526. }
  527. }
  528. }
  529. sortGenerated(generatedMappings, subarrayStart);
  530. this.__generatedMappings = generatedMappings;
  531. for (var i = 0; i < originalMappings.length; i++) {
  532. if (originalMappings[i] != null) {
  533. quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource);
  534. }
  535. }
  536. this.__originalMappings = [].concat(...originalMappings);
  537. };
  538. /**
  539. * Find the mapping that best matches the hypothetical "needle" mapping that
  540. * we are searching for in the given "haystack" of mappings.
  541. */
  542. BasicSourceMapConsumer.prototype._findMapping =
  543. function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
  544. aColumnName, aComparator, aBias) {
  545. // To return the position we are searching for, we must first find the
  546. // mapping for the given position and then return the opposite position it
  547. // points to. Because the mappings are sorted, we can use binary search to
  548. // find the best mapping.
  549. if (aNeedle[aLineName] <= 0) {
  550. throw new TypeError('Line must be greater than or equal to 1, got '
  551. + aNeedle[aLineName]);
  552. }
  553. if (aNeedle[aColumnName] < 0) {
  554. throw new TypeError('Column must be greater than or equal to 0, got '
  555. + aNeedle[aColumnName]);
  556. }
  557. return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
  558. };
  559. /**
  560. * Compute the last column for each generated mapping. The last column is
  561. * inclusive.
  562. */
  563. BasicSourceMapConsumer.prototype.computeColumnSpans =
  564. function SourceMapConsumer_computeColumnSpans() {
  565. for (var index = 0; index < this._generatedMappings.length; ++index) {
  566. var mapping = this._generatedMappings[index];
  567. // Mappings do not contain a field for the last generated columnt. We
  568. // can come up with an optimistic estimate, however, by assuming that
  569. // mappings are contiguous (i.e. given two consecutive mappings, the
  570. // first mapping ends where the second one starts).
  571. if (index + 1 < this._generatedMappings.length) {
  572. var nextMapping = this._generatedMappings[index + 1];
  573. if (mapping.generatedLine === nextMapping.generatedLine) {
  574. mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
  575. continue;
  576. }
  577. }
  578. // The last mapping for each line spans the entire line.
  579. mapping.lastGeneratedColumn = Infinity;
  580. }
  581. };
  582. /**
  583. * Returns the original source, line, and column information for the generated
  584. * source's line and column positions provided. The only argument is an object
  585. * with the following properties:
  586. *
  587. * - line: The line number in the generated source. The line number
  588. * is 1-based.
  589. * - column: The column number in the generated source. The column
  590. * number is 0-based.
  591. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  592. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  593. * closest element that is smaller than or greater than the one we are
  594. * searching for, respectively, if the exact element cannot be found.
  595. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  596. *
  597. * and an object is returned with the following properties:
  598. *
  599. * - source: The original source file, or null.
  600. * - line: The line number in the original source, or null. The
  601. * line number is 1-based.
  602. * - column: The column number in the original source, or null. The
  603. * column number is 0-based.
  604. * - name: The original identifier, or null.
  605. */
  606. BasicSourceMapConsumer.prototype.originalPositionFor =
  607. function SourceMapConsumer_originalPositionFor(aArgs) {
  608. var needle = {
  609. generatedLine: util.getArg(aArgs, 'line'),
  610. generatedColumn: util.getArg(aArgs, 'column')
  611. };
  612. var index = this._findMapping(
  613. needle,
  614. this._generatedMappings,
  615. "generatedLine",
  616. "generatedColumn",
  617. util.compareByGeneratedPositionsDeflated,
  618. util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
  619. );
  620. if (index >= 0) {
  621. var mapping = this._generatedMappings[index];
  622. if (mapping.generatedLine === needle.generatedLine) {
  623. var source = util.getArg(mapping, 'source', null);
  624. if (source !== null) {
  625. source = this._sources.at(source);
  626. source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL);
  627. }
  628. var name = util.getArg(mapping, 'name', null);
  629. if (name !== null) {
  630. name = this._names.at(name);
  631. }
  632. return {
  633. source: source,
  634. line: util.getArg(mapping, 'originalLine', null),
  635. column: util.getArg(mapping, 'originalColumn', null),
  636. name: name
  637. };
  638. }
  639. }
  640. return {
  641. source: null,
  642. line: null,
  643. column: null,
  644. name: null
  645. };
  646. };
  647. /**
  648. * Return true if we have the source content for every source in the source
  649. * map, false otherwise.
  650. */
  651. BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
  652. function BasicSourceMapConsumer_hasContentsOfAllSources() {
  653. if (!this.sourcesContent) {
  654. return false;
  655. }
  656. return this.sourcesContent.length >= this._sources.size() &&
  657. !this.sourcesContent.some(function (sc) { return sc == null; });
  658. };
  659. /**
  660. * Returns the original source content. The only argument is the url of the
  661. * original source file. Returns null if no original source content is
  662. * available.
  663. */
  664. BasicSourceMapConsumer.prototype.sourceContentFor =
  665. function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
  666. if (!this.sourcesContent) {
  667. return null;
  668. }
  669. var index = this._findSourceIndex(aSource);
  670. if (index >= 0) {
  671. return this.sourcesContent[index];
  672. }
  673. var relativeSource = aSource;
  674. if (this.sourceRoot != null) {
  675. relativeSource = util.relative(this.sourceRoot, relativeSource);
  676. }
  677. var url;
  678. if (this.sourceRoot != null
  679. && (url = util.urlParse(this.sourceRoot))) {
  680. // XXX: file:// URIs and absolute paths lead to unexpected behavior for
  681. // many users. We can help them out when they expect file:// URIs to
  682. // behave like it would if they were running a local HTTP server. See
  683. // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
  684. var fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
  685. if (url.scheme == "file"
  686. && this._sources.has(fileUriAbsPath)) {
  687. return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
  688. }
  689. if ((!url.path || url.path == "/")
  690. && this._sources.has("/" + relativeSource)) {
  691. return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
  692. }
  693. }
  694. // This function is used recursively from
  695. // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
  696. // don't want to throw if we can't find the source - we just want to
  697. // return null, so we provide a flag to exit gracefully.
  698. if (nullOnMissing) {
  699. return null;
  700. }
  701. else {
  702. throw new Error('"' + relativeSource + '" is not in the SourceMap.');
  703. }
  704. };
  705. /**
  706. * Returns the generated line and column information for the original source,
  707. * line, and column positions provided. The only argument is an object with
  708. * the following properties:
  709. *
  710. * - source: The filename of the original source.
  711. * - line: The line number in the original source. The line number
  712. * is 1-based.
  713. * - column: The column number in the original source. The column
  714. * number is 0-based.
  715. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  716. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  717. * closest element that is smaller than or greater than the one we are
  718. * searching for, respectively, if the exact element cannot be found.
  719. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  720. *
  721. * and an object is returned with the following properties:
  722. *
  723. * - line: The line number in the generated source, or null. The
  724. * line number is 1-based.
  725. * - column: The column number in the generated source, or null.
  726. * The column number is 0-based.
  727. */
  728. BasicSourceMapConsumer.prototype.generatedPositionFor =
  729. function SourceMapConsumer_generatedPositionFor(aArgs) {
  730. var source = util.getArg(aArgs, 'source');
  731. source = this._findSourceIndex(source);
  732. if (source < 0) {
  733. return {
  734. line: null,
  735. column: null,
  736. lastColumn: null
  737. };
  738. }
  739. var needle = {
  740. source: source,
  741. originalLine: util.getArg(aArgs, 'line'),
  742. originalColumn: util.getArg(aArgs, 'column')
  743. };
  744. var index = this._findMapping(
  745. needle,
  746. this._originalMappings,
  747. "originalLine",
  748. "originalColumn",
  749. util.compareByOriginalPositions,
  750. util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
  751. );
  752. if (index >= 0) {
  753. var mapping = this._originalMappings[index];
  754. if (mapping.source === needle.source) {
  755. return {
  756. line: util.getArg(mapping, 'generatedLine', null),
  757. column: util.getArg(mapping, 'generatedColumn', null),
  758. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  759. };
  760. }
  761. }
  762. return {
  763. line: null,
  764. column: null,
  765. lastColumn: null
  766. };
  767. };
  768. exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
  769. /**
  770. * An IndexedSourceMapConsumer instance represents a parsed source map which
  771. * we can query for information. It differs from BasicSourceMapConsumer in
  772. * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
  773. * input.
  774. *
  775. * The first parameter is a raw source map (either as a JSON string, or already
  776. * parsed to an object). According to the spec for indexed source maps, they
  777. * have the following attributes:
  778. *
  779. * - version: Which version of the source map spec this map is following.
  780. * - file: Optional. The generated file this source map is associated with.
  781. * - sections: A list of section definitions.
  782. *
  783. * Each value under the "sections" field has two fields:
  784. * - offset: The offset into the original specified at which this section
  785. * begins to apply, defined as an object with a "line" and "column"
  786. * field.
  787. * - map: A source map definition. This source map could also be indexed,
  788. * but doesn't have to be.
  789. *
  790. * Instead of the "map" field, it's also possible to have a "url" field
  791. * specifying a URL to retrieve a source map from, but that's currently
  792. * unsupported.
  793. *
  794. * Here's an example source map, taken from the source map spec[0], but
  795. * modified to omit a section which uses the "url" field.
  796. *
  797. * {
  798. * version : 3,
  799. * file: "app.js",
  800. * sections: [{
  801. * offset: {line:100, column:10},
  802. * map: {
  803. * version : 3,
  804. * file: "section.js",
  805. * sources: ["foo.js", "bar.js"],
  806. * names: ["src", "maps", "are", "fun"],
  807. * mappings: "AAAA,E;;ABCDE;"
  808. * }
  809. * }],
  810. * }
  811. *
  812. * The second parameter, if given, is a string whose value is the URL
  813. * at which the source map was found. This URL is used to compute the
  814. * sources array.
  815. *
  816. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
  817. */
  818. function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) {
  819. var sourceMap = aSourceMap;
  820. if (typeof aSourceMap === 'string') {
  821. sourceMap = util.parseSourceMapInput(aSourceMap);
  822. }
  823. var version = util.getArg(sourceMap, 'version');
  824. var sections = util.getArg(sourceMap, 'sections');
  825. if (version != this._version) {
  826. throw new Error('Unsupported version: ' + version);
  827. }
  828. this._sources = new ArraySet();
  829. this._names = new ArraySet();
  830. var lastOffset = {
  831. line: -1,
  832. column: 0
  833. };
  834. this._sections = sections.map(function (s) {
  835. if (s.url) {
  836. // The url field will require support for asynchronicity.
  837. // See https://github.com/mozilla/source-map/issues/16
  838. throw new Error('Support for url field in sections not implemented.');
  839. }
  840. var offset = util.getArg(s, 'offset');
  841. var offsetLine = util.getArg(offset, 'line');
  842. var offsetColumn = util.getArg(offset, 'column');
  843. if (offsetLine < lastOffset.line ||
  844. (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
  845. throw new Error('Section offsets must be ordered and non-overlapping.');
  846. }
  847. lastOffset = offset;
  848. return {
  849. generatedOffset: {
  850. // The offset fields are 0-based, but we use 1-based indices when
  851. // encoding/decoding from VLQ.
  852. generatedLine: offsetLine + 1,
  853. generatedColumn: offsetColumn + 1
  854. },
  855. consumer: new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL)
  856. }
  857. });
  858. }
  859. IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
  860. IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
  861. /**
  862. * The version of the source mapping spec that we are consuming.
  863. */
  864. IndexedSourceMapConsumer.prototype._version = 3;
  865. /**
  866. * The list of original sources.
  867. */
  868. Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
  869. get: function () {
  870. var sources = [];
  871. for (var i = 0; i < this._sections.length; i++) {
  872. for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
  873. sources.push(this._sections[i].consumer.sources[j]);
  874. }
  875. }
  876. return sources;
  877. }
  878. });
  879. /**
  880. * Returns the original source, line, and column information for the generated
  881. * source's line and column positions provided. The only argument is an object
  882. * with the following properties:
  883. *
  884. * - line: The line number in the generated source. The line number
  885. * is 1-based.
  886. * - column: The column number in the generated source. The column
  887. * number is 0-based.
  888. *
  889. * and an object is returned with the following properties:
  890. *
  891. * - source: The original source file, or null.
  892. * - line: The line number in the original source, or null. The
  893. * line number is 1-based.
  894. * - column: The column number in the original source, or null. The
  895. * column number is 0-based.
  896. * - name: The original identifier, or null.
  897. */
  898. IndexedSourceMapConsumer.prototype.originalPositionFor =
  899. function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
  900. var needle = {
  901. generatedLine: util.getArg(aArgs, 'line'),
  902. generatedColumn: util.getArg(aArgs, 'column')
  903. };
  904. // Find the section containing the generated position we're trying to map
  905. // to an original position.
  906. var sectionIndex = binarySearch.search(needle, this._sections,
  907. function(needle, section) {
  908. var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
  909. if (cmp) {
  910. return cmp;
  911. }
  912. return (needle.generatedColumn -
  913. section.generatedOffset.generatedColumn);
  914. });
  915. var section = this._sections[sectionIndex];
  916. if (!section) {
  917. return {
  918. source: null,
  919. line: null,
  920. column: null,
  921. name: null
  922. };
  923. }
  924. return section.consumer.originalPositionFor({
  925. line: needle.generatedLine -
  926. (section.generatedOffset.generatedLine - 1),
  927. column: needle.generatedColumn -
  928. (section.generatedOffset.generatedLine === needle.generatedLine
  929. ? section.generatedOffset.generatedColumn - 1
  930. : 0),
  931. bias: aArgs.bias
  932. });
  933. };
  934. /**
  935. * Return true if we have the source content for every source in the source
  936. * map, false otherwise.
  937. */
  938. IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
  939. function IndexedSourceMapConsumer_hasContentsOfAllSources() {
  940. return this._sections.every(function (s) {
  941. return s.consumer.hasContentsOfAllSources();
  942. });
  943. };
  944. /**
  945. * Returns the original source content. The only argument is the url of the
  946. * original source file. Returns null if no original source content is
  947. * available.
  948. */
  949. IndexedSourceMapConsumer.prototype.sourceContentFor =
  950. function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
  951. for (var i = 0; i < this._sections.length; i++) {
  952. var section = this._sections[i];
  953. var content = section.consumer.sourceContentFor(aSource, true);
  954. if (content || content === '') {
  955. return content;
  956. }
  957. }
  958. if (nullOnMissing) {
  959. return null;
  960. }
  961. else {
  962. throw new Error('"' + aSource + '" is not in the SourceMap.');
  963. }
  964. };
  965. /**
  966. * Returns the generated line and column information for the original source,
  967. * line, and column positions provided. The only argument is an object with
  968. * the following properties:
  969. *
  970. * - source: The filename of the original source.
  971. * - line: The line number in the original source. The line number
  972. * is 1-based.
  973. * - column: The column number in the original source. The column
  974. * number is 0-based.
  975. *
  976. * and an object is returned with the following properties:
  977. *
  978. * - line: The line number in the generated source, or null. The
  979. * line number is 1-based.
  980. * - column: The column number in the generated source, or null.
  981. * The column number is 0-based.
  982. */
  983. IndexedSourceMapConsumer.prototype.generatedPositionFor =
  984. function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
  985. for (var i = 0; i < this._sections.length; i++) {
  986. var section = this._sections[i];
  987. // Only consider this section if the requested source is in the list of
  988. // sources of the consumer.
  989. if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) {
  990. continue;
  991. }
  992. var generatedPosition = section.consumer.generatedPositionFor(aArgs);
  993. if (generatedPosition) {
  994. var ret = {
  995. line: generatedPosition.line +
  996. (section.generatedOffset.generatedLine - 1),
  997. column: generatedPosition.column +
  998. (section.generatedOffset.generatedLine === generatedPosition.line
  999. ? section.generatedOffset.generatedColumn - 1
  1000. : 0)
  1001. };
  1002. return ret;
  1003. }
  1004. }
  1005. return {
  1006. line: null,
  1007. column: null
  1008. };
  1009. };
  1010. /**
  1011. * Parse the mappings in a string in to a data structure which we can easily
  1012. * query (the ordered arrays in the `this.__generatedMappings` and
  1013. * `this.__originalMappings` properties).
  1014. */
  1015. IndexedSourceMapConsumer.prototype._parseMappings =
  1016. function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  1017. this.__generatedMappings = [];
  1018. this.__originalMappings = [];
  1019. for (var i = 0; i < this._sections.length; i++) {
  1020. var section = this._sections[i];
  1021. var sectionMappings = section.consumer._generatedMappings;
  1022. for (var j = 0; j < sectionMappings.length; j++) {
  1023. var mapping = sectionMappings[j];
  1024. var source = section.consumer._sources.at(mapping.source);
  1025. if(source !== null) {
  1026. source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL);
  1027. }
  1028. this._sources.add(source);
  1029. source = this._sources.indexOf(source);
  1030. var name = null;
  1031. if (mapping.name) {
  1032. name = section.consumer._names.at(mapping.name);
  1033. this._names.add(name);
  1034. name = this._names.indexOf(name);
  1035. }
  1036. // The mappings coming from the consumer for the section have
  1037. // generated positions relative to the start of the section, so we
  1038. // need to offset them to be relative to the start of the concatenated
  1039. // generated file.
  1040. var adjustedMapping = {
  1041. source: source,
  1042. generatedLine: mapping.generatedLine +
  1043. (section.generatedOffset.generatedLine - 1),
  1044. generatedColumn: mapping.generatedColumn +
  1045. (section.generatedOffset.generatedLine === mapping.generatedLine
  1046. ? section.generatedOffset.generatedColumn - 1
  1047. : 0),
  1048. originalLine: mapping.originalLine,
  1049. originalColumn: mapping.originalColumn,
  1050. name: name
  1051. };
  1052. this.__generatedMappings.push(adjustedMapping);
  1053. if (typeof adjustedMapping.originalLine === 'number') {
  1054. this.__originalMappings.push(adjustedMapping);
  1055. }
  1056. }
  1057. }
  1058. quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
  1059. quickSort(this.__originalMappings, util.compareByOriginalPositions);
  1060. };
  1061. exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;