source-map-consumer.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  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. * Iterate over each mapping between an original source/line/column and a
  70. * generated line/column in this source map.
  71. *
  72. * @param Function aCallback
  73. * The function that is called with each mapping.
  74. * @param Object aContext
  75. * Optional. If specified, this object will be the value of `this` every
  76. * time that `aCallback` is called.
  77. * @param aOrder
  78. * Either `SourceMapConsumer.GENERATED_ORDER` or
  79. * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
  80. * iterate over the mappings sorted by the generated file's line/column
  81. * order or the original's source/line/column order, respectively. Defaults to
  82. * `SourceMapConsumer.GENERATED_ORDER`.
  83. */
  84. eachMapping(aCallback, aContext, aOrder) {
  85. throw new Error("Subclasses must implement eachMapping");
  86. }
  87. /**
  88. * Returns all generated line and column information for the original source,
  89. * line, and column provided. If no column is provided, returns all mappings
  90. * corresponding to a either the line we are searching for or the next
  91. * closest line that has any mappings. Otherwise, returns all mappings
  92. * corresponding to the given line and either the column we are searching for
  93. * or the next closest column that has any offsets.
  94. *
  95. * The only argument is an object with the following properties:
  96. *
  97. * - source: The filename of the original source.
  98. * - line: The line number in the original source. The line number is 1-based.
  99. * - column: Optional. the column number in the original source.
  100. * The column number is 0-based.
  101. *
  102. * and an array of objects is returned, each with the following properties:
  103. *
  104. * - line: The line number in the generated source, or null. The
  105. * line number is 1-based.
  106. * - column: The column number in the generated source, or null.
  107. * The column number is 0-based.
  108. */
  109. allGeneratedPositionsFor(aArgs) {
  110. throw new Error("Subclasses must implement allGeneratedPositionsFor");
  111. }
  112. destroy() {
  113. throw new Error("Subclasses must implement destroy");
  114. }
  115. }
  116. /**
  117. * The version of the source mapping spec that we are consuming.
  118. */
  119. SourceMapConsumer.prototype._version = 3;
  120. SourceMapConsumer.GENERATED_ORDER = 1;
  121. SourceMapConsumer.ORIGINAL_ORDER = 2;
  122. SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
  123. SourceMapConsumer.LEAST_UPPER_BOUND = 2;
  124. exports.SourceMapConsumer = SourceMapConsumer;
  125. /**
  126. * A BasicSourceMapConsumer instance represents a parsed source map which we can
  127. * query for information about the original file positions by giving it a file
  128. * position in the generated source.
  129. *
  130. * The first parameter is the raw source map (either as a JSON string, or
  131. * already parsed to an object). According to the spec, source maps have the
  132. * following attributes:
  133. *
  134. * - version: Which version of the source map spec this map is following.
  135. * - sources: An array of URLs to the original source files.
  136. * - names: An array of identifiers which can be referenced by individual mappings.
  137. * - sourceRoot: Optional. The URL root from which all sources are relative.
  138. * - sourcesContent: Optional. An array of contents of the original source files.
  139. * - mappings: A string of base64 VLQs which contain the actual mappings.
  140. * - file: Optional. The generated file this source map is associated with.
  141. *
  142. * Here is an example source map, taken from the source map spec[0]:
  143. *
  144. * {
  145. * version : 3,
  146. * file: "out.js",
  147. * sourceRoot : "",
  148. * sources: ["foo.js", "bar.js"],
  149. * names: ["src", "maps", "are", "fun"],
  150. * mappings: "AA,AB;;ABCDE;"
  151. * }
  152. *
  153. * The second parameter, if given, is a string whose value is the URL
  154. * at which the source map was found. This URL is used to compute the
  155. * sources array.
  156. *
  157. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
  158. */
  159. class BasicSourceMapConsumer extends SourceMapConsumer {
  160. constructor(aSourceMap, aSourceMapURL) {
  161. return super(INTERNAL).then(that => {
  162. let sourceMap = aSourceMap;
  163. if (typeof aSourceMap === "string") {
  164. sourceMap = util.parseSourceMapInput(aSourceMap);
  165. }
  166. const version = util.getArg(sourceMap, "version");
  167. const sources = util.getArg(sourceMap, "sources").map(String);
  168. // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
  169. // requires the array) to play nice here.
  170. const names = util.getArg(sourceMap, "names", []);
  171. const sourceRoot = util.getArg(sourceMap, "sourceRoot", null);
  172. const sourcesContent = util.getArg(sourceMap, "sourcesContent", null);
  173. const mappings = util.getArg(sourceMap, "mappings");
  174. const file = util.getArg(sourceMap, "file", null);
  175. const x_google_ignoreList = util.getArg(
  176. sourceMap,
  177. "x_google_ignoreList",
  178. null
  179. );
  180. // Once again, Sass deviates from the spec and supplies the version as a
  181. // string rather than a number, so we use loose equality checking here.
  182. if (version != that._version) {
  183. throw new Error("Unsupported version: " + version);
  184. }
  185. that._sourceLookupCache = new Map();
  186. // Pass `true` below to allow duplicate names and sources. While source maps
  187. // are intended to be compressed and deduplicated, the TypeScript compiler
  188. // sometimes generates source maps with duplicates in them. See Github issue
  189. // #72 and bugzil.la/889492.
  190. that._names = ArraySet.fromArray(names.map(String), true);
  191. that._sources = ArraySet.fromArray(sources, true);
  192. that._absoluteSources = ArraySet.fromArray(
  193. that._sources.toArray().map(function (s) {
  194. return util.computeSourceURL(sourceRoot, s, aSourceMapURL);
  195. }),
  196. true
  197. );
  198. that.sourceRoot = sourceRoot;
  199. that.sourcesContent = sourcesContent;
  200. that._mappings = mappings;
  201. that._sourceMapURL = aSourceMapURL;
  202. that.file = file;
  203. that.x_google_ignoreList = x_google_ignoreList;
  204. that._computedColumnSpans = false;
  205. that._mappingsPtr = 0;
  206. that._wasm = null;
  207. return wasm().then(w => {
  208. that._wasm = w;
  209. return that;
  210. });
  211. });
  212. }
  213. /**
  214. * Utility function to find the index of a source. Returns -1 if not
  215. * found.
  216. */
  217. _findSourceIndex(aSource) {
  218. // In the most common usecases, we'll be constantly looking up the index for the same source
  219. // files, so we cache the index lookup to avoid constantly recomputing the full URLs.
  220. const cachedIndex = this._sourceLookupCache.get(aSource);
  221. if (typeof cachedIndex === "number") {
  222. return cachedIndex;
  223. }
  224. // Treat the source as map-relative overall by default.
  225. const sourceAsMapRelative = util.computeSourceURL(
  226. null,
  227. aSource,
  228. this._sourceMapURL
  229. );
  230. if (this._absoluteSources.has(sourceAsMapRelative)) {
  231. const index = this._absoluteSources.indexOf(sourceAsMapRelative);
  232. this._sourceLookupCache.set(aSource, index);
  233. return index;
  234. }
  235. // Fall back to treating the source as sourceRoot-relative.
  236. const sourceAsSourceRootRelative = util.computeSourceURL(
  237. this.sourceRoot,
  238. aSource,
  239. this._sourceMapURL
  240. );
  241. if (this._absoluteSources.has(sourceAsSourceRootRelative)) {
  242. const index = this._absoluteSources.indexOf(sourceAsSourceRootRelative);
  243. this._sourceLookupCache.set(aSource, index);
  244. return index;
  245. }
  246. // To avoid this cache growing forever, we do not cache lookup misses.
  247. return -1;
  248. }
  249. /**
  250. * Create a BasicSourceMapConsumer from a SourceMapGenerator.
  251. *
  252. * @param SourceMapGenerator aSourceMap
  253. * The source map that will be consumed.
  254. * @param String aSourceMapURL
  255. * The URL at which the source map can be found (optional)
  256. * @returns BasicSourceMapConsumer
  257. */
  258. static fromSourceMap(aSourceMap, aSourceMapURL) {
  259. return new BasicSourceMapConsumer(aSourceMap.toString());
  260. }
  261. get sources() {
  262. return this._absoluteSources.toArray();
  263. }
  264. _getMappingsPtr() {
  265. if (this._mappingsPtr === 0) {
  266. this._parseMappings();
  267. }
  268. return this._mappingsPtr;
  269. }
  270. /**
  271. * Parse the mappings in a string in to a data structure which we can easily
  272. * query (the ordered arrays in the `this.__generatedMappings` and
  273. * `this.__originalMappings` properties).
  274. */
  275. _parseMappings() {
  276. const aStr = this._mappings;
  277. const size = aStr.length;
  278. // Interpret signed result of allocate_mappings as unsigned, otherwise
  279. // addresses higher than 2GB will be negative.
  280. const mappingsBufPtr = this._wasm.exports.allocate_mappings(size) >>> 0;
  281. const mappingsBuf = new Uint8Array(
  282. this._wasm.exports.memory.buffer,
  283. mappingsBufPtr,
  284. size
  285. );
  286. for (let i = 0; i < size; i++) {
  287. mappingsBuf[i] = aStr.charCodeAt(i);
  288. }
  289. const mappingsPtr = this._wasm.exports.parse_mappings(mappingsBufPtr);
  290. if (!mappingsPtr) {
  291. const error = this._wasm.exports.get_last_error();
  292. let msg = `Error parsing mappings (code ${error}): `;
  293. // XXX: keep these error codes in sync with `wasm-mappings`.
  294. switch (error) {
  295. case 1:
  296. msg +=
  297. "the mappings contained a negative line, column, source index, or name index";
  298. break;
  299. case 2:
  300. msg += "the mappings contained a number larger than 2**32";
  301. break;
  302. case 3:
  303. msg += "reached EOF while in the middle of parsing a VLQ";
  304. break;
  305. case 4:
  306. msg += "invalid base 64 character while parsing a VLQ";
  307. break;
  308. default:
  309. msg += "unknown error code";
  310. break;
  311. }
  312. throw new Error(msg);
  313. }
  314. this._mappingsPtr = mappingsPtr;
  315. }
  316. eachMapping(aCallback, aContext, aOrder) {
  317. const context = aContext || null;
  318. const order = aOrder || SourceMapConsumer.GENERATED_ORDER;
  319. this._wasm.withMappingCallback(
  320. mapping => {
  321. if (mapping.source !== null) {
  322. mapping.source = this._absoluteSources.at(mapping.source);
  323. if (mapping.name !== null) {
  324. mapping.name = this._names.at(mapping.name);
  325. }
  326. }
  327. if (this._computedColumnSpans && mapping.lastGeneratedColumn === null) {
  328. mapping.lastGeneratedColumn = Infinity;
  329. }
  330. aCallback.call(context, mapping);
  331. },
  332. () => {
  333. switch (order) {
  334. case SourceMapConsumer.GENERATED_ORDER:
  335. this._wasm.exports.by_generated_location(this._getMappingsPtr());
  336. break;
  337. case SourceMapConsumer.ORIGINAL_ORDER:
  338. this._wasm.exports.by_original_location(this._getMappingsPtr());
  339. break;
  340. default:
  341. throw new Error("Unknown order of iteration.");
  342. }
  343. }
  344. );
  345. }
  346. allGeneratedPositionsFor(aArgs) {
  347. let source = util.getArg(aArgs, "source");
  348. const originalLine = util.getArg(aArgs, "line");
  349. const originalColumn = aArgs.column || 0;
  350. source = this._findSourceIndex(source);
  351. if (source < 0) {
  352. return [];
  353. }
  354. if (originalLine < 1) {
  355. throw new Error("Line numbers must be >= 1");
  356. }
  357. if (originalColumn < 0) {
  358. throw new Error("Column numbers must be >= 0");
  359. }
  360. const mappings = [];
  361. this._wasm.withMappingCallback(
  362. m => {
  363. let lastColumn = m.lastGeneratedColumn;
  364. if (this._computedColumnSpans && lastColumn === null) {
  365. lastColumn = Infinity;
  366. }
  367. mappings.push({
  368. line: m.generatedLine,
  369. column: m.generatedColumn,
  370. lastColumn,
  371. });
  372. },
  373. () => {
  374. this._wasm.exports.all_generated_locations_for(
  375. this._getMappingsPtr(),
  376. source,
  377. originalLine - 1,
  378. "column" in aArgs,
  379. originalColumn
  380. );
  381. }
  382. );
  383. return mappings;
  384. }
  385. destroy() {
  386. if (this._mappingsPtr !== 0) {
  387. this._wasm.exports.free_mappings(this._mappingsPtr);
  388. this._mappingsPtr = 0;
  389. }
  390. }
  391. /**
  392. * Compute the last column for each generated mapping. The last column is
  393. * inclusive.
  394. */
  395. computeColumnSpans() {
  396. if (this._computedColumnSpans) {
  397. return;
  398. }
  399. this._wasm.exports.compute_column_spans(this._getMappingsPtr());
  400. this._computedColumnSpans = true;
  401. }
  402. /**
  403. * Returns the original source, line, and column information for the generated
  404. * source's line and column positions provided. The only argument is an object
  405. * with the following properties:
  406. *
  407. * - line: The line number in the generated source. The line number
  408. * is 1-based.
  409. * - column: The column number in the generated source. The column
  410. * number is 0-based.
  411. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  412. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  413. * closest element that is smaller than or greater than the one we are
  414. * searching for, respectively, if the exact element cannot be found.
  415. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  416. *
  417. * and an object is returned with the following properties:
  418. *
  419. * - source: The original source file, or null.
  420. * - line: The line number in the original source, or null. The
  421. * line number is 1-based.
  422. * - column: The column number in the original source, or null. The
  423. * column number is 0-based.
  424. * - name: The original identifier, or null.
  425. */
  426. originalPositionFor(aArgs) {
  427. const needle = {
  428. generatedLine: util.getArg(aArgs, "line"),
  429. generatedColumn: util.getArg(aArgs, "column"),
  430. };
  431. if (needle.generatedLine < 1) {
  432. throw new Error("Line numbers must be >= 1");
  433. }
  434. if (needle.generatedColumn < 0) {
  435. throw new Error("Column numbers must be >= 0");
  436. }
  437. let bias = util.getArg(
  438. aArgs,
  439. "bias",
  440. SourceMapConsumer.GREATEST_LOWER_BOUND
  441. );
  442. if (bias == null) {
  443. bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
  444. }
  445. let mapping;
  446. this._wasm.withMappingCallback(
  447. m => (mapping = m),
  448. () => {
  449. this._wasm.exports.original_location_for(
  450. this._getMappingsPtr(),
  451. needle.generatedLine - 1,
  452. needle.generatedColumn,
  453. bias
  454. );
  455. }
  456. );
  457. if (mapping) {
  458. if (mapping.generatedLine === needle.generatedLine) {
  459. let source = util.getArg(mapping, "source", null);
  460. if (source !== null) {
  461. source = this._absoluteSources.at(source);
  462. }
  463. let name = util.getArg(mapping, "name", null);
  464. if (name !== null) {
  465. name = this._names.at(name);
  466. }
  467. return {
  468. source,
  469. line: util.getArg(mapping, "originalLine", null),
  470. column: util.getArg(mapping, "originalColumn", null),
  471. name,
  472. };
  473. }
  474. }
  475. return {
  476. source: null,
  477. line: null,
  478. column: null,
  479. name: null,
  480. };
  481. }
  482. /**
  483. * Return true if we have the source content for every source in the source
  484. * map, false otherwise.
  485. */
  486. hasContentsOfAllSources() {
  487. if (!this.sourcesContent) {
  488. return false;
  489. }
  490. return (
  491. this.sourcesContent.length >= this._sources.size() &&
  492. !this.sourcesContent.some(function (sc) {
  493. return sc == null;
  494. })
  495. );
  496. }
  497. /**
  498. * Returns the original source content. The only argument is the url of the
  499. * original source file. Returns null if no original source content is
  500. * available.
  501. */
  502. sourceContentFor(aSource, nullOnMissing) {
  503. if (!this.sourcesContent) {
  504. return null;
  505. }
  506. const index = this._findSourceIndex(aSource);
  507. if (index >= 0) {
  508. return this.sourcesContent[index];
  509. }
  510. // This function is used recursively from
  511. // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
  512. // don't want to throw if we can't find the source - we just want to
  513. // return null, so we provide a flag to exit gracefully.
  514. if (nullOnMissing) {
  515. return null;
  516. }
  517. throw new Error('"' + aSource + '" is not in the SourceMap.');
  518. }
  519. /**
  520. * Returns the generated line and column information for the original source,
  521. * line, and column positions provided. The only argument is an object with
  522. * the following properties:
  523. *
  524. * - source: The filename of the original source.
  525. * - line: The line number in the original source. The line number
  526. * is 1-based.
  527. * - column: The column number in the original source. The column
  528. * number is 0-based.
  529. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  530. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  531. * closest element that is smaller than or greater than the one we are
  532. * searching for, respectively, if the exact element cannot be found.
  533. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  534. *
  535. * and an object is returned with the following properties:
  536. *
  537. * - line: The line number in the generated source, or null. The
  538. * line number is 1-based.
  539. * - column: The column number in the generated source, or null.
  540. * The column number is 0-based.
  541. */
  542. generatedPositionFor(aArgs) {
  543. let source = util.getArg(aArgs, "source");
  544. source = this._findSourceIndex(source);
  545. if (source < 0) {
  546. return {
  547. line: null,
  548. column: null,
  549. lastColumn: null,
  550. };
  551. }
  552. const needle = {
  553. source,
  554. originalLine: util.getArg(aArgs, "line"),
  555. originalColumn: util.getArg(aArgs, "column"),
  556. };
  557. if (needle.originalLine < 1) {
  558. throw new Error("Line numbers must be >= 1");
  559. }
  560. if (needle.originalColumn < 0) {
  561. throw new Error("Column numbers must be >= 0");
  562. }
  563. let bias = util.getArg(
  564. aArgs,
  565. "bias",
  566. SourceMapConsumer.GREATEST_LOWER_BOUND
  567. );
  568. if (bias == null) {
  569. bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
  570. }
  571. let mapping;
  572. this._wasm.withMappingCallback(
  573. m => (mapping = m),
  574. () => {
  575. this._wasm.exports.generated_location_for(
  576. this._getMappingsPtr(),
  577. needle.source,
  578. needle.originalLine - 1,
  579. needle.originalColumn,
  580. bias
  581. );
  582. }
  583. );
  584. if (mapping) {
  585. if (mapping.source === needle.source) {
  586. let lastColumn = mapping.lastGeneratedColumn;
  587. if (this._computedColumnSpans && lastColumn === null) {
  588. lastColumn = Infinity;
  589. }
  590. return {
  591. line: util.getArg(mapping, "generatedLine", null),
  592. column: util.getArg(mapping, "generatedColumn", null),
  593. lastColumn,
  594. };
  595. }
  596. }
  597. return {
  598. line: null,
  599. column: null,
  600. lastColumn: null,
  601. };
  602. }
  603. }
  604. BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
  605. exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
  606. /**
  607. * An IndexedSourceMapConsumer instance represents a parsed source map which
  608. * we can query for information. It differs from BasicSourceMapConsumer in
  609. * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
  610. * input.
  611. *
  612. * The first parameter is a raw source map (either as a JSON string, or already
  613. * parsed to an object). According to the spec for indexed source maps, they
  614. * have the following attributes:
  615. *
  616. * - version: Which version of the source map spec this map is following.
  617. * - file: Optional. The generated file this source map is associated with.
  618. * - sections: A list of section definitions.
  619. *
  620. * Each value under the "sections" field has two fields:
  621. * - offset: The offset into the original specified at which this section
  622. * begins to apply, defined as an object with a "line" and "column"
  623. * field.
  624. * - map: A source map definition. This source map could also be indexed,
  625. * but doesn't have to be.
  626. *
  627. * Instead of the "map" field, it's also possible to have a "url" field
  628. * specifying a URL to retrieve a source map from, but that's currently
  629. * unsupported.
  630. *
  631. * Here's an example source map, taken from the source map spec[0], but
  632. * modified to omit a section which uses the "url" field.
  633. *
  634. * {
  635. * version : 3,
  636. * file: "app.js",
  637. * sections: [{
  638. * offset: {line:100, column:10},
  639. * map: {
  640. * version : 3,
  641. * file: "section.js",
  642. * sources: ["foo.js", "bar.js"],
  643. * names: ["src", "maps", "are", "fun"],
  644. * mappings: "AAAA,E;;ABCDE;"
  645. * }
  646. * }],
  647. * }
  648. *
  649. * The second parameter, if given, is a string whose value is the URL
  650. * at which the source map was found. This URL is used to compute the
  651. * sources array.
  652. *
  653. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
  654. */
  655. class IndexedSourceMapConsumer extends SourceMapConsumer {
  656. constructor(aSourceMap, aSourceMapURL) {
  657. return super(INTERNAL).then(that => {
  658. let sourceMap = aSourceMap;
  659. if (typeof aSourceMap === "string") {
  660. sourceMap = util.parseSourceMapInput(aSourceMap);
  661. }
  662. const version = util.getArg(sourceMap, "version");
  663. const sections = util.getArg(sourceMap, "sections");
  664. if (version != that._version) {
  665. throw new Error("Unsupported version: " + version);
  666. }
  667. let lastOffset = {
  668. line: -1,
  669. column: 0,
  670. };
  671. return Promise.all(
  672. 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(
  677. "Support for url field in sections not implemented."
  678. );
  679. }
  680. const offset = util.getArg(s, "offset");
  681. const offsetLine = util.getArg(offset, "line");
  682. const offsetColumn = util.getArg(offset, "column");
  683. if (
  684. offsetLine < lastOffset.line ||
  685. (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
  686. ) {
  687. throw new Error(
  688. "Section offsets must be ordered and non-overlapping."
  689. );
  690. }
  691. lastOffset = offset;
  692. const cons = new SourceMapConsumer(
  693. util.getArg(s, "map"),
  694. aSourceMapURL
  695. );
  696. return cons.then(consumer => {
  697. return {
  698. generatedOffset: {
  699. // The offset fields are 0-based, but we use 1-based indices when
  700. // encoding/decoding from VLQ.
  701. generatedLine: offsetLine + 1,
  702. generatedColumn: offsetColumn + 1,
  703. },
  704. consumer,
  705. };
  706. });
  707. })
  708. ).then(s => {
  709. that._sections = s;
  710. return that;
  711. });
  712. });
  713. }
  714. /**
  715. * The list of original sources.
  716. */
  717. get sources() {
  718. const sources = [];
  719. for (let i = 0; i < this._sections.length; i++) {
  720. for (let j = 0; j < this._sections[i].consumer.sources.length; j++) {
  721. sources.push(this._sections[i].consumer.sources[j]);
  722. }
  723. }
  724. return sources;
  725. }
  726. /**
  727. * Returns the original source, line, and column information for the generated
  728. * source's line and column positions provided. The only argument is an object
  729. * with the following properties:
  730. *
  731. * - line: The line number in the generated source. The line number
  732. * is 1-based.
  733. * - column: The column number in the generated source. The column
  734. * number is 0-based.
  735. *
  736. * and an object is returned with the following properties:
  737. *
  738. * - source: The original source file, or null.
  739. * - line: The line number in the original source, or null. The
  740. * line number is 1-based.
  741. * - column: The column number in the original source, or null. The
  742. * column number is 0-based.
  743. * - name: The original identifier, or null.
  744. */
  745. originalPositionFor(aArgs) {
  746. const needle = {
  747. generatedLine: util.getArg(aArgs, "line"),
  748. generatedColumn: util.getArg(aArgs, "column"),
  749. };
  750. // Find the section containing the generated position we're trying to map
  751. // to an original position.
  752. const sectionIndex = binarySearch.search(
  753. needle,
  754. this._sections,
  755. function (aNeedle, section) {
  756. const cmp =
  757. aNeedle.generatedLine - section.generatedOffset.generatedLine;
  758. if (cmp) {
  759. return cmp;
  760. }
  761. // The generated column is 0-based, but the section offset column is
  762. // stored 1-based.
  763. return (
  764. aNeedle.generatedColumn -
  765. (section.generatedOffset.generatedColumn - 1)
  766. );
  767. }
  768. );
  769. const section = this._sections[sectionIndex];
  770. if (!section) {
  771. return {
  772. source: null,
  773. line: null,
  774. column: null,
  775. name: null,
  776. };
  777. }
  778. return section.consumer.originalPositionFor({
  779. line: needle.generatedLine - (section.generatedOffset.generatedLine - 1),
  780. column:
  781. needle.generatedColumn -
  782. (section.generatedOffset.generatedLine === needle.generatedLine
  783. ? section.generatedOffset.generatedColumn - 1
  784. : 0),
  785. bias: aArgs.bias,
  786. });
  787. }
  788. /**
  789. * Return true if we have the source content for every source in the source
  790. * map, false otherwise.
  791. */
  792. hasContentsOfAllSources() {
  793. return this._sections.every(function (s) {
  794. return s.consumer.hasContentsOfAllSources();
  795. });
  796. }
  797. /**
  798. * Returns the original source content. The only argument is the url of the
  799. * original source file. Returns null if no original source content is
  800. * available.
  801. */
  802. sourceContentFor(aSource, nullOnMissing) {
  803. for (let i = 0; i < this._sections.length; i++) {
  804. const section = this._sections[i];
  805. const content = section.consumer.sourceContentFor(aSource, true);
  806. if (content) {
  807. return content;
  808. }
  809. }
  810. if (nullOnMissing) {
  811. return null;
  812. }
  813. throw new Error('"' + aSource + '" is not in the SourceMap.');
  814. }
  815. _findSectionIndex(source) {
  816. for (let i = 0; i < this._sections.length; i++) {
  817. const { consumer } = this._sections[i];
  818. if (consumer._findSourceIndex(source) !== -1) {
  819. return i;
  820. }
  821. }
  822. return -1;
  823. }
  824. /**
  825. * Returns the generated line and column information for the original source,
  826. * line, and column positions provided. The only argument is an object with
  827. * the following properties:
  828. *
  829. * - source: The filename of the original source.
  830. * - line: The line number in the original source. The line number
  831. * is 1-based.
  832. * - column: The column number in the original source. The column
  833. * number is 0-based.
  834. *
  835. * and an object is returned with the following properties:
  836. *
  837. * - line: The line number in the generated source, or null. The
  838. * line number is 1-based.
  839. * - column: The column number in the generated source, or null.
  840. * The column number is 0-based.
  841. */
  842. generatedPositionFor(aArgs) {
  843. const index = this._findSectionIndex(util.getArg(aArgs, "source"));
  844. const section = index >= 0 ? this._sections[index] : null;
  845. const nextSection =
  846. index >= 0 && index + 1 < this._sections.length
  847. ? this._sections[index + 1]
  848. : null;
  849. const generatedPosition =
  850. section && section.consumer.generatedPositionFor(aArgs);
  851. if (generatedPosition && generatedPosition.line !== null) {
  852. const lineShift = section.generatedOffset.generatedLine - 1;
  853. const columnShift = section.generatedOffset.generatedColumn - 1;
  854. if (generatedPosition.line === 1) {
  855. generatedPosition.column += columnShift;
  856. if (typeof generatedPosition.lastColumn === "number") {
  857. generatedPosition.lastColumn += columnShift;
  858. }
  859. }
  860. if (
  861. generatedPosition.lastColumn === Infinity &&
  862. nextSection &&
  863. generatedPosition.line === nextSection.generatedOffset.generatedLine
  864. ) {
  865. generatedPosition.lastColumn =
  866. nextSection.generatedOffset.generatedColumn - 2;
  867. }
  868. generatedPosition.line += lineShift;
  869. return generatedPosition;
  870. }
  871. return {
  872. line: null,
  873. column: null,
  874. lastColumn: null,
  875. };
  876. }
  877. allGeneratedPositionsFor(aArgs) {
  878. const index = this._findSectionIndex(util.getArg(aArgs, "source"));
  879. const section = index >= 0 ? this._sections[index] : null;
  880. const nextSection =
  881. index >= 0 && index + 1 < this._sections.length
  882. ? this._sections[index + 1]
  883. : null;
  884. if (!section) return [];
  885. return section.consumer
  886. .allGeneratedPositionsFor(aArgs)
  887. .map(generatedPosition => {
  888. const lineShift = section.generatedOffset.generatedLine - 1;
  889. const columnShift = section.generatedOffset.generatedColumn - 1;
  890. if (generatedPosition.line === 1) {
  891. generatedPosition.column += columnShift;
  892. if (typeof generatedPosition.lastColumn === "number") {
  893. generatedPosition.lastColumn += columnShift;
  894. }
  895. }
  896. if (
  897. generatedPosition.lastColumn === Infinity &&
  898. nextSection &&
  899. generatedPosition.line === nextSection.generatedOffset.generatedLine
  900. ) {
  901. generatedPosition.lastColumn =
  902. nextSection.generatedOffset.generatedColumn - 2;
  903. }
  904. generatedPosition.line += lineShift;
  905. return generatedPosition;
  906. });
  907. }
  908. eachMapping(aCallback, aContext, aOrder) {
  909. this._sections.forEach((section, index) => {
  910. const nextSection =
  911. index + 1 < this._sections.length ? this._sections[index + 1] : null;
  912. const { generatedOffset } = section;
  913. const lineShift = generatedOffset.generatedLine - 1;
  914. const columnShift = generatedOffset.generatedColumn - 1;
  915. section.consumer.eachMapping(
  916. function (mapping) {
  917. if (mapping.generatedLine === 1) {
  918. mapping.generatedColumn += columnShift;
  919. if (typeof mapping.lastGeneratedColumn === "number") {
  920. mapping.lastGeneratedColumn += columnShift;
  921. }
  922. }
  923. if (
  924. mapping.lastGeneratedColumn === Infinity &&
  925. nextSection &&
  926. mapping.generatedLine === nextSection.generatedOffset.generatedLine
  927. ) {
  928. mapping.lastGeneratedColumn =
  929. nextSection.generatedOffset.generatedColumn - 2;
  930. }
  931. mapping.generatedLine += lineShift;
  932. aCallback.call(this, mapping);
  933. },
  934. aContext,
  935. aOrder
  936. );
  937. });
  938. }
  939. computeColumnSpans() {
  940. for (let i = 0; i < this._sections.length; i++) {
  941. this._sections[i].consumer.computeColumnSpans();
  942. }
  943. }
  944. destroy() {
  945. for (let i = 0; i < this._sections.length; i++) {
  946. this._sections[i].consumer.destroy();
  947. }
  948. }
  949. }
  950. exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
  951. /*
  952. * Cheat to get around inter-twingled classes. `factory()` can be at the end
  953. * where it has access to non-hoisted classes, but it gets hoisted itself.
  954. */
  955. function _factory(aSourceMap, aSourceMapURL) {
  956. let sourceMap = aSourceMap;
  957. if (typeof aSourceMap === "string") {
  958. sourceMap = util.parseSourceMapInput(aSourceMap);
  959. }
  960. const consumer =
  961. sourceMap.sections != null
  962. ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
  963. : new BasicSourceMapConsumer(sourceMap, aSourceMapURL);
  964. return Promise.resolve(consumer);
  965. }
  966. function _factoryBSM(aSourceMap, aSourceMapURL) {
  967. return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL);
  968. }