map-store.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*
  2. Copyright 2015, Yahoo Inc.
  3. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
  4. */
  5. "use strict";
  6. var debug = require('debug')('istanbuljs'),
  7. path = require('path'),
  8. fs = require('fs'),
  9. pathutils = require('./pathutils'),
  10. sourceStore = require('./source-store'),
  11. transformer = require('./transformer'),
  12. SMC = require('source-map').SourceMapConsumer;
  13. /**
  14. * tracks source maps for registered files
  15. * @param {Object} opts [opts=undefined] options.
  16. * @param {Boolean} opts.verbose [opts.verbose=false] verbose mode
  17. * @param {String} opts.baseDir [opts.baseDir=null] alternate base directory
  18. * to resolve sourcemap files
  19. * @param {String} opts.sourceStore [opts.sourceStore='memory'] - store that tracks
  20. * embedded sources found in source maps, one of 'memory' or 'file'
  21. * @param {String} opts.tmpdir [opts.tmpdir=undefined] - temporary directory
  22. * to use for storing files.
  23. * @constructor
  24. */
  25. function MapStore(opts) {
  26. opts = opts || {};
  27. this.baseDir = opts.baseDir || null;
  28. this.verbose = opts.verbose || false;
  29. this.sourceStore = sourceStore.create(opts.sourceStore, { tmpdir: opts.tmpdir});
  30. this.data = {};
  31. }
  32. /**
  33. * registers a source map URL with this store. It makes some input sanity checks
  34. * and silently fails on malformed input.
  35. * @param transformedFilePath - the file path for which the source map is valid.
  36. * This must *exactly* match the path stashed for the coverage object to be
  37. * useful.
  38. * @param sourceMapUrl - the source map URL, **not** a comment
  39. */
  40. MapStore.prototype.registerURL = function (transformedFilePath, sourceMapUrl) {
  41. var d = 'data:',
  42. b64 = 'base64,',
  43. pos;
  44. if (sourceMapUrl.length > d.length && sourceMapUrl.substring(0, d.length) === d) {
  45. pos = sourceMapUrl.indexOf(b64);
  46. if (pos > 0) {
  47. this.data[transformedFilePath] = {
  48. type: 'encoded',
  49. data: sourceMapUrl.substring(pos + b64.length)
  50. };
  51. } else {
  52. debug('Unable to interpret source map URL: ' + sourceMapUrl);
  53. }
  54. return;
  55. }
  56. var dir = path.dirname(path.resolve(transformedFilePath)),
  57. file = path.resolve(dir, sourceMapUrl);
  58. this.data[transformedFilePath] = { type: 'file', data: file };
  59. };
  60. /**
  61. * registers a source map object with this store. Makes some basic sanity checks
  62. * and silently fails on malformed input.
  63. * @param transformedFilePath - the file path for which the source map is valid
  64. * @param sourceMap - the source map object
  65. */
  66. MapStore.prototype.registerMap = function (transformedFilePath, sourceMap) {
  67. if (sourceMap && sourceMap.version) {
  68. this.data[transformedFilePath] = { type: 'object', data: sourceMap };
  69. } else {
  70. debug('Invalid source map object:' + JSON.stringify(sourceMap, null, 2));
  71. }
  72. };
  73. /**
  74. * transforms the coverage map provided into one that refers to original
  75. * sources when valid mappings have been registered with this store.
  76. * @param {CoverageMap} coverageMap - the coverage map to transform
  77. * @returns {Object} an object with 2 properties. `map` for the transformed
  78. * coverage map and `sourceFinder` which is a function to return the source
  79. * text for a file.
  80. */
  81. MapStore.prototype.transformCoverage = function (coverageMap) {
  82. var that = this,
  83. mappedCoverage,
  84. sourceFinder;
  85. sourceFinder = function (filePath) {
  86. var content = that.sourceStore.getSource(filePath);
  87. if (content !== null) {
  88. return content;
  89. }
  90. if (pathutils.isAbsolute(filePath)) {
  91. return fs.readFileSync(filePath, 'utf8');
  92. }
  93. return fs.readFileSync(pathutils.asAbsolute(filePath, that.baseDir));
  94. };
  95. coverageMap.files().forEach(function (file) {
  96. var coverage = coverageMap.fileCoverageFor(file);
  97. if (coverage.data.inputSourceMap && !that.data[file]) {
  98. that.registerMap(file, coverage.data.inputSourceMap);
  99. }
  100. });
  101. if (Object.keys(this.data).length === 0) {
  102. return {
  103. map: coverageMap,
  104. sourceFinder: sourceFinder
  105. };
  106. }
  107. mappedCoverage = transformer.create(function (filePath) {
  108. try {
  109. if (!that.data[filePath]) {
  110. return null;
  111. }
  112. var d = that.data[filePath],
  113. obj,
  114. smc;
  115. if (d.type === 'file') {
  116. obj = JSON.parse(fs.readFileSync(d.data, 'utf8'));
  117. } else if (d.type === 'encoded') {
  118. obj = JSON.parse(new Buffer(d.data, 'base64').toString());
  119. } else {
  120. obj = d.data;
  121. }
  122. smc = new SMC(obj);
  123. smc.sources.forEach(function (s) {
  124. var content = smc.sourceContentFor(s),
  125. sourceFilePath = pathutils.relativeTo(s, filePath);
  126. if (content) {
  127. that.sourceStore.registerSource(sourceFilePath, content);
  128. }
  129. });
  130. return smc;
  131. } catch (ex) {
  132. debug('Error returning source map for ' + filePath);
  133. debug(ex.stack);
  134. return null;
  135. }
  136. }).transform(coverageMap);
  137. return {
  138. map: mappedCoverage,
  139. sourceFinder: sourceFinder
  140. };
  141. };
  142. /**
  143. * disposes temporary resources allocated by this map store
  144. */
  145. MapStore.prototype.dispose = function () {
  146. this.sourceStore.dispose();
  147. };
  148. module.exports = {
  149. MapStore: MapStore
  150. };