transformer.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. pathutils = require('./pathutils'),
  8. libCoverage = require('istanbul-lib-coverage'),
  9. MappedCoverage = require('./mapped').MappedCoverage;
  10. function isInvalidPosition (pos) {
  11. return !pos || typeof pos.line !== "number" || typeof pos.column !== "number" || pos.line < 0 || pos.column < 0;
  12. }
  13. /**
  14. * determines the original position for a given location
  15. * @param {SourceMapConsumer} sourceMap the source map
  16. * @param {Object} location the original location Object
  17. * @returns {Object} the remapped location Object
  18. */
  19. function getMapping(sourceMap, location, origFile) {
  20. if (!location) {
  21. return null;
  22. }
  23. if (isInvalidPosition(location.start) || isInvalidPosition(location.end)) {
  24. return null;
  25. }
  26. var start = sourceMap.originalPositionFor(location.start),
  27. end = sourceMap.originalPositionFor(location.end);
  28. /* istanbul ignore if: edge case too hard to test for */
  29. if (!(start && end)) {
  30. return null;
  31. }
  32. if (!(start.source && end.source)) {
  33. return null;
  34. }
  35. if (start.source !== end.source) {
  36. return null;
  37. }
  38. /* istanbul ignore if: edge case too hard to test for */
  39. if (start.line === null || start.column === null) {
  40. return null;
  41. }
  42. /* istanbul ignore if: edge case too hard to test for */
  43. if (end.line === null || end.column === null) {
  44. return null;
  45. }
  46. if (start.line === end.line && start.column === end.column) {
  47. end = sourceMap.originalPositionFor({
  48. line: location.end.line,
  49. column: location.end.column,
  50. bias: 2
  51. });
  52. end.column = end.column - 1;
  53. }
  54. return {
  55. source: pathutils.relativeTo(start.source, origFile),
  56. loc: {
  57. start: {
  58. line: start.line,
  59. column: start.column
  60. },
  61. end: {
  62. line: end.line,
  63. column: end.column
  64. }
  65. }
  66. };
  67. }
  68. function SourceMapTransformer(finder, opts) {
  69. opts = opts || {};
  70. this.finder = finder;
  71. this.baseDir = opts.baseDir || process.cwd();
  72. }
  73. SourceMapTransformer.prototype.processFile = function (fc, sourceMap, coverageMapper) {
  74. var changes = 0;
  75. Object.keys(fc.statementMap).forEach(function (s) {
  76. var loc = fc.statementMap[s],
  77. hits = fc.s[s],
  78. mapping = getMapping(sourceMap, loc, fc.path),
  79. mappedCoverage;
  80. if (mapping) {
  81. changes += 1;
  82. mappedCoverage = coverageMapper(mapping.source);
  83. mappedCoverage.addStatement(mapping.loc, hits);
  84. }
  85. });
  86. Object.keys(fc.fnMap).forEach(function (f) {
  87. var fnMeta = fc.fnMap[f],
  88. hits = fc.f[f],
  89. mapping = getMapping(sourceMap, fnMeta.decl, fc.path),
  90. spanMapping = getMapping(sourceMap, fnMeta.loc, fc.path),
  91. mappedCoverage;
  92. if (mapping && spanMapping && mapping.source === spanMapping.source) {
  93. changes += 1;
  94. mappedCoverage = coverageMapper(mapping.source);
  95. mappedCoverage.addFunction(fnMeta.name, mapping.loc, spanMapping.loc, hits);
  96. }
  97. });
  98. Object.keys(fc.branchMap).forEach(function (b) {
  99. var branchMeta = fc.branchMap[b],
  100. source,
  101. hits = fc.b[b],
  102. mapping,
  103. locs = [],
  104. mappedHits = [],
  105. mappedCoverage,
  106. skip,
  107. i;
  108. for (i = 0; i < branchMeta.locations.length; i += 1) {
  109. mapping = getMapping(sourceMap, branchMeta.locations[i], fc.path);
  110. if (mapping) {
  111. if (!source) {
  112. source = mapping.source;
  113. }
  114. if (mapping.source !== source) {
  115. skip = true;
  116. }
  117. locs.push(mapping.loc);
  118. mappedHits.push(hits[i]);
  119. }
  120. }
  121. if (!skip && locs.length > 0) {
  122. changes += 1;
  123. mappedCoverage = coverageMapper(source);
  124. mappedCoverage.addBranch(branchMeta.type, locs[0] /* XXX */, locs, mappedHits);
  125. }
  126. });
  127. return changes > 0;
  128. };
  129. SourceMapTransformer.prototype.transform = function (coverageMap) {
  130. var that = this,
  131. finder = this.finder,
  132. output = {},
  133. getMappedCoverage = function (file) {
  134. if (!output[file]) {
  135. output[file] = new MappedCoverage(file);
  136. }
  137. return output[file];
  138. };
  139. coverageMap.files().forEach(function (file) {
  140. var fc = coverageMap.fileCoverageFor(file),
  141. sourceMap = finder(file),
  142. changed;
  143. if (!sourceMap) {
  144. output[file] = fc;
  145. return;
  146. }
  147. changed = that.processFile(fc, sourceMap, getMappedCoverage);
  148. if (!changed) {
  149. debug('File [' + file + '] ignored, nothing could be mapped');
  150. }
  151. });
  152. return libCoverage.createCoverageMap(output);
  153. };
  154. module.exports = {
  155. create: function (finder, opts) {
  156. return new SourceMapTransformer(finder, opts);
  157. }
  158. };