/*1Copyright (c) 2012, Yahoo! Inc. All rights reserved.2Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.3*/45/**6* utility methods to process coverage objects. A coverage object has the following7* format.8*9* {10* "/path/to/file1.js": { file1 coverage },11* "/path/to/file2.js": { file2 coverage }12* }13*14* The internals of the file coverage object are intentionally not documented since15* it is not a public interface.16*17* *Note:* When a method of this module has the word `File` in it, it will accept18* one of the sub-objects of the main coverage object as an argument. Other19* methods accept the higher level coverage object with multiple keys.20*21* Works on `node` as well as the browser.22*23* Usage on nodejs24* ---------------25*26* var objectUtils = require('istanbul').utils;27*28* Usage in a browser29* ------------------30*31* Load this file using a `script` tag or other means. This will set `window.coverageUtils`32* to this module's exports.33*34* @class ObjectUtils35* @module main36* @static37*/38(function (isNode) {39/**40* adds line coverage information to a file coverage object, reverse-engineering41* it from statement coverage. The object passed in is updated in place.42*43* Note that if line coverage information is already present in the object,44* it is not recomputed.45*46* @method addDerivedInfoForFile47* @static48* @param {Object} fileCoverage the coverage object for a single file49*/50function addDerivedInfoForFile(fileCoverage) {51var statementMap = fileCoverage.statementMap,52statements = fileCoverage.s,53lineMap;5455if (!fileCoverage.l) {56fileCoverage.l = lineMap = {};57Object.keys(statements).forEach(function (st) {58var line = statementMap[st].start.line,59count = statements[st],60prevVal = lineMap[line];61if (count === 0 && statementMap[st].skip) { count = 1; }62if (typeof prevVal === 'undefined' || prevVal < count) {63lineMap[line] = count;64}65});66}67}68/**69* adds line coverage information to all file coverage objects.70*71* @method addDerivedInfo72* @static73* @param {Object} coverage the coverage object74*/75function addDerivedInfo(coverage) {76Object.keys(coverage).forEach(function (k) {77addDerivedInfoForFile(coverage[k]);78});79}80/**81* removes line coverage information from all file coverage objects82* @method removeDerivedInfo83* @static84* @param {Object} coverage the coverage object85*/86function removeDerivedInfo(coverage) {87Object.keys(coverage).forEach(function (k) {88delete coverage[k].l;89});90}9192function percent(covered, total) {93var tmp;94if (total > 0) {95tmp = 1000 * 100 * covered / total + 5;96return Math.floor(tmp / 10) / 100;97} else {98return 100.00;99}100}101102function computeSimpleTotals(fileCoverage, property, mapProperty) {103var stats = fileCoverage[property],104map = mapProperty ? fileCoverage[mapProperty] : null,105ret = { total: 0, covered: 0, skipped: 0 };106107Object.keys(stats).forEach(function (key) {108var covered = !!stats[key],109skipped = map && map[key].skip;110ret.total += 1;111if (covered || skipped) {112ret.covered += 1;113}114if (!covered && skipped) {115ret.skipped += 1;116}117});118ret.pct = percent(ret.covered, ret.total);119return ret;120}121122function computeBranchTotals(fileCoverage) {123var stats = fileCoverage.b,124branchMap = fileCoverage.branchMap,125ret = { total: 0, covered: 0, skipped: 0 };126127Object.keys(stats).forEach(function (key) {128var branches = stats[key],129map = branchMap[key],130covered,131skipped,132i;133for (i = 0; i < branches.length; i += 1) {134covered = branches[i] > 0;135skipped = map.locations && map.locations[i] && map.locations[i].skip;136if (covered || skipped) {137ret.covered += 1;138}139if (!covered && skipped) {140ret.skipped += 1;141}142}143ret.total += branches.length;144});145ret.pct = percent(ret.covered, ret.total);146return ret;147}148/**149* returns a blank summary metrics object. A metrics object has the following150* format.151*152* {153* lines: lineMetrics,154* statements: statementMetrics,155* functions: functionMetrics,156* branches: branchMetrics157* }158*159* Each individual metric object looks as follows:160*161* {162* total: n,163* covered: m,164* pct: percent165* }166*167* @method blankSummary168* @static169* @return {Object} a blank metrics object170*/171function blankSummary() {172return {173lines: {174total: 0,175covered: 0,176skipped: 0,177pct: 'Unknown'178},179statements: {180total: 0,181covered: 0,182skipped: 0,183pct: 'Unknown'184},185functions: {186total: 0,187covered: 0,188skipped: 0,189pct: 'Unknown'190},191branches: {192total: 0,193covered: 0,194skipped: 0,195pct: 'Unknown'196}197};198}199/**200* returns the summary metrics given the coverage object for a single file. See `blankSummary()`201* to understand the format of the returned object.202*203* @method summarizeFileCoverage204* @static205* @param {Object} fileCoverage the coverage object for a single file.206* @return {Object} the summary metrics for the file207*/208function summarizeFileCoverage(fileCoverage) {209var ret = blankSummary();210addDerivedInfoForFile(fileCoverage);211ret.lines = computeSimpleTotals(fileCoverage, 'l');212ret.functions = computeSimpleTotals(fileCoverage, 'f', 'fnMap');213ret.statements = computeSimpleTotals(fileCoverage, 's', 'statementMap');214ret.branches = computeBranchTotals(fileCoverage);215return ret;216}217/**218* merges two instances of file coverage objects *for the same file*219* such that the execution counts are correct.220*221* @method mergeFileCoverage222* @static223* @param {Object} first the first file coverage object for a given file224* @param {Object} second the second file coverage object for the same file225* @return {Object} an object that is a result of merging the two. Note that226* the input objects are not changed in any way.227*/228function mergeFileCoverage(first, second) {229var ret = JSON.parse(JSON.stringify(first)),230i;231232delete ret.l; //remove derived info233234Object.keys(second.s).forEach(function (k) {235ret.s[k] += second.s[k];236});237Object.keys(second.f).forEach(function (k) {238ret.f[k] += second.f[k];239});240Object.keys(second.b).forEach(function (k) {241var retArray = ret.b[k],242secondArray = second.b[k];243for (i = 0; i < retArray.length; i += 1) {244retArray[i] += secondArray[i];245}246});247248return ret;249}250/**251* merges multiple summary metrics objects by summing up the `totals` and252* `covered` fields and recomputing the percentages. This function is generic253* and can accept any number of arguments.254*255* @method mergeSummaryObjects256* @static257* @param {Object} summary... multiple summary metrics objects258* @return {Object} the merged summary metrics259*/260function mergeSummaryObjects() {261var ret = blankSummary(),262args = Array.prototype.slice.call(arguments),263keys = ['lines', 'statements', 'branches', 'functions'],264increment = function (obj) {265if (obj) {266keys.forEach(function (key) {267ret[key].total += obj[key].total;268ret[key].covered += obj[key].covered;269ret[key].skipped += obj[key].skipped;270});271}272};273args.forEach(function (arg) {274increment(arg);275});276keys.forEach(function (key) {277ret[key].pct = percent(ret[key].covered, ret[key].total);278});279280return ret;281}282/**283* returns the coverage summary for a single coverage object. This is284* wrapper over `summarizeFileCoverage` and `mergeSummaryObjects` for285* the common case of a single coverage object286* @method summarizeCoverage287* @static288* @param {Object} coverage the coverage object289* @return {Object} summary coverage metrics across all files in the coverage object290*/291function summarizeCoverage(coverage) {292var fileSummary = [];293Object.keys(coverage).forEach(function (key) {294fileSummary.push(summarizeFileCoverage(coverage[key]));295});296return mergeSummaryObjects.apply(null, fileSummary);297}298299/**300* makes the coverage object generated by this library yuitest_coverage compatible.301* Note that this transformation is lossy since the returned object will not have302* statement and branch coverage.303*304* @method toYUICoverage305* @static306* @param {Object} coverage The `istanbul` coverage object307* @return {Object} a coverage object in `yuitest_coverage` format.308*/309function toYUICoverage(coverage) {310var ret = {};311312addDerivedInfo(coverage);313314Object.keys(coverage).forEach(function (k) {315var fileCoverage = coverage[k],316lines = fileCoverage.l,317functions = fileCoverage.f,318fnMap = fileCoverage.fnMap,319o;320321o = ret[k] = {322lines: {},323calledLines: 0,324coveredLines: 0,325functions: {},326calledFunctions: 0,327coveredFunctions: 0328};329Object.keys(lines).forEach(function (k) {330o.lines[k] = lines[k];331o.coveredLines += 1;332if (lines[k] > 0) {333o.calledLines += 1;334}335});336Object.keys(functions).forEach(function (k) {337var name = fnMap[k].name + ':' + fnMap[k].line;338o.functions[name] = functions[k];339o.coveredFunctions += 1;340if (functions[k] > 0) {341o.calledFunctions += 1;342}343});344});345return ret;346}347348var exportables = {349addDerivedInfo: addDerivedInfo,350addDerivedInfoForFile: addDerivedInfoForFile,351removeDerivedInfo: removeDerivedInfo,352blankSummary: blankSummary,353summarizeFileCoverage: summarizeFileCoverage,354summarizeCoverage: summarizeCoverage,355mergeFileCoverage: mergeFileCoverage,356mergeSummaryObjects: mergeSummaryObjects,357toYUICoverage: toYUICoverage358};359360/* istanbul ignore else: windows */361if (isNode) {362module.exports = exportables;363} else {364window.coverageUtils = exportables;365}366}(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));367368369370