Generator.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. *
  8. * @format
  9. */
  10. "use strict";
  11. function _objectSpread(target) {
  12. for (var i = 1; i < arguments.length; i++) {
  13. var source = arguments[i] != null ? arguments[i] : {};
  14. var ownKeys = Object.keys(source);
  15. if (typeof Object.getOwnPropertySymbols === "function") {
  16. ownKeys = ownKeys.concat(
  17. Object.getOwnPropertySymbols(source).filter(function(sym) {
  18. return Object.getOwnPropertyDescriptor(source, sym).enumerable;
  19. })
  20. );
  21. }
  22. ownKeys.forEach(function(key) {
  23. _defineProperty(target, key, source[key]);
  24. });
  25. }
  26. return target;
  27. }
  28. function _defineProperty(obj, key, value) {
  29. if (key in obj) {
  30. Object.defineProperty(obj, key, {
  31. value: value,
  32. enumerable: true,
  33. configurable: true,
  34. writable: true
  35. });
  36. } else {
  37. obj[key] = value;
  38. }
  39. return obj;
  40. }
  41. const B64Builder = require("./B64Builder");
  42. /**
  43. * Generates a source map from raw mappings.
  44. *
  45. * Raw mappings are a set of 2, 4, or five elements:
  46. *
  47. * - line and column number in the generated source
  48. * - line and column number in the original source
  49. * - symbol name in the original source
  50. *
  51. * Mappings have to be passed in the order appearance in the generated source.
  52. */
  53. class Generator {
  54. constructor() {
  55. this.builder = new B64Builder();
  56. this.last = {
  57. generatedColumn: 0,
  58. generatedLine: 1,
  59. // lines are passed in 1-indexed
  60. name: 0,
  61. source: 0,
  62. sourceColumn: 0,
  63. sourceLine: 1
  64. };
  65. this.names = new IndexedSet();
  66. this.source = -1;
  67. this.sources = [];
  68. this.sourcesContent = [];
  69. this.x_facebook_sources = [];
  70. }
  71. /**
  72. * Mark the beginning of a new source file.
  73. */
  74. startFile(file, code, functionMap) {
  75. this.source = this.sources.push(file) - 1;
  76. this.sourcesContent.push(code);
  77. this.x_facebook_sources.push(functionMap ? [functionMap] : null);
  78. }
  79. /**
  80. * Mark the end of the current source file
  81. */
  82. endFile() {
  83. this.source = -1;
  84. }
  85. /**
  86. * Adds a mapping for generated code without a corresponding source location.
  87. */
  88. addSimpleMapping(generatedLine, generatedColumn) {
  89. const last = this.last;
  90. if (
  91. this.source === -1 ||
  92. (generatedLine === last.generatedLine &&
  93. generatedColumn < last.generatedColumn) ||
  94. generatedLine < last.generatedLine
  95. ) {
  96. const msg =
  97. this.source === -1
  98. ? "Cannot add mapping before starting a file with `addFile()`"
  99. : "Mapping is for a position preceding an earlier mapping";
  100. throw new Error(msg);
  101. }
  102. if (generatedLine > last.generatedLine) {
  103. this.builder.markLines(generatedLine - last.generatedLine);
  104. last.generatedLine = generatedLine;
  105. last.generatedColumn = 0;
  106. }
  107. this.builder.startSegment(generatedColumn - last.generatedColumn);
  108. last.generatedColumn = generatedColumn;
  109. }
  110. /**
  111. * Adds a mapping for generated code with a corresponding source location.
  112. */
  113. addSourceMapping(generatedLine, generatedColumn, sourceLine, sourceColumn) {
  114. this.addSimpleMapping(generatedLine, generatedColumn);
  115. const last = this.last;
  116. this.builder
  117. .append(this.source - last.source)
  118. .append(sourceLine - last.sourceLine)
  119. .append(sourceColumn - last.sourceColumn);
  120. last.source = this.source;
  121. last.sourceColumn = sourceColumn;
  122. last.sourceLine = sourceLine;
  123. }
  124. /**
  125. * Adds a mapping for code with a corresponding source location + symbol name.
  126. */
  127. addNamedSourceMapping(
  128. generatedLine,
  129. generatedColumn,
  130. sourceLine,
  131. sourceColumn,
  132. name
  133. ) {
  134. this.addSourceMapping(
  135. generatedLine,
  136. generatedColumn,
  137. sourceLine,
  138. sourceColumn
  139. );
  140. const last = this.last;
  141. const nameIndex = this.names.indexFor(name);
  142. this.builder.append(nameIndex - last.name);
  143. last.name = nameIndex;
  144. }
  145. /**
  146. * Return the source map as object.
  147. */
  148. toMap(file, options) {
  149. let content, sourcesMetadata;
  150. if (options && options.excludeSource) {
  151. content = {};
  152. } else {
  153. content = {
  154. sourcesContent: this.sourcesContent.slice()
  155. };
  156. }
  157. if (this.hasSourcesMetadata()) {
  158. sourcesMetadata = {
  159. x_facebook_sources: JSON.parse(JSON.stringify(this.x_facebook_sources))
  160. };
  161. } else {
  162. sourcesMetadata = {};
  163. }
  164. /* $FlowFixMe(>=0.111.0 site=react_native_fb) This comment suppresses an
  165. * error found when Flow v0.111 was deployed. To see the error, delete this
  166. * comment and run Flow. */
  167. return _objectSpread(
  168. {
  169. version: 3,
  170. file,
  171. sources: this.sources.slice()
  172. },
  173. content,
  174. sourcesMetadata,
  175. {
  176. names: this.names.items(),
  177. mappings: this.builder.toString()
  178. }
  179. );
  180. }
  181. /**
  182. * Return the source map as string.
  183. *
  184. * This is ~2.5x faster than calling `JSON.stringify(generator.toMap())`
  185. */
  186. toString(file, options) {
  187. let content, sourcesMetadata;
  188. if (options && options.excludeSource) {
  189. content = "";
  190. } else {
  191. content = `"sourcesContent":${JSON.stringify(this.sourcesContent)},`;
  192. }
  193. if (this.hasSourcesMetadata()) {
  194. sourcesMetadata = `"x_facebook_sources":${JSON.stringify(
  195. this.x_facebook_sources
  196. )},`;
  197. } else {
  198. sourcesMetadata = "";
  199. }
  200. return (
  201. "{" +
  202. '"version":3,' +
  203. (file ? `"file":${JSON.stringify(file)},` : "") +
  204. `"sources":${JSON.stringify(this.sources)},` +
  205. content +
  206. sourcesMetadata +
  207. `"names":${JSON.stringify(this.names.items())},` +
  208. `"mappings":"${this.builder.toString()}"` +
  209. "}"
  210. );
  211. }
  212. /**
  213. * Determine whether we need to write the `x_facebook_sources` field.
  214. * If the metadata is all `null`s, we can omit the field entirely.
  215. */
  216. hasSourcesMetadata() {
  217. return this.x_facebook_sources.some(
  218. metadata => metadata != null && metadata.some(value => value != null)
  219. );
  220. }
  221. }
  222. class IndexedSet {
  223. constructor() {
  224. this.map = new Map();
  225. this.nextIndex = 0;
  226. }
  227. indexFor(x) {
  228. let index = this.map.get(x);
  229. if (index == null) {
  230. index = this.nextIndex++;
  231. this.map.set(x, index);
  232. }
  233. return index;
  234. }
  235. items() {
  236. return Array.from(this.map.keys());
  237. }
  238. }
  239. module.exports = Generator;