source-map-generator.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /* -*- Mode: js; js-indent-level: 2; -*- */
  2. /*
  3. * Copyright 2011 Mozilla Foundation and contributors
  4. * Licensed under the New BSD license. See LICENSE or:
  5. * http://opensource.org/licenses/BSD-3-Clause
  6. */
  7. var base64VLQ = require('./base64-vlq');
  8. var util = require('./util');
  9. var ArraySet = require('./array-set').ArraySet;
  10. var MappingList = require('./mapping-list').MappingList;
  11. /**
  12. * An instance of the SourceMapGenerator represents a source map which is
  13. * being built incrementally. You may pass an object with the following
  14. * properties:
  15. *
  16. * - file: The filename of the generated source.
  17. * - sourceRoot: A root for all relative URLs in this source map.
  18. */
  19. function SourceMapGenerator(aArgs) {
  20. if (!aArgs) {
  21. aArgs = {};
  22. }
  23. this._file = util.getArg(aArgs, 'file', null);
  24. this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
  25. this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
  26. this._ignoreInvalidMapping = util.getArg(aArgs, 'ignoreInvalidMapping', false);
  27. this._sources = new ArraySet();
  28. this._names = new ArraySet();
  29. this._mappings = new MappingList();
  30. this._sourcesContents = null;
  31. }
  32. SourceMapGenerator.prototype._version = 3;
  33. /**
  34. * Creates a new SourceMapGenerator based on a SourceMapConsumer
  35. *
  36. * @param aSourceMapConsumer The SourceMap.
  37. */
  38. SourceMapGenerator.fromSourceMap =
  39. function SourceMapGenerator_fromSourceMap(aSourceMapConsumer, generatorOps) {
  40. var sourceRoot = aSourceMapConsumer.sourceRoot;
  41. var generator = new SourceMapGenerator(Object.assign(generatorOps || {}, {
  42. file: aSourceMapConsumer.file,
  43. sourceRoot: sourceRoot
  44. }));
  45. aSourceMapConsumer.eachMapping(function (mapping) {
  46. var newMapping = {
  47. generated: {
  48. line: mapping.generatedLine,
  49. column: mapping.generatedColumn
  50. }
  51. };
  52. if (mapping.source != null) {
  53. newMapping.source = mapping.source;
  54. if (sourceRoot != null) {
  55. newMapping.source = util.relative(sourceRoot, newMapping.source);
  56. }
  57. newMapping.original = {
  58. line: mapping.originalLine,
  59. column: mapping.originalColumn
  60. };
  61. if (mapping.name != null) {
  62. newMapping.name = mapping.name;
  63. }
  64. }
  65. generator.addMapping(newMapping);
  66. });
  67. aSourceMapConsumer.sources.forEach(function (sourceFile) {
  68. var sourceRelative = sourceFile;
  69. if (sourceRoot !== null) {
  70. sourceRelative = util.relative(sourceRoot, sourceFile);
  71. }
  72. if (!generator._sources.has(sourceRelative)) {
  73. generator._sources.add(sourceRelative);
  74. }
  75. var content = aSourceMapConsumer.sourceContentFor(sourceFile);
  76. if (content != null) {
  77. generator.setSourceContent(sourceFile, content);
  78. }
  79. });
  80. return generator;
  81. };
  82. /**
  83. * Add a single mapping from original source line and column to the generated
  84. * source's line and column for this source map being created. The mapping
  85. * object should have the following properties:
  86. *
  87. * - generated: An object with the generated line and column positions.
  88. * - original: An object with the original line and column positions.
  89. * - source: The original source file (relative to the sourceRoot).
  90. * - name: An optional original token name for this mapping.
  91. */
  92. SourceMapGenerator.prototype.addMapping =
  93. function SourceMapGenerator_addMapping(aArgs) {
  94. var generated = util.getArg(aArgs, 'generated');
  95. var original = util.getArg(aArgs, 'original', null);
  96. var source = util.getArg(aArgs, 'source', null);
  97. var name = util.getArg(aArgs, 'name', null);
  98. if (!this._skipValidation) {
  99. if (this._validateMapping(generated, original, source, name) === false) {
  100. return;
  101. }
  102. }
  103. if (source != null) {
  104. source = String(source);
  105. if (!this._sources.has(source)) {
  106. this._sources.add(source);
  107. }
  108. }
  109. if (name != null) {
  110. name = String(name);
  111. if (!this._names.has(name)) {
  112. this._names.add(name);
  113. }
  114. }
  115. this._mappings.add({
  116. generatedLine: generated.line,
  117. generatedColumn: generated.column,
  118. originalLine: original != null && original.line,
  119. originalColumn: original != null && original.column,
  120. source: source,
  121. name: name
  122. });
  123. };
  124. /**
  125. * Set the source content for a source file.
  126. */
  127. SourceMapGenerator.prototype.setSourceContent =
  128. function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
  129. var source = aSourceFile;
  130. if (this._sourceRoot != null) {
  131. source = util.relative(this._sourceRoot, source);
  132. }
  133. if (aSourceContent != null) {
  134. // Add the source content to the _sourcesContents map.
  135. // Create a new _sourcesContents map if the property is null.
  136. if (!this._sourcesContents) {
  137. this._sourcesContents = Object.create(null);
  138. }
  139. this._sourcesContents[util.toSetString(source)] = aSourceContent;
  140. } else if (this._sourcesContents) {
  141. // Remove the source file from the _sourcesContents map.
  142. // If the _sourcesContents map is empty, set the property to null.
  143. delete this._sourcesContents[util.toSetString(source)];
  144. if (Object.keys(this._sourcesContents).length === 0) {
  145. this._sourcesContents = null;
  146. }
  147. }
  148. };
  149. /**
  150. * Applies the mappings of a sub-source-map for a specific source file to the
  151. * source map being generated. Each mapping to the supplied source file is
  152. * rewritten using the supplied source map. Note: The resolution for the
  153. * resulting mappings is the minimium of this map and the supplied map.
  154. *
  155. * @param aSourceMapConsumer The source map to be applied.
  156. * @param aSourceFile Optional. The filename of the source file.
  157. * If omitted, SourceMapConsumer's file property will be used.
  158. * @param aSourceMapPath Optional. The dirname of the path to the source map
  159. * to be applied. If relative, it is relative to the SourceMapConsumer.
  160. * This parameter is needed when the two source maps aren't in the same
  161. * directory, and the source map to be applied contains relative source
  162. * paths. If so, those relative source paths need to be rewritten
  163. * relative to the SourceMapGenerator.
  164. */
  165. SourceMapGenerator.prototype.applySourceMap =
  166. function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
  167. var sourceFile = aSourceFile;
  168. // If aSourceFile is omitted, we will use the file property of the SourceMap
  169. if (aSourceFile == null) {
  170. if (aSourceMapConsumer.file == null) {
  171. throw new Error(
  172. 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
  173. 'or the source map\'s "file" property. Both were omitted.'
  174. );
  175. }
  176. sourceFile = aSourceMapConsumer.file;
  177. }
  178. var sourceRoot = this._sourceRoot;
  179. // Make "sourceFile" relative if an absolute Url is passed.
  180. if (sourceRoot != null) {
  181. sourceFile = util.relative(sourceRoot, sourceFile);
  182. }
  183. // Applying the SourceMap can add and remove items from the sources and
  184. // the names array.
  185. var newSources = new ArraySet();
  186. var newNames = new ArraySet();
  187. // Find mappings for the "sourceFile"
  188. this._mappings.unsortedForEach(function (mapping) {
  189. if (mapping.source === sourceFile && mapping.originalLine != null) {
  190. // Check if it can be mapped by the source map, then update the mapping.
  191. var original = aSourceMapConsumer.originalPositionFor({
  192. line: mapping.originalLine,
  193. column: mapping.originalColumn
  194. });
  195. if (original.source != null) {
  196. // Copy mapping
  197. mapping.source = original.source;
  198. if (aSourceMapPath != null) {
  199. mapping.source = util.join(aSourceMapPath, mapping.source)
  200. }
  201. if (sourceRoot != null) {
  202. mapping.source = util.relative(sourceRoot, mapping.source);
  203. }
  204. mapping.originalLine = original.line;
  205. mapping.originalColumn = original.column;
  206. if (original.name != null) {
  207. mapping.name = original.name;
  208. }
  209. }
  210. }
  211. var source = mapping.source;
  212. if (source != null && !newSources.has(source)) {
  213. newSources.add(source);
  214. }
  215. var name = mapping.name;
  216. if (name != null && !newNames.has(name)) {
  217. newNames.add(name);
  218. }
  219. }, this);
  220. this._sources = newSources;
  221. this._names = newNames;
  222. // Copy sourcesContents of applied map.
  223. aSourceMapConsumer.sources.forEach(function (sourceFile) {
  224. var content = aSourceMapConsumer.sourceContentFor(sourceFile);
  225. if (content != null) {
  226. if (aSourceMapPath != null) {
  227. sourceFile = util.join(aSourceMapPath, sourceFile);
  228. }
  229. if (sourceRoot != null) {
  230. sourceFile = util.relative(sourceRoot, sourceFile);
  231. }
  232. this.setSourceContent(sourceFile, content);
  233. }
  234. }, this);
  235. };
  236. /**
  237. * A mapping can have one of the three levels of data:
  238. *
  239. * 1. Just the generated position.
  240. * 2. The Generated position, original position, and original source.
  241. * 3. Generated and original position, original source, as well as a name
  242. * token.
  243. *
  244. * To maintain consistency, we validate that any new mapping being added falls
  245. * in to one of these categories.
  246. */
  247. SourceMapGenerator.prototype._validateMapping =
  248. function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
  249. aName) {
  250. // When aOriginal is truthy but has empty values for .line and .column,
  251. // it is most likely a programmer error. In this case we throw a very
  252. // specific error message to try to guide them the right way.
  253. // For example: https://github.com/Polymer/polymer-bundler/pull/519
  254. if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') {
  255. var message = 'original.line and original.column are not numbers -- you probably meant to omit ' +
  256. 'the original mapping entirely and only map the generated position. If so, pass ' +
  257. 'null for the original mapping instead of an object with empty or null values.'
  258. if (this._ignoreInvalidMapping) {
  259. if (typeof console !== 'undefined' && console.warn) {
  260. console.warn(message);
  261. }
  262. return false;
  263. } else {
  264. throw new Error(message);
  265. }
  266. }
  267. if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
  268. && aGenerated.line > 0 && aGenerated.column >= 0
  269. && !aOriginal && !aSource && !aName) {
  270. // Case 1.
  271. return;
  272. }
  273. else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
  274. && aOriginal && 'line' in aOriginal && 'column' in aOriginal
  275. && aGenerated.line > 0 && aGenerated.column >= 0
  276. && aOriginal.line > 0 && aOriginal.column >= 0
  277. && aSource) {
  278. // Cases 2 and 3.
  279. return;
  280. }
  281. else {
  282. var message = 'Invalid mapping: ' + JSON.stringify({
  283. generated: aGenerated,
  284. source: aSource,
  285. original: aOriginal,
  286. name: aName
  287. });
  288. if (this._ignoreInvalidMapping) {
  289. if (typeof console !== 'undefined' && console.warn) {
  290. console.warn(message);
  291. }
  292. return false;
  293. } else {
  294. throw new Error(message)
  295. }
  296. }
  297. };
  298. /**
  299. * Serialize the accumulated mappings in to the stream of base 64 VLQs
  300. * specified by the source map format.
  301. */
  302. SourceMapGenerator.prototype._serializeMappings =
  303. function SourceMapGenerator_serializeMappings() {
  304. var previousGeneratedColumn = 0;
  305. var previousGeneratedLine = 1;
  306. var previousOriginalColumn = 0;
  307. var previousOriginalLine = 0;
  308. var previousName = 0;
  309. var previousSource = 0;
  310. var result = '';
  311. var next;
  312. var mapping;
  313. var nameIdx;
  314. var sourceIdx;
  315. var mappings = this._mappings.toArray();
  316. for (var i = 0, len = mappings.length; i < len; i++) {
  317. mapping = mappings[i];
  318. next = ''
  319. if (mapping.generatedLine !== previousGeneratedLine) {
  320. previousGeneratedColumn = 0;
  321. while (mapping.generatedLine !== previousGeneratedLine) {
  322. next += ';';
  323. previousGeneratedLine++;
  324. }
  325. }
  326. else {
  327. if (i > 0) {
  328. if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
  329. continue;
  330. }
  331. next += ',';
  332. }
  333. }
  334. next += base64VLQ.encode(mapping.generatedColumn
  335. - previousGeneratedColumn);
  336. previousGeneratedColumn = mapping.generatedColumn;
  337. if (mapping.source != null) {
  338. sourceIdx = this._sources.indexOf(mapping.source);
  339. next += base64VLQ.encode(sourceIdx - previousSource);
  340. previousSource = sourceIdx;
  341. // lines are stored 0-based in SourceMap spec version 3
  342. next += base64VLQ.encode(mapping.originalLine - 1
  343. - previousOriginalLine);
  344. previousOriginalLine = mapping.originalLine - 1;
  345. next += base64VLQ.encode(mapping.originalColumn
  346. - previousOriginalColumn);
  347. previousOriginalColumn = mapping.originalColumn;
  348. if (mapping.name != null) {
  349. nameIdx = this._names.indexOf(mapping.name);
  350. next += base64VLQ.encode(nameIdx - previousName);
  351. previousName = nameIdx;
  352. }
  353. }
  354. result += next;
  355. }
  356. return result;
  357. };
  358. SourceMapGenerator.prototype._generateSourcesContent =
  359. function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
  360. return aSources.map(function (source) {
  361. if (!this._sourcesContents) {
  362. return null;
  363. }
  364. if (aSourceRoot != null) {
  365. source = util.relative(aSourceRoot, source);
  366. }
  367. var key = util.toSetString(source);
  368. return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)
  369. ? this._sourcesContents[key]
  370. : null;
  371. }, this);
  372. };
  373. /**
  374. * Externalize the source map.
  375. */
  376. SourceMapGenerator.prototype.toJSON =
  377. function SourceMapGenerator_toJSON() {
  378. var map = {
  379. version: this._version,
  380. sources: this._sources.toArray(),
  381. names: this._names.toArray(),
  382. mappings: this._serializeMappings()
  383. };
  384. if (this._file != null) {
  385. map.file = this._file;
  386. }
  387. if (this._sourceRoot != null) {
  388. map.sourceRoot = this._sourceRoot;
  389. }
  390. if (this._sourcesContents) {
  391. map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
  392. }
  393. return map;
  394. };
  395. /**
  396. * Render the source map being generated to a string.
  397. */
  398. SourceMapGenerator.prototype.toString =
  399. function SourceMapGenerator_toString() {
  400. return JSON.stringify(this.toJSON());
  401. };
  402. exports.SourceMapGenerator = SourceMapGenerator;