diff options
Diffstat (limited to 'node_modules/istanbul/lib/report')
| -rw-r--r-- | node_modules/istanbul/lib/report/clover.js | 227 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/cobertura.js | 221 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/common/defaults.js | 51 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/html.js | 572 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/index.js | 104 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/json-summary.js | 75 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/json.js | 69 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/lcov.js | 65 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/lcovonly.js | 103 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/none.js | 41 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/teamcity.js | 92 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/templates/foot.txt | 20 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/templates/head.txt | 60 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/text-lcov.js | 50 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/text-summary.js | 93 | ||||
| -rw-r--r-- | node_modules/istanbul/lib/report/text.js | 234 | 
16 files changed, 2077 insertions, 0 deletions
diff --git a/node_modules/istanbul/lib/report/clover.js b/node_modules/istanbul/lib/report/clover.js new file mode 100644 index 000000000..fe73aefcd --- /dev/null +++ b/node_modules/istanbul/lib/report/clover.js @@ -0,0 +1,227 @@ +var path = require('path'), +    util = require('util'), +    Report = require('./index'), +    FileWriter = require('../util/file-writer'), +    TreeSummarizer = require('../util/tree-summarizer'), +    utils = require('../object-utils'); + +/** + * a `Report` implementation that produces a clover-style XML file. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('clover'); + * + * @class CloverReport + * @module report + * @extends Report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the clover.xml will be written + * @param {String} [opts.file] the file name, defaulted to config attribute or 'clover.xml' + */ +function CloverReport(opts) { +    Report.call(this); +    opts = opts || {}; +    this.projectRoot = process.cwd(); +    this.dir = opts.dir || this.projectRoot; +    this.file = opts.file || this.getDefaultConfig().file; +    this.opts = opts; +} + +CloverReport.TYPE = 'clover'; +util.inherits(CloverReport, Report); + +function asJavaPackage(node) { +    return node.displayShortName(). +        replace(/\//g, '.'). +        replace(/\\/g, '.'). +        replace(/\.$/, ''); +} + +function asClassName(node) { +    /*jslint regexp: true */ +    return node.fullPath().replace(/.*[\\\/]/, ''); +} + +function quote(thing) { +    return '"' + thing + '"'; +} + +function attr(n, v) { +    return ' ' + n + '=' + quote(v) + ' '; +} + +function branchCoverageByLine(fileCoverage) { +    var branchMap = fileCoverage.branchMap, +        branches = fileCoverage.b, +        ret = {}; +    Object.keys(branchMap).forEach(function (k) { +        var line = branchMap[k].line, +            branchData = branches[k]; +        ret[line] = ret[line] || []; +        ret[line].push.apply(ret[line], branchData); +    }); +    Object.keys(ret).forEach(function (k) { +        var dataArray = ret[k], +            covered = dataArray.filter(function (item) { return item > 0; }), +            coverage = covered.length / dataArray.length * 100; +        ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage }; +    }); +    return ret; +} + +function addClassStats(node, fileCoverage, writer) { +    fileCoverage = utils.incrementIgnoredTotals(fileCoverage); + +    var metrics = node.metrics, +        branchByLine = branchCoverageByLine(fileCoverage), +        fnMap, +        lines; + +    writer.println('\t\t\t<file' + +        attr('name', asClassName(node)) + +        attr('path', node.fullPath()) + +        '>'); + +    writer.println('\t\t\t\t<metrics' + +        attr('statements', metrics.lines.total) + +        attr('coveredstatements', metrics.lines.covered) + +        attr('conditionals', metrics.branches.total) + +        attr('coveredconditionals', metrics.branches.covered) + +        attr('methods', metrics.functions.total) + +        attr('coveredmethods', metrics.functions.covered) + +        '/>'); + +    fnMap = fileCoverage.fnMap; +    lines = fileCoverage.l; +    Object.keys(lines).forEach(function (k) { +        var str = '\t\t\t\t<line' + +            attr('num', k) + +            attr('count', lines[k]), +            branchDetail = branchByLine[k]; + +        if (!branchDetail) { +            str += ' type="stmt" '; +        } else { +                str += ' type="cond" ' + +                attr('truecount', branchDetail.covered) + +                attr('falsecount', (branchDetail.total - branchDetail.covered)); +        } +        writer.println(str + '/>'); +    }); + +    writer.println('\t\t\t</file>'); +} + +function walk(node, collector, writer, level, projectRoot) { +    var metrics, +        totalFiles = 0, +        totalPackages = 0, +        totalLines = 0, +        tempLines = 0; +    if (level === 0) { +        metrics = node.metrics; +        writer.println('<?xml version="1.0" encoding="UTF-8"?>'); +        writer.println('<coverage' + +            attr('generated', Date.now()) + +            'clover="3.2.0">'); + +        writer.println('\t<project' + +            attr('timestamp', Date.now()) + +            attr('name', 'All Files') + +            '>'); + +        node.children.filter(function (child) { return child.kind === 'dir'; }). +            forEach(function (child) { +                totalPackages += 1; +                child.children.filter(function (child) { return child.kind !== 'dir'; }). +                    forEach(function (child) { +                        Object.keys(collector.fileCoverageFor(child.fullPath()).l).forEach(function (k){ +                            tempLines = k; +                        }); +                        totalLines += Number(tempLines); +                        totalFiles += 1; +                }); +        }); + +        writer.println('\t\t<metrics' + +            attr('statements', metrics.lines.total) + +            attr('coveredstatements', metrics.lines.covered) + +            attr('conditionals', metrics.branches.total) + +            attr('coveredconditionals', metrics.branches.covered) + +            attr('methods', metrics.functions.total) + +            attr('coveredmethods', metrics.functions.covered) + +            attr('elements', metrics.lines.total + metrics.branches.total + metrics.functions.total) + +            attr('coveredelements', metrics.lines.covered + metrics.branches.covered + metrics.functions.covered) + +            attr('complexity', 0) + +            attr('packages', totalPackages) + +            attr('files', totalFiles) + +            attr('classes', totalFiles) + +            attr('loc', totalLines) + +            attr('ncloc', totalLines) + +            '/>'); +    } +    if (node.packageMetrics) { +        metrics = node.packageMetrics; +        writer.println('\t\t<package' + +            attr('name', asJavaPackage(node)) + +            '>'); + +        writer.println('\t\t\t<metrics' + +            attr('statements', metrics.lines.total) + +            attr('coveredstatements', metrics.lines.covered) + +            attr('conditionals', metrics.branches.total) + +            attr('coveredconditionals', metrics.branches.covered) + +            attr('methods', metrics.functions.total) + +            attr('coveredmethods', metrics.functions.covered) + +            '/>'); + +        node.children.filter(function (child) { return child.kind !== 'dir'; }). +            forEach(function (child) { +                addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer); +            }); +        writer.println('\t\t</package>'); +    } +    node.children.filter(function (child) { return child.kind === 'dir'; }). +        forEach(function (child) { +            walk(child, collector, writer, level + 1, projectRoot); +        }); + +    if (level === 0) { +        writer.println('\t</project>'); +        writer.println('</coverage>'); +    } +} + +Report.mix(CloverReport, { +    synopsis: function () { +        return 'XML coverage report that can be consumed by the clover tool'; +    }, +    getDefaultConfig: function () { +        return { file: 'clover.xml' }; +    }, +    writeReport: function (collector, sync) { +        var summarizer = new TreeSummarizer(), +            outputFile = path.join(this.dir, this.file), +            writer = this.opts.writer || new FileWriter(sync), +            projectRoot = this.projectRoot, +            that = this, +            tree, +            root; + +        collector.files().forEach(function (key) { +            summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key))); +        }); +        tree = summarizer.getTreeSummary(); +        root = tree.root; +        writer.on('done', function () { that.emit('done'); }); +        writer.writeFile(outputFile, function (contentWriter) { +            walk(root, collector, contentWriter, 0, projectRoot); +            writer.done(); +        }); +    } +}); + +module.exports = CloverReport; diff --git a/node_modules/istanbul/lib/report/cobertura.js b/node_modules/istanbul/lib/report/cobertura.js new file mode 100644 index 000000000..d63456c95 --- /dev/null +++ b/node_modules/istanbul/lib/report/cobertura.js @@ -0,0 +1,221 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    util = require('util'), +    Report = require('./index'), +    FileWriter = require('../util/file-writer'), +    TreeSummarizer = require('../util/tree-summarizer'), +    utils = require('../object-utils'); + +/** + * a `Report` implementation that produces a cobertura-style XML file that conforms to the + * http://cobertura.sourceforge.net/xml/coverage-04.dtd DTD. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('cobertura'); + * + * @class CoberturaReport + * @module report + * @extends Report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the cobertura-coverage.xml will be written + */ +function CoberturaReport(opts) { +    Report.call(this); +    opts = opts || {}; +    this.projectRoot = process.cwd(); +    this.dir = opts.dir || this.projectRoot; +    this.file = opts.file || this.getDefaultConfig().file; +    this.opts = opts; +} + +CoberturaReport.TYPE = 'cobertura'; +util.inherits(CoberturaReport, Report); + +function asJavaPackage(node) { +    return node.displayShortName(). +        replace(/\//g, '.'). +        replace(/\\/g, '.'). +        replace(/\.$/, ''); +} + +function asClassName(node) { +    /*jslint regexp: true */ +    return node.fullPath().replace(/.*[\\\/]/, ''); +} + +function quote(thing) { +    return '"' + thing + '"'; +} + +function attr(n, v) { +    return ' ' + n + '=' + quote(v) + ' '; +} + +function branchCoverageByLine(fileCoverage) { +    var branchMap = fileCoverage.branchMap, +        branches = fileCoverage.b, +        ret = {}; +    Object.keys(branchMap).forEach(function (k) { +        var line = branchMap[k].line, +            branchData = branches[k]; +        ret[line] = ret[line] || []; +        ret[line].push.apply(ret[line], branchData); +    }); +    Object.keys(ret).forEach(function (k) { +        var dataArray = ret[k], +            covered = dataArray.filter(function (item) { return item > 0; }), +            coverage = covered.length / dataArray.length * 100; +        ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage }; +    }); +    return ret; +} + +function addClassStats(node, fileCoverage, writer, projectRoot) { +    fileCoverage = utils.incrementIgnoredTotals(fileCoverage); + +    var metrics = node.metrics, +        branchByLine = branchCoverageByLine(fileCoverage), +        fnMap, +        lines; + +    writer.println('\t\t<class' + +        attr('name', asClassName(node)) + +        attr('filename', path.relative(projectRoot, node.fullPath())) + +        attr('line-rate', metrics.lines.pct / 100.0) + +        attr('branch-rate', metrics.branches.pct / 100.0) + +        '>'); + +    writer.println('\t\t<methods>'); +    fnMap = fileCoverage.fnMap; +    Object.keys(fnMap).forEach(function (k) { +        var name = fnMap[k].name, +            hits = fileCoverage.f[k]; + +        writer.println( +            '\t\t\t<method' + +            attr('name', name) + +            attr('hits', hits) + +            attr('signature', '()V') + //fake out a no-args void return +            '>' +        ); + +        //Add the function definition line and hits so that jenkins cobertura plugin records method hits +        writer.println( +            '\t\t\t\t<lines>' + +             '<line' + +            attr('number', fnMap[k].line) + +            attr('hits', fileCoverage.f[k]) + +            '/>' + +            '</lines>' +        ); + +        writer.println('\t\t\t</method>'); + +    }); +    writer.println('\t\t</methods>'); + +    writer.println('\t\t<lines>'); +    lines = fileCoverage.l; +    Object.keys(lines).forEach(function (k) { +        var str = '\t\t\t<line' + +            attr('number', k) + +            attr('hits', lines[k]), +            branchDetail = branchByLine[k]; + +        if (!branchDetail) { +            str += attr('branch', false); +        } else { +            str += attr('branch', true) + +                attr('condition-coverage', branchDetail.coverage + +                    '% (' + branchDetail.covered + '/' + branchDetail.total + ')'); +        } +        writer.println(str + '/>'); +    }); +    writer.println('\t\t</lines>'); + +    writer.println('\t\t</class>'); +} + +function walk(node, collector, writer, level, projectRoot) { +    var metrics; +    if (level === 0) { +        metrics = node.metrics; +        writer.println('<?xml version="1.0" ?>'); +        writer.println('<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">'); +        writer.println('<coverage' + +            attr('lines-valid', metrics.lines.total) + +            attr('lines-covered', metrics.lines.covered) + +            attr('line-rate', metrics.lines.pct / 100.0) + +            attr('branches-valid', metrics.branches.total) + +            attr('branches-covered', metrics.branches.covered) + +            attr('branch-rate', metrics.branches.pct / 100.0) + +            attr('timestamp', Date.now()) + +            'complexity="0" version="0.1">'); +        writer.println('<sources>'); +        writer.println('\t<source>' + projectRoot + '</source>'); +        writer.println('</sources>'); +        writer.println('<packages>'); +    } +    if (node.packageMetrics) { +        metrics = node.packageMetrics; +        writer.println('\t<package' + +            attr('name', asJavaPackage(node)) + +            attr('line-rate', metrics.lines.pct / 100.0) + +            attr('branch-rate', metrics.branches.pct / 100.0) + +            '>'); +        writer.println('\t<classes>'); +        node.children.filter(function (child) { return child.kind !== 'dir'; }). +            forEach(function (child) { +                addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer, projectRoot); +            }); +        writer.println('\t</classes>'); +        writer.println('\t</package>'); +    } +    node.children.filter(function (child) { return child.kind === 'dir'; }). +        forEach(function (child) { +            walk(child, collector, writer, level + 1, projectRoot); +        }); + +    if (level === 0) { +        writer.println('</packages>'); +        writer.println('</coverage>'); +    } +} + +Report.mix(CoberturaReport, { +    synopsis: function () { +        return 'XML coverage report that can be consumed by the cobertura tool'; +    }, +    getDefaultConfig: function () { +        return { file: 'cobertura-coverage.xml' }; +    }, +    writeReport: function (collector, sync) { +        var summarizer = new TreeSummarizer(), +            outputFile = path.join(this.dir, this.file), +            writer = this.opts.writer || new FileWriter(sync), +            projectRoot = this.projectRoot, +            that = this, +            tree, +            root; + +        collector.files().forEach(function (key) { +            summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key))); +        }); +        tree = summarizer.getTreeSummary(); +        root = tree.root; +        writer.on('done', function () { that.emit('done'); }); +        writer.writeFile(outputFile, function (contentWriter) { +            walk(root, collector, contentWriter, 0, projectRoot); +            writer.done(); +        }); +    } +}); + +module.exports = CoberturaReport; diff --git a/node_modules/istanbul/lib/report/common/defaults.js b/node_modules/istanbul/lib/report/common/defaults.js new file mode 100644 index 000000000..a9851c32f --- /dev/null +++ b/node_modules/istanbul/lib/report/common/defaults.js @@ -0,0 +1,51 @@ +/* + Copyright (c) 2013, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var Report  = require('../index'); +var supportsColor = require('supports-color'); + +module.exports = { +    watermarks: function () { +        return { +            statements: [ 50, 80 ], +            lines: [ 50, 80 ], +            functions: [ 50, 80], +            branches: [ 50, 80 ] +        }; +    }, + +    classFor: function (type, metrics, watermarks) { +        var mark = watermarks[type], +            value = metrics[type].pct; +        return value >= mark[1] ? 'high' : value >= mark[0] ? 'medium' : 'low'; +    }, + +    colorize: function (str, clazz) { +        /* istanbul ignore if: untestable in batch mode */ +        var colors = { +            low: '31;1', +            medium: '33;1', +            high: '32;1' +        }; +         +        if (supportsColor && colors[clazz]) { +            return '\u001b[' + colors[clazz] + 'm' + str + '\u001b[0m'; +        } +        return str; +    }, + +    defaultReportConfig: function () { +        var cfg = {}; +        Report.getReportList().forEach(function (type) { +            var rpt = Report.create(type), +                c = rpt.getDefaultConfig(); +            if (c) { +                cfg[type] = c; +            } +        }); +        return cfg; +    } +}; + diff --git a/node_modules/istanbul/lib/report/html.js b/node_modules/istanbul/lib/report/html.js new file mode 100644 index 000000000..1dab26d56 --- /dev/null +++ b/node_modules/istanbul/lib/report/html.js @@ -0,0 +1,572 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +/*jshint maxlen: 300 */ +var handlebars = require('handlebars').create(), +    defaults = require('./common/defaults'), +    path = require('path'), +    fs = require('fs'), +    util = require('util'), +    FileWriter = require('../util/file-writer'), +    Report = require('./index'), +    Store = require('../store'), +    InsertionText = require('../util/insertion-text'), +    TreeSummarizer = require('../util/tree-summarizer'), +    utils = require('../object-utils'), +    templateFor = function (name) { return handlebars.compile(fs.readFileSync(path.resolve(__dirname, 'templates', name + '.txt'), 'utf8')); }, +    headerTemplate = templateFor('head'), +    footerTemplate = templateFor('foot'), +    detailTemplate = handlebars.compile([ +        '<tr>', +        '<td class="line-count quiet">{{#show_lines}}{{maxLines}}{{/show_lines}}</td>', +        '<td class="line-coverage quiet">{{#show_line_execution_counts fileCoverage}}{{maxLines}}{{/show_line_execution_counts}}</td>', +        '<td class="text"><pre class="prettyprint lang-js">{{#show_code structured}}{{/show_code}}</pre></td>', +        '</tr>\n' +    ].join('')), +    summaryTableHeader = [ +        '<div class="pad1">', +        '<table class="coverage-summary">', +        '<thead>', +        '<tr>', +        '   <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>', +        '   <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>', +        '   <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>', +        '   <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>', +        '   <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>', +        '   <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>', +        '   <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>', +        '   <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>', +        '   <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>', +        '   <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>', +        '</tr>', +        '</thead>', +        '<tbody>' +    ].join('\n'), +    summaryLineTemplate = handlebars.compile([ +        '<tr>', +        '<td class="file {{reportClasses.statements}}" data-value="{{file}}"><a href="{{output}}">{{file}}</a></td>', +        '<td data-value="{{metrics.statements.pct}}" class="pic {{reportClasses.statements}}"><div class="chart">{{#show_picture}}{{metrics.statements.pct}}{{/show_picture}}</div></td>', +        '<td data-value="{{metrics.statements.pct}}" class="pct {{reportClasses.statements}}">{{metrics.statements.pct}}%</td>', +        '<td data-value="{{metrics.statements.total}}" class="abs {{reportClasses.statements}}">{{metrics.statements.covered}}/{{metrics.statements.total}}</td>', +        '<td data-value="{{metrics.branches.pct}}" class="pct {{reportClasses.branches}}">{{metrics.branches.pct}}%</td>', +        '<td data-value="{{metrics.branches.total}}" class="abs {{reportClasses.branches}}">{{metrics.branches.covered}}/{{metrics.branches.total}}</td>', +        '<td data-value="{{metrics.functions.pct}}" class="pct {{reportClasses.functions}}">{{metrics.functions.pct}}%</td>', +        '<td data-value="{{metrics.functions.total}}" class="abs {{reportClasses.functions}}">{{metrics.functions.covered}}/{{metrics.functions.total}}</td>', +        '<td data-value="{{metrics.lines.pct}}" class="pct {{reportClasses.lines}}">{{metrics.lines.pct}}%</td>', +        '<td data-value="{{metrics.lines.total}}" class="abs {{reportClasses.lines}}">{{metrics.lines.covered}}/{{metrics.lines.total}}</td>', +        '</tr>\n' +    ].join('\n\t')), +    summaryTableFooter = [ +        '</tbody>', +        '</table>', +        '</div>' +    ].join('\n'), +    lt = '\u0001', +    gt = '\u0002', +    RE_LT = /</g, +    RE_GT = />/g, +    RE_AMP = /&/g, +    RE_lt = /\u0001/g, +    RE_gt = /\u0002/g; + +handlebars.registerHelper('show_picture', function (opts) { +    var num = Number(opts.fn(this)), +        rest, +        cls = ''; +    if (isFinite(num)) { +        if (num === 100) { +            cls = ' cover-full'; +        } +        num = Math.floor(num); +        rest = 100 - num; +        return '<div class="cover-fill' + cls + '" style="width: ' + num + '%;"></div>' + +            '<div class="cover-empty" style="width:' + rest + '%;"></div>'; +    } else { +        return ''; +    } +}); + +handlebars.registerHelper('if_has_ignores', function (metrics, opts) { +    return (metrics.statements.skipped + +        metrics.functions.skipped + +        metrics.branches.skipped) === 0 ? '' : opts.fn(this); +}); + +handlebars.registerHelper('show_ignores', function (metrics) { +    var statements = metrics.statements.skipped, +        functions = metrics.functions.skipped, +        branches = metrics.branches.skipped, +        result; + +    if (statements === 0 && functions === 0 && branches === 0) { +        return '<span class="ignore-none">none</span>'; +    } + +    result = []; +    if (statements >0) { result.push(statements === 1 ? '1 statement': statements + ' statements'); } +    if (functions >0) { result.push(functions === 1 ? '1 function' : functions + ' functions'); } +    if (branches >0) { result.push(branches === 1 ? '1 branch' : branches + ' branches'); } + +    return result.join(', '); +}); + +handlebars.registerHelper('show_lines', function (opts) { +    var maxLines = Number(opts.fn(this)), +        i, +        array = []; + +    for (i = 0; i < maxLines; i += 1) { +        array[i] = i + 1; +    } +    return array.join('\n'); +}); + +handlebars.registerHelper('show_line_execution_counts', function (context, opts) { +    var lines = context.l, +        maxLines = Number(opts.fn(this)), +        i, +        lineNumber, +        array = [], +        covered, +        value = ''; + +    for (i = 0; i < maxLines; i += 1) { +        lineNumber = i + 1; +        value = ' '; +        covered = 'neutral'; +        if (lines.hasOwnProperty(lineNumber)) { +            if (lines[lineNumber] > 0) { +                covered = 'yes'; +                value = lines[lineNumber] + '×'; +            } else { +                covered = 'no'; +            } +        } +        array.push('<span class="cline-any cline-' + covered + '">' + value + '</span>'); +    } +    return array.join('\n'); +}); + +function customEscape(text) { +    text = text.toString(); +    return text.replace(RE_AMP, '&') +        .replace(RE_LT, '<') +        .replace(RE_GT, '>') +        .replace(RE_lt, '<') +        .replace(RE_gt, '>'); +} + +handlebars.registerHelper('show_code', function (context /*, opts */) { +    var array = []; + +    context.forEach(function (item) { +        array.push(customEscape(item.text) || ' '); +    }); +    return array.join('\n'); +}); + +function title(str) { +    return ' title="' + str + '" '; +} + +function annotateLines(fileCoverage, structuredText) { +    var lineStats = fileCoverage.l; +    if (!lineStats) { return; } +    Object.keys(lineStats).forEach(function (lineNumber) { +        var count = lineStats[lineNumber]; +        if (structuredText[lineNumber]) { +          structuredText[lineNumber].covered = count > 0 ? 'yes' : 'no'; +        } +    }); +    structuredText.forEach(function (item) { +        if (item.covered === null) { +            item.covered = 'neutral'; +        } +    }); +} + +function annotateStatements(fileCoverage, structuredText) { +    var statementStats = fileCoverage.s, +        statementMeta = fileCoverage.statementMap; +    Object.keys(statementStats).forEach(function (stName) { +        var count = statementStats[stName], +            meta = statementMeta[stName], +            type = count > 0 ? 'yes' : 'no', +            startCol = meta.start.column, +            endCol = meta.end.column + 1, +            startLine = meta.start.line, +            endLine = meta.end.line, +            openSpan = lt + 'span class="' + (meta.skip ? 'cstat-skip' : 'cstat-no') + '"' + title('statement not covered') + gt, +            closeSpan = lt + '/span' + gt, +            text; + +        if (type === 'no') { +            if (endLine !== startLine) { +                endLine = startLine; +                endCol = structuredText[startLine].text.originalLength(); +            } +            text = structuredText[startLine].text; +            text.wrap(startCol, +                openSpan, +                startLine === endLine ? endCol : text.originalLength(), +                closeSpan); +        } +    }); +} + +function annotateFunctions(fileCoverage, structuredText) { + +    var fnStats = fileCoverage.f, +        fnMeta = fileCoverage.fnMap; +    if (!fnStats) { return; } +    Object.keys(fnStats).forEach(function (fName) { +        var count = fnStats[fName], +            meta = fnMeta[fName], +            type = count > 0 ? 'yes' : 'no', +            startCol = meta.loc.start.column, +            endCol = meta.loc.end.column + 1, +            startLine = meta.loc.start.line, +            endLine = meta.loc.end.line, +            openSpan = lt + 'span class="' + (meta.skip ? 'fstat-skip' : 'fstat-no') + '"' + title('function not covered') + gt, +            closeSpan = lt + '/span' + gt, +            text; + +        if (type === 'no') { +            if (endLine !== startLine) { +                endLine = startLine; +                endCol = structuredText[startLine].text.originalLength(); +            } +            text = structuredText[startLine].text; +            text.wrap(startCol, +                openSpan, +                startLine === endLine ? endCol : text.originalLength(), +                closeSpan); +        } +    }); +} + +function annotateBranches(fileCoverage, structuredText) { +    var branchStats = fileCoverage.b, +        branchMeta = fileCoverage.branchMap; +    if (!branchStats) { return; } + +    Object.keys(branchStats).forEach(function (branchName) { +        var branchArray = branchStats[branchName], +            sumCount = branchArray.reduce(function (p, n) { return p + n; }, 0), +            metaArray = branchMeta[branchName].locations, +            i, +            count, +            meta, +            type, +            startCol, +            endCol, +            startLine, +            endLine, +            openSpan, +            closeSpan, +            text; + +        if (sumCount > 0) { //only highlight if partial branches are missing +            for (i = 0; i < branchArray.length; i += 1) { +                count = branchArray[i]; +                meta = metaArray[i]; +                type = count > 0 ? 'yes' : 'no'; +                startCol = meta.start.column; +                endCol = meta.end.column + 1; +                startLine = meta.start.line; +                endLine = meta.end.line; +                openSpan = lt + 'span class="branch-' + i + ' ' + (meta.skip ? 'cbranch-skip' : 'cbranch-no') + '"' + title('branch not covered') + gt; +                closeSpan = lt + '/span' + gt; + +                if (count === 0) { //skip branches taken +                    if (endLine !== startLine) { +                        endLine = startLine; +                        endCol = structuredText[startLine].text.originalLength(); +                    } +                    text = structuredText[startLine].text; +                    if (branchMeta[branchName].type === 'if') { // and 'if' is a special case since the else branch might not be visible, being non-existent +                        text.insertAt(startCol, lt + 'span class="' + (meta.skip ? 'skip-if-branch' : 'missing-if-branch') + '"' + +                            title((i === 0 ? 'if' : 'else') + ' path not taken') + gt + +                            (i === 0 ? 'I' : 'E')  + lt + '/span' + gt, true, false); +                    } else { +                        text.wrap(startCol, +                            openSpan, +                            startLine === endLine ? endCol : text.originalLength(), +                            closeSpan); +                    } +                } +            } +        } +    }); +} + +function getReportClass(stats, watermark) { +    var coveragePct = stats.pct, +        identity  = 1; +    if (coveragePct * identity === coveragePct) { +        return coveragePct >= watermark[1] ? 'high' : coveragePct >= watermark[0] ? 'medium' : 'low'; +    } else { +        return ''; +    } +} + +function cleanPath(name) { +    var SEP = path.sep || '/'; +    return (SEP !== '/') ? name.split(SEP).join('/') : name; +} + +function isEmptySourceStore(sourceStore) { +    if (!sourceStore) { +        return true; +    } + +    var cache = sourceStore.sourceCache; +    return cache && !Object.keys(cache).length; +} + +/** + * a `Report` implementation that produces HTML coverage reports. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('html'); + * + * + * @class HtmlReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to generate reports. Defaults to `./html-report` + */ +function HtmlReport(opts) { +    Report.call(this); +    this.opts = opts || {}; +    this.opts.dir = this.opts.dir || path.resolve(process.cwd(), 'html-report'); +    this.opts.sourceStore = isEmptySourceStore(this.opts.sourceStore) ? +        Store.create('fslookup') : this.opts.sourceStore; +    this.opts.linkMapper = this.opts.linkMapper || this.standardLinkMapper(); +    this.opts.writer = this.opts.writer || null; +    this.opts.templateData = { datetime: Date() }; +    this.opts.watermarks = this.opts.watermarks || defaults.watermarks(); +} + +HtmlReport.TYPE = 'html'; +util.inherits(HtmlReport, Report); + +Report.mix(HtmlReport, { + +    synopsis: function () { +        return 'Navigable HTML coverage report for every file and directory'; +    }, + +    getPathHtml: function (node, linkMapper) { +        var parent = node.parent, +            nodePath = [], +            linkPath = [], +            i; + +        while (parent) { +            nodePath.push(parent); +            parent = parent.parent; +        } + +        for (i = 0; i < nodePath.length; i += 1) { +            linkPath.push('<a href="' + linkMapper.ancestor(node, i + 1) + '">' + +                (cleanPath(nodePath[i].relativeName) || 'all files') + '</a>'); +        } +        linkPath.reverse(); +        return linkPath.length > 0 ? linkPath.join(' / ') + ' ' + +            cleanPath(node.displayShortName()) : '/'; +    }, + +    fillTemplate: function (node, templateData) { +        var opts = this.opts, +            linkMapper = opts.linkMapper; + +        templateData.entity = node.name || 'All files'; +        templateData.metrics = node.metrics; +        templateData.reportClass = getReportClass(node.metrics.statements, opts.watermarks.statements); +        templateData.pathHtml = this.getPathHtml(node, linkMapper); +        templateData.base = { +        	css: linkMapper.asset(node, 'base.css') +        }; +        templateData.sorter = { +            js: linkMapper.asset(node, 'sorter.js'), +            image: linkMapper.asset(node, 'sort-arrow-sprite.png') +        }; +        templateData.prettify = { +            js: linkMapper.asset(node, 'prettify.js'), +            css: linkMapper.asset(node, 'prettify.css') +        }; +    }, +    writeDetailPage: function (writer, node, fileCoverage) { +        var opts = this.opts, +            sourceStore = opts.sourceStore, +            templateData = opts.templateData, +            sourceText = fileCoverage.code && Array.isArray(fileCoverage.code) ? +                fileCoverage.code.join('\n') + '\n' : sourceStore.get(fileCoverage.path), +            code = sourceText.split(/(?:\r?\n)|\r/), +            count = 0, +            structured = code.map(function (str) { count += 1; return { line: count, covered: null, text: new InsertionText(str, true) }; }), +            context; + +        structured.unshift({ line: 0, covered: null, text: new InsertionText("") }); + +        this.fillTemplate(node, templateData); +        writer.write(headerTemplate(templateData)); +        writer.write('<pre><table class="coverage">\n'); + +        annotateLines(fileCoverage, structured); +        //note: order is important, since statements typically result in spanning the whole line and doing branches late +        //causes mismatched tags +        annotateBranches(fileCoverage, structured); +        annotateFunctions(fileCoverage, structured); +        annotateStatements(fileCoverage, structured); + +        structured.shift(); +        context = { +            structured: structured, +            maxLines: structured.length, +            fileCoverage: fileCoverage +        }; +        writer.write(detailTemplate(context)); +        writer.write('</table></pre>\n'); +        writer.write(footerTemplate(templateData)); +    }, + +    writeIndexPage: function (writer, node) { +        var linkMapper = this.opts.linkMapper, +            templateData = this.opts.templateData, +            children = Array.prototype.slice.apply(node.children), +            watermarks = this.opts.watermarks; + +        children.sort(function (a, b) { +            return a.name < b.name ? -1 : 1; +        }); + +        this.fillTemplate(node, templateData); +        writer.write(headerTemplate(templateData)); +        writer.write(summaryTableHeader); +        children.forEach(function (child) { +            var metrics = child.metrics, +                reportClasses = { +                    statements: getReportClass(metrics.statements, watermarks.statements), +                    lines: getReportClass(metrics.lines, watermarks.lines), +                    functions: getReportClass(metrics.functions, watermarks.functions), +                    branches: getReportClass(metrics.branches, watermarks.branches) +                }, +                data = { +                    metrics: metrics, +                    reportClasses: reportClasses, +                    file: cleanPath(child.displayShortName()), +                    output: linkMapper.fromParent(child) +                }; +            writer.write(summaryLineTemplate(data) + '\n'); +        }); +        writer.write(summaryTableFooter); +        writer.write(footerTemplate(templateData)); +    }, + +    writeFiles: function (writer, node, dir, collector) { +        var that = this, +            indexFile = path.resolve(dir, 'index.html'), +            childFile; +        if (this.opts.verbose) { console.error('Writing ' + indexFile); } +        writer.writeFile(indexFile, function (contentWriter) { +            that.writeIndexPage(contentWriter, node); +        }); +        node.children.forEach(function (child) { +            if (child.kind === 'dir') { +                that.writeFiles(writer, child, path.resolve(dir, child.relativeName), collector); +            } else { +                childFile = path.resolve(dir, child.relativeName + '.html'); +                if (that.opts.verbose) { console.error('Writing ' + childFile); } +                writer.writeFile(childFile, function (contentWriter) { +                    that.writeDetailPage(contentWriter, child, collector.fileCoverageFor(child.fullPath())); +                }); +            } +        }); +    }, + +    standardLinkMapper: function () { +        return { +            fromParent: function (node) { +                var relativeName = cleanPath(node.relativeName); + +                return node.kind === 'dir' ? relativeName + 'index.html' : relativeName + '.html'; +            }, +            ancestorHref: function (node, num) { +                var href = '', +                    notDot = function(part) { +                        return part !== '.'; +                    }, +                    separated, +                    levels, +                    i, +                    j; + +                for (i = 0; i < num; i += 1) { +                    separated = cleanPath(node.relativeName).split('/').filter(notDot); +                    levels = separated.length - 1; +                    for (j = 0; j < levels; j += 1) { +                        href += '../'; +                    } +                    node = node.parent; +                } +                return href; +            }, +            ancestor: function (node, num) { +                return this.ancestorHref(node, num) + 'index.html'; +            }, +            asset: function (node, name) { +                var i = 0, +                    parent = node.parent; +                while (parent) { i += 1; parent = parent.parent; } +                return this.ancestorHref(node, i) + name; +            } +        }; +    }, + +    writeReport: function (collector, sync) { +        var opts = this.opts, +            dir = opts.dir, +            summarizer = new TreeSummarizer(), +            writer = opts.writer || new FileWriter(sync), +            that = this, +            tree, +            copyAssets = function (subdir) { +                var srcDir = path.resolve(__dirname, '..', 'assets', subdir); +                fs.readdirSync(srcDir).forEach(function (f) { +                    var resolvedSource = path.resolve(srcDir, f), +                        resolvedDestination = path.resolve(dir, f), +                        stat = fs.statSync(resolvedSource); + +                    if (stat.isFile()) { +                        if (opts.verbose) { +                            console.log('Write asset: ' + resolvedDestination); +                        } +                        writer.copyFile(resolvedSource, resolvedDestination); +                    } +                }); +            }; + +        collector.files().forEach(function (key) { +            summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key))); +        }); +        tree = summarizer.getTreeSummary(); +        [ '.', 'vendor'].forEach(function (subdir) { +            copyAssets(subdir); +        }); +        writer.on('done', function () { that.emit('done'); }); +        //console.log(JSON.stringify(tree.root, undefined, 4)); +        this.writeFiles(writer, tree.root, dir, collector); +        writer.done(); +    } +}); + +module.exports = HtmlReport; + diff --git a/node_modules/istanbul/lib/report/index.js b/node_modules/istanbul/lib/report/index.js new file mode 100644 index 000000000..13e7effbe --- /dev/null +++ b/node_modules/istanbul/lib/report/index.js @@ -0,0 +1,104 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var util = require('util'), +    EventEmitter = require('events').EventEmitter, +    Factory = require('../util/factory'), +    factory = new Factory('report', __dirname, false); +/** + * An abstraction for producing coverage reports. + * This class is both the base class as well as a factory for `Report` implementations. + * All reports are event emitters and are expected to emit a `done` event when + * the report writing is complete. + * + * See also the `Reporter` class for easily producing multiple coverage reports + * with a single call. + * + * Usage + * ----- + * + *      var Report = require('istanbul').Report, + *          report = Report.create('html'), + *          collector = new require('istanbul').Collector; + * + *      collector.add(coverageObject); + *      report.on('done', function () { console.log('done'); }); + *      report.writeReport(collector); + * + * @class Report + * @module report + * @main report + * @constructor + * @protected + * @param {Object} options Optional. The options supported by a specific store implementation. + */ +function Report(/* options */) { +    EventEmitter.call(this); +} + +util.inherits(Report, EventEmitter); + +//add register, create, mix, loadAll, getReportList as class methods +factory.bindClassMethods(Report); + +/** + * registers a new report implementation. + * @method register + * @static + * @param {Function} constructor the constructor function for the report. This function must have a + *  `TYPE` property of type String, that will be used in `Report.create()` + */ +/** + * returns a report implementation of the specified type. + * @method create + * @static + * @param {String} type the type of report to create + * @param {Object} opts Optional. Options specific to the report implementation + * @return {Report} a new store of the specified type + */ +/** + * returns the list of available reports as an array of strings + * @method getReportList + * @static + * @return an array of supported report formats + */ + +var proto = { +    /** +     * returns a one-line summary of the report +     * @method synopsis +     * @return {String} a description of what the report is about +     */ +    synopsis: function () { +        throw new Error('synopsis must be overridden'); +    }, +    /** +     * returns a config object that has override-able keys settable via config +     * @method getDefaultConfig +     * @return {Object|null} an object representing keys that can be overridden via +     *  the istanbul configuration where the values are the defaults used when +     *  not specified. A null return implies no config attributes +     */ +    getDefaultConfig: function () { +        return null; +    }, +    /** +     * writes the report for a set of coverage objects added to a collector. +     * @method writeReport +     * @param {Collector} collector the collector for getting the set of files and coverage +     * @param {Boolean} sync true if reports must be written synchronously, false if they can be written using asynchronous means (e.g. stream.write) +     */ +    writeReport: function (/* collector, sync */) { +        throw new Error('writeReport: must be overridden'); +    } +}; + +Object.keys(proto).forEach(function (k) { +    Report.prototype[k] = proto[k]; +}); + +module.exports = Report; + + diff --git a/node_modules/istanbul/lib/report/json-summary.js b/node_modules/istanbul/lib/report/json-summary.js new file mode 100644 index 000000000..6ab7caae9 --- /dev/null +++ b/node_modules/istanbul/lib/report/json-summary.js @@ -0,0 +1,75 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    objectUtils = require('../object-utils'), +    Writer = require('../util/file-writer'), +    util = require('util'), +    Report = require('./index'); +/** + * a `Report` implementation that produces a coverage JSON object with summary info only. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('json-summary'); + * + * + * @class JsonSummaryReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to write the `coverage-summary.json` file. Defaults to `process.cwd()` + */ +function JsonSummaryReport(opts) { +    this.opts = opts || {}; +    this.opts.dir = this.opts.dir || process.cwd(); +    this.opts.file = this.opts.file || this.getDefaultConfig().file; +    this.opts.writer = this.opts.writer || null; +} +JsonSummaryReport.TYPE = 'json-summary'; +util.inherits(JsonSummaryReport, Report); + +Report.mix(JsonSummaryReport, { +    synopsis: function () { +        return 'prints a summary coverage object as JSON to a file'; +    }, +    getDefaultConfig: function () { +        return { +            file: 'coverage-summary.json' +        }; +    }, +    writeReport: function (collector, sync) { +        var outputFile = path.resolve(this.opts.dir, this.opts.file), +            writer = this.opts.writer || new Writer(sync), +            that = this; + +        var summaries = [], +            finalSummary; +        collector.files().forEach(function (file) { +            summaries.push(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(file))); +        }); +        finalSummary = objectUtils.mergeSummaryObjects.apply(null, summaries); + +        writer.on('done', function () { that.emit('done'); }); +        writer.writeFile(outputFile, function (contentWriter) { +            contentWriter.println("{"); +            contentWriter.write('"total":'); +            contentWriter.write(JSON.stringify(finalSummary)); + +            collector.files().forEach(function (key) { +                contentWriter.println(","); +                contentWriter.write(JSON.stringify(key)); +                contentWriter.write(":"); +                contentWriter.write(JSON.stringify(objectUtils.summarizeFileCoverage(collector.fileCoverageFor(key)))); +            }); +            contentWriter.println("}"); +        }); +        writer.done(); +    } +}); + +module.exports = JsonSummaryReport; diff --git a/node_modules/istanbul/lib/report/json.js b/node_modules/istanbul/lib/report/json.js new file mode 100644 index 000000000..2def51ac0 --- /dev/null +++ b/node_modules/istanbul/lib/report/json.js @@ -0,0 +1,69 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    Writer = require('../util/file-writer'), +    util = require('util'), +    Report = require('./index'); +/** + * a `Report` implementation that produces a coverage JSON object. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('json'); + * + * + * @class JsonReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to write the `coverage-final.json` file. Defaults to `process.cwd()` + */ +function JsonReport(opts) { +    this.opts = opts || {}; +    this.opts.dir = this.opts.dir || process.cwd(); +    this.opts.file = this.opts.file || this.getDefaultConfig().file; +    this.opts.writer = this.opts.writer || null; +} +JsonReport.TYPE = 'json'; +util.inherits(JsonReport, Report); + +Report.mix(JsonReport, { +    synopsis: function () { +        return 'prints the coverage object as JSON to a file'; +    }, +    getDefaultConfig: function () { +        return { +            file: 'coverage-final.json' +        }; +    }, +    writeReport: function (collector, sync) { +        var outputFile = path.resolve(this.opts.dir, this.opts.file), +            writer = this.opts.writer || new Writer(sync), +            that = this; + +        writer.on('done', function () { that.emit('done'); }); +        writer.writeFile(outputFile, function (contentWriter) { +            var first = true; +            contentWriter.println("{"); +            collector.files().forEach(function (key) { +                if (first) { +                    first = false; +                } else { +                    contentWriter.println(","); +                } +                contentWriter.write(JSON.stringify(key)); +                contentWriter.write(":"); +                contentWriter.write(JSON.stringify(collector.fileCoverageFor(key))); +            }); +            contentWriter.println("}"); +        }); +        writer.done(); +    } +}); + +module.exports = JsonReport; diff --git a/node_modules/istanbul/lib/report/lcov.js b/node_modules/istanbul/lib/report/lcov.js new file mode 100644 index 000000000..87e01eaab --- /dev/null +++ b/node_modules/istanbul/lib/report/lcov.js @@ -0,0 +1,65 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    util = require('util'), +    mkdirp = require('mkdirp'), +    Report = require('./index'), +    LcovOnlyReport = require('./lcovonly'), +    HtmlReport = require('./html'); + +/** + * a `Report` implementation that produces an LCOV coverage file and an associated HTML report from coverage objects. + * The name and behavior of this report is designed to ease migration for projects that currently use `yuitest_coverage` + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('lcov'); + * + * + * @class LcovReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the `lcov.info` file. + *  HTML files are written in a subdirectory called `lcov-report`. Defaults to `process.cwd()` + */ +function LcovReport(opts) { +    Report.call(this); +    opts = opts || {}; +    var baseDir = path.resolve(opts.dir || process.cwd()), +        htmlDir = path.resolve(baseDir, 'lcov-report'); + +    mkdirp.sync(baseDir); +    this.lcov = new LcovOnlyReport({ dir: baseDir, watermarks: opts.watermarks }); +    this.html = new HtmlReport({ dir: htmlDir, watermarks: opts.watermarks, sourceStore: opts.sourceStore}); +} + +LcovReport.TYPE = 'lcov'; +util.inherits(LcovReport, Report); + +Report.mix(LcovReport, { +    synopsis: function () { +        return 'combined lcovonly and html report that generates an lcov.info file as well as HTML'; +    }, +    writeReport: function (collector, sync) { +        var handler = this.handleDone.bind(this); +        this.inProgress = 2; +        this.lcov.on('done', handler); +        this.html.on('done', handler); +        this.lcov.writeReport(collector, sync); +        this.html.writeReport(collector, sync); +    }, +    handleDone: function () { +        this.inProgress -= 1; +        if (this.inProgress === 0) { +            this.emit('done'); +        } +    } +}); + +module.exports = LcovReport; diff --git a/node_modules/istanbul/lib/report/lcovonly.js b/node_modules/istanbul/lib/report/lcovonly.js new file mode 100644 index 000000000..2c1be46d8 --- /dev/null +++ b/node_modules/istanbul/lib/report/lcovonly.js @@ -0,0 +1,103 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    Writer = require('../util/file-writer'), +    util = require('util'), +    Report = require('./index'), +    utils = require('../object-utils'); +/** + * a `Report` implementation that produces an LCOV coverage file from coverage objects. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('lcovonly'); + * + * + * @class LcovOnlyReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the `lcov.info` file. Defaults to `process.cwd()` + */ +function LcovOnlyReport(opts) { +    this.opts = opts || {}; +    this.opts.dir = this.opts.dir || process.cwd(); +    this.opts.file = this.opts.file || this.getDefaultConfig().file; +    this.opts.writer = this.opts.writer || null; +} +LcovOnlyReport.TYPE = 'lcovonly'; +util.inherits(LcovOnlyReport, Report); + +Report.mix(LcovOnlyReport, { +    synopsis: function () { +        return 'lcov coverage report that can be consumed by the lcov tool'; +    }, +    getDefaultConfig: function () { +        return { file: 'lcov.info' }; +    }, +    writeFileCoverage: function (writer, fc) { +        var functions = fc.f, +            functionMap = fc.fnMap, +            lines = fc.l, +            branches = fc.b, +            branchMap = fc.branchMap, +            summary = utils.summarizeFileCoverage(fc); + +        writer.println('TN:'); //no test name +        writer.println('SF:' + fc.path); + +        Object.keys(functions).forEach(function (key) { +            var meta = functionMap[key]; +            writer.println('FN:' + [ meta.line, meta.name ].join(',')); +        }); +        writer.println('FNF:' + summary.functions.total); +        writer.println('FNH:' + summary.functions.covered); + +        Object.keys(functions).forEach(function (key) { +            var stats = functions[key], +                meta = functionMap[key]; +            writer.println('FNDA:' + [ stats, meta.name ].join(',')); +        }); + +        Object.keys(lines).forEach(function (key) { +            var stat = lines[key]; +            writer.println('DA:' + [ key, stat ].join(',')); +        }); +        writer.println('LF:' + summary.lines.total); +        writer.println('LH:' + summary.lines.covered); + +        Object.keys(branches).forEach(function (key) { +            var branchArray = branches[key], +                meta = branchMap[key], +                line = meta.line, +                i = 0; +            branchArray.forEach(function (b) { +                writer.println('BRDA:' + [line, key, i, b].join(',')); +                i += 1; +            }); +        }); +        writer.println('BRF:' + summary.branches.total); +        writer.println('BRH:' + summary.branches.covered); +        writer.println('end_of_record'); +    }, + +    writeReport: function (collector, sync) { +        var outputFile = path.resolve(this.opts.dir, this.opts.file), +            writer = this.opts.writer || new Writer(sync), +            that = this; +        writer.on('done', function () { that.emit('done'); }); +        writer.writeFile(outputFile, function (contentWriter) { +            collector.files().forEach(function (key) { +                that.writeFileCoverage(contentWriter, collector.fileCoverageFor(key)); +            }); +        }); +        writer.done(); +    } +}); + +module.exports = LcovOnlyReport; diff --git a/node_modules/istanbul/lib/report/none.js b/node_modules/istanbul/lib/report/none.js new file mode 100644 index 000000000..0fd5cfca6 --- /dev/null +++ b/node_modules/istanbul/lib/report/none.js @@ -0,0 +1,41 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var util = require('util'), +    Report = require('./index'); + +/** + * a `Report` implementation that does nothing. Use to specify that no reporting + * is needed. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('none'); + * + * + * @class NoneReport + * @extends Report + * @module report + * @constructor + */ +function NoneReport() { +    Report.call(this); +} + +NoneReport.TYPE = 'none'; +util.inherits(NoneReport, Report); + +Report.mix(NoneReport, { +    synopsis: function () { +        return 'Does nothing. Useful to override default behavior and suppress reporting entirely'; +    }, +    writeReport: function (/* collector, sync */) { +        //noop +        this.emit('done'); +    } +}); + +module.exports = NoneReport; diff --git a/node_modules/istanbul/lib/report/teamcity.js b/node_modules/istanbul/lib/report/teamcity.js new file mode 100644 index 000000000..f6b90fc95 --- /dev/null +++ b/node_modules/istanbul/lib/report/teamcity.js @@ -0,0 +1,92 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    util = require('util'), +    mkdirp = require('mkdirp'), +    fs = require('fs'), +    utils = require('../object-utils'), +    Report = require('./index'); + +/** + * a `Report` implementation that produces system messages interpretable by TeamCity. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('teamcity'); + * + * @class TeamcityReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file + * @param {String} [opts.file] the filename for the report. When omitted, the report is written to console + */ +function TeamcityReport(opts) { +    Report.call(this); +    opts = opts || {}; +    this.dir = opts.dir || process.cwd(); +    this.file = opts.file; +    this.blockName = opts.blockName || this.getDefaultConfig().blockName; +} + +TeamcityReport.TYPE = 'teamcity'; +util.inherits(TeamcityReport, Report); + +function lineForKey(value, teamcityVar) { +    return '##teamcity[buildStatisticValue key=\'' + teamcityVar + '\' value=\'' + value + '\']'; +} + +Report.mix(TeamcityReport, { +    synopsis: function () { +        return 'report with system messages that can be interpreted with TeamCity'; +    }, +    getDefaultConfig: function () { +        return { file: null , blockName: 'Code Coverage Summary'}; +    }, +    writeReport: function (collector /*, sync */) { +        var summaries = [], +            finalSummary, +            lines = [], +            text; + +        collector.files().forEach(function (file) { +            summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file))); +        }); + +        finalSummary = utils.mergeSummaryObjects.apply(null, summaries); + +        lines.push(''); +        lines.push('##teamcity[blockOpened name=\''+ this.blockName +'\']'); + +        //Statements Covered +        lines.push(lineForKey(finalSummary.statements.pct, 'CodeCoverageB')); + +        //Methods Covered +        lines.push(lineForKey(finalSummary.functions.covered, 'CodeCoverageAbsMCovered')); +        lines.push(lineForKey(finalSummary.functions.total, 'CodeCoverageAbsMTotal')); +        lines.push(lineForKey(finalSummary.functions.pct, 'CodeCoverageM')); + +        //Lines Covered +        lines.push(lineForKey(finalSummary.lines.covered, 'CodeCoverageAbsLCovered')); +        lines.push(lineForKey(finalSummary.lines.total, 'CodeCoverageAbsLTotal')); +        lines.push(lineForKey(finalSummary.lines.pct, 'CodeCoverageL')); + +        lines.push('##teamcity[blockClosed name=\''+ this.blockName +'\']'); + +        text = lines.join('\n'); +        if (this.file) { +            mkdirp.sync(this.dir); +            fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8'); +        } else { +            console.log(text); +        } +        this.emit('done'); +    } +}); + +module.exports = TeamcityReport; diff --git a/node_modules/istanbul/lib/report/templates/foot.txt b/node_modules/istanbul/lib/report/templates/foot.txt new file mode 100644 index 000000000..e853251dd --- /dev/null +++ b/node_modules/istanbul/lib/report/templates/foot.txt @@ -0,0 +1,20 @@ +<div class='push'></div><!-- for sticky footer --> +</div><!-- /wrapper --> +<div class='footer quiet pad2 space-top1 center small'> +  Code coverage +  generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at {{datetime}} +</div> +</div> +{{#if prettify}} +<script src="{{prettify.js}}"></script> +<script> +window.onload = function () { +        if (typeof prettyPrint === 'function') { +            prettyPrint(); +        } +}; +</script> +{{/if}} +<script src="{{sorter.js}}"></script> +</body> +</html> diff --git a/node_modules/istanbul/lib/report/templates/head.txt b/node_modules/istanbul/lib/report/templates/head.txt new file mode 100644 index 000000000..f98094e5f --- /dev/null +++ b/node_modules/istanbul/lib/report/templates/head.txt @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en"> +<head> +    <title>Code coverage report for {{entity}}</title> +    <meta charset="utf-8" /> +{{#if prettify}} +    <link rel="stylesheet" href="{{prettify.css}}" /> +{{/if}} +    <link rel="stylesheet" href="{{base.css}}" /> +    <meta name="viewport" content="width=device-width, initial-scale=1"> +    <style type='text/css'> +        .coverage-summary .sorter { +            background-image: url({{sorter.image}}); +        } +    </style> +</head> +<body> +<div class='wrapper'> +  <div class='pad1'> +    <h1> +      {{{pathHtml}}} +    </h1> +    <div class='clearfix'> +      {{#with metrics.statements}} +      <div class='fl pad1y space-right2'> +        <span class="strong">{{pct}}% </span> +        <span class="quiet">Statements</span> +        <span class='fraction'>{{covered}}/{{total}}</span> +      </div> +      {{/with}} +      {{#with metrics.branches}} +      <div class='fl pad1y space-right2'> +        <span class="strong">{{pct}}% </span> +        <span class="quiet">Branches</span> +        <span class='fraction'>{{covered}}/{{total}}</span> +      </div> +      {{/with}} +      {{#with metrics.functions}} +      <div class='fl pad1y space-right2'> +        <span class="strong">{{pct}}% </span> +        <span class="quiet">Functions</span> +        <span class='fraction'>{{covered}}/{{total}}</span> +      </div> +      {{/with}} +      {{#with metrics.lines}} +      <div class='fl pad1y space-right2'> +        <span class="strong">{{pct}}% </span> +        <span class="quiet">Lines</span> +        <span class='fraction'>{{covered}}/{{total}}</span> +      </div> +      {{/with}} +      {{#if_has_ignores metrics}} +      <div class='fl pad1y'> +        <span class="strong">{{#show_ignores metrics}}{{/show_ignores}}</span> +        <span class="quiet">Ignored</span>       +      </div> +      {{/if_has_ignores}} +    </div> +  </div> +  <div class='status-line {{reportClass}}'></div> diff --git a/node_modules/istanbul/lib/report/text-lcov.js b/node_modules/istanbul/lib/report/text-lcov.js new file mode 100644 index 000000000..15e1a48ca --- /dev/null +++ b/node_modules/istanbul/lib/report/text-lcov.js @@ -0,0 +1,50 @@ +var LcovOnly = require('./lcovonly'), +  util = require('util'); + +/** + * a `Report` implementation that produces an LCOV coverage and prints it + *  to standard out. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('text-lcov'); + * + * @class TextLcov + * @module report + * @extends LcovOnly + * @constructor + * @param {Object} opts optional + * @param {String} [opts.log] the method used to log to console. + */ +function TextLcov(opts) { +    var that = this; + +    LcovOnly.call(this); + +    this.opts = opts || {}; +    this.opts.log = this.opts.log || console.log; +    this.opts.writer = { +        println: function (ln) { +            that.opts.log(ln); +        } +    }; +} + +TextLcov.TYPE = 'text-lcov'; +util.inherits(TextLcov, LcovOnly); + +LcovOnly.super_.mix(TextLcov, { +    writeReport: function (collector) { +        var that = this, +            writer = this.opts.writer; + +        collector.files().forEach(function (key) { +            that.writeFileCoverage(writer, collector.fileCoverageFor(key)); +        }); + +        this.emit('done'); +    } +}); + +module.exports = TextLcov; diff --git a/node_modules/istanbul/lib/report/text-summary.js b/node_modules/istanbul/lib/report/text-summary.js new file mode 100644 index 000000000..9537cbe20 --- /dev/null +++ b/node_modules/istanbul/lib/report/text-summary.js @@ -0,0 +1,93 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    util = require('util'), +    mkdirp = require('mkdirp'), +    defaults = require('./common/defaults'), +    fs = require('fs'), +    utils = require('../object-utils'), +    Report = require('./index'); + +/** + * a `Report` implementation that produces text output for overall coverage in summary format. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('text-summary'); + * + * @class TextSummaryReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file + * @param {String} [opts.file] the filename for the report. When omitted, the report is written to console + */ +function TextSummaryReport(opts) { +    Report.call(this); +    opts = opts || {}; +    this.dir = opts.dir || process.cwd(); +    this.file = opts.file; +    this.watermarks = opts.watermarks || defaults.watermarks(); +} + +TextSummaryReport.TYPE = 'text-summary'; +util.inherits(TextSummaryReport, Report); + +function lineForKey(summary, key, watermarks) { +    var metrics = summary[key], +        skipped, +        result, +        clazz = defaults.classFor(key, summary, watermarks); +    key = key.substring(0, 1).toUpperCase() + key.substring(1); +    if (key.length < 12) { key += '                   '.substring(0, 12 - key.length); } +    result = [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' '); +    skipped = metrics.skipped; +    if (skipped > 0) { +        result += ', ' + skipped + ' ignored'; +    } +    return defaults.colorize(result, clazz); +} + +Report.mix(TextSummaryReport, { +    synopsis: function () { +        return 'text report that prints a coverage summary across all files, typically to console'; +    }, +    getDefaultConfig: function () { +        return { file: null }; +    }, +    writeReport: function (collector /*, sync */) { +        var summaries = [], +            finalSummary, +            lines = [], +            watermarks = this.watermarks, +            text; +        collector.files().forEach(function (file) { +            summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file))); +        }); +        finalSummary = utils.mergeSummaryObjects.apply(null, summaries); +        lines.push(''); +        lines.push('=============================== Coverage summary ==============================='); +        lines.push.apply(lines, [ +            lineForKey(finalSummary, 'statements', watermarks), +            lineForKey(finalSummary, 'branches', watermarks), +            lineForKey(finalSummary, 'functions', watermarks), +            lineForKey(finalSummary, 'lines', watermarks) +        ]); +        lines.push('================================================================================'); +        text = lines.join('\n'); +        if (this.file) { +            mkdirp.sync(this.dir); +            fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8'); +        } else { +            console.log(text); +        } +        this.emit('done'); +    } +}); + +module.exports = TextSummaryReport; diff --git a/node_modules/istanbul/lib/report/text.js b/node_modules/istanbul/lib/report/text.js new file mode 100644 index 000000000..8ab2b7d13 --- /dev/null +++ b/node_modules/istanbul/lib/report/text.js @@ -0,0 +1,234 @@ +/* + Copyright (c) 2012, Yahoo! Inc.  All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), +    mkdirp = require('mkdirp'), +    util = require('util'), +    fs = require('fs'), +    defaults = require('./common/defaults'), +    Report = require('./index'), +    TreeSummarizer = require('../util/tree-summarizer'), +    utils = require('../object-utils'), +    PCT_COLS = 9, +    MISSING_COL = 15, +    TAB_SIZE = 1, +    DELIM = ' |', +    COL_DELIM = '-|'; + +/** + * a `Report` implementation that produces text output in a detailed table. + * + * Usage + * ----- + * + *      var report = require('istanbul').Report.create('text'); + * + * @class TextReport + * @extends Report + * @module report + * @constructor + * @param {Object} opts optional + * @param {String} [opts.dir] the directory in which to the text coverage report will be written, when writing to a file + * @param {String} [opts.file] the filename for the report. When omitted, the report is written to console + * @param {Number} [opts.maxCols] the max column width of the report. By default, the width of the report is adjusted based on the length of the paths + *              to be reported. + */ +function TextReport(opts) { +    Report.call(this); +    opts = opts || {}; +    this.dir = opts.dir || process.cwd(); +    this.file = opts.file; +    this.summary = opts.summary; +    this.maxCols = opts.maxCols || 0; +    this.watermarks = opts.watermarks || defaults.watermarks(); +} + +TextReport.TYPE = 'text'; +util.inherits(TextReport, Report); + +function padding(num, ch) { +    var str = '', +        i; +    ch = ch || ' '; +    for (i = 0; i < num; i += 1) { +        str += ch; +    } +    return str; +} + +function fill(str, width, right, tabs, clazz) { +    tabs = tabs || 0; +    str = String(str); + +    var leadingSpaces = tabs * TAB_SIZE, +        remaining = width - leadingSpaces, +        leader = padding(leadingSpaces), +        fmtStr = '', +        fillStr, +        strlen = str.length; + +    if (remaining > 0) { +        if (remaining >= strlen) { +            fillStr = padding(remaining - strlen); +            fmtStr = right ? fillStr + str : str + fillStr; +        } else { +            fmtStr = str.substring(strlen - remaining); +            fmtStr = '... ' + fmtStr.substring(4); +        } +    } + +    fmtStr = defaults.colorize(fmtStr, clazz); +    return leader + fmtStr; +} + +function formatName(name, maxCols, level, clazz) { +    return fill(name, maxCols, false, level, clazz); +} + +function formatPct(pct, clazz, width) { +    return fill(pct, width || PCT_COLS, true, 0, clazz); +} + +function nodeName(node) { +    return node.displayShortName() || 'All files'; +} + +function tableHeader(maxNameCols) { +    var elements = []; +    elements.push(formatName('File', maxNameCols, 0)); +    elements.push(formatPct('% Stmts')); +    elements.push(formatPct('% Branch')); +    elements.push(formatPct('% Funcs')); +    elements.push(formatPct('% Lines')); +    elements.push(formatPct('Uncovered Lines', undefined, MISSING_COL)); +    return elements.join(' |') + ' |'; +} + +function collectMissingLines(kind, linesCovered) { +  var missingLines = []; + +  if (kind !== 'file') { +      return []; +  } + +  Object.keys(linesCovered).forEach(function (key) { +      if (!linesCovered[key]) { +          missingLines.push(key); +      } +  }); + +  return missingLines; +} + +function tableRow(node, maxNameCols, level, watermarks) { +    var name = nodeName(node), +        statements = node.metrics.statements.pct, +        branches = node.metrics.branches.pct, +        functions = node.metrics.functions.pct, +        lines = node.metrics.lines.pct, +        missingLines = collectMissingLines(node.kind, node.metrics.linesCovered), +        elements = []; + +    elements.push(formatName(name, maxNameCols, level, defaults.classFor('statements', node.metrics, watermarks))); +    elements.push(formatPct(statements, defaults.classFor('statements', node.metrics, watermarks))); +    elements.push(formatPct(branches, defaults.classFor('branches', node.metrics, watermarks))); +    elements.push(formatPct(functions, defaults.classFor('functions', node.metrics, watermarks))); +    elements.push(formatPct(lines, defaults.classFor('lines', node.metrics, watermarks))); +    elements.push(formatPct(missingLines.join(','), 'low', MISSING_COL)); + +    return elements.join(DELIM) + DELIM; +} + +function findNameWidth(node, level, last) { +    last = last || 0; +    level = level || 0; +    var idealWidth = TAB_SIZE * level + nodeName(node).length; +    if (idealWidth > last) { +        last = idealWidth; +    } +    node.children.forEach(function (child) { +        last = findNameWidth(child, level + 1, last); +    }); +    return last; +} + +function makeLine(nameWidth) { +    var name = padding(nameWidth, '-'), +        pct = padding(PCT_COLS, '-'), +        elements = []; + +    elements.push(name); +    elements.push(pct); +    elements.push(pct); +    elements.push(pct); +    elements.push(pct); +    elements.push(padding(MISSING_COL, '-')); +    return elements.join(COL_DELIM) + COL_DELIM; +} + +function walk(node, nameWidth, array, level, watermarks) { +    var line; +    if (level === 0) { +        line = makeLine(nameWidth); +        array.push(line); +        array.push(tableHeader(nameWidth)); +        array.push(line); +    } else { +        array.push(tableRow(node, nameWidth, level, watermarks)); +    } +    node.children.forEach(function (child) { +        walk(child, nameWidth, array, level + 1, watermarks); +    }); +    if (level === 0) { +        array.push(line); +        array.push(tableRow(node, nameWidth, level, watermarks)); +        array.push(line); +    } +} + +Report.mix(TextReport, { +    synopsis: function () { +        return 'text report that prints a coverage line for every file, typically to console'; +    }, +    getDefaultConfig: function () { +        return { file: null, maxCols: 0 }; +    }, +    writeReport: function (collector /*, sync */) { +        var summarizer = new TreeSummarizer(), +            tree, +            root, +            nameWidth, +            statsWidth = 4 * (PCT_COLS + 2) + MISSING_COL, +            maxRemaining, +            strings = [], +            text; + +        collector.files().forEach(function (key) { +            summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage( +                collector.fileCoverageFor(key) +            )); +        }); +        tree = summarizer.getTreeSummary(); +        root = tree.root; +        nameWidth = findNameWidth(root); +        if (this.maxCols > 0) { +            maxRemaining = this.maxCols - statsWidth - 2; +            if (nameWidth > maxRemaining) { +                nameWidth = maxRemaining; +            } +        } +        walk(root, nameWidth, strings, 0, this.watermarks); +        text = strings.join('\n') + '\n'; +        if (this.file) { +            mkdirp.sync(this.dir); +            fs.writeFileSync(path.join(this.dir, this.file), text, 'utf8'); +        } else { +            console.log(text); +        } +        this.emit('done'); +    } +}); + +module.exports = TextReport;  | 
