source-map-generator.js 14 KB

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