/* Copyright 2015, Yahoo Inc. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ "use strict"; var debug = require('debug')('istanbuljs'), pathutils = require('./pathutils'), libCoverage = require('istanbul-lib-coverage'), MappedCoverage = require('./mapped').MappedCoverage; function isInvalidPosition (pos) { return !pos || typeof pos.line !== "number" || typeof pos.column !== "number" || pos.line < 0 || pos.column < 0; } /** * determines the original position for a given location * @param {SourceMapConsumer} sourceMap the source map * @param {Object} location the original location Object * @returns {Object} the remapped location Object */ function getMapping(sourceMap, location, origFile) { if (!location) { return null; } if (isInvalidPosition(location.start) || isInvalidPosition(location.end)) { return null; } var start = sourceMap.originalPositionFor(location.start), end = sourceMap.originalPositionFor(location.end); /* istanbul ignore if: edge case too hard to test for */ if (!(start && end)) { return null; } if (!(start.source && end.source)) { return null; } if (start.source !== end.source) { return null; } /* istanbul ignore if: edge case too hard to test for */ if (start.line === null || start.column === null) { return null; } /* istanbul ignore if: edge case too hard to test for */ if (end.line === null || end.column === null) { return null; } if (start.line === end.line && start.column === end.column) { end = sourceMap.originalPositionFor({ line: location.end.line, column: location.end.column, bias: 2 }); end.column = end.column - 1; } return { source: pathutils.relativeTo(start.source, origFile), loc: { start: { line: start.line, column: start.column }, end: { line: end.line, column: end.column } } }; } function SourceMapTransformer(finder, opts) { opts = opts || {}; this.finder = finder; this.baseDir = opts.baseDir || process.cwd(); } SourceMapTransformer.prototype.processFile = function (fc, sourceMap, coverageMapper) { var changes = 0; Object.keys(fc.statementMap).forEach(function (s) { var loc = fc.statementMap[s], hits = fc.s[s], mapping = getMapping(sourceMap, loc, fc.path), mappedCoverage; if (mapping) { changes += 1; mappedCoverage = coverageMapper(mapping.source); mappedCoverage.addStatement(mapping.loc, hits); } }); Object.keys(fc.fnMap).forEach(function (f) { var fnMeta = fc.fnMap[f], hits = fc.f[f], mapping = getMapping(sourceMap, fnMeta.decl, fc.path), spanMapping = getMapping(sourceMap, fnMeta.loc, fc.path), mappedCoverage; if (mapping && spanMapping && mapping.source === spanMapping.source) { changes += 1; mappedCoverage = coverageMapper(mapping.source); mappedCoverage.addFunction(fnMeta.name, mapping.loc, spanMapping.loc, hits); } }); Object.keys(fc.branchMap).forEach(function (b) { var branchMeta = fc.branchMap[b], source, hits = fc.b[b], mapping, locs = [], mappedHits = [], mappedCoverage, skip, i; for (i = 0; i < branchMeta.locations.length; i += 1) { mapping = getMapping(sourceMap, branchMeta.locations[i], fc.path); if (mapping) { if (!source) { source = mapping.source; } if (mapping.source !== source) { skip = true; } locs.push(mapping.loc); mappedHits.push(hits[i]); } } if (!skip && locs.length > 0) { changes += 1; mappedCoverage = coverageMapper(source); mappedCoverage.addBranch(branchMeta.type, locs[0] /* XXX */, locs, mappedHits); } }); return changes > 0; }; SourceMapTransformer.prototype.transform = function (coverageMap) { var that = this, finder = this.finder, output = {}, getMappedCoverage = function (file) { if (!output[file]) { output[file] = new MappedCoverage(file); } return output[file]; }; coverageMap.files().forEach(function (file) { var fc = coverageMap.fileCoverageFor(file), sourceMap = finder(file), changed; if (!sourceMap) { output[file] = fc; return; } changed = that.processFile(fc, sourceMap, getMappedCoverage); if (!changed) { debug('File [' + file + '] ignored, nothing could be mapped'); } }); return libCoverage.createCoverageMap(output); }; module.exports = { create: function (finder, opts) { return new SourceMapTransformer(finder, opts); } };