source-map-consumer.js 41 KB

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