source-map-consumer.js 39 KB

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