istanbul-lib-report
Advanced tools
Comparing version 2.0.8 to 3.0.0-alpha.0
@@ -6,2 +6,26 @@ # Change Log | ||
# [3.0.0-alpha.0](https://github.com/istanbuljs/istanbuljs/compare/istanbul-lib-report@2.0.8...istanbul-lib-report@3.0.0-alpha.0) (2019-06-19) | ||
### Bug Fixes | ||
* **package:** update supports-color to version 7.0.0 ([#420](https://github.com/istanbuljs/istanbuljs/issues/420)) ([631029d](https://github.com/istanbuljs/istanbuljs/commit/631029d)) | ||
* Properly combine directories in nested summarizer ([#380](https://github.com/istanbuljs/istanbuljs/issues/380)) ([50afdbb](https://github.com/istanbuljs/istanbuljs/commit/50afdbb)) | ||
### Features | ||
* Refactor istanbul-lib-report so report can choose summarizer ([#408](https://github.com/istanbuljs/istanbuljs/issues/408)) ([0f328fd](https://github.com/istanbuljs/istanbuljs/commit/0f328fd)) | ||
* Update dependencies, require Node.js 8 ([#401](https://github.com/istanbuljs/istanbuljs/issues/401)) ([bf3a539](https://github.com/istanbuljs/istanbuljs/commit/bf3a539)) | ||
### BREAKING CHANGES | ||
* Existing istanbul-lib-report API's have been changed | ||
* Node.js 8 is now required | ||
## [2.0.8](https://github.com/istanbuljs/istanbuljs/compare/istanbul-lib-report@2.0.7...istanbul-lib-report@2.0.8) (2019-04-24) | ||
@@ -8,0 +32,0 @@ |
32
index.js
@@ -11,5 +11,5 @@ /* | ||
const summarizer = require('./lib/summarizer'); | ||
const context = require('./lib/context'); | ||
const Context = require('./lib/context'); | ||
const watermarks = require('./lib/watermarks'); | ||
const ReportBase = require('./lib/report-base'); | ||
@@ -23,4 +23,5 @@ module.exports = { | ||
createContext(opts) { | ||
return context.create(opts); | ||
return new Context(opts); | ||
}, | ||
/** | ||
@@ -35,25 +36,8 @@ * returns the default watermarks that would be used when not | ||
return watermarks.getDefault(); | ||
} | ||
}; | ||
/** | ||
* standard summary functions | ||
*/ | ||
module.exports.summarizers = { | ||
}, | ||
/** | ||
* a summarizer that creates a flat tree with one root node and bunch of | ||
* files directly under it | ||
* Base class for all reports | ||
*/ | ||
flat: summarizer.createFlatSummary, | ||
/** | ||
* a summarizer that creates a hierarchical tree where the coverage summaries | ||
* of each directly reflect the summaries of all subdirectories and files in it | ||
*/ | ||
nested: summarizer.createNestedSummary, | ||
/** | ||
* a summarizer that creates a tree in which directories are not nested. | ||
* Every subdirectory is a child of the root node and only reflects the | ||
* coverage numbers for the files in it (i.e. excludes subdirectories). | ||
* This is the default summarizer. | ||
*/ | ||
pkg: summarizer.createPackageSummary | ||
ReportBase | ||
}; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/* | ||
@@ -10,2 +11,3 @@ Copyright 2012-2015, Yahoo Inc. | ||
const watermarks = require('./watermarks'); | ||
const SummarizerFactory = require('./summarizer-factory'); | ||
@@ -16,20 +18,17 @@ function defaultSourceLookup(path) { | ||
} catch (ex) { | ||
throw new Error( | ||
'Unable to lookup source: ' + path + '(' + ex.message + ')' | ||
); | ||
throw new Error(`Unable to lookup source: ${path} (${ex.message})`); | ||
} | ||
} | ||
function mergeWatermarks(specified, defaults) { | ||
specified = specified || {}; | ||
Object.keys(defaults).forEach(k => { | ||
function normalizeWatermarks(specified = {}) { | ||
Object.entries(watermarks.getDefault()).forEach(([k, value]) => { | ||
const specValue = specified[k]; | ||
if ( | ||
!(specValue && Array.isArray(specValue) && specValue.length === 2) | ||
) { | ||
specified[k] = defaults[k]; | ||
if (!Array.isArray(specValue) || specValue.length !== 2) { | ||
specified[k] = value; | ||
} | ||
}); | ||
return specified; | ||
} | ||
/** | ||
@@ -46,8 +45,79 @@ * A reporting context that is passed to report implementations | ||
*/ | ||
function Context(opts) { | ||
opts = opts || {}; | ||
this.dir = opts.dir || 'coverage'; | ||
this.watermarks = mergeWatermarks(opts.watermarks, watermarks.getDefault()); | ||
this.sourceFinder = opts.sourceFinder || defaultSourceLookup; | ||
this.data = {}; | ||
class Context { | ||
constructor(opts) { | ||
this.dir = opts.dir || 'coverage'; | ||
this.watermarks = normalizeWatermarks(opts.watermarks); | ||
this.sourceFinder = opts.sourceFinder || defaultSourceLookup; | ||
this._summarizerFactory = new SummarizerFactory( | ||
opts.coverageMap, | ||
opts.defaultSummarizer | ||
); | ||
this.data = {}; | ||
} | ||
/** | ||
* returns a FileWriter implementation for reporting use. Also available | ||
* as the `writer` property on the context. | ||
* @returns {Writer} | ||
*/ | ||
getWriter() { | ||
return this.writer; | ||
} | ||
/** | ||
* returns the source code for the specified file path or throws if | ||
* the source could not be found. | ||
* @param {String} filePath the file path as found in a file coverage object | ||
* @returns {String} the source code | ||
*/ | ||
getSource(filePath) { | ||
return this.sourceFinder(filePath); | ||
} | ||
/** | ||
* returns the coverage class given a coverage | ||
* types and a percentage value. | ||
* @param {String} type - the coverage type, one of `statements`, `functions`, | ||
* `branches`, or `lines` | ||
* @param {Number} value - the percentage value | ||
* @returns {String} one of `high`, `medium` or `low` | ||
*/ | ||
classForPercent(type, value) { | ||
const watermarks = this.watermarks[type]; | ||
if (!watermarks) { | ||
return 'unknown'; | ||
} | ||
if (value < watermarks[0]) { | ||
return 'low'; | ||
} | ||
if (value >= watermarks[1]) { | ||
return 'high'; | ||
} | ||
return 'medium'; | ||
} | ||
/** | ||
* returns an XML writer for the supplied content writer | ||
* @param {ContentWriter} contentWriter the content writer to which the returned XML writer | ||
* writes data | ||
* @returns {XMLWriter} | ||
*/ | ||
getXMLWriter(contentWriter) { | ||
return new XMLWriter(contentWriter); | ||
} | ||
/** | ||
* returns a full visitor given a partial one. | ||
* @param {Object} partialVisitor a partial visitor only having the functions of | ||
* interest to the caller. These functions are called with a scope that is the | ||
* supplied object. | ||
* @returns {Visitor} | ||
*/ | ||
getVisitor(partialVisitor) { | ||
return new tree.Visitor(partialVisitor); | ||
} | ||
getTree(name = 'defaultSummarizer') { | ||
return this._summarizerFactory[name]; | ||
} | ||
} | ||
@@ -65,66 +135,2 @@ | ||
/** | ||
* returns a FileWriter implementation for reporting use. Also available | ||
* as the `writer` property on the context. | ||
* @returns {Writer} | ||
*/ | ||
Context.prototype.getWriter = function() { | ||
return this.writer; | ||
}; | ||
/** | ||
* returns the source code for the specified file path or throws if | ||
* the source could not be found. | ||
* @param {String} filePath the file path as found in a file coverage object | ||
* @returns {String} the source code | ||
*/ | ||
Context.prototype.getSource = function(filePath) { | ||
return this.sourceFinder(filePath); | ||
}; | ||
/** | ||
* returns the coverage class given a coverage | ||
* types and a percentage value. | ||
* @param {String} type - the coverage type, one of `statements`, `functions`, | ||
* `branches`, or `lines` | ||
* @param {Number} value - the percentage value | ||
* @returns {String} one of `high`, `medium` or `low` | ||
*/ | ||
Context.prototype.classForPercent = function(type, value) { | ||
const watermarks = this.watermarks[type]; | ||
if (!watermarks) { | ||
return 'unknown'; | ||
} | ||
if (value < watermarks[0]) { | ||
return 'low'; | ||
} | ||
if (value >= watermarks[1]) { | ||
return 'high'; | ||
} | ||
return 'medium'; | ||
}; | ||
/** | ||
* returns an XML writer for the supplied content writer | ||
* @param {ContentWriter} contentWriter the content writer to which the returned XML writer | ||
* writes data | ||
* @returns {XMLWriter} | ||
*/ | ||
Context.prototype.getXMLWriter = function(contentWriter) { | ||
return new XMLWriter(contentWriter); | ||
}; | ||
/** | ||
* returns a full visitor given a partial one. | ||
* @param {Object} partialVisitor a partial visitor only having the functions of | ||
* interest to the caller. These functions are called with a scope that is the | ||
* supplied object. | ||
* @returns {Visitor} | ||
*/ | ||
Context.prototype.getVisitor = function(partialVisitor) { | ||
return new tree.Visitor(partialVisitor); | ||
}; | ||
module.exports = { | ||
create(opts) { | ||
return new Context(opts); | ||
} | ||
}; | ||
module.exports = Context; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/* | ||
@@ -5,3 +6,2 @@ Copyright 2012-2015, Yahoo Inc. | ||
*/ | ||
const util = require('util'); | ||
const path = require('path'); | ||
@@ -11,52 +11,37 @@ const fs = require('fs'); | ||
const supportsColor = require('supports-color'); | ||
const isAbsolute = | ||
path.isAbsolute || | ||
/* istanbul ignore next */ function(p) { | ||
return path.resolve(p) === path.normalize(p); | ||
}; | ||
/** | ||
* abstract interface for writing content | ||
* Base class for writing content | ||
* @class ContentWriter | ||
* @constructor | ||
*/ | ||
/* istanbul ignore next: abstract class */ | ||
function ContentWriter() {} | ||
class ContentWriter { | ||
/** | ||
* returns the colorized version of a string. Typically, | ||
* content writers that write to files will return the | ||
* same string and ones writing to a tty will wrap it in | ||
* appropriate escape sequences. | ||
* @param {String} str the string to colorize | ||
* @param {String} clazz one of `high`, `medium` or `low` | ||
* @returns {String} the colorized form of the string | ||
*/ | ||
colorize(str /*, clazz*/) { | ||
return str; | ||
} | ||
/** | ||
* writes a string as-is to the destination | ||
* @param {String} str the string to write | ||
*/ | ||
/* istanbul ignore next: abstract class */ | ||
ContentWriter.prototype.write = function() { | ||
throw new Error('write: must be overridden'); | ||
}; | ||
/** | ||
* writes a string appended with a newline to the destination | ||
* @param {String} str the string to write | ||
*/ | ||
println(str) { | ||
this.write(`${str}\n`); | ||
} | ||
/** | ||
* returns the colorized version of a string. Typically, | ||
* content writers that write to files will return the | ||
* same string and ones writing to a tty will wrap it in | ||
* appropriate escape sequences. | ||
* @param {String} str the string to colorize | ||
* @param {String} clazz one of `high`, `medium` or `low` | ||
* @returns {String} the colorized form of the string | ||
*/ | ||
ContentWriter.prototype.colorize = function(str /*, clazz*/) { | ||
return str; | ||
}; | ||
/** | ||
* closes this content writer. Should be called after all writes are complete. | ||
*/ | ||
close() {} | ||
} | ||
/** | ||
* writes a string appended with a newline to the destination | ||
* @param {String} str the string to write | ||
*/ | ||
ContentWriter.prototype.println = function(str) { | ||
this.write(str + '\n'); | ||
}; | ||
/** | ||
* closes this content writer. Should be called after all writes are complete. | ||
*/ | ||
ContentWriter.prototype.close = function() {}; | ||
/** | ||
* a content writer that writes to a file | ||
@@ -67,15 +52,22 @@ * @param {Number} fd - the file descriptor | ||
*/ | ||
function FileContentWriter(fd) { | ||
this.fd = fd; | ||
class FileContentWriter extends ContentWriter { | ||
constructor(fd) { | ||
super(); | ||
this.fd = fd; | ||
} | ||
write(str) { | ||
fs.writeSync(this.fd, str); | ||
} | ||
close() { | ||
fs.closeSync(this.fd); | ||
} | ||
} | ||
util.inherits(FileContentWriter, ContentWriter); | ||
FileContentWriter.prototype.write = function(str) { | ||
fs.writeSync(this.fd, str); | ||
}; | ||
// allow stdout to be captured for tests. | ||
let capture = false; | ||
let output = ''; | ||
FileContentWriter.prototype.close = function() { | ||
fs.closeSync(this.fd); | ||
}; | ||
/** | ||
@@ -86,29 +78,25 @@ * a content writer that writes to the console | ||
*/ | ||
function ConsoleWriter() {} | ||
util.inherits(ConsoleWriter, ContentWriter); | ||
// allow stdout to be captured for tests. | ||
let capture = false; | ||
let output = ''; | ||
ConsoleWriter.prototype.write = function(str) { | ||
if (capture) { | ||
output += str; | ||
} else { | ||
process.stdout.write(str); | ||
class ConsoleWriter extends ContentWriter { | ||
write(str) { | ||
if (capture) { | ||
output += str; | ||
} else { | ||
process.stdout.write(str); | ||
} | ||
} | ||
}; | ||
ConsoleWriter.prototype.colorize = function(str, clazz) { | ||
const colors = { | ||
low: '31;1', | ||
medium: '33;1', | ||
high: '32;1' | ||
}; | ||
colorize(str, clazz) { | ||
const colors = { | ||
low: '31;1', | ||
medium: '33;1', | ||
high: '32;1' | ||
}; | ||
/* istanbul ignore next: different modes for CI and local */ | ||
if (supportsColor.stdout && colors[clazz]) { | ||
return '\u001b[' + colors[clazz] + 'm' + str + '\u001b[0m'; | ||
/* istanbul ignore next: different modes for CI and local */ | ||
if (supportsColor.stdout && colors[clazz]) { | ||
return `\u001b[${colors[clazz]}m${str}\u001b[0m`; | ||
} | ||
return str; | ||
} | ||
return str; | ||
}; | ||
} | ||
@@ -121,79 +109,86 @@ /** | ||
*/ | ||
function FileWriter(baseDir) { | ||
if (!baseDir) { | ||
throw new Error('baseDir must be specified'); | ||
class FileWriter { | ||
constructor(baseDir) { | ||
if (!baseDir) { | ||
throw new Error('baseDir must be specified'); | ||
} | ||
this.baseDir = baseDir; | ||
} | ||
this.baseDir = baseDir; | ||
} | ||
/** | ||
* static helpers for capturing stdout report output; | ||
* super useful for tests! | ||
*/ | ||
FileWriter.startCapture = function() { | ||
capture = true; | ||
}; | ||
FileWriter.stopCapture = function() { | ||
capture = false; | ||
}; | ||
FileWriter.getOutput = function() { | ||
return output; | ||
}; | ||
FileWriter.resetOutput = function() { | ||
output = ''; | ||
}; | ||
/** | ||
* static helpers for capturing stdout report output; | ||
* super useful for tests! | ||
*/ | ||
static startCapture() { | ||
capture = true; | ||
} | ||
/** | ||
* returns a FileWriter that is rooted at the supplied subdirectory | ||
* @param {String} subdir the subdirectory under which to root the | ||
* returned FileWriter | ||
* @returns {FileWriter} | ||
*/ | ||
FileWriter.prototype.writerForDir = function(subdir) { | ||
if (isAbsolute(subdir)) { | ||
throw new Error( | ||
'Cannot create subdir writer for absolute path: ' + subdir | ||
); | ||
static stopCapture() { | ||
capture = false; | ||
} | ||
return new FileWriter(this.baseDir + '/' + subdir); | ||
}; | ||
/** | ||
* copies a file from a source directory to a destination name | ||
* @param {String} source path to source file | ||
* @param {String} dest relative path to destination file | ||
* @param {String} [header=undefined] optional text to prepend to destination | ||
* (e.g., an "this file is autogenerated" comment, copyright notice, etc.) | ||
*/ | ||
FileWriter.prototype.copyFile = function(source, dest, header) { | ||
if (isAbsolute(dest)) { | ||
throw new Error('Cannot write to absolute path: ' + dest); | ||
static getOutput() { | ||
return output; | ||
} | ||
dest = path.resolve(this.baseDir, dest); | ||
mkdirp.sync(path.dirname(dest)); | ||
let contents; | ||
if (header) { | ||
contents = header + fs.readFileSync(source, 'utf8'); | ||
} else { | ||
contents = fs.readFileSync(source); | ||
static resetOutput() { | ||
output = ''; | ||
} | ||
fs.writeFileSync(dest, contents); | ||
}; | ||
/** | ||
* returns a content writer for writing content to the supplied file. | ||
* @param {String|null} file the relative path to the file or the special | ||
* values `"-"` or `null` for writing to the console | ||
* @returns {ContentWriter} | ||
*/ | ||
FileWriter.prototype.writeFile = function(file) { | ||
if (file === null || file === '-') { | ||
return new ConsoleWriter(); | ||
/** | ||
* returns a FileWriter that is rooted at the supplied subdirectory | ||
* @param {String} subdir the subdirectory under which to root the | ||
* returned FileWriter | ||
* @returns {FileWriter} | ||
*/ | ||
writerForDir(subdir) { | ||
if (path.isAbsolute(subdir)) { | ||
throw new Error( | ||
`Cannot create subdir writer for absolute path: ${subdir}` | ||
); | ||
} | ||
return new FileWriter(`${this.baseDir}/${subdir}`); | ||
} | ||
if (isAbsolute(file)) { | ||
throw new Error('Cannot write to absolute path: ' + file); | ||
/** | ||
* copies a file from a source directory to a destination name | ||
* @param {String} source path to source file | ||
* @param {String} dest relative path to destination file | ||
* @param {String} [header=undefined] optional text to prepend to destination | ||
* (e.g., an "this file is autogenerated" comment, copyright notice, etc.) | ||
*/ | ||
copyFile(source, dest, header) { | ||
if (path.isAbsolute(dest)) { | ||
throw new Error(`Cannot write to absolute path: ${dest}`); | ||
} | ||
dest = path.resolve(this.baseDir, dest); | ||
mkdirp.sync(path.dirname(dest)); | ||
let contents; | ||
if (header) { | ||
contents = header + fs.readFileSync(source, 'utf8'); | ||
} else { | ||
contents = fs.readFileSync(source); | ||
} | ||
fs.writeFileSync(dest, contents); | ||
} | ||
file = path.resolve(this.baseDir, file); | ||
mkdirp.sync(path.dirname(file)); | ||
return new FileContentWriter(fs.openSync(file, 'w')); | ||
}; | ||
/** | ||
* returns a content writer for writing content to the supplied file. | ||
* @param {String|null} file the relative path to the file or the special | ||
* values `"-"` or `null` for writing to the console | ||
* @returns {ContentWriter} | ||
*/ | ||
writeFile(file) { | ||
if (file === null || file === '-') { | ||
return new ConsoleWriter(); | ||
} | ||
if (path.isAbsolute(file)) { | ||
throw new Error(`Cannot write to absolute path: ${file}`); | ||
} | ||
file = path.resolve(this.baseDir, file); | ||
mkdirp.sync(path.dirname(file)); | ||
return new FileContentWriter(fs.openSync(file, 'w')); | ||
} | ||
} | ||
module.exports = FileWriter; |
152
lib/path.js
@@ -9,3 +9,3 @@ /* | ||
let parsePath = path.parse; | ||
let SEP = path.sep || /* istanbul ignore next */ '/'; | ||
let SEP = path.sep; | ||
const origParser = parsePath; | ||
@@ -42,3 +42,3 @@ const origSep = SEP; | ||
if (dir !== '') { | ||
dir = dir + '/' + file; | ||
dir = `${dir}/${file}`; | ||
} else { | ||
@@ -54,92 +54,102 @@ dir = file; | ||
function Path(strOrArray) { | ||
if (Array.isArray(strOrArray)) { | ||
this.v = strOrArray; | ||
} else if (typeof strOrArray === 'string') { | ||
this.v = makeRelativeNormalizedPath(strOrArray, SEP); | ||
} else { | ||
throw new Error( | ||
'Invalid Path argument must be string or array:' + strOrArray | ||
); | ||
class Path { | ||
constructor(strOrArray) { | ||
if (Array.isArray(strOrArray)) { | ||
this.v = strOrArray; | ||
} else if (typeof strOrArray === 'string') { | ||
this.v = makeRelativeNormalizedPath(strOrArray, SEP); | ||
} else { | ||
throw new Error( | ||
`Invalid Path argument must be string or array:${strOrArray}` | ||
); | ||
} | ||
} | ||
} | ||
Path.prototype.toString = function() { | ||
return this.v.join('/'); | ||
}; | ||
toString() { | ||
return this.v.join('/'); | ||
} | ||
Path.prototype.hasParent = function() { | ||
return this.v.length > 0; | ||
}; | ||
hasParent() { | ||
return this.v.length > 0; | ||
} | ||
Path.prototype.parent = function() { | ||
if (!this.hasParent()) { | ||
throw new Error('Unable to get parent for 0 elem path'); | ||
parent() { | ||
if (!this.hasParent()) { | ||
throw new Error('Unable to get parent for 0 elem path'); | ||
} | ||
const p = this.v.slice(); | ||
p.pop(); | ||
return new Path(p); | ||
} | ||
const p = this.v.slice(); | ||
p.pop(); | ||
return new Path(p); | ||
}; | ||
Path.prototype.elements = function() { | ||
return this.v.slice(); | ||
}; | ||
elements() { | ||
return this.v.slice(); | ||
} | ||
Path.prototype.contains = function(other) { | ||
let i; | ||
if (other.length > this.length) { | ||
return false; | ||
name() { | ||
return this.v.slice(-1)[0]; | ||
} | ||
for (i = 0; i < other.length; i += 1) { | ||
if (this.v[i] !== other.v[i]) { | ||
contains(other) { | ||
let i; | ||
if (other.length > this.length) { | ||
return false; | ||
} | ||
for (i = 0; i < other.length; i += 1) { | ||
if (this.v[i] !== other.v[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
return true; | ||
}; | ||
Path.prototype.ancestorOf = function(other) { | ||
return other.contains(this) && other.length !== this.length; | ||
}; | ||
ancestorOf(other) { | ||
return other.contains(this) && other.length !== this.length; | ||
} | ||
Path.prototype.descendantOf = function(other) { | ||
return this.contains(other) && other.length !== this.length; | ||
}; | ||
descendantOf(other) { | ||
return this.contains(other) && other.length !== this.length; | ||
} | ||
Path.prototype.commonPrefixPath = function(other) { | ||
const len = this.length > other.length ? other.length : this.length; | ||
let i; | ||
const ret = []; | ||
commonPrefixPath(other) { | ||
const len = this.length > other.length ? other.length : this.length; | ||
let i; | ||
const ret = []; | ||
for (i = 0; i < len; i += 1) { | ||
if (this.v[i] === other.v[i]) { | ||
ret.push(this.v[i]); | ||
} else { | ||
break; | ||
for (i = 0; i < len; i += 1) { | ||
if (this.v[i] === other.v[i]) { | ||
ret.push(this.v[i]); | ||
} else { | ||
break; | ||
} | ||
} | ||
return new Path(ret); | ||
} | ||
return new Path(ret); | ||
}; | ||
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(f => { | ||
Path.prototype[f] = function(...args) { | ||
const v = this.v; | ||
return v[f](...args); | ||
}; | ||
}); | ||
static compare(a, b) { | ||
const al = a.length; | ||
const bl = b.length; | ||
Path.compare = function(a, b) { | ||
const al = a.length; | ||
const bl = b.length; | ||
if (al < bl) { | ||
return -1; | ||
if (al < bl) { | ||
return -1; | ||
} | ||
if (al > bl) { | ||
return 1; | ||
} | ||
const astr = a.toString(); | ||
const bstr = b.toString(); | ||
return astr < bstr ? -1 : astr > bstr ? 1 : 0; | ||
} | ||
if (al > bl) { | ||
return 1; | ||
} | ||
const astr = a.toString(); | ||
const bstr = b.toString(); | ||
return astr < bstr ? -1 : astr > bstr ? 1 : 0; | ||
}; | ||
} | ||
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(fn => { | ||
Object.defineProperty(Path.prototype, fn, { | ||
value(...args) { | ||
return this.v[fn](...args); | ||
} | ||
}); | ||
}); | ||
Object.defineProperty(Path.prototype, 'length', { | ||
@@ -146,0 +156,0 @@ enumerable: true, |
198
lib/tree.js
@@ -7,3 +7,2 @@ /* | ||
const util = require('util'); | ||
/** | ||
@@ -27,102 +26,78 @@ * An object with methods that are called during the traversal of the coverage tree. | ||
*/ | ||
function Visitor(delegate) { | ||
this.delegate = delegate; | ||
class Visitor { | ||
constructor(delegate) { | ||
this.delegate = delegate; | ||
} | ||
} | ||
['Start', 'End', 'Summary', 'SummaryEnd', 'Detail'].forEach(k => { | ||
const f = 'on' + k; | ||
Visitor.prototype[f] = function(node, state) { | ||
if (this.delegate[f] && typeof this.delegate[f] === 'function') { | ||
this.delegate[f].call(this.delegate, node, state); | ||
['Start', 'End', 'Summary', 'SummaryEnd', 'Detail'] | ||
.map(k => `on${k}`) | ||
.forEach(fn => { | ||
Object.defineProperty(Visitor.prototype, fn, { | ||
writable: true, | ||
value(node, state) { | ||
if (typeof this.delegate[fn] === 'function') { | ||
this.delegate[fn](node, state); | ||
} | ||
} | ||
}); | ||
}); | ||
class CompositeVisitor extends Visitor { | ||
constructor(visitors) { | ||
super(); | ||
if (!Array.isArray(visitors)) { | ||
visitors = [visitors]; | ||
} | ||
}; | ||
}); | ||
function CompositeVisitor(visitors) { | ||
if (!Array.isArray(visitors)) { | ||
visitors = [visitors]; | ||
this.visitors = visitors.map(v => { | ||
if (v instanceof Visitor) { | ||
return v; | ||
} | ||
return new Visitor(v); | ||
}); | ||
} | ||
this.visitors = visitors.map(v => { | ||
if (v instanceof Visitor) { | ||
return v; | ||
} | ||
return new Visitor(v); | ||
}); | ||
} | ||
util.inherits(CompositeVisitor, Visitor); | ||
['Start', 'Summary', 'SummaryEnd', 'Detail', 'End'].forEach(k => { | ||
const f = 'on' + k; | ||
CompositeVisitor.prototype[f] = function(node, state) { | ||
this.visitors.forEach(v => { | ||
v[f](node, state); | ||
['Start', 'Summary', 'SummaryEnd', 'Detail', 'End'] | ||
.map(k => `on${k}`) | ||
.forEach(fn => { | ||
Object.defineProperty(CompositeVisitor.prototype, fn, { | ||
value(node, state) { | ||
this.visitors.forEach(v => { | ||
v[fn](node, state); | ||
}); | ||
} | ||
}); | ||
}; | ||
}); | ||
}); | ||
function Node() {} | ||
class BaseNode { | ||
isRoot() { | ||
return !this.getParent(); | ||
} | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getQualifiedName = function() { | ||
throw new Error('getQualifiedName must be overridden'); | ||
}; | ||
/** | ||
* visit all nodes depth-first from this node down. Note that `onStart` | ||
* and `onEnd` are never called on the visitor even if the current | ||
* node is the root of the tree. | ||
* @param visitor a full visitor that is called during tree traversal | ||
* @param state optional state that is passed around | ||
*/ | ||
visit(visitor, state) { | ||
if (this.isSummary()) { | ||
visitor.onSummary(this, state); | ||
} else { | ||
visitor.onDetail(this, state); | ||
} | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getRelativeName = function() { | ||
throw new Error('getRelativeName must be overridden'); | ||
}; | ||
this.getChildren().forEach(child => { | ||
child.visit(visitor, state); | ||
}); | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.isRoot = function() { | ||
return !this.getParent(); | ||
}; | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getParent = function() { | ||
throw new Error('getParent must be overridden'); | ||
}; | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getChildren = function() { | ||
throw new Error('getChildren must be overridden'); | ||
}; | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.isSummary = function() { | ||
throw new Error('isSummary must be overridden'); | ||
}; | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getCoverageSummary = function(/* filesOnly */) { | ||
throw new Error('getCoverageSummary must be overridden'); | ||
}; | ||
/* istanbul ignore next: abstract method */ | ||
Node.prototype.getFileCoverage = function() { | ||
throw new Error('getFileCoverage must be overridden'); | ||
}; | ||
/** | ||
* visit all nodes depth-first from this node down. Note that `onStart` | ||
* and `onEnd` are never called on the visitor even if the current | ||
* node is the root of the tree. | ||
* @param visitor a full visitor that is called during tree traversal | ||
* @param state optional state that is passed around | ||
*/ | ||
Node.prototype.visit = function(visitor, state) { | ||
if (this.isSummary()) { | ||
visitor.onSummary(this, state); | ||
} else { | ||
visitor.onDetail(this, state); | ||
if (this.isSummary()) { | ||
visitor.onSummaryEnd(this, state); | ||
} | ||
} | ||
} | ||
this.getChildren().forEach(child => { | ||
child.visit(visitor, state); | ||
}); | ||
if (this.isSummary()) { | ||
visitor.onSummaryEnd(this, state); | ||
} | ||
}; | ||
/** | ||
@@ -132,31 +107,34 @@ * abstract base class for a coverage tree. | ||
*/ | ||
function Tree() {} | ||
class BaseTree { | ||
constructor(root) { | ||
this.root = root; | ||
} | ||
/** | ||
* returns the root node of the tree | ||
*/ | ||
/* istanbul ignore next: abstract method */ | ||
Tree.prototype.getRoot = function() { | ||
throw new Error('getRoot must be overridden'); | ||
}; | ||
/** | ||
* returns the root node of the tree | ||
*/ | ||
getRoot() { | ||
return this.root; | ||
} | ||
/** | ||
* visits the tree depth-first with the supplied partial visitor | ||
* @param visitor - a potentially partial visitor | ||
* @param state - the state to be passed around during tree traversal | ||
*/ | ||
Tree.prototype.visit = function(visitor, state) { | ||
if (!(visitor instanceof Visitor)) { | ||
visitor = new Visitor(visitor); | ||
/** | ||
* visits the tree depth-first with the supplied partial visitor | ||
* @param visitor - a potentially partial visitor | ||
* @param state - the state to be passed around during tree traversal | ||
*/ | ||
visit(visitor, state) { | ||
if (!(visitor instanceof Visitor)) { | ||
visitor = new Visitor(visitor); | ||
} | ||
visitor.onStart(this.getRoot(), state); | ||
this.getRoot().visit(visitor, state); | ||
visitor.onEnd(this.getRoot(), state); | ||
} | ||
visitor.onStart(this.getRoot(), state); | ||
this.getRoot().visit(visitor, state); | ||
visitor.onEnd(this.getRoot(), state); | ||
}; | ||
} | ||
module.exports = { | ||
Tree, | ||
Node, | ||
BaseTree, | ||
BaseNode, | ||
Visitor, | ||
CompositeVisitor | ||
}; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/* | ||
@@ -2,0 +3,0 @@ Copyright 2012-2015, Yahoo Inc. |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/* | ||
@@ -7,2 +8,8 @@ Copyright 2012-2015, Yahoo Inc. | ||
function attrString(attrs) { | ||
return Object.entries(attrs || {}) | ||
.map(([k, v]) => ` ${k}="${v}"`) | ||
.join(''); | ||
} | ||
/** | ||
@@ -13,85 +20,73 @@ * a utility class to produce well-formed, indented XML | ||
*/ | ||
function XMLWriter(contentWriter) { | ||
this.cw = contentWriter; | ||
this.stack = []; | ||
} | ||
class XMLWriter { | ||
constructor(contentWriter) { | ||
this.cw = contentWriter; | ||
this.stack = []; | ||
} | ||
function attrString(attrs) { | ||
if (!attrs) { | ||
return ''; | ||
indent(str) { | ||
return this.stack.map(() => INDENT).join('') + str; | ||
} | ||
const ret = []; | ||
Object.keys(attrs).forEach(k => { | ||
const v = attrs[k]; | ||
ret.push(k + '="' + v + '"'); | ||
}); | ||
return ret.length === 0 ? '' : ' ' + ret.join(' '); | ||
} | ||
XMLWriter.prototype.indent = function(str) { | ||
return this.stack.map(() => INDENT).join('') + str; | ||
}; | ||
/** | ||
* writes the opening XML tag with the supplied attributes | ||
* @param {String} name tag name | ||
* @param {Object} [attrs=null] attrs attributes for the tag | ||
*/ | ||
openTag(name, attrs) { | ||
const str = this.indent(`<${name + attrString(attrs)}>`); | ||
this.cw.println(str); | ||
this.stack.push(name); | ||
} | ||
/** | ||
* writes the opening XML tag with the supplied attributes | ||
* @param {String} name tag name | ||
* @param {Object} [attrs=null] attrs attributes for the tag | ||
*/ | ||
XMLWriter.prototype.openTag = function(name, attrs) { | ||
const str = this.indent('<' + name + attrString(attrs) + '>'); | ||
this.cw.println(str); | ||
this.stack.push(name); | ||
}; | ||
/** | ||
* closes an open XML tag. | ||
* @param {String} name - tag name to close. This must match the writer's | ||
* notion of the tag that is currently open. | ||
*/ | ||
closeTag(name) { | ||
if (this.stack.length === 0) { | ||
throw new Error(`Attempt to close tag ${name} when not opened`); | ||
} | ||
const stashed = this.stack.pop(); | ||
const str = `</${name}>`; | ||
/** | ||
* closes an open XML tag. | ||
* @param {String} name - tag name to close. This must match the writer's | ||
* notion of the tag that is currently open. | ||
*/ | ||
XMLWriter.prototype.closeTag = function(name) { | ||
if (this.stack.length === 0) { | ||
throw new Error('Attempt to close tag ' + name + ' when not opened'); | ||
if (stashed !== name) { | ||
throw new Error( | ||
`Attempt to close tag ${name} when ${stashed} was the one open` | ||
); | ||
} | ||
this.cw.println(this.indent(str)); | ||
} | ||
const stashed = this.stack.pop(); | ||
const str = '</' + name + '>'; | ||
if (stashed !== name) { | ||
throw new Error( | ||
'Attempt to close tag ' + | ||
name + | ||
' when ' + | ||
stashed + | ||
' was the one open' | ||
); | ||
/** | ||
* writes a tag and its value opening and closing it at the same time | ||
* @param {String} name tag name | ||
* @param {Object} [attrs=null] attrs tag attributes | ||
* @param {String} [content=null] content optional tag content | ||
*/ | ||
inlineTag(name, attrs, content) { | ||
let str = '<' + name + attrString(attrs); | ||
if (content) { | ||
str += `>${content}</${name}>`; | ||
} else { | ||
str += '/>'; | ||
} | ||
str = this.indent(str); | ||
this.cw.println(str); | ||
} | ||
this.cw.println(this.indent(str)); | ||
}; | ||
/** | ||
* writes a tag and its value opening and closing it at the same time | ||
* @param {String} name tag name | ||
* @param {Object} [attrs=null] attrs tag attributes | ||
* @param {String} [content=null] content optional tag content | ||
*/ | ||
XMLWriter.prototype.inlineTag = function(name, attrs, content) { | ||
let str = '<' + name + attrString(attrs); | ||
if (content) { | ||
str += '>' + content + '</' + name + '>'; | ||
} else { | ||
str += '/>'; | ||
/** | ||
* closes all open tags and ends the document | ||
*/ | ||
closeAll() { | ||
this.stack | ||
.slice() | ||
.reverse() | ||
.forEach(name => { | ||
this.closeTag(name); | ||
}); | ||
} | ||
str = this.indent(str); | ||
this.cw.println(str); | ||
}; | ||
/** | ||
* closes all open tags and ends the document | ||
*/ | ||
XMLWriter.prototype.closeAll = function() { | ||
this.stack | ||
.slice() | ||
.reverse() | ||
.forEach(name => { | ||
this.closeTag(name); | ||
}); | ||
}; | ||
} | ||
module.exports = XMLWriter; |
{ | ||
"name": "istanbul-lib-report", | ||
"version": "2.0.8", | ||
"version": "3.0.0-alpha.0", | ||
"description": "Base reporting library for istanbul", | ||
@@ -12,9 +12,15 @@ "author": "Krishnan Anantheswaran <kananthmail-github@yahoo.com>", | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "nyc --nycrc-path=../../monorepo-per-package-nycrc.json mocha" | ||
}, | ||
"dependencies": { | ||
"istanbul-lib-coverage": "^2.0.5", | ||
"make-dir": "^2.1.0", | ||
"supports-color": "^6.1.0" | ||
"istanbul-lib-coverage": "^3.0.0-alpha.0", | ||
"make-dir": "^3.0.0", | ||
"supports-color": "^7.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.1.4", | ||
"nyc": "^14.1.1", | ||
"rimraf": "^2.6.3" | ||
}, | ||
"license": "BSD-3-Clause", | ||
@@ -36,5 +42,5 @@ "bugs": { | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=8" | ||
}, | ||
"gitHead": "90e60cc47833bb780680f916488ca24f0be36ca2" | ||
"gitHead": "2e885073a9398806c9b8763dd39418398182ca34" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35810
13
4
940
1
+ Addedhas-flag@4.0.0(transitive)
+ Addedistanbul-lib-coverage@3.2.2(transitive)
+ Addedmake-dir@3.1.0(transitive)
+ Addedsemver@6.3.1(transitive)
+ Addedsupports-color@7.2.0(transitive)
- Removedhas-flag@3.0.0(transitive)
- Removedistanbul-lib-coverage@2.0.5(transitive)
- Removedmake-dir@2.1.0(transitive)
- Removedpify@4.0.1(transitive)
- Removedsemver@5.7.2(transitive)
- Removedsupports-color@6.1.0(transitive)
Updatedmake-dir@^3.0.0
Updatedsupports-color@^7.0.0