*/
+ return true
+ }
+ state.pos = start;
+ return false
+ }
+ var ch = state.current();
+ if (ch < 0 || ch === state.lookahead() && isClassSetReservedDoublePunctuatorCharacter(ch)) { return false }
+ if (isClassSetSyntaxCharacter(ch)) { return false }
+ state.advance();
+ state.lastIntValue = ch;
+ return true
+ };
+
+ // https://tc39.es/ecma262/#prod-ClassSetReservedDoublePunctuator
+ function isClassSetReservedDoublePunctuatorCharacter(ch) {
+ return (
+ ch === 0x21 /* ! */ ||
+ ch >= 0x23 /* # */ && ch <= 0x26 /* & */ ||
+ ch >= 0x2A /* * */ && ch <= 0x2C /* , */ ||
+ ch === 0x2E /* . */ ||
+ ch >= 0x3A /* : */ && ch <= 0x40 /* @ */ ||
+ ch === 0x5E /* ^ */ ||
+ ch === 0x60 /* ` */ ||
+ ch === 0x7E /* ~ */
+ )
+ }
+
+ // https://tc39.es/ecma262/#prod-ClassSetSyntaxCharacter
+ function isClassSetSyntaxCharacter(ch) {
+ return (
+ ch === 0x28 /* ( */ ||
+ ch === 0x29 /* ) */ ||
+ ch === 0x2D /* - */ ||
+ ch === 0x2F /* / */ ||
+ ch >= 0x5B /* [ */ && ch <= 0x5D /* ] */ ||
+ ch >= 0x7B /* { */ && ch <= 0x7D /* } */
+ )
+ }
+
+ // https://tc39.es/ecma262/#prod-ClassSetReservedPunctuator
+ pp$1.regexp_eatClassSetReservedPunctuator = function(state) {
+ var ch = state.current();
+ if (isClassSetReservedPunctuator(ch)) {
+ state.lastIntValue = ch;
+ state.advance();
+ return true
+ }
+ return false
+ };
+
+ // https://tc39.es/ecma262/#prod-ClassSetReservedPunctuator
+ function isClassSetReservedPunctuator(ch) {
+ return (
+ ch === 0x21 /* ! */ ||
+ ch === 0x23 /* # */ ||
+ ch === 0x25 /* % */ ||
+ ch === 0x26 /* & */ ||
+ ch === 0x2C /* , */ ||
+ ch === 0x2D /* - */ ||
+ ch >= 0x3A /* : */ && ch <= 0x3E /* > */ ||
+ ch === 0x40 /* @ */ ||
+ ch === 0x60 /* ` */ ||
+ ch === 0x7E /* ~ */
+ )
+ }
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ClassControlLetter
+ pp$1.regexp_eatClassControlLetter = function(state) {
+ var ch = state.current();
+ if (isDecimalDigit(ch) || ch === 0x5F /* _ */) {
+ state.lastIntValue = ch % 0x20;
+ state.advance();
+ return true
+ }
+ return false
+ };
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-HexEscapeSequence
+ pp$1.regexp_eatHexEscapeSequence = function(state) {
+ var start = state.pos;
+ if (state.eat(0x78 /* x */)) {
+ if (this.regexp_eatFixedHexDigits(state, 2)) {
+ return true
+ }
+ if (state.switchU) {
+ state.raise("Invalid escape");
+ }
+ state.pos = start;
+ }
+ return false
+ };
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-DecimalDigits
+ pp$1.regexp_eatDecimalDigits = function(state) {
+ var start = state.pos;
+ var ch = 0;
+ state.lastIntValue = 0;
+ while (isDecimalDigit(ch = state.current())) {
+ state.lastIntValue = 10 * state.lastIntValue + (ch - 0x30 /* 0 */);
+ state.advance();
+ }
+ return state.pos !== start
+ };
+ function isDecimalDigit(ch) {
+ return ch >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */
+ }
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-HexDigits
+ pp$1.regexp_eatHexDigits = function(state) {
+ var start = state.pos;
+ var ch = 0;
+ state.lastIntValue = 0;
+ while (isHexDigit(ch = state.current())) {
+ state.lastIntValue = 16 * state.lastIntValue + hexToInt(ch);
+ state.advance();
+ }
+ return state.pos !== start
+ };
+ function isHexDigit(ch) {
+ return (
+ (ch >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */) ||
+ (ch >= 0x41 /* A */ && ch <= 0x46 /* F */) ||
+ (ch >= 0x61 /* a */ && ch <= 0x66 /* f */)
+ )
+ }
+ function hexToInt(ch) {
+ if (ch >= 0x41 /* A */ && ch <= 0x46 /* F */) {
+ return 10 + (ch - 0x41 /* A */)
+ }
+ if (ch >= 0x61 /* a */ && ch <= 0x66 /* f */) {
+ return 10 + (ch - 0x61 /* a */)
+ }
+ return ch - 0x30 /* 0 */
+ }
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-LegacyOctalEscapeSequence
+ // Allows only 0-377(octal) i.e. 0-255(decimal).
+ pp$1.regexp_eatLegacyOctalEscapeSequence = function(state) {
+ if (this.regexp_eatOctalDigit(state)) {
+ var n1 = state.lastIntValue;
+ if (this.regexp_eatOctalDigit(state)) {
+ var n2 = state.lastIntValue;
+ if (n1 <= 3 && this.regexp_eatOctalDigit(state)) {
+ state.lastIntValue = n1 * 64 + n2 * 8 + state.lastIntValue;
+ } else {
+ state.lastIntValue = n1 * 8 + n2;
+ }
+ } else {
+ state.lastIntValue = n1;
+ }
+ return true
+ }
+ return false
+ };
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-OctalDigit
+ pp$1.regexp_eatOctalDigit = function(state) {
+ var ch = state.current();
+ if (isOctalDigit(ch)) {
+ state.lastIntValue = ch - 0x30; /* 0 */
+ state.advance();
+ return true
+ }
+ state.lastIntValue = 0;
+ return false
+ };
+ function isOctalDigit(ch) {
+ return ch >= 0x30 /* 0 */ && ch <= 0x37 /* 7 */
+ }
+
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-Hex4Digits
+ // https://www.ecma-international.org/ecma-262/8.0/#prod-HexDigit
+ // And HexDigit HexDigit in https://www.ecma-international.org/ecma-262/8.0/#prod-HexEscapeSequence
+ pp$1.regexp_eatFixedHexDigits = function(state, length) {
+ var start = state.pos;
+ state.lastIntValue = 0;
+ for (var i = 0; i < length; ++i) {
+ var ch = state.current();
+ if (!isHexDigit(ch)) {
+ state.pos = start;
+ return false
+ }
+ state.lastIntValue = 16 * state.lastIntValue + hexToInt(ch);
+ state.advance();
+ }
+ return true
+ };
+
+ // Object type used to represent tokens. Note that normally, tokens
+ // simply exist as properties on the parser object. This is only
+ // used for the onToken callback and the external tokenizer.
+
+ var Token = function Token(p) {
+ this.type = p.type;
+ this.value = p.value;
+ this.start = p.start;
+ this.end = p.end;
+ if (p.options.locations)
+ { this.loc = new SourceLocation(p, p.startLoc, p.endLoc); }
+ if (p.options.ranges)
+ { this.range = [p.start, p.end]; }
+ };
+
+ // ## Tokenizer
+
+ var pp = Parser.prototype;
+
+ // Move to the next token
+
+ pp.next = function(ignoreEscapeSequenceInKeyword) {
+ if (!ignoreEscapeSequenceInKeyword && this.type.keyword && this.containsEsc)
+ { this.raiseRecoverable(this.start, "Escape sequence in keyword " + this.type.keyword); }
+ if (this.options.onToken)
+ { this.options.onToken(new Token(this)); }
+
+ this.lastTokEnd = this.end;
+ this.lastTokStart = this.start;
+ this.lastTokEndLoc = this.endLoc;
+ this.lastTokStartLoc = this.startLoc;
+ this.nextToken();
+ };
+
+ pp.getToken = function() {
+ this.next();
+ return new Token(this)
+ };
+
+ // If we're in an ES6 environment, make parsers iterable
+ if (typeof Symbol !== "undefined")
+ { pp[Symbol.iterator] = function() {
+ var this$1$1 = this;
+
+ return {
+ next: function () {
+ var token = this$1$1.getToken();
+ return {
+ done: token.type === types$1.eof,
+ value: token
+ }
+ }
+ }
+ }; }
+
+ // Toggle strict mode. Re-reads the next number or string to please
+ // pedantic tests (`"use strict"; 010;` should fail).
+
+ // Read a single token, updating the parser object's token-related
+ // properties.
+
+ pp.nextToken = function() {
+ var curContext = this.curContext();
+ if (!curContext || !curContext.preserveSpace) { this.skipSpace(); }
+
+ this.start = this.pos;
+ if (this.options.locations) { this.startLoc = this.curPosition(); }
+ if (this.pos >= this.input.length) { return this.finishToken(types$1.eof) }
+
+ if (curContext.override) { return curContext.override(this) }
+ else { this.readToken(this.fullCharCodeAtPos()); }
+ };
+
+ pp.readToken = function(code) {
+ // Identifier or keyword. '\uXXXX' sequences are allowed in
+ // identifiers, so '\' also dispatches to that.
+ if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */)
+ { return this.readWord() }
+
+ return this.getTokenFromCode(code)
+ };
+
+ pp.fullCharCodeAt = function(pos) {
+ var code = this.input.charCodeAt(pos);
+ if (code <= 0xd7ff || code >= 0xdc00) { return code }
+ var next = this.input.charCodeAt(pos + 1);
+ return next <= 0xdbff || next >= 0xe000 ? code : (code << 10) + next - 0x35fdc00
+ };
+
+ pp.fullCharCodeAtPos = function() {
+ return this.fullCharCodeAt(this.pos)
+ };
+
+ pp.skipBlockComment = function() {
+ var startLoc = this.options.onComment && this.curPosition();
+ var start = this.pos, end = this.input.indexOf("*/", this.pos += 2);
+ if (end === -1) { this.raise(this.pos - 2, "Unterminated comment"); }
+ this.pos = end + 2;
+ if (this.options.locations) {
+ for (var nextBreak = (void 0), pos = start; (nextBreak = nextLineBreak(this.input, pos, this.pos)) > -1;) {
+ ++this.curLine;
+ pos = this.lineStart = nextBreak;
+ }
+ }
+ if (this.options.onComment)
+ { this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos,
+ startLoc, this.curPosition()); }
+ };
+
+ pp.skipLineComment = function(startSkip) {
+ var start = this.pos;
+ var startLoc = this.options.onComment && this.curPosition();
+ var ch = this.input.charCodeAt(this.pos += startSkip);
+ while (this.pos < this.input.length && !isNewLine(ch)) {
+ ch = this.input.charCodeAt(++this.pos);
+ }
+ if (this.options.onComment)
+ { this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos,
+ startLoc, this.curPosition()); }
+ };
+
+ // Called at the start of the parse and after every token. Skips
+ // whitespace and comments, and.
+
+ pp.skipSpace = function() {
+ loop: while (this.pos < this.input.length) {
+ var ch = this.input.charCodeAt(this.pos);
+ switch (ch) {
+ case 32: case 160: // ' '
+ ++this.pos;
+ break
+ case 13:
+ if (this.input.charCodeAt(this.pos + 1) === 10) {
+ ++this.pos;
+ }
+ case 10: case 8232: case 8233:
+ ++this.pos;
+ if (this.options.locations) {
+ ++this.curLine;
+ this.lineStart = this.pos;
+ }
+ break
+ case 47: // '/'
+ switch (this.input.charCodeAt(this.pos + 1)) {
+ case 42: // '*'
+ this.skipBlockComment();
+ break
+ case 47:
+ this.skipLineComment(2);
+ break
+ default:
+ break loop
+ }
+ break
+ default:
+ if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+ ++this.pos;
+ } else {
+ break loop
+ }
+ }
+ }
+ };
+
+ // Called at the end of every token. Sets `end`, `val`, and
+ // maintains `context` and `exprAllowed`, and skips the space after
+ // the token, so that the next one's `start` will point at the
+ // right position.
+
+ pp.finishToken = function(type, val) {
+ this.end = this.pos;
+ if (this.options.locations) { this.endLoc = this.curPosition(); }
+ var prevType = this.type;
+ this.type = type;
+ this.value = val;
+
+ this.updateContext(prevType);
+ };
+
+ // ### Token reading
+
+ // This is the function that is called to fetch the next token. It
+ // is somewhat obscure, because it works in character codes rather
+ // than characters, and because operator parsing has been inlined
+ // into it.
+ //
+ // All in the name of speed.
+ //
+ pp.readToken_dot = function() {
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next >= 48 && next <= 57) { return this.readNumber(true) }
+ var next2 = this.input.charCodeAt(this.pos + 2);
+ if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.'
+ this.pos += 3;
+ return this.finishToken(types$1.ellipsis)
+ } else {
+ ++this.pos;
+ return this.finishToken(types$1.dot)
+ }
+ };
+
+ pp.readToken_slash = function() { // '/'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (this.exprAllowed) { ++this.pos; return this.readRegexp() }
+ if (next === 61) { return this.finishOp(types$1.assign, 2) }
+ return this.finishOp(types$1.slash, 1)
+ };
+
+ pp.readToken_mult_modulo_exp = function(code) { // '%*'
+ var next = this.input.charCodeAt(this.pos + 1);
+ var size = 1;
+ var tokentype = code === 42 ? types$1.star : types$1.modulo;
+
+ // exponentiation operator ** and **=
+ if (this.options.ecmaVersion >= 7 && code === 42 && next === 42) {
+ ++size;
+ tokentype = types$1.starstar;
+ next = this.input.charCodeAt(this.pos + 2);
+ }
+
+ if (next === 61) { return this.finishOp(types$1.assign, size + 1) }
+ return this.finishOp(tokentype, size)
+ };
+
+ pp.readToken_pipe_amp = function(code) { // '|&'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === code) {
+ if (this.options.ecmaVersion >= 12) {
+ var next2 = this.input.charCodeAt(this.pos + 2);
+ if (next2 === 61) { return this.finishOp(types$1.assign, 3) }
+ }
+ return this.finishOp(code === 124 ? types$1.logicalOR : types$1.logicalAND, 2)
+ }
+ if (next === 61) { return this.finishOp(types$1.assign, 2) }
+ return this.finishOp(code === 124 ? types$1.bitwiseOR : types$1.bitwiseAND, 1)
+ };
+
+ pp.readToken_caret = function() { // '^'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === 61) { return this.finishOp(types$1.assign, 2) }
+ return this.finishOp(types$1.bitwiseXOR, 1)
+ };
+
+ pp.readToken_plus_min = function(code) { // '+-'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === code) {
+ if (next === 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 62 &&
+ (this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) {
+ // A `-->` line comment
+ this.skipLineComment(3);
+ this.skipSpace();
+ return this.nextToken()
+ }
+ return this.finishOp(types$1.incDec, 2)
+ }
+ if (next === 61) { return this.finishOp(types$1.assign, 2) }
+ return this.finishOp(types$1.plusMin, 1)
+ };
+
+ pp.readToken_lt_gt = function(code) { // '<>'
+ var next = this.input.charCodeAt(this.pos + 1);
+ var size = 1;
+ if (next === code) {
+ size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2;
+ if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types$1.assign, size + 1) }
+ return this.finishOp(types$1.bitShift, size)
+ }
+ if (next === 33 && code === 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 45 &&
+ this.input.charCodeAt(this.pos + 3) === 45) {
+ // `` line comment
+ this.skipLineComment(3);
+ this.skipSpace();
+ return this.nextToken()
+ }
+ return this.finishOp(types$1.incDec, 2)
+ }
+ if (next === 61) { return this.finishOp(types$1.assign, 2) }
+ return this.finishOp(types$1.plusMin, 1)
+};
+
+pp.readToken_lt_gt = function(code) { // '<>'
+ var next = this.input.charCodeAt(this.pos + 1);
+ var size = 1;
+ if (next === code) {
+ size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2;
+ if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types$1.assign, size + 1) }
+ return this.finishOp(types$1.bitShift, size)
+ }
+ if (next === 33 && code === 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 45 &&
+ this.input.charCodeAt(this.pos + 3) === 45) {
+ // `)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]);
diff --git a/node_modules/istanbul-reports/lib/html/index.js b/node_modules/istanbul-reports/lib/html/index.js
new file mode 100644
index 0000000..2d7f7be
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/html/index.js
@@ -0,0 +1,421 @@
+'use strict';
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+const fs = require('fs');
+const path = require('path');
+const html = require('html-escaper');
+const { ReportBase } = require('istanbul-lib-report');
+const annotator = require('./annotator');
+
+function htmlHead(details) {
+ return `
+
+ Code coverage report for ${html.escape(details.entity)}
+
+
+
+
+
+
+
+ `;
+}
+
+function headerTemplate(details) {
+ function metricsTemplate({ pct, covered, total }, kind) {
+ return `
+
+ ${pct}%
+ ${kind}
+ ${covered}/${total}
+
+ `;
+ }
+
+ function skipTemplate(metrics) {
+ const statements = metrics.statements.skipped;
+ const branches = metrics.branches.skipped;
+ const functions = metrics.functions.skipped;
+
+ const countLabel = (c, label, plural) =>
+ c === 0 ? [] : `${c} ${label}${c === 1 ? '' : plural}`;
+ const skips = [].concat(
+ countLabel(statements, 'statement', 's'),
+ countLabel(functions, 'function', 's'),
+ countLabel(branches, 'branch', 'es')
+ );
+
+ if (skips.length === 0) {
+ return '';
+ }
+
+ return `
+
+ ${skips.join(', ')}
+ Ignored
+
+ `;
+ }
+
+ return `
+
+
+${htmlHead(details)}
+
+
+
+
${details.pathHtml}
+
+ ${metricsTemplate(details.metrics.statements, 'Statements')}
+ ${metricsTemplate(details.metrics.branches, 'Branches')}
+ ${metricsTemplate(details.metrics.functions, 'Functions')}
+ ${metricsTemplate(details.metrics.lines, 'Lines')}
+ ${skipTemplate(details.metrics)}
+
+
+ Press n or j to go to the next uncovered block, b, p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+ `;
+}
+
+function footerTemplate(details) {
+ return `
+
+
+
+
+
+
+
+
+
+ `;
+}
+
+function detailTemplate(data) {
+ const lineNumbers = new Array(data.maxLines).fill().map((_, i) => i + 1);
+ const lineLink = num =>
+ `${num}`;
+ const lineCount = line =>
+ `${line.hits}`;
+
+ /* This is rendered in a ``, need control of all whitespace. */
+ return [
+ '',
+ `| ${lineNumbers
+ .map(lineLink)
+ .join('\n')} | `,
+ `${data.lineCoverage
+ .map(lineCount)
+ .join('\n')} | `,
+ `${data.annotatedCode.join(
+ '\n'
+ )} | `,
+ '
'
+ ].join('');
+}
+const summaryTableHeader = [
+ '',
+ '
',
+ '',
+ '',
+ ' | File | ',
+ ' | ',
+ ' Statements | ',
+ ' | ',
+ ' Branches | ',
+ ' | ',
+ ' Functions | ',
+ ' | ',
+ ' Lines | ',
+ ' | ',
+ '
',
+ '',
+ ''
+].join('\n');
+
+function summaryLineTemplate(details) {
+ const { reportClasses, metrics, file, output } = details;
+ const percentGraph = pct => {
+ if (!isFinite(pct)) {
+ return '';
+ }
+
+ const cls = ['cover-fill'];
+ if (pct === 100) {
+ cls.push('cover-full');
+ }
+
+ pct = Math.floor(pct);
+ return [
+ ``,
+ ``
+ ].join('');
+ };
+ const summaryType = (type, showGraph = false) => {
+ const info = metrics[type];
+ const reportClass = reportClasses[type];
+ const result = [
+ `${info.pct}% | `,
+ `${info.covered}/${info.total} | `
+ ];
+ if (showGraph) {
+ result.unshift(
+ ``,
+ ` ${percentGraph(info.pct)} `,
+ ` | `
+ );
+ }
+
+ return result;
+ };
+
+ return []
+ .concat(
+ '',
+ `| ${html.escape(file)} | `,
+ summaryType('statements', true),
+ summaryType('branches'),
+ summaryType('functions'),
+ summaryType('lines'),
+ '
\n'
+ )
+ .join('\n\t');
+}
+
+const summaryTableFooter = ['', '
', '
'].join('\n');
+const emptyClasses = {
+ statements: 'empty',
+ lines: 'empty',
+ functions: 'empty',
+ branches: 'empty'
+};
+
+const standardLinkMapper = {
+ getPath(node) {
+ if (typeof node === 'string') {
+ return node;
+ }
+ let filePath = node.getQualifiedName();
+ if (node.isSummary()) {
+ if (filePath !== '') {
+ filePath += '/index.html';
+ } else {
+ filePath = 'index.html';
+ }
+ } else {
+ filePath += '.html';
+ }
+ return filePath;
+ },
+
+ relativePath(source, target) {
+ const targetPath = this.getPath(target);
+ const sourcePath = path.dirname(this.getPath(source));
+ return path.posix.relative(sourcePath, targetPath);
+ },
+
+ assetPath(node, name) {
+ return this.relativePath(this.getPath(node), name);
+ }
+};
+
+function fixPct(metrics) {
+ Object.keys(emptyClasses).forEach(key => {
+ metrics[key].pct = 0;
+ });
+ return metrics;
+}
+
+class HtmlReport extends ReportBase {
+ constructor(opts) {
+ super();
+
+ this.verbose = opts.verbose;
+ this.linkMapper = opts.linkMapper || standardLinkMapper;
+ this.subdir = opts.subdir || '';
+ this.date = new Date().toISOString();
+ this.skipEmpty = opts.skipEmpty;
+ }
+
+ getBreadcrumbHtml(node) {
+ let parent = node.getParent();
+ const nodePath = [];
+
+ while (parent) {
+ nodePath.push(parent);
+ parent = parent.getParent();
+ }
+
+ const linkPath = nodePath.map(ancestor => {
+ const target = this.linkMapper.relativePath(node, ancestor);
+ const name = ancestor.getRelativeName() || 'All files';
+ return '' + name + '';
+ });
+
+ linkPath.reverse();
+ return linkPath.length > 0
+ ? linkPath.join(' / ') + ' ' + node.getRelativeName()
+ : 'All files';
+ }
+
+ fillTemplate(node, templateData, context) {
+ const linkMapper = this.linkMapper;
+ const summary = node.getCoverageSummary();
+ templateData.entity = node.getQualifiedName() || 'All files';
+ templateData.metrics = summary;
+ templateData.reportClass = context.classForPercent(
+ 'statements',
+ summary.statements.pct
+ );
+ templateData.pathHtml = this.getBreadcrumbHtml(node);
+ templateData.base = {
+ css: linkMapper.assetPath(node, 'base.css')
+ };
+ templateData.sorter = {
+ js: linkMapper.assetPath(node, 'sorter.js'),
+ image: linkMapper.assetPath(node, 'sort-arrow-sprite.png')
+ };
+ templateData.blockNavigation = {
+ js: linkMapper.assetPath(node, 'block-navigation.js')
+ };
+ templateData.prettify = {
+ js: linkMapper.assetPath(node, 'prettify.js'),
+ css: linkMapper.assetPath(node, 'prettify.css')
+ };
+ templateData.favicon = linkMapper.assetPath(node, 'favicon.png');
+ }
+
+ getTemplateData() {
+ return { datetime: this.date };
+ }
+
+ getWriter(context) {
+ if (!this.subdir) {
+ return context.writer;
+ }
+ return context.writer.writerForDir(this.subdir);
+ }
+
+ onStart(root, context) {
+ const assetHeaders = {
+ '.js': '/* eslint-disable */\n'
+ };
+
+ ['.', 'vendor'].forEach(subdir => {
+ const writer = this.getWriter(context);
+ const srcDir = path.resolve(__dirname, 'assets', subdir);
+ fs.readdirSync(srcDir).forEach(f => {
+ const resolvedSource = path.resolve(srcDir, f);
+ const resolvedDestination = '.';
+ const stat = fs.statSync(resolvedSource);
+ let dest;
+
+ if (stat.isFile()) {
+ dest = resolvedDestination + '/' + f;
+ if (this.verbose) {
+ console.log('Write asset: ' + dest);
+ }
+ writer.copyFile(
+ resolvedSource,
+ dest,
+ assetHeaders[path.extname(f)]
+ );
+ }
+ });
+ });
+ }
+
+ onSummary(node, context) {
+ const linkMapper = this.linkMapper;
+ const templateData = this.getTemplateData();
+ const children = node.getChildren();
+ const skipEmpty = this.skipEmpty;
+
+ this.fillTemplate(node, templateData, context);
+ const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
+ cw.write(headerTemplate(templateData));
+ cw.write(summaryTableHeader);
+ children.forEach(child => {
+ const metrics = child.getCoverageSummary();
+ const isEmpty = metrics.isEmpty();
+ if (skipEmpty && isEmpty) {
+ return;
+ }
+ const reportClasses = isEmpty
+ ? emptyClasses
+ : {
+ statements: context.classForPercent(
+ 'statements',
+ metrics.statements.pct
+ ),
+ lines: context.classForPercent(
+ 'lines',
+ metrics.lines.pct
+ ),
+ functions: context.classForPercent(
+ 'functions',
+ metrics.functions.pct
+ ),
+ branches: context.classForPercent(
+ 'branches',
+ metrics.branches.pct
+ )
+ };
+ const data = {
+ metrics: isEmpty ? fixPct(metrics) : metrics,
+ reportClasses,
+ file: child.getRelativeName(),
+ output: linkMapper.relativePath(node, child)
+ };
+ cw.write(summaryLineTemplate(data) + '\n');
+ });
+ cw.write(summaryTableFooter);
+ cw.write(footerTemplate(templateData));
+ cw.close();
+ }
+
+ onDetail(node, context) {
+ const linkMapper = this.linkMapper;
+ const templateData = this.getTemplateData();
+
+ this.fillTemplate(node, templateData, context);
+ const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
+ cw.write(headerTemplate(templateData));
+ cw.write('
\n');
+ cw.write(detailTemplate(annotator(node.getFileCoverage(), context)));
+ cw.write('
\n');
+ cw.write(footerTemplate(templateData));
+ cw.close();
+ }
+}
+
+module.exports = HtmlReport;
diff --git a/node_modules/istanbul-reports/lib/html/insertion-text.js b/node_modules/istanbul-reports/lib/html/insertion-text.js
new file mode 100644
index 0000000..6f80642
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/html/insertion-text.js
@@ -0,0 +1,114 @@
+'use strict';
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+function InsertionText(text, consumeBlanks) {
+ this.text = text;
+ this.origLength = text.length;
+ this.offsets = [];
+ this.consumeBlanks = consumeBlanks;
+ this.startPos = this.findFirstNonBlank();
+ this.endPos = this.findLastNonBlank();
+}
+
+const WHITE_RE = /[ \f\n\r\t\v\u00A0\u2028\u2029]/;
+
+InsertionText.prototype = {
+ findFirstNonBlank() {
+ let pos = -1;
+ const text = this.text;
+ const len = text.length;
+ let i;
+ for (i = 0; i < len; i += 1) {
+ if (!text.charAt(i).match(WHITE_RE)) {
+ pos = i;
+ break;
+ }
+ }
+ return pos;
+ },
+ findLastNonBlank() {
+ const text = this.text;
+ const len = text.length;
+ let pos = text.length + 1;
+ let i;
+ for (i = len - 1; i >= 0; i -= 1) {
+ if (!text.charAt(i).match(WHITE_RE)) {
+ pos = i;
+ break;
+ }
+ }
+ return pos;
+ },
+ originalLength() {
+ return this.origLength;
+ },
+
+ insertAt(col, str, insertBefore, consumeBlanks) {
+ consumeBlanks =
+ typeof consumeBlanks === 'undefined'
+ ? this.consumeBlanks
+ : consumeBlanks;
+ col = col > this.originalLength() ? this.originalLength() : col;
+ col = col < 0 ? 0 : col;
+
+ if (consumeBlanks) {
+ if (col <= this.startPos) {
+ col = 0;
+ }
+ if (col > this.endPos) {
+ col = this.origLength;
+ }
+ }
+
+ const len = str.length;
+ const offset = this.findOffset(col, len, insertBefore);
+ const realPos = col + offset;
+ const text = this.text;
+ this.text = text.substring(0, realPos) + str + text.substring(realPos);
+ return this;
+ },
+
+ findOffset(pos, len, insertBefore) {
+ const offsets = this.offsets;
+ let offsetObj;
+ let cumulativeOffset = 0;
+ let i;
+
+ for (i = 0; i < offsets.length; i += 1) {
+ offsetObj = offsets[i];
+ if (
+ offsetObj.pos < pos ||
+ (offsetObj.pos === pos && !insertBefore)
+ ) {
+ cumulativeOffset += offsetObj.len;
+ }
+ if (offsetObj.pos >= pos) {
+ break;
+ }
+ }
+ if (offsetObj && offsetObj.pos === pos) {
+ offsetObj.len += len;
+ } else {
+ offsets.splice(i, 0, { pos, len });
+ }
+ return cumulativeOffset;
+ },
+
+ wrap(startPos, startText, endPos, endText, consumeBlanks) {
+ this.insertAt(startPos, startText, true, consumeBlanks);
+ this.insertAt(endPos, endText, false, consumeBlanks);
+ return this;
+ },
+
+ wrapLine(startText, endText) {
+ this.wrap(0, startText, this.originalLength(), endText);
+ },
+
+ toString() {
+ return this.text;
+ }
+};
+
+module.exports = InsertionText;
diff --git a/node_modules/istanbul-reports/lib/json-summary/index.js b/node_modules/istanbul-reports/lib/json-summary/index.js
new file mode 100644
index 0000000..318a47f
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/json-summary/index.js
@@ -0,0 +1,56 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+class JsonSummaryReport extends ReportBase {
+ constructor(opts) {
+ super();
+
+ this.file = opts.file || 'coverage-summary.json';
+ this.contentWriter = null;
+ this.first = true;
+ }
+
+ onStart(root, context) {
+ this.contentWriter = context.writer.writeFile(this.file);
+ this.contentWriter.write('{');
+ }
+
+ writeSummary(filePath, sc) {
+ const cw = this.contentWriter;
+ if (this.first) {
+ this.first = false;
+ } else {
+ cw.write(',');
+ }
+ cw.write(JSON.stringify(filePath));
+ cw.write(': ');
+ cw.write(JSON.stringify(sc));
+ cw.println('');
+ }
+
+ onSummary(node) {
+ if (!node.isRoot()) {
+ return;
+ }
+ this.writeSummary('total', node.getCoverageSummary());
+ }
+
+ onDetail(node) {
+ this.writeSummary(
+ node.getFileCoverage().path,
+ node.getCoverageSummary()
+ );
+ }
+
+ onEnd() {
+ const cw = this.contentWriter;
+ cw.println('}');
+ cw.close();
+ }
+}
+
+module.exports = JsonSummaryReport;
diff --git a/node_modules/istanbul-reports/lib/json/index.js b/node_modules/istanbul-reports/lib/json/index.js
new file mode 100644
index 0000000..bcae6ae
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/json/index.js
@@ -0,0 +1,44 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+class JsonReport extends ReportBase {
+ constructor(opts) {
+ super();
+
+ this.file = opts.file || 'coverage-final.json';
+ this.first = true;
+ }
+
+ onStart(root, context) {
+ this.contentWriter = context.writer.writeFile(this.file);
+ this.contentWriter.write('{');
+ }
+
+ onDetail(node) {
+ const fc = node.getFileCoverage();
+ const key = fc.path;
+ const cw = this.contentWriter;
+
+ if (this.first) {
+ this.first = false;
+ } else {
+ cw.write(',');
+ }
+ cw.write(JSON.stringify(key));
+ cw.write(': ');
+ cw.write(JSON.stringify(fc));
+ cw.println('');
+ }
+
+ onEnd() {
+ const cw = this.contentWriter;
+ cw.println('}');
+ cw.close();
+ }
+}
+
+module.exports = JsonReport;
diff --git a/node_modules/istanbul-reports/lib/lcov/index.js b/node_modules/istanbul-reports/lib/lcov/index.js
new file mode 100644
index 0000000..383c202
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/lcov/index.js
@@ -0,0 +1,33 @@
+'use strict';
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+const { ReportBase } = require('istanbul-lib-report');
+const LcovOnlyReport = require('../lcovonly');
+const HtmlReport = require('../html');
+
+class LcovReport extends ReportBase {
+ constructor(opts) {
+ super();
+ this.lcov = new LcovOnlyReport({ file: 'lcov.info', ...opts });
+ this.html = new HtmlReport({ subdir: 'lcov-report' });
+ }
+}
+
+['Start', 'End', 'Summary', 'SummaryEnd', 'Detail'].forEach(what => {
+ const meth = 'on' + what;
+ LcovReport.prototype[meth] = function(...args) {
+ const lcov = this.lcov;
+ const html = this.html;
+
+ if (lcov[meth]) {
+ lcov[meth](...args);
+ }
+ if (html[meth]) {
+ html[meth](...args);
+ }
+ };
+});
+
+module.exports = LcovReport;
diff --git a/node_modules/istanbul-reports/lib/lcovonly/index.js b/node_modules/istanbul-reports/lib/lcovonly/index.js
new file mode 100644
index 0000000..0720e46
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/lcovonly/index.js
@@ -0,0 +1,77 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+class LcovOnlyReport extends ReportBase {
+ constructor(opts) {
+ super();
+ opts = opts || {};
+ this.file = opts.file || 'lcov.info';
+ this.projectRoot = opts.projectRoot || process.cwd();
+ this.contentWriter = null;
+ }
+
+ onStart(root, context) {
+ this.contentWriter = context.writer.writeFile(this.file);
+ }
+
+ onDetail(node) {
+ const fc = node.getFileCoverage();
+ const writer = this.contentWriter;
+ const functions = fc.f;
+ const functionMap = fc.fnMap;
+ const lines = fc.getLineCoverage();
+ const branches = fc.b;
+ const branchMap = fc.branchMap;
+ const summary = node.getCoverageSummary();
+ const path = require('path');
+
+ writer.println('TN:');
+ const fileName = path.relative(this.projectRoot, fc.path);
+ writer.println('SF:' + fileName);
+
+ Object.values(functionMap).forEach(meta => {
+ // Some versions of the instrumenter in the wild populate 'loc'
+ // but not 'decl':
+ const decl = meta.decl || meta.loc;
+ writer.println('FN:' + [decl.start.line, meta.name].join(','));
+ });
+ writer.println('FNF:' + summary.functions.total);
+ writer.println('FNH:' + summary.functions.covered);
+
+ Object.entries(functionMap).forEach(([key, meta]) => {
+ const stats = functions[key];
+ writer.println('FNDA:' + [stats, meta.name].join(','));
+ });
+
+ Object.entries(lines).forEach(entry => {
+ writer.println('DA:' + entry.join(','));
+ });
+ writer.println('LF:' + summary.lines.total);
+ writer.println('LH:' + summary.lines.covered);
+
+ Object.entries(branches).forEach(([key, branchArray]) => {
+ const meta = branchMap[key];
+ if (meta) {
+ const { line } = meta.loc.start;
+ branchArray.forEach((b, i) => {
+ writer.println('BRDA:' + [line, key, i, b].join(','));
+ });
+ } else {
+ console.warn('Missing coverage entries in', fileName, key);
+ }
+ });
+ writer.println('BRF:' + summary.branches.total);
+ writer.println('BRH:' + summary.branches.covered);
+ writer.println('end_of_record');
+ }
+
+ onEnd() {
+ this.contentWriter.close();
+ }
+}
+
+module.exports = LcovOnlyReport;
diff --git a/node_modules/istanbul-reports/lib/none/index.js b/node_modules/istanbul-reports/lib/none/index.js
new file mode 100644
index 0000000..81c1408
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/none/index.js
@@ -0,0 +1,10 @@
+'use strict';
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+const { ReportBase } = require('istanbul-lib-report');
+
+class NoneReport extends ReportBase {}
+
+module.exports = NoneReport;
diff --git a/node_modules/istanbul-reports/lib/teamcity/index.js b/node_modules/istanbul-reports/lib/teamcity/index.js
new file mode 100644
index 0000000..2bca26a
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/teamcity/index.js
@@ -0,0 +1,67 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+class TeamcityReport extends ReportBase {
+ constructor(opts) {
+ super();
+
+ opts = opts || {};
+ this.file = opts.file || null;
+ this.blockName = opts.blockName || 'Code Coverage Summary';
+ }
+
+ onStart(node, context) {
+ const metrics = node.getCoverageSummary();
+ const cw = context.writer.writeFile(this.file);
+
+ cw.println('');
+ cw.println("##teamcity[blockOpened name='" + this.blockName + "']");
+
+ //Statements Covered
+ cw.println(
+ lineForKey(metrics.statements.covered, 'CodeCoverageAbsBCovered')
+ );
+ cw.println(
+ lineForKey(metrics.statements.total, 'CodeCoverageAbsBTotal')
+ );
+
+ //Branches Covered
+ cw.println(
+ lineForKey(metrics.branches.covered, 'CodeCoverageAbsRCovered')
+ );
+ cw.println(lineForKey(metrics.branches.total, 'CodeCoverageAbsRTotal'));
+
+ //Functions Covered
+ cw.println(
+ lineForKey(metrics.functions.covered, 'CodeCoverageAbsMCovered')
+ );
+ cw.println(
+ lineForKey(metrics.functions.total, 'CodeCoverageAbsMTotal')
+ );
+
+ //Lines Covered
+ cw.println(
+ lineForKey(metrics.lines.covered, 'CodeCoverageAbsLCovered')
+ );
+ cw.println(lineForKey(metrics.lines.total, 'CodeCoverageAbsLTotal'));
+
+ cw.println("##teamcity[blockClosed name='" + this.blockName + "']");
+ cw.close();
+ }
+}
+
+function lineForKey(value, teamcityVar) {
+ return (
+ "##teamcity[buildStatisticValue key='" +
+ teamcityVar +
+ "' value='" +
+ value +
+ "']"
+ );
+}
+
+module.exports = TeamcityReport;
diff --git a/node_modules/istanbul-reports/lib/text-lcov/index.js b/node_modules/istanbul-reports/lib/text-lcov/index.js
new file mode 100644
index 0000000..847aedf
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/text-lcov/index.js
@@ -0,0 +1,17 @@
+'use strict';
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+const LcovOnly = require('../lcovonly');
+
+class TextLcov extends LcovOnly {
+ constructor(opts) {
+ super({
+ ...opts,
+ file: '-'
+ });
+ }
+}
+
+module.exports = TextLcov;
diff --git a/node_modules/istanbul-reports/lib/text-summary/index.js b/node_modules/istanbul-reports/lib/text-summary/index.js
new file mode 100644
index 0000000..a9e6eab
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/text-summary/index.js
@@ -0,0 +1,62 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+class TextSummaryReport extends ReportBase {
+ constructor(opts) {
+ super();
+
+ opts = opts || {};
+ this.file = opts.file || null;
+ }
+
+ onStart(node, context) {
+ const summary = node.getCoverageSummary();
+ const cw = context.writer.writeFile(this.file);
+ const printLine = function(key) {
+ const str = lineForKey(summary, key);
+ const clazz = context.classForPercent(key, summary[key].pct);
+ cw.println(cw.colorize(str, clazz));
+ };
+
+ cw.println('');
+ cw.println(
+ '=============================== Coverage summary ==============================='
+ );
+ printLine('statements');
+ printLine('branches');
+ printLine('functions');
+ printLine('lines');
+ cw.println(
+ '================================================================================'
+ );
+ cw.close();
+ }
+}
+
+function lineForKey(summary, key) {
+ const metrics = summary[key];
+
+ key = key.substring(0, 1).toUpperCase() + key.substring(1);
+ if (key.length < 12) {
+ key += ' '.substring(0, 12 - key.length);
+ }
+ const result = [
+ key,
+ ':',
+ metrics.pct + '%',
+ '(',
+ metrics.covered + '/' + metrics.total,
+ ')'
+ ].join(' ');
+ const skipped = metrics.skipped;
+ if (skipped > 0) {
+ return result + ', ' + skipped + ' ignored';
+ }
+ return result;
+}
+
+module.exports = TextSummaryReport;
diff --git a/node_modules/istanbul-reports/lib/text/index.js b/node_modules/istanbul-reports/lib/text/index.js
new file mode 100644
index 0000000..c28cedb
--- /dev/null
+++ b/node_modules/istanbul-reports/lib/text/index.js
@@ -0,0 +1,298 @@
+/*
+ Copyright 2012-2015, Yahoo Inc.
+ Copyrights licensed under the New BSD License. See the accompanying LICENSE
+ file for terms.
+ */
+'use strict';
+const { ReportBase } = require('istanbul-lib-report');
+
+const NAME_COL = 4;
+const PCT_COLS = 7;
+const MISSING_COL = 17;
+const TAB_SIZE = 1;
+const DELIM = ' | ';
+
+function padding(num, ch) {
+ let str = '';
+ let i;
+ ch = ch || ' ';
+ for (i = 0; i < num; i += 1) {
+ str += ch;
+ }
+ return str;
+}
+
+function fill(str, width, right, tabs) {
+ tabs = tabs || 0;
+ str = String(str);
+
+ const leadingSpaces = tabs * TAB_SIZE;
+ const remaining = width - leadingSpaces;
+ const leader = padding(leadingSpaces);
+ let fmtStr = '';
+
+ if (remaining > 0) {
+ const strlen = str.length;
+ let fillStr;
+
+ if (remaining >= strlen) {
+ fillStr = padding(remaining - strlen);
+ } else {
+ fillStr = '...';
+ const length = remaining - fillStr.length;
+
+ str = str.substring(strlen - length);
+ right = true;
+ }
+ fmtStr = right ? fillStr + str : str + fillStr;
+ }
+
+ return leader + fmtStr;
+}
+
+function formatName(name, maxCols, level) {
+ return fill(name, maxCols, false, level);
+}
+
+function formatPct(pct, width) {
+ return fill(pct, width || PCT_COLS, true, 0);
+}
+
+function nodeMissing(node) {
+ if (node.isSummary()) {
+ return '';
+ }
+
+ const metrics = node.getCoverageSummary();
+ const isEmpty = metrics.isEmpty();
+ const lines = isEmpty ? 0 : metrics.lines.pct;
+
+ let coveredLines;
+
+ const fileCoverage = node.getFileCoverage();
+ if (lines === 100) {
+ const branches = fileCoverage.getBranchCoverageByLine();
+ coveredLines = Object.entries(branches).map(([key, { coverage }]) => [
+ key,
+ coverage === 100
+ ]);
+ } else {
+ coveredLines = Object.entries(fileCoverage.getLineCoverage());
+ }
+
+ let newRange = true;
+ const ranges = coveredLines
+ .reduce((acum, [line, hit]) => {
+ if (hit) newRange = true;
+ else {
+ line = parseInt(line);
+ if (newRange) {
+ acum.push([line]);
+ newRange = false;
+ } else acum[acum.length - 1][1] = line;
+ }
+
+ return acum;
+ }, [])
+ .map(range => {
+ const { length } = range;
+
+ if (length === 1) return range[0];
+
+ return `${range[0]}-${range[1]}`;
+ });
+
+ return [].concat(...ranges).join(',');
+}
+
+function nodeName(node) {
+ return node.getRelativeName() || 'All files';
+}
+
+function depthFor(node) {
+ let ret = 0;
+ node = node.getParent();
+ while (node) {
+ ret += 1;
+ node = node.getParent();
+ }
+ return ret;
+}
+
+function nullDepthFor() {
+ return 0;
+}
+
+function findWidth(node, context, nodeExtractor, depthFor = nullDepthFor) {
+ let last = 0;
+ function compareWidth(node) {
+ last = Math.max(
+ last,
+ TAB_SIZE * depthFor(node) + nodeExtractor(node).length
+ );
+ }
+ const visitor = {
+ onSummary: compareWidth,
+ onDetail: compareWidth
+ };
+ node.visit(context.getVisitor(visitor));
+ return last;
+}
+
+function makeLine(nameWidth, missingWidth) {
+ const name = padding(nameWidth, '-');
+ const pct = padding(PCT_COLS, '-');
+ const elements = [];
+
+ elements.push(name);
+ elements.push(pct);
+ elements.push(padding(PCT_COLS + 1, '-'));
+ elements.push(pct);
+ elements.push(pct);
+ elements.push(padding(missingWidth, '-'));
+ return elements.join(DELIM.replace(/ /g, '-')) + '-';
+}
+
+function tableHeader(maxNameCols, missingWidth) {
+ const elements = [];
+ elements.push(formatName('File', maxNameCols, 0));
+ elements.push(formatPct('% Stmts'));
+ elements.push(formatPct('% Branch', PCT_COLS + 1));
+ elements.push(formatPct('% Funcs'));
+ elements.push(formatPct('% Lines'));
+ elements.push(formatName('Uncovered Line #s', missingWidth));
+ return elements.join(DELIM) + ' ';
+}
+
+function isFull(metrics) {
+ return (
+ metrics.statements.pct === 100 &&
+ metrics.branches.pct === 100 &&
+ metrics.functions.pct === 100 &&
+ metrics.lines.pct === 100
+ );
+}
+
+function tableRow(
+ node,
+ context,
+ colorizer,
+ maxNameCols,
+ level,
+ skipEmpty,
+ skipFull,
+ missingWidth
+) {
+ const name = nodeName(node);
+ const metrics = node.getCoverageSummary();
+ const isEmpty = metrics.isEmpty();
+ if (skipEmpty && isEmpty) {
+ return '';
+ }
+ if (skipFull && isFull(metrics)) {
+ return '';
+ }
+
+ const mm = {
+ statements: isEmpty ? 0 : metrics.statements.pct,
+ branches: isEmpty ? 0 : metrics.branches.pct,
+ functions: isEmpty ? 0 : metrics.functions.pct,
+ lines: isEmpty ? 0 : metrics.lines.pct
+ };
+ const colorize = isEmpty
+ ? function(str) {
+ return str;
+ }
+ : function(str, key) {
+ return colorizer(str, context.classForPercent(key, mm[key]));
+ };
+ const elements = [];
+
+ elements.push(colorize(formatName(name, maxNameCols, level), 'statements'));
+ elements.push(colorize(formatPct(mm.statements), 'statements'));
+ elements.push(colorize(formatPct(mm.branches, PCT_COLS + 1), 'branches'));
+ elements.push(colorize(formatPct(mm.functions), 'functions'));
+ elements.push(colorize(formatPct(mm.lines), 'lines'));
+ elements.push(
+ colorizer(
+ formatName(nodeMissing(node), missingWidth),
+ mm.lines === 100 ? 'medium' : 'low'
+ )
+ );
+
+ return elements.join(DELIM) + ' ';
+}
+
+class TextReport extends ReportBase {
+ constructor(opts) {
+ super(opts);
+
+ opts = opts || {};
+ const { maxCols } = opts;
+
+ this.file = opts.file || null;
+ this.maxCols = maxCols != null ? maxCols : process.stdout.columns || 80;
+ this.cw = null;
+ this.skipEmpty = opts.skipEmpty;
+ this.skipFull = opts.skipFull;
+ }
+
+ onStart(root, context) {
+ this.cw = context.writer.writeFile(this.file);
+ this.nameWidth = Math.max(
+ NAME_COL,
+ findWidth(root, context, nodeName, depthFor)
+ );
+ this.missingWidth = Math.max(
+ MISSING_COL,
+ findWidth(root, context, nodeMissing)
+ );
+
+ if (this.maxCols > 0) {
+ const pct_cols = DELIM.length + 4 * (PCT_COLS + DELIM.length) + 2;
+
+ const maxRemaining = this.maxCols - (pct_cols + MISSING_COL);
+ if (this.nameWidth > maxRemaining) {
+ this.nameWidth = maxRemaining;
+ this.missingWidth = MISSING_COL;
+ } else if (this.nameWidth < maxRemaining) {
+ const maxRemaining = this.maxCols - (this.nameWidth + pct_cols);
+ if (this.missingWidth > maxRemaining) {
+ this.missingWidth = maxRemaining;
+ }
+ }
+ }
+ const line = makeLine(this.nameWidth, this.missingWidth);
+ this.cw.println(line);
+ this.cw.println(tableHeader(this.nameWidth, this.missingWidth));
+ this.cw.println(line);
+ }
+
+ onSummary(node, context) {
+ const nodeDepth = depthFor(node);
+ const row = tableRow(
+ node,
+ context,
+ this.cw.colorize.bind(this.cw),
+ this.nameWidth,
+ nodeDepth,
+ this.skipEmpty,
+ this.skipFull,
+ this.missingWidth
+ );
+ if (row) {
+ this.cw.println(row);
+ }
+ }
+
+ onDetail(node, context) {
+ return this.onSummary(node, context);
+ }
+
+ onEnd() {
+ this.cw.println(makeLine(this.nameWidth, this.missingWidth));
+ this.cw.close();
+ }
+}
+
+module.exports = TextReport;
diff --git a/node_modules/istanbul-reports/package.json b/node_modules/istanbul-reports/package.json
new file mode 100644
index 0000000..cc668dd
--- /dev/null
+++ b/node_modules/istanbul-reports/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "istanbul-reports",
+ "version": "3.2.0",
+ "description": "istanbul reports",
+ "author": "Krishnan Anantheswaran ",
+ "main": "index.js",
+ "files": [
+ "index.js",
+ "lib"
+ ],
+ "scripts": {
+ "test": "nyc mocha --recursive",
+ "prepare": "webpack --config lib/html-spa/webpack.config.js --mode production",
+ "prepare:watch": "webpack --config lib/html-spa/webpack.config.js --watch --mode development"
+ },
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.7.5",
+ "@babel/preset-env": "^7.7.5",
+ "@babel/preset-react": "^7.7.4",
+ "babel-loader": "^8.0.6",
+ "chai": "^4.2.0",
+ "is-windows": "^1.0.2",
+ "istanbul-lib-coverage": "^3.0.0",
+ "mocha": "^6.2.2",
+ "nyc": "^15.0.0-beta.2",
+ "react": "^16.12.0",
+ "react-dom": "^16.12.0",
+ "webpack": "^4.41.2",
+ "webpack-cli": "^3.3.10"
+ },
+ "license": "BSD-3-Clause",
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/istanbuljs/istanbuljs.git",
+ "directory": "packages/istanbul-reports"
+ },
+ "keywords": [
+ "istanbul",
+ "reports"
+ ],
+ "bugs": {
+ "url": "https://github.com/istanbuljs/istanbuljs/issues"
+ },
+ "homepage": "https://istanbul.js.org/",
+ "nyc": {
+ "exclude": [
+ "lib/html/assets/**",
+ "lib/html-spa/assets/**",
+ "lib/html-spa/rollup.config.js",
+ "test/**"
+ ]
+ },
+ "engines": {
+ "node": ">=8"
+ }
+}
diff --git a/node_modules/lz-utils/LICENSE b/node_modules/lz-utils/LICENSE
new file mode 100644
index 0000000..9139430
--- /dev/null
+++ b/node_modules/lz-utils/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 cenfun
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/lz-utils/README.md b/node_modules/lz-utils/README.md
new file mode 100644
index 0000000..c5c9c7e
--- /dev/null
+++ b/node_modules/lz-utils/README.md
@@ -0,0 +1,82 @@
+# lz-utils - Utils for string compression
+
+[](https://www.npmjs.com/package/lz-utils)
+
+
+[](https://www.npmjs.com/package/lz-utils)
+
+
+* `deflateSync` and `deflate`
+ - Compress raw string and encode in base64
+ - Node.js only
+ - The highest performance (Using native `zlib`)
+ - Synchronous and Asynchronous
+ - The smallest size (minified) `0.13KB` / `0.17KB`
+* `inflateSync` and `inflate`
+ - Decompress base64 string to raw string
+ - Browser only (Using `Uint8Array` and `TextDecoder`)
+ - Higher performance (Using [tiny-inflate](https://github.com/foliojs/tiny-inflate))
+ - Synchronous and Asynchronous/Multi-thread (Using Worker)
+ - Smaller size (minified) `3.16KB` / `3.62KB`
+* `compress` / `decompress`
+ - Using [lz-string](https://github.com/pieroxy/lz-string) compress/decompress in base64 only
+ - Both browser and Node.js
+ - Normal performance
+ - Synchronous only
+ - The smallest size (minified) `1.75KB` / `1.67KB`
+* `createScriptLoader`
+ - create script loader
+
+
+## Install
+```sh
+npm install lz-utils
+```
+## Usage
+```js
+import {
+ compress, decompress,
+ deflateSync, deflate,
+ inflateSync, inflate
+} from 'lz-utils';
+
+const raw = "this is string";
+const cs = compress(raw);
+const ds = decompress(cs);
+
+```
+
+## Examples
+- [test.js](/scripts/test.js)
+- [test.html](/test/test.html)
+
+## Business Requirements and Why lz-utils?
+- The business here is to generate a lot of `html reports` to users or customers, so the report needs to be generated as `fast` as possible and the file size should be as `small` as possible.
+- The possible process is to compress the report data and bundle it with the html file. When the user opens the html file in the browser, the report data will be `self-decompressed` and rendered in the browser. So that's why `inflate` browser only.
+- Why `base64`? First of all, the data is stored in `JSON` format, which is easily serialized and compressed. At this time, we get `binary` data. Although its size is the smallest, it has many problems, such as `security issues` (CORS) because it is not JS type or object, so we need to convert binary data into JS string, and `base64` is a good choice.
+
+
+## String Compression Benchmark
+- [string-compression](https://github.com/cenfun/string-compression) lz-string, pako, uzip.js, fflate, tiny-inflate
+
+
+## Link
+* [https://github.com/pieroxy/lz-string](https://github.com/pieroxy/lz-string)
+* [https://github.com/foliojs/tiny-inflate](https://github.com/foliojs/tiny-inflate)
+
+## Changelog
+
+* 2.1.0
+ - added `createScriptLoader`
+
+* 2.0.2
+ - added types
+
+* 2.0.0
+ - added tiny-inflate
+
+* 1.0.7
+ - added ESM supported
+
+* 1.0.5
+ - added browser version
\ No newline at end of file
diff --git a/node_modules/lz-utils/dist/browser.js b/node_modules/lz-utils/dist/browser.js
new file mode 100644
index 0000000..f477940
--- /dev/null
+++ b/node_modules/lz-utils/dist/browser.js
@@ -0,0 +1,2 @@
+(()=>{var _=(e,o)=>()=>(o||e((o={exports:{}}).exports,o),o.exports);var T=_((de,O)=>{var N=function(e,o,t){let n,r,c={},w={},l="",d="",b="",h=2,p=3,f=2,u=[],i=0,a=0,s;for(s=0;s>=1}else{for(r=1,n=0;n>=1}h--,h===0&&(h=Math.pow(2,f),f++),delete w[b]}else for(r=c[b],n=0;n>=1;h--,h===0&&(h=Math.pow(2,f),f++),c[d]=p++,b=String(l)}if(b!==""){if(Object.prototype.hasOwnProperty.call(w,b)){if(b.charCodeAt(0)<256){for(n=0;n>=1}else{for(r=1,n=0;n>=1}h--,h===0&&(h=Math.pow(2,f),f++),delete w[b]}else for(r=c[b],n=0;n>=1;h--,h===0&&(h=Math.pow(2,f),f++)}for(r=2,n=0;n>=1;for(;;)if(i<<=1,a===o-1){u.push(t(i));break}else a++;return u.join("")},P="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",F=function(e){let o=N(e,6,function(t){return P.charAt(t)});switch(o.length%4){case 0:return o;case 1:return`${o}===`;case 2:return`${o}==`;case 3:return`${o}=`;default:}};O.exports=function(e){return e===null||e===""||typeof e>"u"?"":F(e)}});var B=_((ve,S)=>{var A=String.fromCharCode,C="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",k={};function V(e,o){if(!k[e]){k[e]={};for(let t=0;t>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;switch(p){case 0:for(p=0,u=Math.pow(2,8),i=1;i!==u;)f=s.val&s.position,s.position>>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;a=A(p);break;case 1:for(p=0,u=Math.pow(2,16),i=1;i!==u;)f=s.val&s.position,s.position>>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;a=A(p);break;case 2:return"";default:}for(n[3]=a,h=a,d.push(a);;){if(s.index>e)return"";for(p=0,u=Math.pow(2,w),i=1;i!==u;)f=s.val&s.position,s.position>>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;switch(a=p){case 0:for(p=0,u=Math.pow(2,8),i=1;i!==u;)f=s.val&s.position,s.position>>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;n[c++]=A(p),a=c-1,r--;break;case 1:for(p=0,u=Math.pow(2,16),i=1;i!==u;)f=s.val&s.position,s.position>>=1,s.position===0&&(s.position=o,s.val=t(s.index++)),p|=(f>0?1:0)*i,i<<=1;n[c++]=A(p),a=c-1,r--;break;case 2:return d.join("");default:}if(r===0&&(r=Math.pow(2,w),w++),n[a])l=n[a];else if(a===c)l=h+h.charAt(0);else return null;d.push(l),n[c++]=h+l.charAt(0),r--,h=l,r===0&&(r=Math.pow(2,w),w++)}},te=function(e){return ee(e.length,32,function(o){return V(C,e.charAt(o))})};S.exports=function(e){return e===null||e===""||typeof e>"u"?"":te(e)}});var j=_((xe,D)=>{D.exports=`(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error("Data error")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage("workerReady");})();
+`});var q=_((_e,R)=>{var re=j();R.exports=e=>new Promise(o=>{let t=new Worker(URL.createObjectURL(new Blob([re],{type:"application/javascript"})));t.onmessage=n=>{if(n.data==="workerReady"){t.postMessage(e);return}o(n.data),t.terminate()},t.onerror=n=>{o({error:n}),t.terminate()}})});var X=_((ye,Q)=>{var L=0,K=-3;function y(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function ne(e,o){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=o,this.destLen=0,this.ltree=new y,this.dtree=new y}var G=new y,H=new y,I=new Uint8Array(30),M=new Uint16Array(30),J=new Uint8Array(30),W=new Uint16Array(30),oe=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),z=new y,v=new Uint8Array(320);function $(e,o,t,n){var r,c;for(r=0;r>>=1,o}function x(e,o,t){if(!o)return t;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-o;return e.tag>>>=o,e.bitcount-=o,n+t}function U(e,o){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++r,t+=o.table[r],n-=o.table[r];while(n>=0);return e.tag=c,e.bitcount-=r,o.trans[t+n]}function ae(e,o,t){var n,r,c,w,l,d;for(n=x(e,5,257),r=x(e,5,1),c=x(e,4,4),w=0;w<19;++w)v[w]=0;for(w=0;w8;)e.sourceIndex--,e.bitcount-=8;if(o=e.source[e.sourceIndex+1],o=256*o+e.source[e.sourceIndex],t=e.source[e.sourceIndex+3],t=256*t+e.source[e.sourceIndex+2],o!==(~t&65535))return K;for(e.sourceIndex+=4,n=o;n;--n)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,L}function fe(e,o){var t=new ne(e,o),n,r,c;do{switch(n=se(t),r=x(t,2,0),r){case 0:c=ce(t);break;case 1:c=m(t,G,H);break;case 2:ae(t,t.ltree,t.dtree),c=m(t,t.ltree,t.dtree);break;default:c=K}if(c!==L)throw new Error("Data error")}while(!n);return t.destLen{var ue=X(),le=e=>Uint8Array.from(atob(e),o=>o.charCodeAt(0)),we=e=>new TextDecoder().decode(e);Y.exports=function(e){if(e){let[o,t]=e.split(".");if(o&&t){let n=le(o),r=new Uint8Array(parseInt(t));return ue(n,r),we(r)}}}});var be=self||window;be["lz-utils"]={compress:T(),decompress:B(),inflate:q(),inflateSync:Z()};})();
diff --git a/node_modules/lz-utils/dist/compress.js b/node_modules/lz-utils/dist/compress.js
new file mode 100644
index 0000000..dad219e
--- /dev/null
+++ b/node_modules/lz-utils/dist/compress.js
@@ -0,0 +1 @@
+var y=function(u,l,n){let o,c,a={},r={},d="",_="",f="",p=2,x=3,s=2,i=[],e=0,t=0,w;for(w=0;w>=1}else{for(c=1,o=0;o>=1}p--,p===0&&(p=Math.pow(2,s),s++),delete r[f]}else for(c=a[f],o=0;o>=1;p--,p===0&&(p=Math.pow(2,s),s++),a[_]=x++,f=String(d)}if(f!==""){if(Object.prototype.hasOwnProperty.call(r,f)){if(f.charCodeAt(0)<256){for(o=0;o>=1}else{for(c=1,o=0;o>=1}p--,p===0&&(p=Math.pow(2,s),s++),delete r[f]}else for(c=a[f],o=0;o>=1;p--,p===0&&(p=Math.pow(2,s),s++)}for(c=2,o=0;o>=1;for(;;)if(e<<=1,t===l-1){i.push(n(e));break}else t++;return i.join("")},h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",A=function(u){let l=y(u,6,function(n){return h.charAt(n)});switch(l.length%4){case 0:return l;case 1:return`${l}===`;case 2:return`${l}==`;case 3:return`${l}=`;default:}};module.exports=function(u){return u===null||u===""||typeof u>"u"?"":A(u)};
diff --git a/node_modules/lz-utils/dist/create-script-loader.js b/node_modules/lz-utils/dist/create-script-loader.js
new file mode 100644
index 0000000..56ff7aa
--- /dev/null
+++ b/node_modules/lz-utils/dist/create-script-loader.js
@@ -0,0 +1 @@
+var t=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var a=t((p,n)=>{n.exports='var a=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var s=a((b,o)=>{o.exports=`(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error("Data error")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage("workerReady");})();\n`});var c=a((l,i)=>{var u=s();i.exports=r=>new Promise(e=>{let t=new Worker(URL.createObjectURL(new Blob([u],{type:"application/javascript"})));t.onmessage=n=>{if(n.data==="workerReady"){t.postMessage(r);return}e(n.data),t.terminate()},t.onerror=n=>{e({error:n}),t.terminate()}})});var f=c();f("{placeholder}").then(r=>{let e=document.createElement("script");e.innerHTML=r,document.body.appendChild(e)});\n'});var s=t((v,o)=>{var c=require("zlib");o.exports=r=>{let e=Buffer.from(r),i=e.length;return`${c.deflateRawSync(e).toString("base64")}.${i}`}});var u=a(),f=s();module.exports=r=>u.replace("{placeholder}",f(r));
diff --git a/node_modules/lz-utils/dist/decompress.js b/node_modules/lz-utils/dist/decompress.js
new file mode 100644
index 0000000..fbf647a
--- /dev/null
+++ b/node_modules/lz-utils/dist/decompress.js
@@ -0,0 +1 @@
+var u=String.fromCharCode,x="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",M={};function k(t,c){if(!M[t]){M[t]={};for(let s=0;s>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;switch(n){case 0:for(n=0,r=Math.pow(2,8),o=1;o!==r;)e=i.val&i.position,i.position>>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;p=u(n);break;case 1:for(n=0,r=Math.pow(2,16),o=1;o!==r;)e=i.val&i.position,i.position>>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;p=u(n);break;case 2:return"";default:}for(l[3]=p,v=p,m.push(p);;){if(i.index>t)return"";for(n=0,r=Math.pow(2,h),o=1;o!==r;)e=i.val&i.position,i.position>>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;switch(p=n){case 0:for(n=0,r=Math.pow(2,8),o=1;o!==r;)e=i.val&i.position,i.position>>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;l[f++]=u(n),p=f-1,a--;break;case 1:for(n=0,r=Math.pow(2,16),o=1;o!==r;)e=i.val&i.position,i.position>>=1,i.position===0&&(i.position=c,i.val=s(i.index++)),n|=(e>0?1:0)*o,o<<=1;l[f++]=u(n),p=f-1,a--;break;case 2:return m.join("");default:}if(a===0&&(a=Math.pow(2,h),h++),l[p])w=l[p];else if(p===f)w=v+v.charAt(0);else return null;m.push(w),l[f++]=v+w.charAt(0),a--,v=w,a===0&&(a=Math.pow(2,h),h++)}},A=function(t){return y(t.length,32,function(c){return k(x,t.charAt(c))})};module.exports=function(t){return t===null||t===""||typeof t>"u"?"":A(t)};
diff --git a/node_modules/lz-utils/dist/deflate-sync.js b/node_modules/lz-utils/dist/deflate-sync.js
new file mode 100644
index 0000000..6b9b095
--- /dev/null
+++ b/node_modules/lz-utils/dist/deflate-sync.js
@@ -0,0 +1 @@
+var n=require("zlib");module.exports=e=>{let t=Buffer.from(e),r=t.length;return`${n.deflateRawSync(t).toString("base64")}.${r}`};
diff --git a/node_modules/lz-utils/dist/deflate.js b/node_modules/lz-utils/dist/deflate.js
new file mode 100644
index 0000000..0f98258
--- /dev/null
+++ b/node_modules/lz-utils/dist/deflate.js
@@ -0,0 +1 @@
+var u=require("zlib");module.exports=n=>new Promise(t=>{let e=Buffer.from(n),r=e.length;u.deflateRaw(e,(o,s)=>{if(o){t();return}let f=`${s.toString("base64")}.${r}`;t(f)})});
diff --git a/node_modules/lz-utils/dist/index.js b/node_modules/lz-utils/dist/index.js
new file mode 100644
index 0000000..c1eb63c
--- /dev/null
+++ b/node_modules/lz-utils/dist/index.js
@@ -0,0 +1,9 @@
+module.exports = {
+ compress: require('./compress.js'),
+ decompress: require('./decompress.js'),
+ deflate: require('./deflate.js'),
+ inflate: require('./inflate.js'),
+ deflateSync: require('./deflate-sync.js'),
+ inflateSync: require('./inflate-sync.js'),
+ createScriptLoader: require('./create-script-loader.js')
+};
diff --git a/node_modules/lz-utils/dist/index.mjs b/node_modules/lz-utils/dist/index.mjs
new file mode 100644
index 0000000..eca2d8a
--- /dev/null
+++ b/node_modules/lz-utils/dist/index.mjs
@@ -0,0 +1,19 @@
+import compress from './compress.js';
+import decompress from './decompress.js';
+
+import deflate from './deflate.js';
+import inflate from './inflate.js';
+
+import deflateSync from './deflate-sync.js';
+import inflateSync from './inflate-sync.js';
+
+export {
+ compress,
+ decompress,
+
+ deflate,
+ inflate,
+
+ deflateSync,
+ inflateSync
+};
diff --git a/node_modules/lz-utils/dist/inflate-sync.js b/node_modules/lz-utils/dist/inflate-sync.js
new file mode 100644
index 0000000..64a0cad
--- /dev/null
+++ b/node_modules/lz-utils/dist/inflate-sync.js
@@ -0,0 +1 @@
+var F=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var R=F((M,D)=>{var _=0,U=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var k=new b,p=new b,h=new Uint8Array(30),w=new Uint16Array(30),y=new Uint8Array(30),L=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),g=new b,c=new Uint8Array(320);function T(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function B(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return U;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function K(e,n){var r=new N(e,n),a,t,i;do{switch(a=z(r),t=u(r,2,0),t){case 0:i=C(r);break;case 1:i=I(r,k,p);break;case 2:B(r,r.ltree,r.dtree),i=I(r,r.ltree,r.dtree);break;default:i=U}if(i!==_)throw new Error("Data error")}while(!a);return r.destLenUint8Array.from(atob(e),n=>n.charCodeAt(0)),H=e=>new TextDecoder().decode(e);module.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=G(n),t=new Uint8Array(parseInt(r));return j(a,t),H(t)}}};
diff --git a/node_modules/lz-utils/dist/inflate-worker-data.js b/node_modules/lz-utils/dist/inflate-worker-data.js
new file mode 100644
index 0000000..2370764
--- /dev/null
+++ b/node_modules/lz-utils/dist/inflate-worker-data.js
@@ -0,0 +1 @@
+module.exports = "(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error(\"Data error\")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(\".\");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage(\"workerReady\");})();\n";
\ No newline at end of file
diff --git a/node_modules/lz-utils/dist/inflate-worker.js b/node_modules/lz-utils/dist/inflate-worker.js
new file mode 100644
index 0000000..ed7019e
--- /dev/null
+++ b/node_modules/lz-utils/dist/inflate-worker.js
@@ -0,0 +1 @@
+(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error("Data error")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage("workerReady");})();
diff --git a/node_modules/lz-utils/dist/inflate.js b/node_modules/lz-utils/dist/inflate.js
new file mode 100644
index 0000000..8430083
--- /dev/null
+++ b/node_modules/lz-utils/dist/inflate.js
@@ -0,0 +1,2 @@
+var s=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports);var o=s((u,a)=>{a.exports=`(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error("Data error")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage("workerReady");})();
+`});var i=o();module.exports=n=>new Promise(e=>{let r=new Worker(URL.createObjectURL(new Blob([i],{type:"application/javascript"})));r.onmessage=t=>{if(t.data==="workerReady"){r.postMessage(n);return}e(t.data),r.terminate()},r.onerror=t=>{e({error:t}),r.terminate()}});
diff --git a/node_modules/lz-utils/dist/script-loader-data.js b/node_modules/lz-utils/dist/script-loader-data.js
new file mode 100644
index 0000000..6fa54ad
--- /dev/null
+++ b/node_modules/lz-utils/dist/script-loader-data.js
@@ -0,0 +1 @@
+module.exports = "var a=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var s=a((b,o)=>{o.exports=`(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error(\"Data error\")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(\".\");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage(\"workerReady\");})();\n`});var c=a((l,i)=>{var u=s();i.exports=r=>new Promise(e=>{let t=new Worker(URL.createObjectURL(new Blob([u],{type:\"application/javascript\"})));t.onmessage=n=>{if(n.data===\"workerReady\"){t.postMessage(r);return}e(n.data),t.terminate()},t.onerror=n=>{e({error:n}),t.terminate()}})});var f=c();f(\"{placeholder}\").then(r=>{let e=document.createElement(\"script\");e.innerHTML=r,document.body.appendChild(e)});\n";
\ No newline at end of file
diff --git a/node_modules/lz-utils/dist/script-loader.js b/node_modules/lz-utils/dist/script-loader.js
new file mode 100644
index 0000000..2e116ad
--- /dev/null
+++ b/node_modules/lz-utils/dist/script-loader.js
@@ -0,0 +1,2 @@
+var a=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var s=a((b,o)=>{o.exports=`(()=>{var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var S=x((V,R)=>{var _=0,p=-3;function b(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function N(e,n){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=n,this.destLen=0,this.ltree=new b,this.dtree=new b}var y=new b,k=new b,w=new Uint8Array(30),h=new Uint16Array(30),L=new Uint8Array(30),T=new Uint16Array(30),O=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),A=new b,c=new Uint8Array(320);function D(e,n,r,a){var t,i;for(t=0;t>>=1,n}function u(e,n,r){if(!n)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-n;return e.tag>>>=n,e.bitcount-=n,a+r}function v(e,n){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++t,r+=n.table[t],a-=n.table[t];while(a>=0);return e.tag=i,e.bitcount-=t,n.trans[r+a]}function C(e,n,r){var a,t,i,o,s,f;for(a=u(e,5,257),t=u(e,5,1),i=u(e,4,4),o=0;o<19;++o)c[o]=0;for(o=0;o8;)e.sourceIndex--,e.bitcount-=8;if(n=e.source[e.sourceIndex+1],n=256*n+e.source[e.sourceIndex],r=e.source[e.sourceIndex+3],r=256*r+e.source[e.sourceIndex+2],n!==(~r&65535))return p;for(e.sourceIndex+=4,a=n;a;--a)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,_}function j(e,n){var r=new N(e,n),a,t,i;do{switch(a=B(r),t=u(r,2,0),t){case 0:i=K(r);break;case 1:i=U(r,y,k);break;case 2:C(r,r.ltree,r.dtree),i=U(r,r.ltree,r.dtree);break;default:i=p}if(i!==_)throw new Error("Data error")}while(!a);return r.destLen{var G=S(),H=e=>Uint8Array.from(atob(e),n=>n.charCodeAt(0)),J=e=>new TextDecoder().decode(e);q.exports=function(e){if(e){let[n,r]=e.split(".");if(n&&r){let a=H(n),t=new Uint8Array(parseInt(r));return G(a,t),J(t)}}}});var P=E();onmessage=function(e){postMessage(P(e.data))};postMessage("workerReady");})();
+`});var c=a((l,i)=>{var u=s();i.exports=r=>new Promise(e=>{let t=new Worker(URL.createObjectURL(new Blob([u],{type:"application/javascript"})));t.onmessage=n=>{if(n.data==="workerReady"){t.postMessage(r);return}e(n.data),t.terminate()},t.onerror=n=>{e({error:n}),t.terminate()}})});var f=c();f("{placeholder}").then(r=>{let e=document.createElement("script");e.innerHTML=r,document.body.appendChild(e)});
diff --git a/node_modules/lz-utils/lib/browser.js b/node_modules/lz-utils/lib/browser.js
new file mode 100644
index 0000000..fb0dded
--- /dev/null
+++ b/node_modules/lz-utils/lib/browser.js
@@ -0,0 +1,7 @@
+const root = self || window;
+root['lz-utils'] = {
+ compress: require('./compress.js'),
+ decompress: require('./decompress.js'),
+ inflate: require('./inflate.js'),
+ inflateSync: require('./inflate-sync.js')
+};
diff --git a/node_modules/lz-utils/lib/compress.js b/node_modules/lz-utils/lib/compress.js
new file mode 100644
index 0000000..8b341de
--- /dev/null
+++ b/node_modules/lz-utils/lib/compress.js
@@ -0,0 +1,244 @@
+
+// https://github.com/pieroxy/lz-string
+// https://pieroxy.net/blog/pages/lz-string/index.html
+/* eslint-disable max-statements,complexity,no-constant-condition,max-depth */
+
+const _compress = function(uncompressed, bitsPerChar, getCharFromInt) {
+ let i;
+ let value;
+ const context_dictionary = {};
+ const context_dictionaryToCreate = {};
+ let context_c = '';
+ let context_wc = '';
+ let context_w = '';
+ let context_enlargeIn = 2;
+ let context_dictSize = 3;
+ let context_numBits = 2;
+ const context_data = [];
+ let context_data_val = 0;
+ let context_data_position = 0;
+ let ii;
+
+ for (ii = 0; ii < uncompressed.length; ii += 1) {
+ context_c = uncompressed.charAt(ii);
+ if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
+ context_dictionary[context_c] = context_dictSize++;
+ context_dictionaryToCreate[context_c] = true;
+ }
+
+ context_wc = context_w + context_c;
+ if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
+ context_w = context_wc;
+ } else {
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
+ if (context_w.charCodeAt(0) < 256) {
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val <<= 1;
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ }
+ value = context_w.charCodeAt(0);
+ for (i = 0; i < 8; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+ } else {
+ value = 1;
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val = (context_data_val << 1) | value;
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value = 0;
+ }
+ value = context_w.charCodeAt(0);
+ for (i = 0; i < 16; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+ }
+ context_enlargeIn--;
+ if (context_enlargeIn === 0) {
+ context_enlargeIn = Math.pow(2, context_numBits);
+ context_numBits++;
+ }
+ delete context_dictionaryToCreate[context_w];
+ } else {
+ value = context_dictionary[context_w];
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+
+
+ }
+ context_enlargeIn--;
+ if (context_enlargeIn === 0) {
+ context_enlargeIn = Math.pow(2, context_numBits);
+ context_numBits++;
+ }
+ // Add wc to the dictionary.
+ context_dictionary[context_wc] = context_dictSize++;
+ context_w = String(context_c);
+ }
+ }
+
+ // Output the code for w.
+ if (context_w !== '') {
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
+ if (context_w.charCodeAt(0) < 256) {
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val <<= 1;
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ }
+ value = context_w.charCodeAt(0);
+ for (i = 0; i < 8; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+ } else {
+ value = 1;
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val = (context_data_val << 1) | value;
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value = 0;
+ }
+ value = context_w.charCodeAt(0);
+ for (i = 0; i < 16; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+ }
+ context_enlargeIn--;
+ if (context_enlargeIn === 0) {
+ context_enlargeIn = Math.pow(2, context_numBits);
+ context_numBits++;
+ }
+ delete context_dictionaryToCreate[context_w];
+ } else {
+ value = context_dictionary[context_w];
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+
+
+ }
+ context_enlargeIn--;
+ if (context_enlargeIn === 0) {
+ context_enlargeIn = Math.pow(2, context_numBits);
+ context_numBits++;
+ }
+ }
+
+ // Mark the end of the stream
+ value = 2;
+ for (i = 0; i < context_numBits; i++) {
+ context_data_val = (context_data_val << 1) | (value & 1);
+ if (context_data_position === bitsPerChar - 1) {
+ context_data_position = 0;
+ context_data.push(getCharFromInt(context_data_val));
+ context_data_val = 0;
+ } else {
+ context_data_position++;
+ }
+ value >>= 1;
+ }
+
+ // Flush the last char
+ while (true) {
+ context_data_val <<= 1;
+ if (context_data_position === bitsPerChar - 1) {
+ context_data.push(getCharFromInt(context_data_val));
+ break;
+ } else {
+ context_data_position++;
+ }
+ }
+ return context_data.join('');
+};
+
+const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+const compressToBase64 = function(input) {
+ const res = _compress(input, 6, function(a) {
+ return keyStrBase64.charAt(a);
+ });
+ switch (res.length % 4) {
+ case 0: return res;
+ case 1: return `${res}===`;
+ case 2: return `${res}==`;
+ case 3: return `${res}=`;
+ default:
+ }
+};
+
+module.exports = function(input) {
+ if (input === null || input === '' || typeof input === 'undefined') {
+ return '';
+ }
+ // 166% bigger
+ return compressToBase64(input);
+};
diff --git a/node_modules/lz-utils/lib/create-script-loader.js b/node_modules/lz-utils/lib/create-script-loader.js
new file mode 100644
index 0000000..3311807
--- /dev/null
+++ b/node_modules/lz-utils/lib/create-script-loader.js
@@ -0,0 +1,5 @@
+const loader = require('../dist/script-loader-data.js');
+const deflateSync = require('./deflate-sync.js');
+module.exports = (str) => {
+ return loader.replace('{placeholder}', deflateSync(str));
+};
diff --git a/node_modules/lz-utils/lib/decompress.js b/node_modules/lz-utils/lib/decompress.js
new file mode 100644
index 0000000..91cf34d
--- /dev/null
+++ b/node_modules/lz-utils/lib/decompress.js
@@ -0,0 +1,201 @@
+
+// https://github.com/pieroxy/lz-string
+// https://pieroxy.net/blog/pages/lz-string/index.html
+/* eslint-disable max-statements,complexity,no-constant-condition */
+
+const fcc = String.fromCharCode;
+const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+const baseReverseDic = {};
+
+function getBaseValue(alphabet, character) {
+ if (!baseReverseDic[alphabet]) {
+ baseReverseDic[alphabet] = {};
+ for (let i = 0; i < alphabet.length; i++) {
+ baseReverseDic[alphabet][alphabet.charAt(i)] = i;
+ }
+ }
+ return baseReverseDic[alphabet][character];
+}
+
+const _decompress = function(length, resetValue, getNextValue) {
+ const dictionary = [];
+
+ let enlargeIn = 4;
+ let dictSize = 4;
+ let numBits = 3;
+ let entry = '';
+ const result = [];
+ let i;
+ let w;
+ let bits;
+ let resb;
+ let maxpower;
+ let power;
+ let c;
+ const data = {
+ val: getNextValue(0), position: resetValue, index: 1
+ };
+
+ for (i = 0; i < 3; i += 1) {
+ dictionary[i] = i;
+ }
+
+ bits = 0;
+ maxpower = Math.pow(2, 2);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+
+ const next = bits;
+ switch (next) {
+ case 0:
+ bits = 0;
+ maxpower = Math.pow(2, 8);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+ c = fcc(bits);
+ break;
+ case 1:
+ bits = 0;
+ maxpower = Math.pow(2, 16);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+ c = fcc(bits);
+ break;
+ case 2:
+ return '';
+ default:
+ }
+ dictionary[3] = c;
+ w = c;
+ result.push(c);
+ while (true) {
+ if (data.index > length) {
+ return '';
+ }
+
+ bits = 0;
+ maxpower = Math.pow(2, numBits);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+
+ switch (c = bits) {
+ case 0:
+ bits = 0;
+ maxpower = Math.pow(2, 8);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+
+ dictionary[dictSize++] = fcc(bits);
+ c = dictSize - 1;
+ enlargeIn--;
+ break;
+ case 1:
+ bits = 0;
+ maxpower = Math.pow(2, 16);
+ power = 1;
+ while (power !== maxpower) {
+ resb = data.val & data.position;
+ data.position >>= 1;
+ if (data.position === 0) {
+ data.position = resetValue;
+ data.val = getNextValue(data.index++);
+ }
+ bits |= (resb > 0 ? 1 : 0) * power;
+ power <<= 1;
+ }
+ dictionary[dictSize++] = fcc(bits);
+ c = dictSize - 1;
+ enlargeIn--;
+ break;
+ case 2:
+ return result.join('');
+ default:
+ }
+
+ if (enlargeIn === 0) {
+ enlargeIn = Math.pow(2, numBits);
+ numBits++;
+ }
+
+ if (dictionary[c]) {
+ entry = dictionary[c];
+ } else {
+ if (c === dictSize) {
+ entry = w + w.charAt(0);
+ } else {
+ return null;
+ }
+ }
+ result.push(entry);
+
+ // Add w+entry[0] to the dictionary.
+ dictionary[dictSize++] = w + entry.charAt(0);
+ enlargeIn--;
+
+ w = entry;
+
+ if (enlargeIn === 0) {
+ enlargeIn = Math.pow(2, numBits);
+ numBits++;
+ }
+
+ }
+};
+
+const decompressFromBase64 = function(input) {
+ return _decompress(input.length, 32, function(index) {
+ return getBaseValue(keyStrBase64, input.charAt(index));
+ });
+};
+
+module.exports = function(input) {
+ if (input === null || input === '' || typeof input === 'undefined') {
+ return '';
+ }
+ return decompressFromBase64(input);
+};
diff --git a/node_modules/lz-utils/lib/deflate-sync.js b/node_modules/lz-utils/lib/deflate-sync.js
new file mode 100644
index 0000000..d962e41
--- /dev/null
+++ b/node_modules/lz-utils/lib/deflate-sync.js
@@ -0,0 +1,9 @@
+const zlib = require('zlib');
+module.exports = (str) => {
+ const buf = Buffer.from(str);
+ const length = buf.length;
+ const buffer = zlib.deflateRawSync(buf);
+ const b64 = buffer.toString('base64');
+ const result = `${b64}.${length}`;
+ return result;
+};
diff --git a/node_modules/lz-utils/lib/deflate.js b/node_modules/lz-utils/lib/deflate.js
new file mode 100644
index 0000000..1a0f32d
--- /dev/null
+++ b/node_modules/lz-utils/lib/deflate.js
@@ -0,0 +1,16 @@
+const zlib = require('zlib');
+module.exports = (str) => {
+ return new Promise((resolve) => {
+ const buf = Buffer.from(str);
+ const length = buf.length;
+ zlib.deflateRaw(buf, (err, buffer) => {
+ if (err) {
+ resolve();
+ return;
+ }
+ const b64 = buffer.toString('base64');
+ const result = `${b64}.${length}`;
+ resolve(result);
+ });
+ });
+};
diff --git a/node_modules/lz-utils/lib/index.d.ts b/node_modules/lz-utils/lib/index.d.ts
new file mode 100644
index 0000000..c9f4f93
--- /dev/null
+++ b/node_modules/lz-utils/lib/index.d.ts
@@ -0,0 +1,14 @@
+declare namespace LZ {
+ function compress(input: string): string;
+ function decompress(input: string): string;
+
+ function deflateSync(input: string): string;
+ function deflate(input: string): Promise;
+
+ function inflateSync(input: string): string;
+ function inflate(input: string): Promise;
+
+ function createScriptLoader(input: string): string;
+}
+
+export = LZ
\ No newline at end of file
diff --git a/node_modules/lz-utils/lib/index.js b/node_modules/lz-utils/lib/index.js
new file mode 100644
index 0000000..c1eb63c
--- /dev/null
+++ b/node_modules/lz-utils/lib/index.js
@@ -0,0 +1,9 @@
+module.exports = {
+ compress: require('./compress.js'),
+ decompress: require('./decompress.js'),
+ deflate: require('./deflate.js'),
+ inflate: require('./inflate.js'),
+ deflateSync: require('./deflate-sync.js'),
+ inflateSync: require('./inflate-sync.js'),
+ createScriptLoader: require('./create-script-loader.js')
+};
diff --git a/node_modules/lz-utils/lib/index.mjs b/node_modules/lz-utils/lib/index.mjs
new file mode 100644
index 0000000..eca2d8a
--- /dev/null
+++ b/node_modules/lz-utils/lib/index.mjs
@@ -0,0 +1,19 @@
+import compress from './compress.js';
+import decompress from './decompress.js';
+
+import deflate from './deflate.js';
+import inflate from './inflate.js';
+
+import deflateSync from './deflate-sync.js';
+import inflateSync from './inflate-sync.js';
+
+export {
+ compress,
+ decompress,
+
+ deflate,
+ inflate,
+
+ deflateSync,
+ inflateSync
+};
diff --git a/node_modules/lz-utils/lib/inflate-sync.js b/node_modules/lz-utils/lib/inflate-sync.js
new file mode 100644
index 0000000..04b14ce
--- /dev/null
+++ b/node_modules/lz-utils/lib/inflate-sync.js
@@ -0,0 +1,18 @@
+const inflate = require('tiny-inflate');
+
+const base64ToUint8 = (str) => Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
+
+const uint8ArrToString = (uint8arr) => new TextDecoder().decode(uint8arr);
+
+module.exports = function(compressedB64) {
+ if (compressedB64) {
+ const [b64Str, sizeStr] = compressedB64.split('.');
+ if (b64Str && sizeStr) {
+ const compressedBuffer = base64ToUint8(b64Str);
+ const outputBuffer = new Uint8Array(parseInt(sizeStr));
+ inflate(compressedBuffer, outputBuffer);
+ return uint8ArrToString(outputBuffer);
+ }
+ }
+};
+
diff --git a/node_modules/lz-utils/lib/inflate-worker.js b/node_modules/lz-utils/lib/inflate-worker.js
new file mode 100644
index 0000000..9d27bde
--- /dev/null
+++ b/node_modules/lz-utils/lib/inflate-worker.js
@@ -0,0 +1,5 @@
+const inflateSync = require('./inflate-sync.js');
+onmessage = function(e) {
+ postMessage(inflateSync(e.data));
+};
+postMessage('workerReady');
diff --git a/node_modules/lz-utils/lib/inflate.js b/node_modules/lz-utils/lib/inflate.js
new file mode 100644
index 0000000..9493797
--- /dev/null
+++ b/node_modules/lz-utils/lib/inflate.js
@@ -0,0 +1,22 @@
+const workerData = require('../dist/inflate-worker-data.js');
+module.exports = (compressedB64) => {
+ return new Promise((resolve) => {
+ const worker = new Worker(URL.createObjectURL(new Blob([workerData], {
+ type: 'application/javascript'
+ })));
+ worker.onmessage = (e) => {
+ if (e.data === 'workerReady') {
+ worker.postMessage(compressedB64);
+ return;
+ }
+ resolve(e.data);
+ worker.terminate();
+ };
+ worker.onerror = (err) => {
+ resolve({
+ error: err
+ });
+ worker.terminate();
+ };
+ });
+};
diff --git a/node_modules/lz-utils/lib/script-loader.js b/node_modules/lz-utils/lib/script-loader.js
new file mode 100644
index 0000000..6144283
--- /dev/null
+++ b/node_modules/lz-utils/lib/script-loader.js
@@ -0,0 +1,6 @@
+const inflate = require('./inflate.js');
+inflate('{placeholder}').then((res) => {
+ const script = document.createElement('script');
+ script.innerHTML = res;
+ document.body.appendChild(script);
+});
diff --git a/node_modules/lz-utils/package.json b/node_modules/lz-utils/package.json
new file mode 100644
index 0000000..8ecfd62
--- /dev/null
+++ b/node_modules/lz-utils/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "lz-utils",
+ "version": "2.1.0",
+ "description": "lz-utils",
+ "main": "./dist/index.js",
+ "browser": {
+ "lz-utils": "./dist/browser.js"
+ },
+ "exports": {
+ ".": {
+ "types": "./lib/index.d.ts",
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.js",
+ "default": "./dist/index.js"
+ },
+ "./compress": "./dist/compress.js",
+ "./decompress": "./dist/decompress.js",
+ "./deflate": "./dist/deflate.js",
+ "./deflate-sync": "./dist/deflate-sync.js",
+ "./inflate": "./dist/inflate.js",
+ "./inflate-sync": "./dist/inflate-sync.js",
+ "./create-script-loader": "./dist/create-script-loader.js",
+ "./dist/*": "./dist/*",
+ "./lib/*": "./lib/*",
+ "./package.json": "./package.json"
+ },
+ "types": "./lib/index.d.ts",
+ "scripts": {
+ "build": "node ./scripts/build.js",
+ "test": "node ./scripts/test.js",
+ "patch": "npm run build && sf publish patch -r"
+ },
+ "files": [
+ "dist",
+ "lib"
+ ],
+ "keywords": [
+ "lz-utils"
+ ],
+ "license": "MIT",
+ "dependencies": {},
+ "devDependencies": {
+ "babel-loader": "^9.1.3",
+ "eight-colors": "^1.3.0",
+ "esbuild": "^0.23.0",
+ "eslint": "^9.7.0",
+ "eslint-config-plus": "^2.0.2",
+ "eslint-plugin-html": "^8.1.1",
+ "tiny-inflate": "^1.0.3"
+ }
+}
diff --git a/node_modules/make-dir/index.d.ts b/node_modules/make-dir/index.d.ts
new file mode 100644
index 0000000..3a78251
--- /dev/null
+++ b/node_modules/make-dir/index.d.ts
@@ -0,0 +1,66 @@
+///
+import * as fs from 'fs';
+
+declare namespace makeDir {
+ interface Options {
+ /**
+ Directory [permissions](https://x-team.com/blog/file-system-permissions-umask-node-js/).
+
+ @default 0o777
+ */
+ readonly mode?: number;
+
+ /**
+ Use a custom `fs` implementation. For example [`graceful-fs`](https://github.com/isaacs/node-graceful-fs).
+
+ Using a custom `fs` implementation will block the use of the native `recursive` option if `fs.mkdir` or `fs.mkdirSync` is not the native function.
+
+ @default require('fs')
+ */
+ readonly fs?: typeof fs;
+ }
+}
+
+declare const makeDir: {
+ /**
+ Make a directory and its parents if needed - Think `mkdir -p`.
+
+ @param path - Directory to create.
+ @returns The path to the created directory.
+
+ @example
+ ```
+ import makeDir = require('make-dir');
+
+ (async () => {
+ const path = await makeDir('unicorn/rainbow/cake');
+
+ console.log(path);
+ //=> '/Users/sindresorhus/fun/unicorn/rainbow/cake'
+
+ // Multiple directories:
+ const paths = await Promise.all([
+ makeDir('unicorn/rainbow'),
+ makeDir('foo/bar')
+ ]);
+
+ console.log(paths);
+ // [
+ // '/Users/sindresorhus/fun/unicorn/rainbow',
+ // '/Users/sindresorhus/fun/foo/bar'
+ // ]
+ })();
+ ```
+ */
+ (path: string, options?: makeDir.Options): Promise;
+
+ /**
+ Synchronously make a directory and its parents if needed - Think `mkdir -p`.
+
+ @param path - Directory to create.
+ @returns The path to the created directory.
+ */
+ sync(path: string, options?: makeDir.Options): string;
+};
+
+export = makeDir;
diff --git a/node_modules/make-dir/index.js b/node_modules/make-dir/index.js
new file mode 100644
index 0000000..2c0814e
--- /dev/null
+++ b/node_modules/make-dir/index.js
@@ -0,0 +1,155 @@
+'use strict';
+const fs = require('fs');
+const path = require('path');
+const {promisify} = require('util');
+const semverGte = require('semver/functions/gte');
+
+const useNativeRecursiveOption = semverGte(process.version, '10.12.0');
+
+// https://github.com/nodejs/node/issues/8987
+// https://github.com/libuv/libuv/pull/1088
+const checkPath = pth => {
+ if (process.platform === 'win32') {
+ const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, ''));
+
+ if (pathHasInvalidWinCharacters) {
+ const error = new Error(`Path contains invalid characters: ${pth}`);
+ error.code = 'EINVAL';
+ throw error;
+ }
+ }
+};
+
+const processOptions = options => {
+ const defaults = {
+ mode: 0o777,
+ fs
+ };
+
+ return {
+ ...defaults,
+ ...options
+ };
+};
+
+const permissionError = pth => {
+ // This replicates the exception of `fs.mkdir` with native the
+ // `recusive` option when run on an invalid drive under Windows.
+ const error = new Error(`operation not permitted, mkdir '${pth}'`);
+ error.code = 'EPERM';
+ error.errno = -4048;
+ error.path = pth;
+ error.syscall = 'mkdir';
+ return error;
+};
+
+const makeDir = async (input, options) => {
+ checkPath(input);
+ options = processOptions(options);
+
+ const mkdir = promisify(options.fs.mkdir);
+ const stat = promisify(options.fs.stat);
+
+ if (useNativeRecursiveOption && options.fs.mkdir === fs.mkdir) {
+ const pth = path.resolve(input);
+
+ await mkdir(pth, {
+ mode: options.mode,
+ recursive: true
+ });
+
+ return pth;
+ }
+
+ const make = async pth => {
+ try {
+ await mkdir(pth, options.mode);
+
+ return pth;
+ } catch (error) {
+ if (error.code === 'EPERM') {
+ throw error;
+ }
+
+ if (error.code === 'ENOENT') {
+ if (path.dirname(pth) === pth) {
+ throw permissionError(pth);
+ }
+
+ if (error.message.includes('null bytes')) {
+ throw error;
+ }
+
+ await make(path.dirname(pth));
+
+ return make(pth);
+ }
+
+ try {
+ const stats = await stat(pth);
+ if (!stats.isDirectory()) {
+ throw new Error('The path is not a directory');
+ }
+ } catch {
+ throw error;
+ }
+
+ return pth;
+ }
+ };
+
+ return make(path.resolve(input));
+};
+
+module.exports = makeDir;
+
+module.exports.sync = (input, options) => {
+ checkPath(input);
+ options = processOptions(options);
+
+ if (useNativeRecursiveOption && options.fs.mkdirSync === fs.mkdirSync) {
+ const pth = path.resolve(input);
+
+ fs.mkdirSync(pth, {
+ mode: options.mode,
+ recursive: true
+ });
+
+ return pth;
+ }
+
+ const make = pth => {
+ try {
+ options.fs.mkdirSync(pth, options.mode);
+ } catch (error) {
+ if (error.code === 'EPERM') {
+ throw error;
+ }
+
+ if (error.code === 'ENOENT') {
+ if (path.dirname(pth) === pth) {
+ throw permissionError(pth);
+ }
+
+ if (error.message.includes('null bytes')) {
+ throw error;
+ }
+
+ make(path.dirname(pth));
+ return make(pth);
+ }
+
+ try {
+ if (!options.fs.statSync(pth).isDirectory()) {
+ throw new Error('The path is not a directory');
+ }
+ } catch {
+ throw error;
+ }
+ }
+
+ return pth;
+ };
+
+ return make(path.resolve(input));
+};
diff --git a/node_modules/make-dir/license b/node_modules/make-dir/license
new file mode 100644
index 0000000..fa7ceba
--- /dev/null
+++ b/node_modules/make-dir/license
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/make-dir/package.json b/node_modules/make-dir/package.json
new file mode 100644
index 0000000..db305b1
--- /dev/null
+++ b/node_modules/make-dir/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "make-dir",
+ "version": "4.0.0",
+ "description": "Make a directory and its parents if needed - Think `mkdir -p`",
+ "license": "MIT",
+ "repository": "sindresorhus/make-dir",
+ "funding": "https://github.com/sponsors/sindresorhus",
+ "author": {
+ "name": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "https://sindresorhus.com"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "scripts": {
+ "test": "xo && nyc ava && tsd"
+ },
+ "files": [
+ "index.js",
+ "index.d.ts"
+ ],
+ "keywords": [
+ "mkdir",
+ "mkdirp",
+ "make",
+ "directories",
+ "folders",
+ "directory",
+ "folder",
+ "path",
+ "parent",
+ "parents",
+ "intermediate",
+ "recursively",
+ "recursive",
+ "create",
+ "fs",
+ "filesystem",
+ "file-system"
+ ],
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "devDependencies": {
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "^14.14.6",
+ "ava": "^2.4.0",
+ "codecov": "^3.2.0",
+ "graceful-fs": "^4.1.15",
+ "nyc": "^15.0.0",
+ "path-type": "^4.0.0",
+ "tempy": "^1.0.0",
+ "tsd": "^0.13.1",
+ "xo": "^0.34.2"
+ },
+ "nyc": {
+ "reporter": [
+ "text",
+ "lcov"
+ ]
+ }
+}
diff --git a/node_modules/make-dir/readme.md b/node_modules/make-dir/readme.md
new file mode 100644
index 0000000..13fa8fd
--- /dev/null
+++ b/node_modules/make-dir/readme.md
@@ -0,0 +1,125 @@
+# make-dir [](https://codecov.io/gh/sindresorhus/make-dir)
+
+> Make a directory and its parents if needed - Think `mkdir -p`
+
+## Advantages over [`mkdirp`](https://github.com/substack/node-mkdirp)
+
+- Promise API *(Async/await ready!)*
+- Fixes many `mkdirp` issues: [#96](https://github.com/substack/node-mkdirp/pull/96) [#70](https://github.com/substack/node-mkdirp/issues/70) [#66](https://github.com/substack/node-mkdirp/issues/66)
+- 100% test coverage
+- CI-tested on macOS, Linux, and Windows
+- Actively maintained
+- Doesn't bundle a CLI
+- Uses the native `fs.mkdir/mkdirSync` [`recursive` option](https://nodejs.org/dist/latest/docs/api/fs.html#fs_fs_mkdir_path_options_callback) in Node.js >=10.12.0 unless [overridden](#fs)
+
+## Install
+
+```
+$ npm install make-dir
+```
+
+## Usage
+
+```
+$ pwd
+/Users/sindresorhus/fun
+$ tree
+.
+```
+
+```js
+const makeDir = require('make-dir');
+
+(async () => {
+ const path = await makeDir('unicorn/rainbow/cake');
+
+ console.log(path);
+ //=> '/Users/sindresorhus/fun/unicorn/rainbow/cake'
+})();
+```
+
+```
+$ tree
+.
+└── unicorn
+ └── rainbow
+ └── cake
+```
+
+Multiple directories:
+
+```js
+const makeDir = require('make-dir');
+
+(async () => {
+ const paths = await Promise.all([
+ makeDir('unicorn/rainbow'),
+ makeDir('foo/bar')
+ ]);
+
+ console.log(paths);
+ /*
+ [
+ '/Users/sindresorhus/fun/unicorn/rainbow',
+ '/Users/sindresorhus/fun/foo/bar'
+ ]
+ */
+})();
+```
+
+## API
+
+### makeDir(path, options?)
+
+Returns a `Promise` for the path to the created directory.
+
+### makeDir.sync(path, options?)
+
+Returns the path to the created directory.
+
+#### path
+
+Type: `string`
+
+Directory to create.
+
+#### options
+
+Type: `object`
+
+##### mode
+
+Type: `integer`\
+Default: `0o777`
+
+Directory [permissions](https://x-team.com/blog/file-system-permissions-umask-node-js/).
+
+##### fs
+
+Type: `object`\
+Default: `require('fs')`
+
+Use a custom `fs` implementation. For example [`graceful-fs`](https://github.com/isaacs/node-graceful-fs).
+
+Using a custom `fs` implementation will block the use of the native `recursive` option if `fs.mkdir` or `fs.mkdirSync` is not the native function.
+
+## Related
+
+- [make-dir-cli](https://github.com/sindresorhus/make-dir-cli) - CLI for this module
+- [del](https://github.com/sindresorhus/del) - Delete files and directories
+- [globby](https://github.com/sindresorhus/globby) - User-friendly glob matching
+- [cpy](https://github.com/sindresorhus/cpy) - Copy files
+- [cpy-cli](https://github.com/sindresorhus/cpy-cli) - Copy files on the command-line
+- [move-file](https://github.com/sindresorhus/move-file) - Move a file
+
+---
+
+
diff --git a/node_modules/monocart-coverage-reports/LICENSE b/node_modules/monocart-coverage-reports/LICENSE
new file mode 100644
index 0000000..513af51
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 cenfun
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/monocart-coverage-reports/README.md b/node_modules/monocart-coverage-reports/README.md
new file mode 100644
index 0000000..be9db15
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/README.md
@@ -0,0 +1,1186 @@
+# Monocart Coverage Reports
+
+[](https://www.npmjs.com/package/monocart-coverage-reports)
+
+
+[](https://packagephobia.com/result?p=monocart-coverage-reports)
+[](https://npmgraph.js.org/?q=monocart-coverage-reports)
+
+[](https://www.npmjs.com/package/monocart-coverage-reports)
+
+🌐 English | [简体中文](README.zh-Hans.md)
+
+> A JavaScript code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
+
+* [Usage](#usage)
+* [Options](#options)
+* [Available Reports](#available-reports)
+* [Compare Reports](#compare-reports)
+* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
+* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
+ - [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
+ - [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+ - [Collecting V8 Coverage Data from Node.js](#collecting-v8-coverage-data-from-nodejs)
+ - [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
+ - [V8 Coverage Data API](#v8-coverage-data-api)
+* [Filtering Results](#filtering-results)
+* [Resolve `sourcePath` for the Source Files](#resolve-sourcepath-for-the-source-files)
+* [Adding Empty Coverage for Untested Files](#adding-empty-coverage-for-untested-files)
+* [onEnd Hook](#onend-hook)
+* [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
+* [Multiprocessing Support](#multiprocessing-support)
+* [Command Line](#command-line)
+* [Config File](#config-file)
+* [Merge Coverage Reports](#merge-coverage-reports)
+ - [Automatic Merging](#automatic-merging)
+ - [Manual Merging](#manual-merging)
+* [Common issues](#common-issues)
+ - [Unexpected coverage](#unexpected-coverage)
+ - [Unparsable source](#unparsable-source)
+ - [JavaScript heap out of memory](#javascript-heap-out-of-memory)
+* [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
+* [Integration with Any Testing Framework](#integration-with-any-testing-framework)
+* [Integration Examples](#integration-examples)
+ - [Playwright](#playwright)
+ - [c8](#c8)
+ - [CodeceptJS](#codeceptjs)
+ - [VSCode](#vscode)
+ - [Jest](#jest)
+ - [Vitest](#vitest)
+ - [Node Test Runner](#node-test-runner)
+ - [Puppeteer](#puppeteer)
+ - [Cypress](#cypress)
+ - [WebdriverIO](#webdriverio)
+ - [Storybook Test Runner](#storybook-test-runner)
+ - [TestCafe](#testcafe)
+ - [Selenium Webdriver](#selenium-webdriver)
+ - [Mocha](#mocha)
+ - [TypeScript](#typescript)
+ - [AVA](#ava)
+ - [Codecov](#codecov)
+ - [Codacy](#codacy)
+ - [Coveralls](#coveralls)
+ - [Sonar Cloud](#sonar-cloud)
+* [Contributing](#contributing)
+* [Changelog](CHANGELOG.md)
+* [Thanks](#thanks)
+
+## Usage
+> It's recommended to use [Node.js 20+](https://nodejs.org/).
+- Install
+```sh
+npm install monocart-coverage-reports
+```
+- API
+```js
+const MCR = require('monocart-coverage-reports');
+const mcr = MCR({
+ name: 'My Coverage Report - 2024-02-28',
+ outputDir: './coverage-reports',
+ reports: ["v8", "console-details"],
+ cleanCache: true
+});
+await mcr.add(coverageData);
+await mcr.generate();
+```
+Using `import` and load options from [config file](#config-file)
+```js
+import { CoverageReport } from 'monocart-coverage-reports';
+const mcr = new CoverageReport();
+await mcr.loadConfig();
+```
+For more information, see [Multiprocessing Support](#multiprocessing-support)
+
+- CLI
+```sh
+mcr node my-app.js -r v8,console-details
+```
+For more information, see [Command Line](#command-line)
+
+## Options
+- Default options: [lib/default/options.js](./lib/default/options.js)
+- Options declaration see `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
+- [Config file](#config-file)
+
+## Available Reports
+
+> V8 build-in reports (V8 data only):
+
+- `v8`
+ - Features:
+ - A Brand-New V8 Coverage Report User Interface
+ - Support for Native Byte Statistics
+ - Support processing big data with high performance
+ - Coverage for Any Runtime Code
+ - CSS Coverage Support
+ - Better Support for Sourcemap Conversion
+ - Demos: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [more](https://cenfun.github.io/monocart-coverage-reports/)
+
+
+
+- `v8-json`
+ - Save `CoverageResults` to a json file (defaults to [`coverage-report.json`](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)).
+ - Shows native V8 code coverage with VSCode extension: [Monocart Coverage for VSCode](https://github.com/cenfun/monocart-coverage-vscode)
+
+
+
+> Istanbul build-in reports (both V8 and Istanbul data):
+
+- `clover`
+- `cobertura`
+- `html`
+ - [Istanbul html](https://cenfun.github.io/monocart-coverage-reports/istanbul/)
+ - [V8 to Istanbul](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/istanbul)
+- `html-spa`
+- `json`
+- `json-summary`
+- `lcov`
+- `lcovonly`
+ - [V8 lcov.info](https://cenfun.github.io/monocart-coverage-reports/v8/lcov.info)
+ - [Istanbul lcov.info](https://cenfun.github.io/monocart-coverage-reports/istanbul/lcov.info)
+- `none`
+- `teamcity`
+- `text`
+- `text-lcov`
+- `text-summary`
+
+> Other build-in reports (both V8 and Istanbul data):
+
+- `codecov` Save coverage data to a json file with [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format) format (defaults to `codecov.json`), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports).
+
+- `codacy` Save coverage data to a json file with [Codacy API](https://api.codacy.com/swagger#tocscoveragereport) format (defaults to `codacy.json`).
+
+- `console-summary` shows coverage summary in the console.
+
+
+
+- `console-details` Show coverage details in the console. Like `text`, but for V8. For Github actions, we can enforce color with env: `FORCE_COLOR: true`.
+
+
+
+- `markdown-summary` Save coverage summary to a markdown file (defaults to `coverage-summary.md`). For Github actions, we can show the markdown content to [a job summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary)
+```sh
+cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
+```
+
+
+- `markdown-details` Save coverage details to a markdown file (defaults to `coverage-details.md`).
+ - Preview in [runs](https://github.com/cenfun/monocart-coverage-reports/actions/workflows/ci.yml)
+
+- `raw` only keep all original data, which can be used for other reports input with `inputDir`. see [Merge Coverage Reports](#merge-coverage-reports)
+
+- Custom Reporter
+ ```js
+ {
+ reports: [
+ [path.resolve('./test/custom-istanbul-reporter.js'), {
+ type: 'istanbul',
+ file: 'custom-istanbul-coverage.text'
+ }],
+ [path.resolve('./test/custom-v8-reporter.js'), {
+ type: 'v8',
+ outputFile: 'custom-v8-coverage.json'
+ }],
+ [path.resolve('./test/custom-v8-reporter.mjs'), {
+ type: 'both'
+ }]
+ ]
+ }
+ ```
+ - Istanbul custom reporter
+ > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
+ - V8 custom reporter
+ > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
+
+### Multiple Reports:
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = {
+ outputDir: './coverage-reports',
+ reports: [
+ // build-in reports
+ ['console-summary'],
+ ['v8'],
+ ['html', {
+ subdir: 'istanbul'
+ }],
+ ['json', {
+ file: 'my-json-file.json'
+ }],
+ 'lcovonly',
+
+ // custom reports
+ // Specify reporter name with the NPM package
+ ["custom-reporter-1"],
+ ["custom-reporter-2", {
+ type: "istanbul",
+ key: "value"
+ }],
+ // Specify reporter name with local path
+ ['/absolute/path/to/custom-reporter.js']
+ ]
+}
+const mcr = MCR(coverageOptions);
+```
+
+## Compare Reports
+> If the V8 data format is used for Istanbul reports, it will be automatically converted from V8 to Istanbul.
+
+| | Istanbul | V8 | V8 to Istanbul |
+| :--------------| :------ | :------ | :---------------------- |
+| Coverage data | [Istanbul](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) (Object) | [V8](#v8-coverage-data-format) (Array) | [V8](#v8-coverage-data-format) (Array) |
+| Output | [Istanbul reports](#available-reports) | [V8 reports](#available-reports) | [Istanbul reports](#available-reports) |
+| - Bytes | ❌ | ✅ | ❌ |
+| - Statements | ✅ | ✅ | ✅ |
+| - Branches | ✅ | ✅ | ✅ |
+| - Functions | ✅ | ✅ | ✅ |
+| - Lines | ✅ | ✅ | ✅ |
+| - Execution counts | ✅ | ✅ | ✅ |
+| CSS coverage | ❌ | ✅ | ✅ |
+| Minified code | ❌ | ✅ | ❌ |
+
+## Collecting Istanbul Coverage Data
+- Before coverage collection: Instrumenting source code with Istanbul
+ - webpack with babel loader: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), see example: [webpack.config-istanbul.js](./test/build/webpack.config-istanbul.js)
+ - CLI: [nyc instrument](https://github.com/istanbuljs/nyc/blob/master/docs/instrument.md) or API: [istanbul-lib-instrument](https://github.com/istanbuljs/istanbuljs/blob/main/packages/istanbul-lib-instrument/api.md)
+ - vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
+ - rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
+ - swc: [swc-plugin-coverage-instrument](https://github.com/kwonoj/swc-plugin-coverage-instrument)
+
+- Browser
+ - Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](./test/test-istanbul.js)
+
+- Node.js
+ - Collecting coverage data from `global.__coverage__`
+
+- CDP
+ - `getIstanbulCoverage()` see [`CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
+
+## Collecting V8 Coverage Data
+- Before coverage collection: Enabling `sourcemap` for source code
+ - [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](./test/build/webpack.config-v8.js)
+ - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true` and `treeshake: false`
+ - [esbuild](https://esbuild.github.io/api/): `sourcemap: true`, `treeShaking: false` and `minify: false`
+ - [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
+
+- Browser (Chromium-based Only)
+ - [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
+ - [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+
+- Node.js
+ - [Collecting V8 Coverage Data from Node.js](#collecting-v8-coverage-data-from-nodejs)
+
+- CDP
+ - [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
+
+### Collecting V8 Coverage Data with Playwright
+```js
+await Promise.all([
+ page.coverage.startJSCoverage({
+ // reportAnonymousScripts: true,
+ resetOnNavigation: false
+ }),
+ page.coverage.startCSSCoverage({
+ // Note, anonymous styles (without sourceURLs) are not supported, alternatively, you can use CDPClient
+ resetOnNavigation: false
+ })
+]);
+
+await page.goto("your page url");
+
+const [jsCoverage, cssCoverage] = await Promise.all([
+ page.coverage.stopJSCoverage(),
+ page.coverage.stopCSSCoverage()
+]);
+
+const coverageData = [... jsCoverage, ... cssCoverage];
+
+```
+Collect coverage with `@playwright/test` [`Automatic fixtures`](https://playwright.dev/docs/test-fixtures#automatic-fixtures), see example: [fixtures.ts](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.ts)
+For more examples, see [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
+
+
+### Collecting Raw V8 Coverage Data with Puppeteer
+```js
+await Promise.all([
+ page.coverage.startJSCoverage({
+ // reportAnonymousScripts: true,
+ resetOnNavigation: false,
+ // provide raw v8 coverage data
+ includeRawScriptCoverage: true
+ }),
+ page.coverage.startCSSCoverage({
+ resetOnNavigation: false
+ })
+]);
+
+await page.goto("your page url");
+
+const [jsCoverage, cssCoverage] = await Promise.all([
+ page.coverage.stopJSCoverage(),
+ page.coverage.stopCSSCoverage()
+]);
+
+// to raw V8 script coverage
+const coverageData = [... jsCoverage.map((it) => {
+ return {
+ source: it.text,
+ ... it.rawScriptCoverage
+ };
+}), ... cssCoverage];
+```
+Example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
+
+### Collecting V8 Coverage Data from Node.js
+Possible solutions:
+- [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
+ - Sets Node.js env `NODE_V8_COVERAGE`=`dir` before the program running, the coverage data will be saved to the `dir` after the program exits gracefully.
+ - Read the JSON file(s) from the `dir` and generate coverage report.
+ - Example:
+ > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-env` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-report.js](./test/generate-report.js)
+
+- [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
+ - Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()`, it does not require waiting until the program exits gracefully.
+ - Example:
+ > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
+
+- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
+ - Connecting to the V8 inspector and enable V8 coverage.
+ - Taking coverage data and adding it to the report.
+ - Example:
+ > node [./test/test-node-ins.js](./test/test-node-ins.js)
+ - vm Example (scriptOffset):
+ > node [./test/test-node-vm.js](./test/test-node-vm.js)
+
+- [CDP](https://chromedevtools.github.io/devtools-protocol/) API
+ - Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/).
+ - Collecting coverage data with CDP API.
+ - Example:
+ > node --inspect=9229 [./test/test-node-cdp.js](./test/test-node-cdp.js)
+
+- [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started) + CDP + NODE_V8_COVERAGE + V8 API
+ - When the program starts a server, it will not exit on its own, thus requiring a manual invocation of the `v8.takeCoverage()` interface to manually collect coverage data. Remote invocation of the `v8.takeCoverage()` interface can be accomplished through the `Runtime.evaluate` of the CDP.
+ - Example for [koa](https://github.com/koajs/koa) web server:
+ > node [./test/test-node-koa.js](./test/test-node-koa.js)
+
+- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
+ - see [Command Line](#command-line)
+
+### Collecting V8 Coverage Data with `CDPClient` API
+- `CDPClient` available APIs
+```js
+startJSCoverage: () => Promise;
+stopJSCoverage: () => Promise;
+
+startCSSCoverage: () => Promise;
+stopCSSCoverage: () => Promise;
+
+/** start both js and css coverage */
+startCoverage: () => Promise;
+/** stop and return both js and css coverage */
+stopCoverage: () => Promise;
+
+/** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
+writeCoverage: () => Promise;
+
+/** get istanbul coverage data */
+getIstanbulCoverage: (coverageKey?: string) => Promise;
+```
+
+- Work with node debugger port `--inspect=9229` or browser debugging port `--remote-debugging-port=9229`
+```js
+const MCR = require('monocart-coverage-reports');
+const client = await MCR.CDPClient({
+ port: 9229
+});
+await client.startJSCoverage();
+// run your test here
+const coverageData = await client.stopJSCoverage();
+```
+
+- Work with [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
+```js
+const { chromium } = require('playwright');
+const MCR = require('monocart-coverage-reports');
+const browser = await chromium.launch();
+const page = await browser.newPage();
+const session = await page.context().newCDPSession(page);
+const client = await MCR.CDPClient({
+ session
+});
+// both js and css coverage
+await client.startCoverage();
+// run your test page here
+await page.goto("your page url");
+const coverageData = await client.stopCoverage();
+```
+
+- Work with [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
+```js
+const puppeteer = require('puppeteer');
+const MCR = require('monocart-coverage-reports');
+const browser = await puppeteer.launch({});
+const page = await browser.newPage();
+const session = await page.target().createCDPSession();
+const client = await MCR.CDPClient({
+ session
+});
+// both js and css coverage
+await client.startCoverage();
+// run your test page here
+await page.goto("your page url");
+const coverageData = await client.stopCoverage();
+```
+
+- Work with [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (Chrome/Edge Browser)
+```js
+const { Builder, Browser } = require('selenium-webdriver');
+const MCR = require('monocart-coverage-reports');
+const driver = await new Builder().forBrowser(Browser.CHROME).build();
+const pageCdpConnection = await driver.createCDPConnection('page');
+const session = new MCR.WSSession(pageCdpConnection._wsConnection);
+const client = await MCR.CDPClient({
+ session
+})
+```
+
+### V8 Coverage Data API
+- [JavaScript code coverage in V8](https://v8.dev/blog/javascript-code-coverage)
+- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
+- [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
+- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
+```js
+// Coverage data for a source range.
+export interface CoverageRange {
+ // JavaScript script source offset for the range start.
+ startOffset: integer;
+ // JavaScript script source offset for the range end.
+ endOffset: integer;
+ // Collected execution count of the source range.
+ count: integer;
+}
+// Coverage data for a JavaScript function.
+/**
+ * @functionName can be an empty string.
+ * @ranges is always non-empty. The first range is called the "root range".
+ * @isBlockCoverage indicates if the function has block coverage information.
+ If this is false, it usually means that the functions was never called.
+ It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
+*/
+export interface FunctionCoverage {
+ // JavaScript function name.
+ functionName: string;
+ // Source ranges inside the function with coverage data.
+ ranges: CoverageRange[];
+ // Whether coverage data for this function has block granularity.
+ isBlockCoverage: boolean;
+}
+// Coverage data for a JavaScript script.
+export interface ScriptCoverage {
+ // JavaScript script id.
+ scriptId: Runtime.ScriptId;
+ // JavaScript script name or url.
+ url: string;
+ // Functions contained in the script that has coverage data.
+ functions: FunctionCoverage[];
+}
+export type V8CoverageData = ScriptCoverage[];
+```
+
+| JavaScript Runtime | V8 Coverage | |
+| :--------------| :----: | :---------------------- |
+| Chrome (65%) | ✅ | Chromium-based |
+| Safari (18%) | ❌ | |
+| Edge (5%) | ✅ | Chromium-based |
+| Firefox (2%) | ❌ | |
+| Node.js | ✅ | |
+| Deno | ❌ | [issue](https://github.com/denoland/deno/issues/23359) |
+| Bun | ❌ | |
+
+## Filtering Results
+### Using `entryFilter` and `sourceFilter` to filter the results for V8 report
+When V8 coverage data collected, it actually contains the data of all entry files, for example:
+
+- *dist/main.js*
+- *dist/vendor.js*
+- *dist/something-else.js*
+
+We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
+
+- *dist/main.js*
+
+When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
+
+- *src/index.js*
+- *src/components/app.js*
+- *node_modules/dependency/dist/dependency.js*
+
+We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
+
+- *src/index.js*
+- *src/components/app.js*
+
+For example:
+```js
+const coverageOptions = {
+ entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
+ sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
+};
+```
+Or using [`minimatch`](https://github.com/isaacs/minimatch) pattern:
+```js
+const coverageOptions = {
+ entryFilter: "**/main.js",
+ sourceFilter: "**/src/**"
+};
+```
+Support multiple patterns:
+```js
+const coverageOptions = {
+ entryFilter: {
+ '**/node_modules/**': false,
+ '**/vendor.js': false,
+ '**/src/**': true
+ },
+ sourceFilter: {
+ '**/node_modules/**': false,
+ '**/**': true
+ }
+};
+```
+As CLI args (JSON-like string. Added in: v2.8):
+```sh
+mcr --sourceFilter "{'**/node_modules/**':false,'**/**':true}"
+```
+Note, those patterns will be transformed to a function, and the order of the patterns will impact the results:
+```js
+const coverageOptions = {
+ entryFilter: (entry) => {
+ if (minimatch(entry.url, '**/node_modules/**')) { return false; }
+ if (minimatch(entry.url, '**/vendor.js')) { return false; }
+ if (minimatch(entry.url, '**/src/**')) { return true; }
+ return false; // else unmatched
+ }
+};
+```
+
+### Using `filter` instead of `entryFilter` and `sourceFilter`
+If you don't want to define both `entryFilter` and `sourceFilter`, you can use `filter` instead. (Added in: v2.8)
+```js
+const coverageOptions = {
+ // combined patterns
+ filter: {
+ '**/node_modules/**': false,
+ '**/vendor.js': false,
+ '**/src/**': true
+ '**/**': true
+ }
+};
+```
+
+## Resolve `sourcePath` for the Source Files
+If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to resolve a custom path.
+For example, we have tested multiple dist files, which contain some common files. We hope to merge the coverage of the same files, so we need to unify the `sourcePath` in order to be able to merge the coverage data.
+```js
+const coverageOptions = {
+ sourcePath: (filePath) => {
+ // Remove the virtual prefix
+ const list = ['my-dist-file1/', 'my-dist-file2/'];
+ for (const str of list) {
+ if (filePath.startsWith(str)) {
+ return filePath.slice(str.length);
+ }
+ }
+ return filePath;
+ }
+};
+```
+It also supports simple key/value replacement:
+```js
+const coverageOptions = {
+ sourcePath: {
+ 'my-dist-file1/': '',
+ 'my-dist-file2/': ''
+ }
+};
+```
+Normalize the full path of the file:
+```js
+const path = require("path")
+
+// MCR coverage options
+const coverageOptions = {
+ sourcePath: (filePath, info)=> {
+ if (!filePath.includes('/') && info.distFile) {
+ return `${path.dirname(info.distFile)}/${filePath}`;
+ }
+ return filePath;
+ }
+}
+```
+
+## Adding Empty Coverage for Untested Files
+By default the untested files will not be included in the coverage report, we can add empty coverage for untested files with option `all`, the untested files will show 0% coverage.
+```js
+const coverageOptions = {
+ all: './src',
+
+ // or multiple dirs
+ all: ['./src', './lib'],
+};
+```
+The untested files will apply to the `sourceFilter`. And it also supports additional `filter` (return the file type for js or css coverage):
+```js
+const coverageOptions = {
+ all: {
+ dir: ['./src'],
+ filter: {
+ // exclude files
+ '**/ignored-*.js': false,
+ '**/*.html': false,
+ // empty css coverage
+ '**/*.scss': "css",
+ '**/*': true
+ }
+ }
+};
+```
+We can also compile these untested files, such as .ts, .jsx, or .vue, etc., so that they can be analyzed by the default AST parser, thus get more coverage metric data.
+```js
+const path = require("path");
+const swc = require("@swc/core");
+const coverageOptions = {
+ all: {
+ dir: ['./src'],
+ transformer: async (entry) => {
+ const { code, map } = await swc.transform(entry.source, {
+ filename: path.basename(entry.url),
+ sourceMaps: true,
+ isModule: true,
+ jsc: {
+ parser: {
+ syntax: "typescript",
+ jsx: true
+ },
+ transform: {}
+ }
+ });
+ entry.source = code;
+ entry.sourceMap = JSON.parse(map);
+ }
+ }
+};
+```
+
+## onEnd Hook
+For example, checking thresholds:
+```js
+const EC = require('eight-colors');
+const coverageOptions = {
+ name: 'My Coverage Report',
+ outputDir: './coverage-reports',
+ onEnd: (coverageResults) => {
+ const thresholds = {
+ bytes: 80,
+ lines: 60
+ };
+ console.log('check thresholds ...', thresholds);
+ const errors = [];
+ const { summary } = coverageResults;
+ Object.keys(thresholds).forEach((k) => {
+ const pct = summary[k].pct;
+ if (pct < thresholds[k]) {
+ errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
+ }
+ });
+ if (errors.length) {
+ const errMsg = errors.join('\n');
+ console.log(EC.red(errMsg));
+ // throw new Error(errMsg);
+ // process.exit(1);
+ }
+ }
+}
+```
+
+## Ignoring Uncovered Codes
+To ignore codes, use the special comment which starts with `v8 ignore `:
+- Ignoring all until stop
+```js
+/* v8 ignore start */
+function uncovered() {
+}
+/* v8 ignore stop */
+```
+- Ignoring the next line or next N lines
+```js
+/* v8 ignore next */
+const os = platform === 'wind32' ? 'Windows' : 'Other';
+
+const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
+
+// v8 ignore next 3
+if (platform === 'linux') {
+ console.log('hello linux');
+}
+```
+- Compatible with [c8 coverage](https://github.com/bcoe/c8/?tab=readme-ov-file#ignoring-all-lines-until-told) or [nodejs coverage](https://nodejs.org/docs/latest/api/test.html#collecting-code-coverage) syntax
+```js
+/* c8 ignore start */
+function uncovered() {
+}
+/* c8 ignore stop */
+
+/* node:coverage disable */
+function uncovered() {
+}
+/* node:coverage enable */
+```
+
+## Multiprocessing Support
+> The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
+- Main process, before the start of testing
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+// clean previous cache before the start of testing
+// unless the running environment is new and no cache
+mcr.cleanCache();
+```
+
+- Sub process 1, testing stage 1
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.add(coverageData1);
+```
+
+- Sub process 2, testing stage 2
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.add(coverageData2);
+```
+
+- Main process, after the completion of testing
+```js
+// generate coverage reports after the completion of testing
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.generate();
+```
+
+## Command Line
+> The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
+
+- Installing globally
+```sh
+npm i monocart-coverage-reports -g
+mcr node ./test/specs/node.test.js -r v8,console-details --lcov
+```
+
+- Locally in your project
+```sh
+npm i monocart-coverage-reports
+npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
+```
+
+- CLI Options
+see all options with running `mcr` or `mcr --help`
+
+- Use `--` to separate sub CLI args
+```sh
+mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
+```
+
+- Examples
+ - [Mocha](#mocha)
+ - [TypeScript](#typescript)
+ - [AVA](#ava)
+
+## Config File
+Loading config file by priority:
+- Custom config file:
+ - CLI: `mcr --config `
+ - API: `await mcr.loadConfig("my-config-file-path")`
+- `mcr.config.js`
+- `mcr.config.cjs`
+- `mcr.config.mjs`
+- `mcr.config.json` - json format
+- `mcr.config.ts` (requires preloading the ts execution module)
+
+## Merge Coverage Reports
+The following usage scenarios may require merging coverage reports:
+- When the code is executed in different environments, like Node.js `server side` and browser `client side` (`Next.js` for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage.
+- When the code is subjected to different kinds of testing. For example, `unit tests` with `Jest` might cover certain parts of the code, while `end-to-end tests` with `Playwright` might cover other parts. Merging these different coverage reports can provide a holistic view of what code has been tested.
+- When tests are run on different machines or containers, each might produce its own coverage report. Merging these can give a complete picture of the test coverage across all machines or shards.
+
+### Automatic Merging
+- The `MCR` will automatically merge all the added coverage data when executing `generate()`. And it supports adding coverage data asynchronously across processes, see [Multiprocessing Support](#multiprocessing-support)
+- For `Next.js`, it can actually add coverage data including both server side and client side before executing `generate()`, see example [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright)
+- Using `Codecov`, a popular online code coverage service, which supports automatic merging of reports. Please use report `codecov`, it will generate report file `codecov.json`. If multiple `codecov.json` files are generated, upload all these files, they will be automatically merged. see [Codecov](#codecov) and [merging reports](https://docs.codecov.com/docs/merging-reports)
+
+### Manual Merging
+If the reports cannot be merged automatically, then here is how to manually merge the reports.
+First, using the `raw` report to export the original coverage data to the specified directory.
+- For example, we have `raw` coverage data from `unit test`, which is output to `./coverage-reports/unit/raw`. Unit test examples:
+ - `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
+ - `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
+```js
+const coverageOptions = {
+ name: 'My Unit Test Coverage Report',
+ outputDir: "./coverage-reports/unit",
+ reports: [
+ ['raw', {
+ // relative path will be "./coverage-reports/unit/raw"
+ // defaults to raw
+ outputDir: "raw"
+ }],
+ ['v8'],
+ ['console-details']
+ ]
+};
+```
+
+- We also have `raw` coverage data from `e2e test`, which is output to `./coverage-reports/e2e/raw`. E2E test examples:
+ - `Playwright` + [monocart-reporter](https://github.com/cenfun/monocart-reporter) with coverage API
+ - `Playwright` + `MCR`, see [playwright-coverage](https://github.com/cenfun/playwright-coverage)
+ - see more [Integration Examples](#integration-examples)
+
+- Then create a script `merge-coverage.js` to generate a merged report with option `inputDir`.
+```js
+// merge-coverage.js
+const fs = require('fs');
+const { CoverageReport } = require('monocart-coverage-reports');
+const inputDir = [
+ './coverage-reports/unit/raw',
+ './coverage-reports/e2e/raw'
+];
+const coverageOptions = {
+ name: 'My Merged Coverage Report',
+ inputDir,
+ outputDir: './coverage-reports/merged',
+
+ // filter for both unit and e2e
+ entryFilter: {
+ '**/node_modules/**': false,
+ '**/*': true
+ },
+ sourceFilter: {
+ '**/node_modules/**': false,
+ '**/src/**': true
+ },
+
+ sourcePath: (filePath, info) => {
+ // Unify the file path for the same files
+ // For example, the file index.js has different paths:
+ // unit: unit-dist/src/index.js
+ // e2e: e2e-dist/src/index.js
+ // return filePath.replace("unit-dist/", "").replace("e2e-dist/", "")
+ return filePath;
+ },
+
+ reports: [
+ ['v8'],
+ ['console-details']
+ ],
+
+ onEnd: () => {
+ // remove the raw files if it useless
+ // inputDir.forEach((p) => {
+ // fs.rmSync(p, {
+ // recursive: true,
+ // force: true
+ // });
+ // });
+ }
+};
+await new CoverageReport(coverageOptions).generate();
+```
+- Running script `node path/to/merge-coverage.js` after all the tests are completed. All the command scripts are probably like following:
+```json
+{
+ "scripts": {
+ "test:unit": "jest",
+ "test:e2e": "playwright test",
+ "merge-coverage": "node path/to/merge-coverage.js",
+ "test": "npm run test:unit && npm run test:e2e && npm run merge-coverage"
+ }
+}
+```
+see example: [merge-code-coverage](https://github.com/cenfun/merge-code-coverage)
+
+## Common issues
+### Unexpected coverage
+In most cases, it happens when the coverage of the generated code is converted to the coverage of the original code through a sourcemap. In other words, it's an issue with the sourcemap. Most of the time, we can solve this by setting `minify` to `false` in the configuration of build tools. Let's take a look at an example:
+```js
+const a = tf ? 'true' : 'false';
+ ^ ^ ^
+ m1 p m2
+```
+In the generated code, there is a position `p`, and we need to find out its corresponding position in the original code. Unfortunately, there is no matched mapping for the position `p`. Instead, it has two adjacent upstream and downstream mappings `m1` and `m2`, so, the original position of `p` that we are looking for, might not be able to be precisely located. Especially, the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact original position without matched mapping.
+- Further understanding of sourcemap, try [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
+
+How `MCR` Works:
+- 1, Trying to fix the original position with string comparison and [`diff-sequences`](https://github.com/jestjs/jest/tree/main/packages/diff-sequences). However, for non-JS code, such as Vue template, JSX, etc., it might be hard to find a perfect solution.
+- 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). (There is a small issue is the V8 cannot provide effective branch coverage information for `AssignmentPattern`)
+
+
+### Unparsable source
+It happens during the parsing of the source code into AST, if the source code is not in the standard ECMAScript. For example `ts`, `jsx` and so on. There is a option to fix it, which is to manually compile the source code for these files.
+```js
+import * as fs from "fs";
+import * as path from "path";
+import { fileURLToPath } from "url";
+import * as TsNode from 'ts-node';
+const coverageOptions = {
+ onEntry: async (entry) => {
+ const filePath = fileURLToPath(entry.url)
+ const originalSource = fs.readFileSync(filePath).toString("utf-8");
+ const fileName = path.basename(filePath);
+ const tn = TsNode.create({});
+ const source = tn.compile(originalSource, fileName);
+ entry.fake = false;
+ entry.source = source;
+ }
+}
+```
+
+### JavaScript heap out of memory
+When there are a lot of raw v8 coverage files to process, it may cause OOM. We can try the following Node.js options:
+```sh
+- run: npm run test:coverage
+ env:
+ NODE_OPTIONS: --max-old-space-size=8192
+```
+
+
+## Debug for Coverage and Sourcemap
+> Sometimes, the coverage is not what we expect. The next step is to figure out why, and we can easily find out the answer step by step through debugging.
+- Start debugging for v8 report with option `logging: 'debug'`
+```js
+const coverageOptions = {
+ logging: 'debug',
+ reports: [
+ ['v8'],
+ ['console-details']
+ ]
+};
+```
+When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless.
+
+
+- Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/)
+
+
+
+- Generate additional source and sourcemap files to cache or raw dir
+```js
+const coverageOptions = {
+ logging: 'debug',
+ sourceMap: true
+};
+```
+
+- Show time logs with env `MCR_LOG_TIME`
+```js
+process.env.MCR_LOG_TIME = true
+```
+
+## Integration with Any Testing Framework
+- API
+ - Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter. `await mcr.add(coverageData)`
+ - Generating the coverage reports after the completion of all tests. `await mcr.generate()`
+ - see [Multiprocessing Support](#multiprocessing-support)
+- CLI
+ - Wrapping with any CLI. `mcr your-cli --your-arguments`
+ - see [Command line](#command-line)
+
+## Integration Examples
+
+### [Playwright](https://github.com/microsoft/playwright)
+- [playwright-coverage](https://github.com/cenfun/playwright-coverage) - Example for Playwright coverage reports
+- [playwright-bdd-coverage](https://github.com/cenfun/playwright-bdd-coverage) - Example for Playwright BDD coverage reports
+- [monocart-reporter](https://github.com/cenfun/monocart-reporter) - Playwright custom reporter, supports generating [Code coverage report](https://github.com/cenfun/monocart-reporter?#code-coverage-report)
+- Coverage for component testing with `monocart-reporter`:
+ - [playwright-ct-vue](https://github.com/cenfun/playwright-ct-vue)
+ - [playwright-ct-react](https://github.com/cenfun/playwright-ct-react)
+ - [playwright-ct-svelte](https://github.com/cenfun/playwright-ct-svelte)
+- Coverage for Next.js, both server side and client side:
+ - [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright)
+ - [nextjs-with-playwright-istanbul](https://github.com/cenfun/nextjs-with-playwright-istanbul)
+- Coverage for Remix:
+ - [remix-with-playwright](https://github.com/cenfun/remix-with-playwright)
+- see [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
+
+### [c8](https://github.com/bcoe/c8)
+- c8 has integrated `MCR` as an experimental feature since [v10.1.0](https://github.com/bcoe/c8/releases/tag/v10.1.0)
+```sh
+c8 --experimental-monocart --reporter=v8 --reporter=console-details node foo.js
+```
+
+### [CodeceptJS](https://github.com/codeceptjs/CodeceptJS)
+- CodeceptJS is a [BDD](https://codecept.io/bdd/) + [AI](https://codecept.io/ai/) testing framework for e2e testing, it has integrated `MCR` since [v3.5.15](https://github.com/codeceptjs/CodeceptJS/releases/tag/3.5.15), see [plugins/coverage](https://codecept.io/plugins/#coverage)
+
+### [VSCode](https://github.com/microsoft/vscode)
+- [Monocart Coverage for VSCode](https://github.com/cenfun/monocart-coverage-vscode) - Shows native V8 code coverage in VSCode
+
+### [Jest](https://github.com/jestjs/jest/)
+- [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - Jest custom reporter for coverage reports
+- [merge-code-coverage](https://github.com/cenfun/merge-code-coverage) - Example for merging code coverage (Jest unit + Playwright e2e sharding)
+
+### [Vitest](https://github.com/vitest-dev/vitest)
+- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Vitest custom provider module for coverage reports
+- [merge-code-coverage-vitest](https://github.com/cenfun/merge-code-coverage-vitest) - Example for merging code coverage (Vitest unit + Playwright e2e sharding)
+
+### [Node Test Runner](https://nodejs.org/docs/latest/api/test.html)
+- [node-monocart-coverage](https://github.com/cenfun/node-monocart-coverage) - Custom reporter for Node test runner for coverage
+
+### [Puppeteer](https://github.com/puppeteer/puppeteer/)
+- [jest-puppeteer-coverage](https://github.com/cenfun/jest-puppeteer-coverage) - Example for Jest puppeteer coverage
+- [maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js) - Example for Jest (unit) + Puppeteer (e2e) + Codecov
+- see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+
+### [Cypress](https://github.com/cypress-io/cypress)
+- [cypress-monocart-coverage](https://github.com/cenfun/cypress-monocart-coverage) - Cypress plugin for coverage reports
+
+### [WebdriverIO](https://github.com/webdriverio/webdriverio)
+- [wdio-monocart-service](https://github.com/cenfun/wdio-monocart-service) - WebdriverIO service for coverage reports
+
+### [Storybook Test Runner](https://github.com/storybookjs/test-runner)
+- [storybook-monocart-coverage](https://github.com/cenfun/storybook-monocart-coverage) - Example for Storybook V8 coverage reports
+
+### [TestCafe](https://github.com/DevExpress/testcafe)
+- [testcafe-reporter-coverage](https://github.com/cenfun/testcafe-reporter-coverage) - TestCafe custom reporter for coverage reports
+
+### [Selenium Webdriver](https://github.com/seleniumhq/selenium)
+- [selenium-webdriver-coverage](https://github.com/cenfun/selenium-webdriver-coverage) - Example for Selenium Webdriver V8 coverage reports
+
+### [Mocha](https://github.com/mochajs/mocha)
+```sh
+mcr mocha ./test/**/*.js
+```
+
+### [TypeScript](https://github.com/microsoft/typescript)
+
+- [tsx](https://github.com/privatenumber/tsx)
+```sh
+cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
+cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
+# Node.js v18.19.0 +
+mcr --import tsx tsx ./src/example.ts
+mcr --import tsx mocha ./test/**/*.ts
+```
+- [ts-node](https://github.com/TypeStrong/ts-node)
+```sh
+cross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr ts-node ./src/example.ts
+cross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr mocha ./test/**/*.ts
+```
+
+### [AVA](https://github.com/avajs/ava)
+```sh
+mcr ava
+```
+
+### [Codecov](https://codecov.com/)
+[](https://codecov.io/gh/cenfun/monocart-coverage-reports)
+- Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ reports: [
+ ['codecov']
+ ]
+};
+```
+- Github actions:
+```yml
+- name: Codecov
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./coverage-reports/codecov.json
+```
+
+### [Codacy](https://www.codacy.com/)
+[](https://app.codacy.com/gh/cenfun/monocart-coverage-reports/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
+- Using `lcov` report:
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ lcov: true
+};
+```
+- Github actions:
+```yml
+- name: Codacy Coverage Reporter
+ uses: codacy/codacy-coverage-reporter-action@v1
+ with:
+ project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
+ coverage-reports: ./docs/mcr/lcov.info
+```
+
+### [Coveralls](https://coveralls.io/)
+[](https://coveralls.io/github/cenfun/monocart-coverage-reports?branch=main)
+- Using `lcov` report:
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ lcov: true
+};
+```
+- Github actions:
+```yml
+- name: Coveralls
+ uses: coverallsapp/github-action@v2
+ with:
+ files: ./coverage-reports/lcov.info
+```
+
+### [Sonar Cloud](https://sonarcloud.io/)
+[](https://sonarcloud.io/summary/new_code?id=monocart-coverage-reports)
+- Using `lcov` report. Github actions example:
+```yml
+- name: Analyze with SonarCloud
+ uses: sonarsource/sonarcloud-github-action@master
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ with:
+ projectBaseDir: ./
+ args: >
+ -Dsonar.organization=cenfun
+ -Dsonar.projectKey=monocart-coverage-reports
+ -Dsonar.projectName=monocart-coverage-reports
+ -Dsonar.javascript.lcov.reportPaths=docs/mcr/lcov.info
+ -Dsonar.sources=lib
+ -Dsonar.tests=test
+ -Dsonar.exclusions=dist/*,packages/*
+```
+
+## Contributing
+- Node.js 20+
+- VSCode (extensions: eslint/stylelint/vue)
+```sh
+npm install
+npx playwright install --with-deps
+
+npm run build
+npm run test
+
+npm run dev
+```
+- Refreshing `eol=lf` for snapshot of test (Windows)
+```sh
+git add . -u
+git commit -m "Saving files before refreshing line endings"
+
+npm run eol
+```
+
+## Thanks
+- [@bcoe](https://github.com/bcoe)
+- [@edumserrano](https://github.com/edumserrano)
\ No newline at end of file
diff --git a/node_modules/monocart-coverage-reports/README.zh-Hans.md b/node_modules/monocart-coverage-reports/README.zh-Hans.md
new file mode 100644
index 0000000..3d33448
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/README.zh-Hans.md
@@ -0,0 +1,1191 @@
+# Monocart Coverage Reports
+
+[](https://www.npmjs.com/package/monocart-coverage-reports)
+
+
+[](https://packagephobia.com/result?p=monocart-coverage-reports)
+[](https://npmgraph.js.org/?q=monocart-coverage-reports)
+
+[](https://www.npmjs.com/package/monocart-coverage-reports)
+
+🌐 [English](README.md) | 简体中文
+
+> JS代码覆盖率工具,用来生成原生的[V8](https://v8.dev/blog/javascript-code-coverage)或者[Istanbul](https://istanbul.js.org/)代码覆盖率报告
+
+* [用法](#usage)
+* [选项配置](#options)
+* [所有支持的报告类型](#available-reports)
+* [比较两种报告](#compare-reports)
+* [如何收集Istanbul覆盖率数据](#collecting-istanbul-coverage-data)
+* [如何收集V8覆盖率数据](#collecting-v8-coverage-data)
+ - [用Playwright](#collecting-v8-coverage-data-with-playwright)
+ - [用Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+ - [从Node.js](#collecting-v8-coverage-data-from-nodejs)
+ - [使用`CDPClient`API](#collecting-v8-coverage-data-with-cdpclient-api)
+ - [参考V8覆盖率的API](#v8-coverage-data-api)
+* [过滤V8覆盖率数据](#filtering-results)
+* [使用 `sourcePath` 修改源文件路径](#resolve-sourcepath-for-the-source-files)
+* [为未测试的文件添加空的覆盖率报告](#adding-empty-coverage-for-untested-files)
+* [onEnd回调函数](#onend-hook)
+* [如何忽略未覆盖的代码](#ignoring-uncovered-codes)
+* [多进程支持](#multiprocessing-support)
+* [如何使用CLI命令行](#command-line)
+* [如何加载配置文件](#config-file)
+* [如何合并覆盖率报告](#merge-coverage-reports)
+ - [自动合并](#automatic-merging)
+ - [手动合并](#manual-merging)
+* [常见问题](#common-issues)
+ - [Unexpected coverage](#unexpected-coverage)
+ - [Unparsable source](#unparsable-source)
+ - [JavaScript heap out of memory](#javascript-heap-out-of-memory)
+* [如何调试覆盖率数据和查看sourcemap](#debug-for-coverage-and-sourcemap)
+* [如何跟其他框架集成](#integration-with-any-testing-framework)
+* [集成的例子](#integration-examples)
+ - [Playwright](#playwright)
+ - [c8](#c8)
+ - [CodeceptJS](#codeceptjs)
+ - [VSCode](#vscode)
+ - [Jest](#jest)
+ - [Vitest](#vitest)
+ - [Node Test Runner](#node-test-runner)
+ - [Puppeteer](#puppeteer)
+ - [Cypress](#cypress)
+ - [WebdriverIO](#webdriverio)
+ - [Storybook Test Runner](#storybook-test-runner)
+ - [TestCafe](#testcafe)
+ - [Selenium Webdriver](#selenium-webdriver)
+ - [Mocha](#mocha)
+ - [TypeScript](#typescript)
+ - [AVA](#ava)
+ - [Codecov](#codecov)
+ - [Codacy](#codacy)
+ - [Coveralls](#coveralls)
+ - [Sonar Cloud](#sonar-cloud)
+* [Contributing](#contributing)
+* [更新日志](CHANGELOG.md)
+* [感谢](#thanks)
+
+## Usage
+> 推荐使用 [Node.js 20+](https://nodejs.org/).
+- 安装
+```sh
+npm install monocart-coverage-reports
+```
+- API
+```js
+const MCR = require('monocart-coverage-reports');
+const mcr = MCR({
+ name: 'My Coverage Report - 2024-02-28',
+ outputDir: './coverage-reports',
+ reports: ["v8", "console-details"],
+ cleanCache: true
+});
+await mcr.add(coverageData);
+await mcr.generate();
+```
+也可以使用ESM的 `import` 然后加载[配置文件](#config-file)
+```js
+import { CoverageReport } from 'monocart-coverage-reports';
+const mcr = new CoverageReport();
+await mcr.loadConfig();
+```
+参见 [多进程支持](#multiprocessing-support)
+
+- CLI
+```sh
+mcr node my-app.js -r v8,console-details
+```
+参见 [命令行](#command-line)
+
+## Options
+- 默认选项: [lib/default/options.js](./lib/default/options.js)
+- 选项的类型描述,见 `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
+- [配置文件](#config-file)
+
+## Available Reports
+
+> 内置V8报告(仅V8格式数据支持):
+
+- `v8`
+ - 推荐使用:
+ - 全新的原生V8覆盖率报告界面,更好的用户体验
+ - 支持原生的Bytes覆盖率指标
+ - 支持高性能处理大数据
+ - 支持任何运行时代码的覆盖率(压缩后的)
+ - 支持CSS代码覆盖率(用于分析CSS的冗余代码)
+ - 对Sourcemap转换有更好的支持
+ - 预览: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [more](https://cenfun.github.io/monocart-coverage-reports/)
+
+
+
+- `v8-json`
+ - 保存 `CoverageResults` 到一个json文件 (默认是 [`coverage-report.json`](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json))
+ - 用于VSCode扩展来显示原生V8代码覆盖率: [Monocart Coverage for VSCode](https://github.com/cenfun/monocart-coverage-vscode)
+
+
+
+> 内置Istanbul报告 (V8和Istanbul格式数据都支持):
+
+- `clover`
+- `cobertura`
+- `html`
+ - [Istanbul html](https://cenfun.github.io/monocart-coverage-reports/istanbul/)
+ - [V8 to Istanbul](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/istanbul)
+- `html-spa`
+- `json`
+- `json-summary`
+- `lcov`
+- `lcovonly`
+ - [V8 lcov.info](https://cenfun.github.io/monocart-coverage-reports/v8/lcov.info)
+ - [Istanbul lcov.info](https://cenfun.github.io/monocart-coverage-reports/istanbul/lcov.info)
+- `none`
+- `teamcity`
+- `text`
+- `text-lcov`
+- `text-summary`
+
+> 其他内置报告 (V8和Istanbul格式数据都支持):
+
+- `codecov` 保存覆盖率数据到 [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format) 专属的json文件 (默认是`codecov.json`), 见[例子](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
+
+- `codacy` 保存覆盖率数据到 [Codacy](https://api.codacy.com/swagger#tocscoveragereport) 专属的json文件 (默认是`codacy.json`)
+
+- `console-summary` 在控制台显示覆盖率概要
+
+
+
+- `console-details` 在控制台显示每个文件的覆盖率概要。如果是Github actions,可以使用环境变量`FORCE_COLOR: true`来强制开启颜色支持
+
+
+
+- `markdown-summary` 保存概要信息到markdown文件 (默认是`coverage-summary.md`)。 如果是Github actions, 可以把markdown的内容添加到[a job summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary)
+```sh
+cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
+```
+
+
+- `markdown-details` 保存覆盖率详情到markdown文件 (默认是 `coverage-details.md`)
+ - 预览运行结果 [runs](https://github.com/cenfun/monocart-coverage-reports/actions/workflows/ci.yml)
+
+- `raw` 只是保存原始覆盖率数据, 用于使用`inputDir`参数来导入多个原始数据进行合并报告。参见 [合并覆盖率报告](#merge-coverage-reports)
+
+- 自定义报告
+ ```js
+ {
+ reports: [
+ [path.resolve('./test/custom-istanbul-reporter.js'), {
+ type: 'istanbul',
+ file: 'custom-istanbul-coverage.text'
+ }],
+ [path.resolve('./test/custom-v8-reporter.js'), {
+ type: 'v8',
+ outputFile: 'custom-v8-coverage.json'
+ }],
+ [path.resolve('./test/custom-v8-reporter.mjs'), {
+ type: 'both'
+ }]
+ ]
+ }
+ ```
+ - Istanbul自定义报告
+ > 例子: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
+ - V8自定义报告
+ > 例子: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
+
+### Multiple Reports:
+如何配置多个报告
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = {
+ outputDir: './coverage-reports',
+ reports: [
+ // build-in reports
+ ['console-summary'],
+ ['v8'],
+ ['html', {
+ subdir: 'istanbul'
+ }],
+ ['json', {
+ file: 'my-json-file.json'
+ }],
+ 'lcovonly',
+
+ // custom reports
+ // Specify reporter name with the NPM package
+ ["custom-reporter-1"],
+ ["custom-reporter-2", {
+ type: "istanbul",
+ key: "value"
+ }],
+ // Specify reporter name with local path
+ ['/absolute/path/to/custom-reporter.js']
+ ]
+}
+const mcr = MCR(coverageOptions);
+```
+
+## Compare Reports
+> 如果是V8数据格式使用Istanbul的报告,将自动从V8转换到Istanbul
+
+| | Istanbul | V8 | V8 to Istanbul |
+| :--------------| :------ | :------ | :---------------------- |
+| 数据格式 | [Istanbul](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) (Object) | [V8](#v8-coverage-data-format) (Array) | [V8](#v8-coverage-data-format) (Array) |
+| 输出报告 | [Istanbul reports](#available-reports) | [V8 reports](#available-reports) | [Istanbul reports](#available-reports) |
+| - Bytes 字节覆盖率 | ❌ | ✅ | ❌ |
+| - Statements 语句覆盖率 | ✅ | ✅ | ✅ |
+| - Branches 分支覆盖率 | ✅ | ✅ | ✅ |
+| - Functions 函数覆盖率 | ✅ | ✅ | ✅ |
+| - Lines 行覆盖率 | ✅ | ✅ | ✅ |
+| - Execution counts 函数执行数 | ✅ | ✅ | ✅ |
+| CSS 覆盖率 | ❌ | ✅ | ✅ |
+| 压缩过的代码 | ❌ | ✅ | ❌ |
+
+## Collecting Istanbul Coverage Data
+- 在收集Istanbul覆盖率数据之前,需要编译源代码来安装Istanbul计数器
+ - webpack babel-loader: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), 参见例子: [webpack.config-istanbul.js](./test/build/webpack.config-istanbul.js)
+ - 官方CLI: [nyc instrument](https://github.com/istanbuljs/nyc/blob/master/docs/instrument.md) 或API: [istanbul-lib-instrument](https://github.com/istanbuljs/istanbuljs/blob/main/packages/istanbul-lib-instrument/api.md)
+ - vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
+ - rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
+ - swc: [swc-plugin-coverage-instrument](https://github.com/kwonoj/swc-plugin-coverage-instrument)
+
+- 从浏览器
+ - Istanbul的覆盖率数据会保存到全局的`window.__coverage__`,直接读取即可, 参见例子: [test-istanbul.js](./test/test-istanbul.js)
+
+- 从Node.js
+ - 同理对于Node.js会保存到全局的`global.__coverage__`
+
+- 使用CDP
+ - `getIstanbulCoverage()` 参见[`CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
+
+## Collecting V8 Coverage Data
+- 在收集V8覆盖率数据之前,需要开启构建工具的`sourcemap`支持,并且不要压缩代码
+ - [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](./test/build/webpack.config-v8.js)
+ - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true` and `treeshake: false`
+ - [esbuild](https://esbuild.github.io/api/): `sourcemap: true`, `treeShaking: false` and `minify: false`
+ - [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
+
+- 浏览器 (仅支持基于Chromium的浏览器)
+ - [使用Playwright](#collecting-v8-coverage-data-with-playwright)
+ - [使用Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+
+- 从Node.js
+ - [从Node.js收集V8覆盖率数据](#collecting-v8-coverage-data-from-nodejs)
+
+- 使用CDP
+ - [使用`CDPClient` API收集V8覆盖率数据](#collecting-v8-coverage-data-with-cdpclient-api)
+
+### Collecting V8 Coverage Data with Playwright
+使用Playwright的覆盖接口收集覆盖率数据
+```js
+await Promise.all([
+ page.coverage.startJSCoverage({
+ // reportAnonymousScripts: true,
+ resetOnNavigation: false
+ }),
+ page.coverage.startCSSCoverage({
+ // Note, anonymous styles (without sourceURLs) are not supported, alternatively, you can use CDPClient
+ resetOnNavigation: false
+ })
+]);
+
+await page.goto("your page url");
+
+const [jsCoverage, cssCoverage] = await Promise.all([
+ page.coverage.stopJSCoverage(),
+ page.coverage.stopCSSCoverage()
+]);
+
+const coverageData = [... jsCoverage, ... cssCoverage];
+
+```
+使用 `@playwright/test` 的 [`Automatic fixtures`](https://playwright.dev/docs/test-fixtures#automatic-fixtures)收集覆盖率数据, 见例子: [fixtures.ts](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.ts)
+参见例子 [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
+
+
+### Collecting Raw V8 Coverage Data with Puppeteer
+使用Puppeteer的覆盖接口收集覆盖率数据,注意Puppeteer默认不会提供原生V8的覆盖率数据,需要设置`includeRawScriptCoverage`
+```js
+await Promise.all([
+ page.coverage.startJSCoverage({
+ // reportAnonymousScripts: true,
+ resetOnNavigation: false,
+ // provide raw v8 coverage data
+ includeRawScriptCoverage: true
+ }),
+ page.coverage.startCSSCoverage({
+ resetOnNavigation: false
+ })
+]);
+
+await page.goto("your page url");
+
+const [jsCoverage, cssCoverage] = await Promise.all([
+ page.coverage.stopJSCoverage(),
+ page.coverage.stopCSSCoverage()
+]);
+
+// to raw V8 script coverage
+const coverageData = [... jsCoverage.map((it) => {
+ return {
+ source: it.text,
+ ... it.rawScriptCoverage
+ };
+}), ... cssCoverage];
+```
+参见: [./test/test-puppeteer.js](./test/test-puppeteer.js)
+
+### Collecting V8 Coverage Data from Node.js
+有多种方法可以从Node.js收集V8覆盖率数据:
+- [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
+ - 使用Node.js环境变量`NODE_V8_COVERAGE`=`dir`来启动程序, 然后在进程正常结束之后,覆盖率数据将自动保存到指定的`dir`目录.
+ - 从`dir`目录读取所有的JSON文件,来生成覆盖率报告
+ - 参见例子:
+ > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-env` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-report.js](./test/generate-report.js)
+
+- [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
+ - 如果进程不能正常结束,比如被强制关闭,或者压根就不结束,比如启动了一个服务类的,那么需要手动写入覆盖率数据,这里需要调用接口`v8.takeCoverage()`
+ - 参见例子:
+ > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
+
+- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
+ - 首先连接到Node.js的V8 inspector
+ - 然后使用inspector的覆盖相关API来开启和收集覆盖率数据
+ - 参见例子:
+ > node [./test/test-node-ins.js](./test/test-node-ins.js)
+ - vm的例子 (注意这里需要使用`scriptOffset`,因为vm里一般都会加一层包裹代码,需要这个偏移位置来修正覆盖率数据块的位置):
+ > node [./test/test-node-vm.js](./test/test-node-vm.js)
+
+- [CDP](https://chromedevtools.github.io/devtools-protocol/) API
+ - 开启[Node调试](https://nodejs.org/en/guides/debugging-getting-started/)
+ - 使用CDP的覆盖率接口开启和收集覆盖率数据
+ - 参见例子:
+ > node --inspect=9229 [./test/test-node-cdp.js](./test/test-node-cdp.js)
+
+- [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started) + CDP + NODE_V8_COVERAGE + V8 API
+ - 如果启动了一个Node服务,可以手动调用`v8.takeCoverage()`接口来保存覆盖率数据,开启Node调试就可以远程通过CDP连接的`Runtime.evaluate`,来调用这个接口.
+ - 参见[koa](https://github.com/koajs/koa)的例子:
+ > node [./test/test-node-koa.js](./test/test-node-koa.js)
+
+- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
+ - 如果是子进程,可参见 [命令行](#command-line)
+
+### Collecting V8 Coverage Data with `CDPClient` API
+- `CDPClient`为`MCR`提供的内置接口类,用来更便捷的处理覆盖率相关数据,所有的API如下
+```js
+// 开始和停止并收集JS的覆盖率数据
+startJSCoverage: () => Promise;
+stopJSCoverage: () => Promise;
+
+// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
+startCSSCoverage: () => Promise;
+stopCSSCoverage: () => Promise;
+
+// 开始和停止并收集JS和CSS的覆盖率数据
+startCoverage: () => Promise;
+stopCoverage: () => Promise;
+
+/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
+writeCoverage: () => Promise;
+
+/** 收集istanbul覆盖率数据 */
+getIstanbulCoverage: (coverageKey?: string) => Promise;
+```
+
+- 结合使用Node调试端口`--inspect=9229` 或者浏览器调试端口 `--remote-debugging-port=9229`
+```js
+const MCR = require('monocart-coverage-reports');
+const client = await MCR.CDPClient({
+ port: 9229
+});
+await client.startJSCoverage();
+// run your test here
+const coverageData = await client.stopJSCoverage();
+```
+
+- 结合使用 [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
+```js
+const { chromium } = require('playwright');
+const MCR = require('monocart-coverage-reports');
+const browser = await chromium.launch();
+const page = await browser.newPage();
+const session = await page.context().newCDPSession(page);
+const client = await MCR.CDPClient({
+ session
+});
+// both js and css coverage
+await client.startCoverage();
+// run your test page here
+await page.goto("your page url");
+const coverageData = await client.stopCoverage();
+```
+
+- 结合使用 [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
+```js
+const puppeteer = require('puppeteer');
+const MCR = require('monocart-coverage-reports');
+const browser = await puppeteer.launch({});
+const page = await browser.newPage();
+const session = await page.target().createCDPSession();
+const client = await MCR.CDPClient({
+ session
+});
+// both js and css coverage
+await client.startCoverage();
+// run your test page here
+await page.goto("your page url");
+const coverageData = await client.stopCoverage();
+```
+
+- 结合使用 [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (仅支持Chrome/Edge浏览器)
+```js
+const { Builder, Browser } = require('selenium-webdriver');
+const MCR = require('monocart-coverage-reports');
+const driver = await new Builder().forBrowser(Browser.CHROME).build();
+const pageCdpConnection = await driver.createCDPConnection('page');
+const session = new MCR.WSSession(pageCdpConnection._wsConnection);
+const client = await MCR.CDPClient({
+ session
+})
+```
+
+### V8 Coverage Data API
+- [JavaScript V8代码覆盖官方说明](https://v8.dev/blog/javascript-code-coverage)
+- [Playwright的覆盖率接口](https://playwright.dev/docs/api/class-coverage)
+- [Puppeteer的覆盖率接口](https://pptr.dev/api/puppeteer.coverage)
+- [DevTools Protocol的覆盖率接口](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) 参见 [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) 和 [v8-coverage](https://github.com/bcoe/v8-coverage)
+```js
+// Coverage data for a source range.
+export interface CoverageRange {
+ // JavaScript script source offset for the range start.
+ startOffset: integer;
+ // JavaScript script source offset for the range end.
+ endOffset: integer;
+ // Collected execution count of the source range.
+ count: integer;
+}
+// Coverage data for a JavaScript function.
+/**
+ * @functionName can be an empty string.
+ * @ranges is always non-empty. The first range is called the "root range".
+ * @isBlockCoverage indicates if the function has block coverage information.
+ If this is false, it usually means that the functions was never called.
+ It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
+*/
+export interface FunctionCoverage {
+ // JavaScript function name.
+ functionName: string;
+ // Source ranges inside the function with coverage data.
+ ranges: CoverageRange[];
+ // Whether coverage data for this function has block granularity.
+ isBlockCoverage: boolean;
+}
+// Coverage data for a JavaScript script.
+export interface ScriptCoverage {
+ // JavaScript script id.
+ scriptId: Runtime.ScriptId;
+ // JavaScript script name or url.
+ url: string;
+ // Functions contained in the script that has coverage data.
+ functions: FunctionCoverage[];
+}
+export type V8CoverageData = ScriptCoverage[];
+```
+
+| JavaScript Runtime | V8 Coverage | |
+| :--------------| :----: | :---------------------- |
+| Chrome (65%) | ✅ | Chromium-based |
+| Safari (18%) | ❌ | |
+| Edge (5%) | ✅ | Chromium-based |
+| Firefox (2%) | ❌ | |
+| Node.js | ✅ | |
+| Deno | ❌ | [issue](https://github.com/denoland/deno/issues/23359) |
+| Bun | ❌ | |
+
+## Filtering Results
+## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
+当收集到V8的覆盖数据时,它实际上包含了所有的入口文件的覆盖率数据, 比如有以下3个文件:
+
+- *dist/main.js*
+- *dist/vendor.js*
+- *dist/something-else.js*
+
+这个时候可以使用`entryFilter`来过滤这些入口文件. 比如我们不需要看到`vendor.js`和`something-else.js`的覆盖率,就可以过滤掉,只剩下1个文件
+
+- *dist/main.js*
+
+如果一个入口文件存在行内或者链接的sourcemap文件,那么我们会尝试读取并解析sourcemap,以获取入口文件包含的所有源文件,并添加到列表。此时如果`logging`没有设置成`debug`,那么这个入口文件在成功解出源文件后会被移除
+
+- *src/index.js*
+- *src/components/app.js*
+- *node_modules/dependency/dist/dependency.js*
+
+这个时候可以使用`sourceFilter`来过滤这些源文件。比如我们不需要看到源文件`dependency.js`的覆盖率,就可以过滤掉,最后只剩下如下文件
+
+- *src/index.js*
+- *src/components/app.js*
+
+过滤可以使用函数:
+```js
+const coverageOptions = {
+ entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
+ sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
+};
+```
+也可以使用便捷的[`minimatch`](https://github.com/isaacs/minimatch)来匹配(推荐):
+```js
+const coverageOptions = {
+ entryFilter: "**/main.js",
+ sourceFilter: "**/src/**"
+};
+```
+支持多个匹配:
+```js
+const coverageOptions = {
+ entryFilter: {
+ '**/node_modules/**': false,
+ '**/vendor.js': false,
+ '**/src/**': true
+ },
+ sourceFilter: {
+ '**/node_modules/**': false,
+ '**/**': true
+ }
+};
+```
+作为CLI参数(JSON字符串,Added in: v2.8):
+```sh
+mcr --sourceFilter "{'**/node_modules/**':false,'**/**':true}"
+```
+注意,这些匹配实际上会转换成一个过滤函数(如下),所以如果一个匹配成功则会直接返回,后面的将不再继续匹配。请注意先后顺序,如果存在包含关系的,可以调整上下顺序,最后如果都未匹配,则默认返回false
+```js
+const coverageOptions = {
+ entryFilter: (entry) => {
+ if (minimatch(entry.url, '**/node_modules/**')) { return false; }
+ if (minimatch(entry.url, '**/vendor.js')) { return false; }
+ if (minimatch(entry.url, '**/src/**')) { return true; }
+ return false; // else unmatched
+ }
+};
+```
+
+### Using `filter` instead of `entryFilter` and `sourceFilter`
+如果你不想定义两个过滤器,可以使用 `filter` 选项代替,可以将多个匹配合并在一起. (Added in: v2.8)
+```js
+const coverageOptions = {
+ // combined patterns
+ filter: {
+ '**/node_modules/**': false,
+ '**/vendor.js': false,
+ '**/src/**': true
+ '**/**': true
+ }
+};
+```
+
+## Resolve `sourcePath` for the Source Files
+当一个文件从sourcemap解包,它的路径可能是个虚拟路径, 此时可以使用`sourcePath`选项来修改文件路径。比如,我们测试了多个dist包的入口文件,它们的源文件可能包含了一些共同的文件,但路径可能不同,如果我们需要相同的文件覆盖率数据可以自动合并,那么需要使用`sourcePath`来统一这些相同文件的路径
+```js
+const coverageOptions = {
+ sourcePath: (filePath) => {
+ // Remove the virtual prefix
+ const list = ['my-dist-file1/', 'my-dist-file2/'];
+ for (const str of list) {
+ if (filePath.startsWith(str)) {
+ return filePath.slice(str.length);
+ }
+ }
+ return filePath;
+ }
+};
+```
+它也支持简单key/value的替换:
+```js
+const coverageOptions = {
+ sourcePath: {
+ 'my-dist-file1/': '',
+ 'my-dist-file2/': ''
+ }
+};
+```
+解决文件路径不完整的问题:
+```js
+const path = require("path")
+
+// MCR coverage options
+const coverageOptions = {
+ sourcePath: (filePath, info)=> {
+ if (!filePath.includes('/') && info.distFile) {
+ return `${path.dirname(info.distFile)}/${filePath}`;
+ }
+ return filePath;
+ }
+}
+```
+
+## Adding Empty Coverage for Untested Files
+默认,未测试的文件是不会包含到覆盖率报告的,需要使用`all`选项来为这些文件添加一个空的覆盖率,也就是0%
+```js
+const coverageOptions = {
+ all: './src',
+
+ // 支持多个目录
+ all: ['./src', './lib'],
+};
+```
+未测试的文件也适用于`sourceFilter`过滤器. 而且也可以指定自己的`filter`过滤器 (可以返回文件类型来支持js或css的覆盖率格式):
+```js
+const coverageOptions = {
+ all: {
+ dir: ['./src'],
+ filter: {
+ // exclude files
+ '**/ignored-*.js': false,
+ '**/*.html': false,
+ // empty css coverage
+ '**/*.scss': "css",
+ '**/*': true
+ }
+ }
+};
+```
+我们可能需要编译.ts, .jsx, .vue等等这样的文件, 这样才能被默认的AST解析器解析,以得到更多的覆盖率指标的数据
+```js
+const path = require("path");
+const swc = require("@swc/core");
+const coverageOptions = {
+ all: {
+ dir: ['./src'],
+ transformer: async (entry) => {
+ const { code, map } = await swc.transform(entry.source, {
+ filename: path.basename(entry.url),
+ sourceMaps: true,
+ isModule: true,
+ jsc: {
+ parser: {
+ syntax: "typescript",
+ jsx: true
+ },
+ transform: {}
+ }
+ });
+ entry.source = code;
+ entry.sourceMap = JSON.parse(map);
+ }
+ }
+};
+```
+
+## onEnd Hook
+结束回调可以用来自定义业务需求,比如检测覆盖率是否达标,对比每个指标的thresholds,如果低于要求的值则可以抛出一个错误退出
+```js
+const EC = require('eight-colors');
+const coverageOptions = {
+ name: 'My Coverage Report',
+ outputDir: './coverage-reports',
+ onEnd: (coverageResults) => {
+ const thresholds = {
+ bytes: 80,
+ lines: 60
+ };
+ console.log('check thresholds ...', thresholds);
+ const errors = [];
+ const { summary } = coverageResults;
+ Object.keys(thresholds).forEach((k) => {
+ const pct = summary[k].pct;
+ if (pct < thresholds[k]) {
+ errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
+ }
+ });
+ if (errors.length) {
+ const errMsg = errors.join('\n');
+ console.log(EC.red(errMsg));
+ // throw new Error(errMsg);
+ // process.exit(1);
+ }
+ }
+}
+```
+
+## Ignoring Uncovered Codes
+使用特定的注释,以`v8 ignore `开头可以忽略未覆盖的代码:
+- 忽略开始到结束
+```js
+/* v8 ignore start */
+function uncovered() {
+}
+/* v8 ignore stop */
+```
+- 忽略接下来一行或者多行
+```js
+/* v8 ignore next */
+const os = platform === 'wind32' ? 'Windows' : 'Other';
+
+const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
+
+// v8 ignore next 3
+if (platform === 'linux') {
+ console.log('hello linux');
+}
+```
+- 兼容支持 [c8 coverage](https://github.com/bcoe/c8/?tab=readme-ov-file#ignoring-all-lines-until-told) 或 [nodejs coverage](https://nodejs.org/docs/latest/api/test.html#collecting-code-coverage) 的语法格式
+```js
+/* c8 ignore start */
+function uncovered() {
+}
+/* c8 ignore stop */
+
+/* node:coverage disable */
+function uncovered() {
+}
+/* node:coverage enable */
+```
+
+## Multiprocessing Support
+> 多进程支持可以很好的解决异步并行的情况。所有的覆盖率数据会保存到`[outputDir]/.cache`,在报告生成之后,这些缓存数据会被清除。除非开启了[调试模式](#debug-for-coverage-and-sourcemap),或者使用了`raw`报告
+- 主进程,初始化,清理之前的缓存
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+// clean previous cache before the start of testing
+// unless the running environment is new and no cache
+mcr.cleanCache();
+```
+
+- 子进程1, 测试业务1
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.add(coverageData1);
+```
+
+- 子进程2, 测试业务2
+```js
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.add(coverageData2);
+```
+
+- 主进程,所有测试完成之后
+```js
+// generate coverage reports after the completion of testing
+const MCR = require('monocart-coverage-reports');
+const coverageOptions = require('path-to/same-options.js');
+const mcr = MCR(coverageOptions);
+await mcr.generate();
+```
+
+## Command Line
+> 使用`mcr`命令行将使用`NODE_V8_COVERAGE=dir`来启动一个[子进程](https://nodejs.org/docs/latest/api/child_process.html)运行程序,直到正常退出,然后自动从`dir`目录来读取覆盖率数据,并生成覆盖率报告
+
+- 全局安装
+```sh
+npm i monocart-coverage-reports -g
+mcr node ./test/specs/node.test.js -r v8,console-details --lcov
+```
+
+- 本地项目安装
+```sh
+npm i monocart-coverage-reports
+npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
+```
+
+- 命令行参数
+直接运行 `mcr` 或 `mcr --help` 查看所有CLI的参数
+
+- 使用 `--` 可以隔离子程序参数,以免两种参数混淆
+```sh
+mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
+```
+
+- 参见例子
+ - [Mocha](#mocha)
+ - [TypeScript](#typescript)
+ - [AVA](#ava)
+
+## Config File
+根据以下优先级加载配置文件
+- 自定义配置文件(如果没有指定则加载后面的默认配置文件):
+ - CLI: `mcr --config `
+ - API: `await mcr.loadConfig("my-config-file-path")`
+- `mcr.config.js`
+- `mcr.config.cjs`
+- `mcr.config.mjs`
+- `mcr.config.json` - json format
+- `mcr.config.ts` (requires preloading the ts execution module)
+
+## Merge Coverage Reports
+以下这些使用场景可能需要使用合并覆盖率报告:
+- 多个执行环境,比如Node.js服务端,以及浏览器客户端,比如`Next.js`
+- 多种测试类型,比如`Jest`单元测试,以及`Playwright`的端到端自动化测试
+- 分布式测试,测试结果保存到了多台机器或不同的容器中
+
+### Automatic Merging
+- 默认`MCR`在执行`generate()`时会自动合并覆盖率数据。所以可以在[多进程支持](#multiprocessing-support)下,多次添加覆盖率数据,最后将自动合并
+- 比如`Next.js`就可以同时添加前后端覆盖率数据,最后再执行`generate()`生成覆盖率报告,见例子[nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright)
+- 使用`Codecov`在线覆盖率报告服务,请设置输出`codecov`报告, 它会生成专属的`codecov.json`,如果有多个`codecov.json`文件上传,它们会自动合并数据,参见[Codecov](#codecov) 和 [合并报告说明](https://docs.codecov.com/docs/merging-reports)
+
+### Manual Merging
+手动合并覆盖率报告需要使用`raw`报告来导出原始的覆盖率数据到指定的目录
+- 比如,单元测试保存到`./coverage-reports/unit/raw`,见例子
+ - `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
+ - `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
+```js
+const coverageOptions = {
+ name: 'My Unit Test Coverage Report',
+ outputDir: "./coverage-reports/unit",
+ reports: [
+ ['raw', {
+ // relative path will be "./coverage-reports/unit/raw"
+ // defaults to raw
+ outputDir: "raw"
+ }],
+ ['v8'],
+ ['console-details']
+ ]
+};
+```
+
+- 同样的,E2E测试保存到`./coverage-reports/e2e/raw`. 见例子:
+ - `Playwright` + [monocart-reporter](https://github.com/cenfun/monocart-reporter) with coverage API
+ - `Playwright` + `MCR`, see [playwright-coverage](https://github.com/cenfun/playwright-coverage)
+ - see more [Integration Examples](#integration-examples)
+
+- 然后创建一个`merge-coverage.js`文件,使用`inputDir`参数导入`raw`数据,来生成合并的覆盖率报告.
+```js
+// merge-coverage.js
+const fs = require('fs');
+const { CoverageReport } = require('monocart-coverage-reports');
+const inputDir = [
+ './coverage-reports/unit/raw',
+ './coverage-reports/e2e/raw'
+];
+const coverageOptions = {
+ name: 'My Merged Coverage Report',
+ inputDir,
+ outputDir: './coverage-reports/merged',
+
+ // filter for both unit and e2e
+ entryFilter: {
+ '**/node_modules/**': false,
+ '**/*': true
+ },
+ sourceFilter: {
+ '**/node_modules/**': false,
+ '**/src/**': true
+ },
+
+ sourcePath: (filePath, info) => {
+ // Unify the file path for the same files
+ // For example, the file index.js has different paths:
+ // unit: unit-dist/src/index.js
+ // e2e: e2e-dist/src/index.js
+ // return filePath.replace("unit-dist/", "").replace("e2e-dist/", "")
+ return filePath;
+ },
+
+ reports: [
+ ['v8'],
+ ['console-details']
+ ],
+
+ onEnd: () => {
+ // remove the raw files if it useless
+ // inputDir.forEach((p) => {
+ // fs.rmSync(p, {
+ // recursive: true,
+ // force: true
+ // });
+ // });
+ }
+};
+await new CoverageReport(coverageOptions).generate();
+```
+- 最后在所有测试完成后运行`node path/to/merge-coverage.js`. 所有的执行脚本大概如下:
+```json
+{
+ "scripts": {
+ "test:unit": "jest",
+ "test:e2e": "playwright test",
+ "merge-coverage": "node path/to/merge-coverage.js",
+ "test": "npm run test:unit && npm run test:e2e && npm run merge-coverage"
+ }
+}
+```
+参见例子: [merge-code-coverage](https://github.com/cenfun/merge-code-coverage)
+
+## Common issues
+> 常见问题
+### Unexpected coverage
+覆盖率看起来不正确,多数情况是因为sourcemap转换的问题导致的. 可以先尝试设置构建工具的 `minify=false` 也就是不要压缩代码来解决。下面来看看sourcemap存在问题的具体原因:
+```js
+const a = tf ? 'true' : 'false';
+ ^ ^ ^
+ m1 p m2
+```
+上面是经过构建工具编译过的代码,通过AST分析,位置`p`对应的原始位置是我们要找的,而从sourcemap里仅能找到离`p`最近的位置映射`m1`和`m2`,也就是位置`p`并没有精确的映射保存到sourcemap里,从而无法直接获取精确的原始位置,但我们能知道`p`的原始位置应该在`m1`和`m2`之间。
+- 参见 [调试覆盖率和sourcemap](#debug-for-coverage-and-sourcemap)
+
+`MCR`如何解决这个问题:
+- 1, 首先会尝试使用[`diff-sequences`](https://github.com/jestjs/jest/tree/main/packages/diff-sequences)工具来比较`m1`和`m2`之间的生成代码和原始代码,找到`p`对应的字符位置,可以解决绝大多数问题。但是如果代码是非JS格式的,比如Vue模板是HTML,或JSX这些,不管怎么比较也是很难精确找到对应位置的,甚至此时的sourcemap本身都比较乱。
+- 2, 然后就是通过分析[AST](https://github.com/acornjs/acorn),找到所有的functions, statements 和 branches,因为V8覆盖率本身不提供这些指标的覆盖率. (对于分支覆盖暂不支持`AssignmentPattern`类型,因为即使分析AST也无法从V8覆盖率找到它的数据)。
+
+
+### Unparsable source
+源码无法解析问题。由上面我们知道`MCR`通过分析源码的AST获取更多指标的覆盖率信息,但源码如果不是标准的 ECMAScript,比如`ts`, `jsx`这些,那么分析的时候就会报错,此时我们可以手动来编译这些文件(可行但不推荐).
+```js
+import * as fs from "fs";
+import * as path from "path";
+import { fileURLToPath } from "url";
+import * as TsNode from 'ts-node';
+const coverageOptions = {
+ onEntry: async (entry) => {
+ const filePath = fileURLToPath(entry.url)
+ const originalSource = fs.readFileSync(filePath).toString("utf-8");
+ const fileName = path.basename(filePath);
+ const tn = TsNode.create({});
+ const source = tn.compile(originalSource, fileName);
+ entry.fake = false;
+ entry.source = source;
+ }
+}
+```
+
+### JavaScript heap out of memory
+内存溢出问题可能出现在有太多的原生V8覆盖率文件要处理. 我们可以使用Node.js的一个选项来增加内存使用:
+```sh
+- run: npm run test:coverage
+ env:
+ NODE_OPTIONS: --max-old-space-size=8192
+```
+
+
+## Debug for Coverage and Sourcemap
+> 当你觉得覆盖率存在问题的时候,`MCR`支持自行调试来核验覆盖率的准确性
+- 首先打开调试设置`logging: 'debug'`
+```js
+const coverageOptions = {
+ logging: 'debug',
+ reports: [
+ ['v8'],
+ ['console-details']
+ ]
+};
+```
+调试模式下,也就是`logging`为`debug`的时候, 原始的覆盖率数据将保留在`[outputDir]/.cache`缓存目录下,不会删除,如果使用了`raw`报告,那么位置变为`[outputDir]/raw`下,这样我们可以打开v8报告的html文件,通过下面新增的一些调试帮助信息来核对覆盖率
+
+
+- 调试sourcemap可以直接使用[Source Map Visualization](https://evanw.github.io/source-map-visualization/) (esbuild作者提供的sourcemap在线查看器)
+
+
+
+- 生成额外的source和sourcemap文件到cache或raw文件夹
+```js
+const coverageOptions = {
+ logging: 'debug',
+ sourceMap: true
+};
+```
+
+- 使用环境变量`MCR_LOG_TIME`显示时间日志
+```js
+process.env.MCR_LOG_TIME = true
+```
+
+## Integration with Any Testing Framework
+通用集成方案
+- 通过API接口在程序集成
+ - 首先,要自行收集覆盖率数据,然后,添加到报告实例 `await mcr.add(coverageData)`
+ - 最后,生成覆盖率报告 `await mcr.generate()`
+ - 参见 [多进程支持](#multiprocessing-support)
+- 通过CLI命令行与其他命令行集成
+ - 直接在其他命令行前面添加mcr的命令行即可 `mcr your-cli --your-arguments`
+ - 参见 [命令行](#command-line)
+
+## Integration Examples
+
+### [Playwright](https://github.com/microsoft/playwright)
+- [playwright-coverage](https://github.com/cenfun/playwright-coverage) - Example for Playwright coverage reports
+- [playwright-bdd-coverage](https://github.com/cenfun/playwright-bdd-coverage) - Example for Playwright BDD coverage reports
+- [monocart-reporter](https://github.com/cenfun/monocart-reporter) - Playwright custom reporter, supports generating [Code coverage report](https://github.com/cenfun/monocart-reporter?#code-coverage-report)
+- Coverage for component testing with `monocart-reporter`:
+ - [playwright-ct-vue](https://github.com/cenfun/playwright-ct-vue)
+ - [playwright-ct-react](https://github.com/cenfun/playwright-ct-react)
+ - [playwright-ct-svelte](https://github.com/cenfun/playwright-ct-svelte)
+- Coverage for Next.js, both server side and client side:
+ - [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright)
+ - [nextjs-with-playwright-istanbul](https://github.com/cenfun/nextjs-with-playwright-istanbul)
+- Coverage for Remix:
+ - [remix-with-playwright](https://github.com/cenfun/remix-with-playwright)
+- see [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
+
+### [c8](https://github.com/bcoe/c8)
+- c8 has integrated `MCR` as an experimental feature since [v10.1.0](https://github.com/bcoe/c8/releases/tag/v10.1.0)
+```sh
+c8 --experimental-monocart --reporter=v8 --reporter=console-details node foo.js
+```
+
+### [CodeceptJS](https://github.com/codeceptjs/CodeceptJS)
+- CodeceptJS is a [BDD](https://codecept.io/bdd/) + [AI](https://codecept.io/ai/) testing framework for e2e testing, it has integrated `MCR` since [v3.5.15](https://github.com/codeceptjs/CodeceptJS/releases/tag/3.5.15), see [plugins/coverage](https://codecept.io/plugins/#coverage)
+
+### [VSCode](https://github.com/microsoft/vscode)
+- [Monocart Coverage for VSCode](https://github.com/cenfun/monocart-coverage-vscode) - Shows native V8 code coverage in VSCode
+
+### [Jest](https://github.com/jestjs/jest/)
+- [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - Jest custom reporter for coverage reports
+- [merge-code-coverage](https://github.com/cenfun/merge-code-coverage) - Example for merging code coverage (Jest unit + Playwright e2e sharding)
+
+### [Vitest](https://github.com/vitest-dev/vitest)
+- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Vitest custom provider module for coverage reports
+- [merge-code-coverage-vitest](https://github.com/cenfun/merge-code-coverage-vitest) - Example for merging code coverage (Vitest unit + Playwright e2e sharding)
+
+### [Node Test Runner](https://nodejs.org/docs/latest/api/test.html)
+- [node-monocart-coverage](https://github.com/cenfun/node-monocart-coverage) - Custom reporter for Node test runner for coverage
+
+### [Puppeteer](https://github.com/puppeteer/puppeteer/)
+- [jest-puppeteer-coverage](https://github.com/cenfun/jest-puppeteer-coverage) - Example for Jest puppeteer coverage
+- [maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js) - Example for Jest (unit) + Puppeteer (e2e) + Codecov
+- see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
+
+### [Cypress](https://github.com/cypress-io/cypress)
+- [cypress-monocart-coverage](https://github.com/cenfun/cypress-monocart-coverage) - Cypress plugin for coverage reports
+
+### [WebdriverIO](https://github.com/webdriverio/webdriverio)
+- [wdio-monocart-service](https://github.com/cenfun/wdio-monocart-service) - WebdriverIO service for coverage reports
+
+### [Storybook Test Runner](https://github.com/storybookjs/test-runner)
+- [storybook-monocart-coverage](https://github.com/cenfun/storybook-monocart-coverage) - Example for Storybook V8 coverage reports
+
+### [TestCafe](https://github.com/DevExpress/testcafe)
+- [testcafe-reporter-coverage](https://github.com/cenfun/testcafe-reporter-coverage) - TestCafe custom reporter for coverage reports
+
+### [Selenium Webdriver](https://github.com/seleniumhq/selenium)
+- [selenium-webdriver-coverage](https://github.com/cenfun/selenium-webdriver-coverage) - Example for Selenium Webdriver V8 coverage reports
+
+### [Mocha](https://github.com/mochajs/mocha)
+```sh
+mcr mocha ./test/**/*.js
+```
+
+### [TypeScript](https://github.com/microsoft/typescript)
+
+- [tsx](https://github.com/privatenumber/tsx)
+```sh
+cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
+cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
+# Node.js v18.19.0 +
+mcr --import tsx tsx ./src/example.ts
+mcr --import tsx mocha ./test/**/*.ts
+```
+- [ts-node](https://github.com/TypeStrong/ts-node)
+```sh
+cross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr ts-node ./src/example.ts
+cross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr mocha ./test/**/*.ts
+```
+
+
+### [AVA](https://github.com/avajs/ava)
+```sh
+mcr ava
+```
+
+### [Codecov](https://codecov.com/)
+[](https://codecov.io/gh/cenfun/monocart-coverage-reports)
+- Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ reports: [
+ ['codecov']
+ ]
+};
+```
+- Github actions:
+```yml
+- name: Codecov
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./coverage-reports/codecov.json
+```
+
+### [Codacy](https://www.codacy.com/)
+[](https://app.codacy.com/gh/cenfun/monocart-coverage-reports/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
+- Using `lcov` report:
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ lcov: true
+};
+```
+- Github actions:
+```yml
+- name: Codacy Coverage Reporter
+ uses: codacy/codacy-coverage-reporter-action@v1
+ with:
+ project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
+ coverage-reports: ./docs/mcr/lcov.info
+```
+
+### [Coveralls](https://coveralls.io/)
+[](https://coveralls.io/github/cenfun/monocart-coverage-reports?branch=main)
+- Using `lcov` report:
+```js
+const coverageOptions = {
+ outputDir: "./coverage-reports",
+ lcov: true
+};
+```
+- Github actions:
+```yml
+- name: Coveralls
+ uses: coverallsapp/github-action@v2
+ with:
+ files: ./coverage-reports/lcov.info
+```
+
+### [Sonar Cloud](https://sonarcloud.io/)
+[](https://sonarcloud.io/summary/new_code?id=monocart-coverage-reports)
+- Using `lcov` report. Github actions example:
+```yml
+- name: Analyze with SonarCloud
+ uses: sonarsource/sonarcloud-github-action@master
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ with:
+ projectBaseDir: ./
+ args: >
+ -Dsonar.organization=cenfun
+ -Dsonar.projectKey=monocart-coverage-reports
+ -Dsonar.projectName=monocart-coverage-reports
+ -Dsonar.javascript.lcov.reportPaths=docs/mcr/lcov.info
+ -Dsonar.sources=lib
+ -Dsonar.tests=test
+ -Dsonar.exclusions=dist/*,packages/*
+```
+
+## Contributing
+- Node.js 20+
+- VSCode (extensions: eslint/stylelint/vue)
+```sh
+npm install
+npx playwright install --with-deps
+
+npm run build
+npm run test
+
+npm run dev
+```
+- Refreshing `eol=lf` for snapshot of test (Windows)
+```sh
+git add . -u
+git commit -m "Saving files before refreshing line endings"
+
+npm run eol
+```
+
+## Thanks
+- [@bcoe](https://github.com/bcoe)
+- [@edumserrano](https://github.com/edumserrano)
\ No newline at end of file
diff --git a/node_modules/monocart-coverage-reports/lib/assets.js b/node_modules/monocart-coverage-reports/lib/assets.js
new file mode 100644
index 0000000..218a3df
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/assets.js
@@ -0,0 +1,98 @@
+const fs = require('fs');
+const path = require('path');
+const { deflateSync } = require('lz-utils');
+const Util = require('./utils/util.js');
+const assetsMap = require('./packages/monocart-coverage-assets.js');
+
+const Assets = {
+
+ getFileContent: (id) => {
+ const content = assetsMap[id];
+ if (!content) {
+ Util.logError(`Not found module: ${id}`);
+ return '';
+ }
+ return content;
+ },
+
+ saveHtmlReport: async (options) => {
+
+ const {
+ inline,
+ reportData,
+ jsFiles,
+ assetsPath,
+ outputDir,
+ htmlFile,
+
+ saveReportPath,
+
+ reportDataFile
+ } = options;
+
+ // save path
+ const htmlPath = path.resolve(outputDir, htmlFile);
+ const reportPath = Util.relativePath(htmlPath);
+ if (saveReportPath) {
+ reportData[saveReportPath] = reportPath;
+ }
+
+ // report data
+ const reportDataCompressed = deflateSync(JSON.stringify(reportData));
+ const reportDataStr = `window.reportData = '${reportDataCompressed}';`;
+
+ // js libs
+ const jsList = [];
+
+ // deps
+ jsFiles.forEach((id) => {
+ jsList.push({
+ filename: `${id}.js`,
+ str: Assets.getFileContent(id)
+ });
+ });
+
+ // html content
+ let htmlStr = '';
+ const EOL = Util.getEOL();
+ if (inline) {
+ htmlStr = [
+ ''
+ ].join(EOL);
+ } else {
+
+ await Util.writeFile(path.resolve(outputDir, reportDataFile), reportDataStr);
+
+ const assetsDir = path.resolve(outputDir, assetsPath);
+ const relAssetsDir = Util.relativePath(assetsDir, outputDir);
+
+ for (const item of jsList) {
+ const filePath = path.resolve(assetsDir, item.filename);
+ if (!fs.existsSync(filePath)) {
+ await Util.writeFile(filePath, item.str);
+ }
+ }
+
+ htmlStr = [
+ ``,
+ ... jsList.map((it) => ``)
+ ].join(EOL);
+ }
+
+ // html
+ const template = Assets.getFileContent('template');
+ const html = Util.replace(template, {
+ title: reportData.title,
+ content: htmlStr
+ });
+
+ await Util.writeFile(htmlPath, html);
+
+ return reportPath;
+ }
+};
+
+module.exports = Assets;
diff --git a/node_modules/monocart-coverage-reports/lib/cli.js b/node_modules/monocart-coverage-reports/lib/cli.js
new file mode 100755
index 0000000..caae0ac
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/cli.js
@@ -0,0 +1,251 @@
+#!/usr/bin/env node
+
+const path = require('path');
+const EC = require('eight-colors');
+
+const { program } = require('commander');
+const { foregroundChild } = require('foreground-child');
+
+const MCR = require('./index.js');
+const Util = require('./utils/util.js');
+
+const version = require('../package.json').version;
+
+const getRegisterPath = (filename) => {
+ const rel = Util.relativePath(path.resolve(__dirname, 'register', filename));
+ if (rel.startsWith('.')) {
+ return rel;
+ }
+ return `./${rel}`;
+};
+
+const getInitNodeOptions = async (cliOptions) => {
+ const nodeOptions = [];
+ if (process.env.NODE_OPTIONS) {
+ nodeOptions.push(process.env.NODE_OPTIONS);
+ }
+
+ if (cliOptions.import) {
+ nodeOptions.push(`--import ${cliOptions.import}`);
+ // for load mcr.config.ts
+ await import(cliOptions.import);
+ } else if (cliOptions.require) {
+ nodeOptions.push(`--require ${cliOptions.require}`);
+ // for load mcr.config.ts
+ await import(cliOptions.require);
+ }
+
+ return nodeOptions;
+};
+
+const getPreloadType = (nodeOptions) => {
+ const hasImport = nodeOptions.find((it) => it.includes('--import'));
+ if (hasImport) {
+ return '--import';
+ }
+ return '--require';
+};
+
+const checkRegisterFeature = () => {
+ const nv = process.versions.node;
+
+ // "module.register" added in Node.js: v20.6.0
+ // if (Util.cmpVersion(nv, '20.6.0') >= 0) {
+ // return true;
+ // }
+ // but also added in: v18.19.0
+ const requiredNV = '18.19.0';
+ if (Util.cmpVersion(nv, requiredNV) < 0) {
+ Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "${requiredNV}" or higher.`);
+ return false;
+ }
+
+ // could be < 20.6.0 but just ignore it, please using latest minor version
+
+ return true;
+};
+
+const loadEnv = (cliOptions) => {
+ if (!cliOptions.env) {
+ return;
+ }
+ const envFile = cliOptions.env === true ? '.env' : cliOptions.env;
+ const loadEnvFile = process.loadEnvFile;
+ if (typeof loadEnvFile === 'function') {
+ loadEnvFile(envFile);
+ }
+};
+
+const initNodeOptions = async (cliOptions) => {
+
+ loadEnv(cliOptions);
+
+ const supportRegister = checkRegisterFeature();
+ if (!supportRegister) {
+ return;
+ }
+
+ const nodeOptions = await getInitNodeOptions(cliOptions);
+ // console.log(nodeOptions);
+
+ const preloadType = getPreloadType(nodeOptions);
+
+ // export source after
+ if (preloadType === '--import') {
+ const importPath = getRegisterPath('register.mjs');
+ nodeOptions.unshift(`--import ${importPath}`);
+ } else {
+ const requirePath = getRegisterPath('register.js');
+ nodeOptions.unshift(`--require ${requirePath}`);
+ }
+
+ // console.log(nodeOptions);
+ const nodeOptionsStr = nodeOptions.join(' ');
+ Util.logDebug(`node options: ${EC.cyan(nodeOptionsStr)}`);
+
+ process.env.NODE_OPTIONS = nodeOptionsStr;
+
+};
+
+const initNodeV8CoverageDir = (coverageOptions) => {
+ // dir for node v8 coverage
+ const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
+ process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
+ // clean v8 cache before running
+ Util.rmSync(nodeV8CoverageDir);
+ // Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
+
+ return nodeV8CoverageDir;
+};
+
+const mergeCoverage = async (cliOptions) => {
+ const coverageReport = MCR(cliOptions);
+ await coverageReport.loadConfig(cliOptions.config);
+ coverageReport.cleanCache();
+ await coverageReport.generate();
+};
+
+const executeCommand = async (command, cliOptions) => {
+
+ Util.logInfo(`Execute: ${EC.cyan(command)}`);
+
+ if (command === 'merge') {
+ return mergeCoverage(cliOptions);
+ }
+
+ // before load config
+ await initNodeOptions(cliOptions);
+
+ // console.log(options);
+ const coverageReport = MCR(cliOptions);
+ await coverageReport.loadConfig(cliOptions.config);
+ coverageReport.cleanCache();
+
+ const coverageOptions = coverageReport.options;
+ const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
+
+ // =========================================
+ // onStart hook
+ const onStart = coverageOptions.onStart;
+ if (typeof onStart === 'function') {
+ await onStart(coverageReport);
+ }
+ // =========================================
+
+ const subprocess = foregroundChild(command, {
+ shell: true
+ }, async (code, signal) => {
+
+ // generate coverage even it is failed. code != 0
+
+ // =========================================
+ // onReady hook before adding coverage data.
+ // Sometimes, the child process has not yet finished writing the coverage data, and it needs to wait here.
+ const onReady = coverageOptions.onReady;
+ if (typeof onReady === 'function') {
+ await onReady(coverageReport, nodeV8CoverageDir, subprocess);
+ }
+ // =========================================
+
+ await coverageReport.addFromDir(nodeV8CoverageDir);
+ await coverageReport.generate();
+
+ // remove nodeV8CoverageDir
+ if (!Util.isDebug()) {
+ Util.rmSync(nodeV8CoverageDir);
+ }
+
+ return process.exitCode;
+ });
+
+};
+
+process.on('uncaughtException', function(err) {
+ Util.logError(`Process uncaughtException: ${err.message}`);
+ console.log(err.stack);
+});
+
+// the -- separator
+const argv = [];
+const subArgv = [];
+let separator = false;
+process.argv.forEach((it) => {
+ if (!separator && it === '--') {
+ separator = true;
+ }
+ if (separator) {
+ subArgv.push(it);
+ } else {
+ argv.push(it);
+ }
+});
+
+program
+ .name('mcr')
+ .description('CLI to generate coverage reports')
+ .version(version, '-v, --version', 'output the current version')
+ .argument('[command]', 'command to execute')
+ .allowUnknownOption()
+ .allowExcessArguments()
+ .option('-c, --config ', 'custom config file path')
+ .option('-l, --logging ', 'off, error, info, debug')
+
+ .option('-n, --name ', 'report name for title')
+ .option('-r, --reports ', 'coverage reports to use')
+
+ .option('-o, --outputDir ', 'output dir for reports')
+ .option('-i, --inputDir ', 'input dir for merging raw files')
+ .option('-b, --baseDir ', 'base dir for normalizing path')
+
+ .option('-a, --all ', 'include all files from dir')
+
+ .option('--entryFilter ', 'entry url filter')
+ .option('--sourceFilter ', 'source path filter')
+ .option('--filter ', 'the combined filter')
+
+ .option('--outputFile ', 'output file for v8 report')
+ .option('--inline', 'inline html for v8 report')
+ .option('--assetsPath ', 'assets path if not inline')
+
+ .option('--lcov', 'generate lcov.info file')
+
+ .option('--import ', 'preload module at startup')
+ .option('--require ', 'preload module at startup')
+
+ .option('--env [path]', 'env file (default: ".env")')
+
+ .action((_command, cliOptions) => {
+ const args = [].concat(program.args).concat(subArgv);
+ if (args[0] === '--') {
+ args.shift();
+ }
+ const command = args.join(' ').trim();
+ if (!command) {
+ program.outputHelp();
+ return;
+ }
+
+ executeCommand(command, cliOptions);
+ });
+
+program.parse(argv);
diff --git a/node_modules/monocart-coverage-reports/lib/client/cdp-client.js b/node_modules/monocart-coverage-reports/lib/client/cdp-client.js
new file mode 100644
index 0000000..c91efaf
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/client/cdp-client.js
@@ -0,0 +1,143 @@
+
+const EC = require('eight-colors');
+
+const { WebSocket } = require('../packages/monocart-coverage-vendor.js');
+const Util = require('../utils/util.js');
+
+const WSSession = require('./ws-session.js');
+const CoverageClient = require('./coverage-client.js');
+
+const getDebuggerUrl = async (options) => {
+
+ const protocol = options.secure ? 'https' : 'http';
+
+ const url = `${protocol}://${options.host}:${options.port}/json/list`;
+ const [err, res] = await Util.request(url);
+ if (err) {
+ return [err];
+ }
+
+ const targets = res.data;
+ if (!Util.isList(targets)) {
+ return [new Error(`Invalid response data: ${url}`)];
+ }
+
+ // console.log(targets);
+ const target = options.target(targets);
+ if (!target) {
+ return [new Error(`Not found target: ${url}`)];
+ }
+
+ return [null, target.webSocketDebuggerUrl];
+};
+
+const getCDPUrl = async (options) => {
+ if (options.url) {
+ return [null, options.url];
+ }
+
+ // get debugger url
+ const [err, debuggerUrl] = await getDebuggerUrl(options);
+ if (err) {
+ return [err];
+ }
+
+ return [null, debuggerUrl];
+
+};
+
+const getCDPSession = async (options) => {
+
+ if (options.session) {
+ return [null, options.session];
+ }
+
+ // create session
+ const [err, url] = await getCDPUrl(options);
+
+ return new Promise((resolve) => {
+
+ if (err) {
+ resolve([err]);
+ return;
+ }
+
+ const timeoutId = setTimeout(() => {
+ resolve([new Error(`Timeout to connect: ${url}`)]);
+ }, options.timeout);
+
+ Util.logDebug(`Connect to ${url}`);
+ const ws = new WebSocket(url, [], {
+ maxPayload: 256 * 1024 * 1024,
+ perMessageDeflate: false,
+ followRedirects: true,
+ ... options.ws
+ });
+
+ ws.once('error', (wsErr) => {
+ clearTimeout(timeoutId);
+ resolve([wsErr]);
+ });
+
+ ws.once('open', () => {
+ clearTimeout(timeoutId);
+ Util.logDebug(`${EC.green('Connected')} ${url}`);
+ const session = new WSSession(ws);
+ resolve([null, session]);
+ });
+
+ });
+
+
+};
+
+
+const CDPClient = async (cdpOptions) => {
+
+ const defaultOptions = {
+ session: null,
+ url: null,
+ port: 9222,
+ host: 'localhost',
+ secure: false,
+ target: (targets) => {
+ // defaults to first page
+ const page = targets.find((it) => it.webSocketDebuggerUrl && it.type === 'page');
+ if (page) {
+ return page;
+ }
+ return targets.find((it) => it.webSocketDebuggerUrl);
+ },
+ ws: {},
+ timeout: 10 * 1000
+ };
+
+ if (typeof cdpOptions === 'string') {
+ cdpOptions = {
+ url: cdpOptions
+ };
+ }
+
+ const options = {
+ ... defaultOptions,
+ ... cdpOptions
+ };
+
+ const [err, session] = await getCDPSession(options);
+
+ return new Promise((resolve) => {
+
+ if (err) {
+ Util.logError(err);
+ resolve();
+ return;
+ }
+
+ const helper = new CoverageClient(session);
+ resolve(helper);
+
+ });
+
+};
+
+module.exports = CDPClient;
diff --git a/node_modules/monocart-coverage-reports/lib/client/coverage-client.js b/node_modules/monocart-coverage-reports/lib/client/coverage-client.js
new file mode 100644
index 0000000..c6e2d65
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/client/coverage-client.js
@@ -0,0 +1,322 @@
+const Util = require('../utils/util.js');
+
+const bindEvents = (target, events) => {
+ Object.keys(events).forEach((eventType) => {
+ target.on(eventType, events[eventType]);
+ });
+};
+
+const unbindEvents = (target, events) => {
+ Object.keys(events).forEach((eventType) => {
+ target.off(eventType, events[eventType]);
+ });
+};
+
+// keep same ranges with playwright
+// eslint-disable-next-line complexity
+const convertToDisjointRanges = (nestedRanges) => {
+ const points = [];
+ for (const range of nestedRanges) {
+ points.push({
+ offset: range.start,
+ type: 0,
+ range
+ });
+ points.push({
+ offset: range.end,
+ type: 1,
+ range
+ });
+ }
+ // Sort points to form a valid parenthesis sequence.
+ points.sort((a, b) => {
+ // Sort with increasing offsets.
+ if (a.offset !== b.offset) {
+ return a.offset - b.offset;
+ }
+ // All "end" points should go before "start" points.
+ if (a.type !== b.type) {
+ return b.type - a.type;
+ }
+ const aLength = a.range.end - a.range.start;
+ const bLength = b.range.end - b.range.start;
+ // For two "start" points, the one with longer range goes first.
+ if (a.type === 0) {
+ return bLength - aLength;
+ }
+ // For two "end" points, the one with shorter range goes first.
+ return aLength - bLength;
+ });
+
+ const hitCountStack = [];
+ const results = [];
+ let lastOffset = 0;
+ // Run scanning line to intersect all ranges.
+ for (const point of points) {
+ if (hitCountStack.length && lastOffset < point.offset && hitCountStack[hitCountStack.length - 1] > 0) {
+ const lastResult = results.length ? results[results.length - 1] : null;
+ if (lastResult && lastResult.end === lastOffset) {
+ lastResult.end = point.offset;
+ } else {
+ results.push({
+ start: lastOffset,
+ end: point.offset
+ });
+ }
+ }
+ lastOffset = point.offset;
+ if (point.type === 0) {
+ hitCountStack.push(point.range.count);
+ } else {
+ hitCountStack.pop();
+ }
+ }
+ // Filter out empty ranges.
+ return results.filter((range) => range.end - range.start > 1);
+};
+
+class CoverageClient {
+ constructor(session) {
+ this.session = session;
+ }
+
+ // =================================================================================================
+ async startJSCoverage() {
+ if (!this.session || this.enabledJS) {
+ return;
+ }
+ this.enabledJS = true;
+ this.scriptSources = new Map();
+ this.jsEvents = {
+ 'Debugger.scriptParsed': (params) => {
+ const { scriptId } = params;
+ this.session.send('Debugger.getScriptSource', {
+ scriptId
+ }).then((res) => {
+ this.scriptSources.set(scriptId, res && res.scriptSource);
+ });
+ },
+ 'Debugger.paused': () => {
+ this.send('Debugger.resume');
+ }
+ };
+
+ bindEvents(this.session, this.jsEvents);
+
+ await this.session.send('Debugger.enable');
+ await this.session.send('Debugger.setSkipAllPauses', {
+ skip: true
+ });
+
+ await this.session.send('Profiler.enable');
+ await this.session.send('Profiler.startPreciseCoverage', {
+ callCount: true,
+ detailed: true
+ });
+
+ // Util.logDebug('startJSCoverage');
+
+ }
+
+ async stopJSCoverage() {
+ if (!this.session || !this.enabledJS) {
+ return;
+ }
+
+ const profileResponse = await this.session.send('Profiler.takePreciseCoverage');
+ await this.session.send('Profiler.stopPreciseCoverage');
+ await this.session.send('Profiler.disable');
+ // await this.session.send('Debugger.disable');
+
+ unbindEvents(this.session, this.jsEvents);
+ this.jsEvents = null;
+
+ const jsCoverage = [];
+
+ // console.log('coverageList', coverageList);
+ if (profileResponse && profileResponse.result) {
+ profileResponse.result.forEach((entry) => {
+ // anonymous url
+ entry.url = entry.url || '';
+ // add source
+ const source = this.scriptSources.get(entry.scriptId);
+ if (!source) {
+ Util.logDebug(`Not found js source: ${entry.url}`);
+ }
+ entry.source = source || '';
+ jsCoverage.push(entry);
+ });
+ }
+
+ this.scriptSources.clear();
+ this.enabledJS = false;
+
+ return jsCoverage;
+ }
+
+ // =================================================================================================
+
+ async startCSSCoverage() {
+ if (!this.session || this.enabledCSS) {
+ return;
+ }
+ this.enabledCSS = true;
+ this.styleEntries = new Map();
+
+ this.cssEvents = {
+ 'CSS.styleSheetAdded': (e) => {
+ const { sourceURL, styleSheetId } = e.header;
+
+ // anonymous url
+ const url = sourceURL || '';
+ this.session.send('CSS.getStyleSheetText', {
+ styleSheetId
+ }).then((res) => {
+ // add source
+ const text = res && res.text;
+ if (!text) {
+ Util.logDebug(`Not found css source: ${url}`);
+ }
+ this.styleEntries.set(styleSheetId, {
+ url,
+ text: text || '',
+ ranges: []
+ });
+ });
+ }
+ };
+
+ bindEvents(this.session, this.cssEvents);
+
+ await this.session.send('DOM.enable');
+ await this.session.send('CSS.enable');
+ await this.session.send('CSS.startRuleUsageTracking');
+
+ // Util.logDebug('startCSSCoverage');
+ }
+
+ async stopCSSCoverage() {
+ if (!this.session || !this.enabledCSS) {
+ return;
+ }
+
+ const ruleTrackingResponse = await this.session.send('CSS.stopRuleUsageTracking');
+ await this.session.send('CSS.disable');
+ await this.session.send('DOM.disable');
+
+ unbindEvents(this.session, this.cssEvents);
+ this.cssEvents = null;
+
+ const cssCoverage = [];
+
+ if (ruleTrackingResponse) {
+ for (const usage of ruleTrackingResponse.ruleUsage) {
+ const entry = this.styleEntries.get(usage.styleSheetId);
+ if (entry) {
+ entry.ranges.push({
+ start: usage.startOffset,
+ end: usage.endOffset,
+ count: usage.used ? 1 : 0
+ });
+ }
+ }
+ this.styleEntries.forEach((entry) => {
+ entry.ranges = convertToDisjointRanges(entry.ranges);
+ cssCoverage.push(entry);
+ });
+ }
+
+ this.styleEntries.clear();
+ this.enabledCSS = false;
+
+ return cssCoverage;
+ }
+
+ // =================================================================================================
+
+ async startCoverage() {
+ await Promise.all([
+ this.startJSCoverage(),
+ this.startCSSCoverage()
+ ]);
+ }
+
+ async stopCoverage() {
+ if (!this.session) {
+ return;
+ }
+ const [jsCoverage, cssCoverage] = await Promise.all([
+ this.stopJSCoverage(),
+ this.stopCSSCoverage()
+ ]);
+ // could be undefined
+ let coverageList = [];
+ if (jsCoverage) {
+ coverageList = coverageList.concat(jsCoverage);
+ }
+ if (cssCoverage) {
+ coverageList = coverageList.concat(cssCoverage);
+ }
+ return coverageList;
+ }
+
+ // =================================================================================================
+ // write the coverage started by NODE_V8_COVERAGE to disk on demand
+ async writeCoverage() {
+ if (!this.session) {
+ return;
+ }
+
+ await this.session.send('Runtime.enable');
+
+ // write the coverage started by NODE_V8_COVERAGE to disk on demand
+ const res = await this.session.send('Runtime.evaluate', {
+ expression: `new Promise((resolve) => {
+ require("v8").takeCoverage();
+ resolve(process.env.NODE_V8_COVERAGE);
+ })`,
+ includeCommandLineAPI: true,
+ returnByValue: true,
+ awaitPromise: true
+ });
+
+ await this.session.send('Runtime.disable');
+
+ return res && res.result && res.result.value;
+ }
+
+ // get istanbul coverage data
+ async getIstanbulCoverage(coverageKey = '__coverage__') {
+ if (!this.session) {
+ return;
+ }
+
+ await this.session.send('Runtime.enable');
+
+ // both browser and Node.js
+ const res = await this.session.send('Runtime.evaluate', {
+ expression: `new Promise((resolve) => {
+ const globalTarget = typeof window !== 'undefined' ? window : global;
+ resolve(globalTarget['${coverageKey}']);
+ })`,
+ includeCommandLineAPI: true,
+ returnByValue: true,
+ awaitPromise: true
+ });
+
+ await this.session.send('Runtime.disable');
+
+ return res && res.result && res.result.value;
+ }
+
+ // =================================================================================================
+ async close() {
+ if (!this.session) {
+ return;
+ }
+ await this.session.detach();
+ this.session = null;
+ }
+}
+
+module.exports = CoverageClient;
diff --git a/node_modules/monocart-coverage-reports/lib/client/ws-session.js b/node_modules/monocart-coverage-reports/lib/client/ws-session.js
new file mode 100644
index 0000000..cb35ad3
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/client/ws-session.js
@@ -0,0 +1,63 @@
+const { EventEmitter } = require('events');
+
+class WSSession extends EventEmitter {
+ constructor(ws) {
+ super();
+ this.ws = ws;
+ this.requestId = 1;
+ this.requestCache = new Map();
+ ws.on('message', (data, isBinary) => {
+
+ const message = JSON.parse(data);
+ // console.log(message);
+
+ const { id, method } = message;
+ if (id) {
+ const request = this.requestCache.get(id);
+ this.requestCache.delete(id);
+ if (request) {
+ request.resolve(message.result);
+ }
+ return;
+ }
+
+ if (method) {
+ this.emit(method, message.params, message.sessionId);
+ }
+
+ });
+ }
+
+ send(method, params) {
+ return new Promise((resolve, reject) => {
+ if (!this.ws) {
+ reject(new Error('Invalid websocket'));
+ return;
+ }
+ const id = this.requestId++;
+ const message = {
+ id,
+ method,
+ params: params || {}
+ };
+ this.ws.send(JSON.stringify(message), (err) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ this.requestCache.set(id, {
+ resolve
+ });
+ });
+ });
+ }
+
+ detach() {
+ if (this.ws) {
+ this.ws.terminate();
+ this.ws = null;
+ }
+ }
+}
+
+module.exports = WSSession;
diff --git a/node_modules/monocart-coverage-reports/lib/converter/ast-visitor.js b/node_modules/monocart-coverage-reports/lib/converter/ast-visitor.js
new file mode 100644
index 0000000..b25fb5b
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/ast-visitor.js
@@ -0,0 +1,809 @@
+const Util = require('../utils/util.js');
+
+const BranchTypes = {
+ ConditionalExpression: 'ConditionalExpression',
+ LogicalExpression: 'LogicalExpression',
+ IfStatement: 'IfStatement',
+ SwitchStatement: 'SwitchStatement',
+ AssignmentPattern: 'AssignmentPattern'
+};
+
+const setGeneratedOnly = (block, generatedOnly) => {
+ if (block && generatedOnly) {
+ block.generatedOnly = true;
+ }
+};
+
+const getParentFunctionState = (reverseParents) => {
+ const parentFunction = reverseParents.find((it) => it._state && it._state.isFunction);
+ if (!parentFunction) {
+ return;
+ }
+ return parentFunction._state;
+};
+
+const getParentCount = (reverseParents, functionCount) => {
+ // parent count
+ const parent = reverseParents.find((it) => it._state);
+ if (parent) {
+ return parent._state.count;
+ }
+ // root function count
+ return functionCount;
+};
+
+const getFunctionRange = (start, end, type, coverageInfo) => {
+
+ const {
+ functionMap, functionNameMap, functionStaticRanges, functionUncoveredRanges
+ } = coverageInfo;
+
+ // exact matched in functionMap
+ const range = functionMap.get(start);
+ if (range) {
+ return range;
+ }
+ // exact matched in functionNameMap
+ const nameRange = functionNameMap.get(start);
+ if (nameRange) {
+ return nameRange;
+ }
+
+ if (type === 'StaticBlock' && functionStaticRanges.length) {
+ const staticRange = Util.findInRanges(start, end, functionStaticRanges, 'startOffset', 'endOffset');
+ if (staticRange) {
+ return staticRange;
+ }
+ }
+
+ // find in uncoveredRanges
+ return Util.findInRanges(start, end, functionUncoveredRanges, 'startOffset', 'endOffset');
+};
+
+const getFunctionBlock = (start, end, functionState) => {
+ if (!functionState) {
+ return;
+ }
+
+ const { range } = functionState;
+ if (!range) {
+ return;
+ }
+
+ const {
+ blockMap, blockUncoveredRanges, blockCoveredRanges
+ } = range;
+
+ if (!blockMap) {
+ return;
+ }
+
+ const block = blockMap.get(start);
+ if (block) {
+ return block;
+ }
+
+ const uncoveredBlock = Util.findInRanges(start, end, blockUncoveredRanges, 'startOffset', 'endOffset');
+ if (uncoveredBlock) {
+ return uncoveredBlock;
+ }
+
+ // the block is not exact correct, if there is a block wrapped
+ // [x8]{ var a = b || [x4]c }, b is no block, but it can be found in x8 block
+ const coveredBlocks = blockCoveredRanges.filter((it) => start >= it.startOffset && end <= it.endOffset);
+ if (coveredBlocks.length) {
+ return coveredBlocks.pop();
+ }
+
+};
+
+const addNodeCount = (item) => {
+
+ const { node, reverseParents } = item;
+
+ if (node._state) {
+ return;
+ }
+
+ const { start, end } = node;
+ const functionState = getParentFunctionState(reverseParents);
+ const block = getFunctionBlock(start, end, functionState);
+ if (block) {
+ node._state = {
+ count: block.count
+ };
+ }
+
+};
+
+// =======================================================================================
+
+const createBranchGroup = (type, node, parents, branchMap) => {
+ const { start, end } = node;
+ // clone and reverse parents
+ const reverseParents = [].concat(parents).reverse();
+ const group = {
+ type,
+
+ start,
+ // could be updated if multiple locations
+ end,
+
+ locations: [],
+ reverseParents
+ };
+
+ if (type === BranchTypes.LogicalExpression) {
+ // && or ||
+ group.operator = node.operator;
+ }
+
+ // could be same start
+ const branchKey = `${start}_${end}`;
+
+ branchMap.set(branchKey, group);
+ return group;
+};
+
+const addBranch = (group, node, locationMap) => {
+ const {
+ start, end, type
+ } = node;
+
+ const branchKey = `${group.start}_${group.end}`;
+
+ const branchInfo = {
+ // for get previous group for LogicalExpression
+ branchKey,
+ start,
+ end,
+ // branch count default to 0
+ count: 0
+ };
+
+ if (type === 'SwitchCase') {
+
+ // console.log(node);
+
+ // check break
+ if (node.consequent) {
+ const breakItem = node.consequent.find((it) => it.type === 'BreakStatement');
+ if (breakItem) {
+ branchInfo.hasBreak = true;
+ }
+ }
+
+ // check default
+ if (!node.test) {
+ branchInfo.isDefault = true;
+ }
+ }
+
+ group.locations.push(branchInfo);
+
+ // update group end
+ if (end > group.end) {
+ group.end = end;
+ }
+
+ locationMap.set(start, branchInfo);
+
+ return branchInfo;
+};
+
+const addNoneBranch = (group) => {
+ group.locations.push({
+ none: true,
+ count: 0
+ });
+};
+
+// =======================================================================================
+
+const updateBlockLocations = (locations) => {
+ const noBlockList = [];
+ let blockCount = 0;
+ locations.forEach((item, i) => {
+ if (item.block) {
+ item.count = item.block.count;
+ blockCount += item.count;
+ return;
+ }
+ // for calculate mo break branches
+ item.index = i;
+ noBlockList.push(item);
+ });
+
+ return {
+ noBlockList,
+ blockCount
+ };
+};
+
+// const a = tf1 ? 'true' : 'false';
+const ConditionalExpression = (group, parentCount) => {
+ const { noBlockList, blockCount } = updateBlockLocations(group.locations);
+ if (!noBlockList.length) {
+ return;
+ }
+ let count = parentCount - blockCount;
+ noBlockList.forEach((item) => {
+ item.count = count;
+ count = 0;
+ });
+};
+
+const IfStatement = (group, parentCount) => {
+ const { noBlockList, blockCount } = updateBlockLocations(group.locations);
+ if (!noBlockList.length) {
+ return;
+ }
+ // console.log(parentCount, 'uncovered list', noBlockList.length, group.start);
+ let count = parentCount - blockCount;
+ noBlockList.forEach((item) => {
+ item.count = count;
+ count = 0;
+ });
+
+};
+
+// const b = tf2 || tf1 || a;
+const LogicalExpression = (group, parentCount) => {
+
+ // from left to right
+ group.locations.forEach((item, i) => {
+ if (item.block) {
+ item.count = item.block.count;
+ } else {
+ item.count = parentCount;
+ }
+ });
+
+};
+
+const SwitchStatement = (group, parentCount) => {
+
+ const locations = group.locations;
+
+ const { noBlockList, blockCount } = updateBlockLocations(locations);
+ if (!noBlockList.length) {
+ return;
+ }
+
+ // calculate switch/case count
+ const countLeft = parentCount - blockCount;
+ noBlockList.forEach((item) => {
+
+ let hasCount = false;
+
+ // check no break branches
+ for (let i = item.index - 1; i >= 0; i--) {
+ const b = locations[i];
+ if (b && !b.hasBreak && b.count > 0) {
+ item.count += b.count;
+ hasCount = true;
+ continue;
+ }
+ break;
+ }
+
+ if (!hasCount) {
+ item.count = countLeft;
+ }
+ });
+};
+
+const AssignmentPattern = (group, parentCount) => {
+ group.locations.forEach((item) => {
+ item.count = parentCount;
+ });
+};
+
+// =======================================================================================
+
+const updateBranchCount = (group) => {
+
+ const {
+ type, locations, reverseParents
+ } = group;
+
+ const functionState = getParentFunctionState(reverseParents);
+
+ const functionCount = functionState.count;
+
+ // default is 0, no need continue
+ if (functionCount === 0) {
+ return;
+ }
+
+ // parent is block statement or function
+ let parentCount = getParentCount(reverseParents, functionCount);
+ // parent is group range
+ const groupBlock = getFunctionBlock(group.start, group.end, functionState);
+ if (groupBlock) {
+ parentCount = groupBlock.count;
+ setGeneratedOnly(groupBlock, group.generatedOnly);
+ }
+
+ // calculate branches count
+ locations.forEach((item) => {
+ const {
+ start, end, none
+ } = item;
+ if (none) {
+ return;
+ }
+
+ item.block = getFunctionBlock(start, end, functionState);
+ setGeneratedOnly(item.block, group.generatedOnly);
+
+ });
+
+
+ const handlers = {
+ ConditionalExpression,
+ LogicalExpression,
+ IfStatement,
+ SwitchStatement,
+ AssignmentPattern
+ };
+
+ const handler = handlers[type];
+ if (handler) {
+ handler(group, parentCount);
+ }
+
+};
+
+const generateBranches = (branchMap) => {
+
+ // calculate count for all branches
+ branchMap.forEach((group) => {
+ updateBranchCount(group);
+ });
+
+ // init branches
+ const branches = [];
+ branchMap.forEach((group) => {
+
+ // add start/end for none with group start/end
+ group.locations.forEach((item) => {
+ if (item.none) {
+ item.start = group.start;
+ item.end = group.end;
+ }
+ });
+
+ const branch = {
+ type: group.type,
+ start: group.start,
+ end: group.end,
+ locations: group.locations
+ };
+
+ setGeneratedOnly(branch, group.generatedOnly);
+
+ branches.push(branch);
+ });
+
+ // sort branches
+ branches.sort((a, b) => {
+ return a.start - b.start;
+ });
+
+ return branches;
+};
+
+// =======================================================================================
+// All programs in JavaScript are made of statements and they end with semicolons (;) except block statements which is used to group zero or more statements.
+// Statements are just perform some actions but do not produce any value or output whereas expressions return some value.
+// Expressions return value, statements do not.
+const generateStatements = (statementNodes) => {
+
+ statementNodes.forEach((item) => {
+ const {
+ node,
+ reverseParents
+ } = item;
+
+ const { start, end } = node;
+
+ item.count = 1;
+
+ // isFunction
+ if (node._state) {
+ item.count = node._state.count;
+ return;
+ }
+
+ const functionState = getParentFunctionState(reverseParents);
+ if (!functionState) {
+ // there is a root range, it impossible not found
+ return;
+ }
+
+ // function uncovered
+ if (functionState.count === 0) {
+ item.count = 0;
+ return;
+ }
+
+ const block = getFunctionBlock(start, end, functionState);
+ if (block) {
+ item.count = block.count;
+ return;
+ }
+
+ item.count = functionState.count;
+
+ });
+
+ // remove block statement
+ statementNodes = statementNodes.filter((item) => {
+ return item.node.type !== 'BlockStatement';
+ });
+
+ const statements = statementNodes.map((item) => {
+ const {
+ node,
+ count
+ } = item;
+ const { start, end } = node;
+ return {
+ start,
+ end,
+ count
+ };
+ });
+
+ return statements;
+};
+
+// =======================================================================================
+// handle special generated codes for original coverage
+
+const webpackWrapHandler = (node) => {
+ // ignore webpack wrap for original function
+ // id: { type: 'Identifier', name: '__webpack_modules__' },
+ const name = node.id && node.id.name;
+ if (name === '__webpack_modules__' && node.init && node.init.properties) {
+ // mark all as wrap function
+ node.init.properties.forEach((it) => {
+ it.value._webpackWrapKey = it.key && it.key.value;
+ });
+ // console.log('==========================', node.type);
+ // console.log(name, parents.length);
+ }
+};
+
+const viteImportDefaultHandler = (node, group) => {
+ // ignore vite conditional expression for __esModule default
+ const testObj = node.test.object;
+ const testName = testObj && testObj.name;
+ if (testName && testName.startsWith('__vite__cjsImport')) {
+ setGeneratedOnly(group, true);
+ }
+
+};
+
+
+// =======================================================================================
+
+const collectNodes = (ast) => {
+ const functionNodes = [];
+ const statementNodes = [];
+ const blockNodes = [];
+ const branchMap = new Map();
+ // locationMap for chain LogicalExpression locations
+ const locationMap = new Map();
+
+ Util.visitAst(ast, {
+
+ VariableDeclarator(node, parents) {
+
+ webpackWrapHandler(node);
+
+ },
+
+ // ===============================================================================
+ // statements
+
+ Statement: (node, parents) => {
+ const reverseParents = [].concat(parents).reverse();
+ statementNodes.push({
+ node,
+ reverseParents
+ });
+ },
+
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
+ // as a function "functionName": "",
+ StaticBlock: (node, parents) => {
+ const reverseParents = [].concat(parents).reverse();
+ functionNodes.push({
+ node,
+ reverseParents
+ });
+ },
+
+ // ===============================================================================
+ // functions
+ // Function include FunctionDeclaration, ArrowFunctionExpression, FunctionExpression
+ Function(node, parents) {
+ const reverseParents = [].concat(parents).reverse();
+ functionNodes.push({
+ node,
+ reverseParents
+ });
+ },
+
+ // ===============================================================================
+ // branches
+
+ // `for` block, the count not equal to parent
+ BlockStatement: (node, parents) => {
+
+ // fix branch count: BRDA
+ const reverseParents = [].concat(parents).reverse();
+ blockNodes.push({
+ node,
+ reverseParents
+ });
+
+ },
+
+ // default-arg assignment logic.
+ // function default arguments
+ AssignmentPattern: (node, parents) => {
+ const group = createBranchGroup(BranchTypes.AssignmentPattern, node, parents, branchMap);
+ addBranch(group, node, locationMap);
+ },
+
+ // cond-expr a ternary expression. e.g.: x ? y : z
+ // Ternary
+ // var b = a ? 'consequent' : 'alternate';
+ ConditionalExpression: (node, parents) => {
+
+ const { consequent, alternate } = node;
+ const group = createBranchGroup(BranchTypes.ConditionalExpression, node, parents, branchMap);
+ addBranch(group, consequent, locationMap);
+ addBranch(group, alternate, locationMap);
+
+ viteImportDefaultHandler(node, group);
+
+ },
+
+ // if an if statement; can also be else if.
+ // An IF statement always has exactly two branches:
+ // one where the condition is FALSE and one where the condition is TRUE
+ IfStatement: (node, parents) => {
+ const { consequent, alternate } = node;
+ const group = createBranchGroup(BranchTypes.IfStatement, node, parents, branchMap);
+ addBranch(group, consequent, locationMap);
+
+ // console.log('if type', consequent.type);
+
+ if (alternate) {
+ addBranch(group, alternate, locationMap);
+ } else {
+ // add none branch
+ addNoneBranch(group);
+ // no need update group end, there is no end
+ }
+ },
+
+ // binary-expr a logical expression with a binary operand. e.g.: x && y
+ // var b = a || b || c;
+ // do not use BinaryExpression
+ LogicalExpression: (node, parents) => {
+ const { left, right } = node;
+ // console.log(left.start, right.start);
+
+ // could be same branch start
+ // const da = arguments.length > 1 && typeof arguments[1] !== 'undefined' ? arguments[1] : true;
+
+ let group;
+ // link to same branch start if LogicalExpression
+ const prevLocation = locationMap.get(node.start);
+ if (prevLocation) {
+ // console.log('link branch ==================', type);
+ group = branchMap.get(prevLocation.branchKey);
+ } else {
+ group = createBranchGroup(BranchTypes.LogicalExpression, node, parents, branchMap);
+ addBranch(group, left, locationMap);
+ }
+
+ addBranch(group, right, locationMap);
+
+ // console.log(group.locations.map((it) => it.start));
+
+ // sort branch locations
+ // a || b || c
+ // first, left a and right c
+ // then, left a and right b
+ if (prevLocation) {
+ const { locations } = group;
+ locations.sort((a, b) => {
+ return a.start - b.start;
+ });
+ // update group end after sorted
+ const lastEnd = locations[locations.length - 1].end;
+ if (lastEnd > group.end) {
+ group.end = lastEnd;
+ }
+ }
+ },
+
+ // switch a switch statement.
+ SwitchStatement: (node, parents) => {
+ const group = createBranchGroup(BranchTypes.SwitchStatement, node, parents, branchMap);
+ const cases = node.cases;
+ cases.forEach((switchCase) => {
+ // console.log('switchCase', switchCase.start);
+ addBranch(group, switchCase, locationMap);
+ });
+ }
+
+ });
+
+ return {
+ functionNodes,
+ statementNodes,
+ blockNodes,
+ branchMap
+ };
+};
+
+const getRootFunctionState = (ast, coverageInfo) => {
+ const rootState = {
+ isFunction: true,
+ count: 1
+ };
+ const rootRange = coverageInfo.rootRange;
+ if (rootRange) {
+ rootState.range = rootRange;
+ rootState.count = rootRange.count;
+
+ // could be not from 0
+ // 0 881 { startOffset: 77, endOffset: 881,
+ // const { start, end } = ast;
+ // console.log(start, end, rootRange);
+
+ }
+ ast._state = rootState;
+
+ return rootState;
+};
+
+// eslint-disable-next-line complexity
+const findFunctionRange = (item, coverageInfo) => {
+
+ const { node, reverseParents } = item;
+ const {
+ start, end, type
+ } = node;
+
+ // try function start/end
+ const functionRange = getFunctionRange(start, end, type, coverageInfo);
+ if (functionRange) {
+ return functionRange;
+ }
+
+ // `static async` case:
+ // [1]static [2]async [3]covered(active) {
+ // v8 start: 2
+ // ast start: 1,3
+
+ // fixed by async or static
+
+ // handle for static async
+ if (node.async) {
+ // 'async '.length
+ const asyncLen = 6;
+ const asyncStart = start - asyncLen;
+ // console.log(asyncStart, 'asyncStart ===============================================');
+ const asyncRange = getFunctionRange(asyncStart, end, type, coverageInfo);
+ if (asyncRange) {
+ return asyncRange;
+ }
+ }
+
+ // try if class MethodDefinition
+ // 0 is function self
+ const parent = reverseParents[1];
+ if (parent && parent.type === 'MethodDefinition') {
+
+ const parentRange = getFunctionRange(parent.start, parent.end, parent.type, coverageInfo);
+ if (parentRange) {
+ return parentRange;
+ }
+
+ if (parent.static) {
+ // 'static '.length
+ const staticLen = 7;
+ const staticStart = parent.start + staticLen;
+ // console.log(staticStart, ' staticStart ============================================');
+ const staticRange = getFunctionRange(staticStart, parent.end, parent.type, coverageInfo);
+ if (staticRange) {
+ return staticRange;
+ }
+ }
+
+ }
+
+
+};
+
+const collectAstInfo = (ast, coverageInfo) => {
+
+ const {
+ functionNodes, statementNodes, blockNodes, branchMap
+ } = collectNodes(ast);
+
+ // root function state
+ const rootState = getRootFunctionState(ast, coverageInfo);
+
+ const functions = [];
+ functionNodes.forEach((item) => {
+ const { node } = item;
+ const {
+ start, end, id
+ } = node;
+
+ const bodyStart = node.body.start;
+ const bodyEnd = node.body.end;
+ const functionName = id && id.name;
+ const _webpackWrapKey = node._webpackWrapKey;
+
+ const functionItem = {
+ start,
+ end,
+ bodyStart,
+ bodyEnd,
+ functionName,
+ count: rootState.count
+ };
+
+ setGeneratedOnly(functionItem, _webpackWrapKey);
+
+ functions.push(functionItem);
+
+ const functionState = {
+ isFunction: true,
+ count: functionItem.count
+ };
+
+ const functionRange = findFunctionRange(item, coverageInfo);
+ if (functionRange) {
+
+ setGeneratedOnly(functionRange, _webpackWrapKey);
+
+ functionState.range = functionRange;
+ functionState.count = functionRange.count;
+ functionItem.count = functionRange.count;
+ }
+ node._state = functionState;
+
+ // console.log(item.reverseParents.map((it) => it.type));
+
+ });
+
+ blockNodes.forEach((item) => {
+ addNodeCount(item);
+ });
+
+ const branches = generateBranches(branchMap);
+ const statements = generateStatements(statementNodes);
+
+ return {
+ functions,
+ branches,
+ statements
+ };
+
+};
+
+module.exports = {
+ BranchTypes,
+ collectAstInfo
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/ast.js b/node_modules/monocart-coverage-reports/lib/converter/ast.js
new file mode 100644
index 0000000..5c7dd70
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/ast.js
@@ -0,0 +1,364 @@
+const acorn = require('acorn');
+const acornLoose = require('acorn-loose');
+const { parseCss } = require('../packages/monocart-coverage-vendor.js');
+const Util = require('../utils/util.js');
+
+const { collectAstInfo } = require('./ast-visitor.js');
+
+const getCoverageInfo = (coverageList) => {
+ const functionMap = new Map();
+ const functionNameMap = new Map();
+ const functionStaticRanges = [];
+ const functionUncoveredRanges = [];
+
+ let rootRange;
+ coverageList.forEach((block) => {
+ const {
+ functionName, ranges, root
+ } = block;
+
+ let functionRange;
+ const blockRanges = [];
+
+ ranges.forEach((range, i) => {
+ if (i === 0) {
+ // function range
+ functionRange = range;
+ return;
+ }
+ blockRanges.push(range);
+ });
+
+ functionRange.functionName = functionName;
+
+ // blocks
+ if (blockRanges.length) {
+ const blockMap = new Map();
+ blockRanges.forEach((item) => {
+ blockMap.set(item.startOffset, item);
+ });
+ functionRange.blockMap = blockMap;
+ // uncovered is unique
+ const blockUncoveredRanges = blockRanges.filter((it) => it.count === 0);
+ Util.sortOffsetRanges(blockUncoveredRanges);
+ functionRange.blockUncoveredRanges = blockUncoveredRanges;
+ functionRange.blockCoveredRanges = blockRanges.filter((it) => it.count > 0);
+ }
+
+ // root function from vm
+ if (root) {
+ rootRange = functionRange;
+ return;
+ }
+
+ // root function from first range
+ if (!rootRange) {
+ rootRange = functionRange;
+ }
+
+ functionMap.set(functionRange.startOffset, functionRange);
+ if (functionName) {
+ // can not handle `async functionName`
+ const possibleNameOffset = functionRange.startOffset + functionName.length;
+ functionNameMap.set(possibleNameOffset, functionRange);
+ }
+
+ if (functionName === '') {
+ functionStaticRanges.push(functionRange);
+ }
+
+ // cache uncovered
+ if (functionRange.count === 0) {
+ functionUncoveredRanges.push(functionRange);
+ }
+
+ });
+
+ // sort ranges
+ Util.sortOffsetRanges(functionUncoveredRanges);
+
+ return {
+ functionMap,
+ functionNameMap,
+ functionStaticRanges,
+ functionUncoveredRanges,
+ rootRange
+ };
+
+};
+
+const getFakeAstInfo = (coverageList) => {
+ const functions = [];
+ const branches = [];
+ const statements = [];
+ coverageList.forEach((block) => {
+ const {
+ isBlockCoverage, functionName, ranges, root
+ } = block;
+
+ ranges.forEach((range, i) => {
+
+ const {
+ startOffset, endOffset, count
+ } = range;
+
+ if (i === 0 && root) {
+ return;
+ }
+
+ statements.push({
+ start: startOffset,
+ end: endOffset,
+ count
+ });
+
+ if (isBlockCoverage) {
+ // branches
+ branches.push({
+ type: 'branch',
+ start: startOffset,
+ end: endOffset,
+ locations: [{
+ start: startOffset,
+ end: endOffset,
+ count
+ }]
+ });
+ }
+
+ if (i === 0) {
+ // function range
+ functions.push({
+ start: startOffset,
+ end: endOffset,
+ bodyStart: startOffset,
+ bodyEnd: endOffset,
+ count,
+ functionName
+ });
+
+ }
+ });
+
+ });
+
+ // console.log('fake functions', functions.map((it) => it));
+
+ return {
+ functions,
+ branches,
+ statements
+ };
+};
+
+const getJsAstInfo = (item, coverageList) => {
+
+ const {
+ source, fake, empty
+ } = item;
+
+ if (fake) {
+ return getFakeAstInfo(coverageList);
+ }
+
+ const options = {
+ ecmaVersion: 'latest',
+ // most time for node.js file
+ allowReturnOutsideFunction: true,
+ allowImportExportEverywhere: true,
+ allowAwaitOutsideFunction: true,
+ allowSuperOutsideMethod: true,
+ // first line: #!/usr/bin/env node
+ allowHashBang: true
+ };
+
+ let err;
+ let ast;
+ try {
+ ast = acorn.parse(source, options);
+ } catch (e) {
+ err = e;
+ }
+
+ if (err) {
+
+ // empty = untested file
+ // could be .ts, .vue and so on, that can not be parsed normally
+ if (empty) {
+ return {
+ functions: [],
+ branches: [],
+ statements: []
+ };
+ }
+
+ // runtime code should be parsed successful
+ Util.logInfo(`Unparsable source: ${item.sourcePath} ${err.message}`);
+
+ // it could be jsx even it is `.js`
+
+ // https://github.com/acornjs/acorn/tree/master/acorn-loose
+ // It is recommended to always try a parse with the regular acorn parser first,
+ // and only fall back to this parser when that one finds syntax errors.
+ ast = acornLoose.parse(source, options);
+
+ }
+
+ const coverageInfo = getCoverageInfo(coverageList);
+ coverageInfo.item = item;
+
+ return collectAstInfo(ast, coverageInfo);
+};
+
+// =========================================================================================================
+
+const addRule = (item, coverageList, coveredRanges, count = 0) => {
+
+ const { source } = item;
+ const { start, end } = source;
+ const startOffset = start.offset;
+ const endOffset = end.offset;
+
+ if (!count) {
+ const coveredRange = Util.findInRanges(startOffset, endOffset, coveredRanges, 'startOffset', 'endOffset');
+ if (coveredRange) {
+ count = 1;
+ }
+ }
+
+ coverageList.push({
+ start: startOffset,
+ end: endOffset,
+ count
+ });
+
+ return count;
+
+};
+
+const addAtRule = (item, coverageList, coveredRanges, empty) => {
+ const { name, nodes } = item;
+
+ const defaultCount = empty ? 0 : 1;
+
+ if (['charset', 'import', 'namespace'].includes(name)) {
+ addRule(item, coverageList, coveredRanges, defaultCount);
+ return;
+ }
+
+ if (['media', 'supports', 'container', 'layer'].includes(name)) {
+
+ const childCoverageList = [];
+ const count = addCssRules(nodes, childCoverageList, coveredRanges, empty);
+ if (count) {
+
+ coverageList.push({
+ start: item.source.start.offset,
+ end: childCoverageList[0].start,
+ count: 1
+ });
+
+ let end;
+ childCoverageList.forEach((it) => {
+ coverageList.push(it);
+ end = it.end;
+ });
+
+ coverageList.push({
+ start: end,
+ end: item.source.end.offset,
+ count: 1
+ });
+
+ return;
+ }
+
+ }
+
+ addRule(item, coverageList, coveredRanges);
+
+};
+
+const addCssRules = (list, coverageList, coveredRanges, empty) => {
+
+ if (!Util.isList(list)) {
+ return 0;
+ }
+
+ let count = 0;
+
+ // line and line is 1-base
+ list.forEach((item) => {
+
+ const { type } = item;
+
+ if (type === 'comment') {
+ return;
+ }
+
+ if (type === 'rule') {
+ count += addRule(item, coverageList, coveredRanges);
+ return;
+ }
+
+ // console.log('=============================================================================');
+ // Object.keys(item).forEach((k) => {
+ // if (k === 'parent') {
+ // return;
+ // }
+ // if (k === 'raws') {
+ // return;
+ // }
+ // if (k === 'nodes') {
+ // console.log(k, item[k].length);
+ // return;
+ // }
+ // if (k === 'source') {
+ // console.log(k, item[k].start, item[k].end);
+ // return;
+ // }
+ // console.log(k, item[k]);
+ // });
+
+ if (type === 'atrule') {
+ addAtRule(item, coverageList, coveredRanges, empty);
+ }
+
+ });
+
+ return count;
+};
+
+const getCssAstInfo = (item, coverageList) => {
+
+ const {
+ source, ranges, empty
+ } = item;
+
+ // sort covered ranges
+ ranges.sort((a, b) => a.start - b.start);
+
+ // to offset covered ranges
+ const coveredRanges = ranges.map((it) => {
+ return {
+ startOffset: it.start,
+ endOffset: it.end
+ };
+ });
+
+ const ast = parseCss(source);
+
+ addCssRules(ast.nodes, coverageList, coveredRanges, empty);
+
+ return {
+ functions: [],
+ branches: [],
+ statements: []
+ };
+};
+
+
+module.exports = {
+ getJsAstInfo,
+ getCssAstInfo
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/collect-source-maps.js b/node_modules/monocart-coverage-reports/lib/converter/collect-source-maps.js
new file mode 100644
index 0000000..6d3bcf4
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/collect-source-maps.js
@@ -0,0 +1,287 @@
+const fs = require('fs');
+const path = require('path');
+const EC = require('eight-colors');
+const { fileURLToPath, pathToFileURL } = require('url');
+const Concurrency = require('../platform/concurrency.js');
+const { convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
+const { flattenSourceMaps } = require('./flatten-source-maps.js');
+
+const Util = require('../utils/util.js');
+
+const defaultSourceMapResolver = async (url = '') => {
+
+ if (url.startsWith('file:')) {
+ const p = fileURLToPath(url);
+ const content = Util.readFileSync(p);
+ if (!content) {
+ Util.logDebug(EC.red(`failed to load sourcemap ${p}`));
+ return;
+ }
+ return Util.jsonParse(content);
+ }
+
+ const [err, res] = await Util.request(url);
+
+ if (err) {
+ Util.logDebug(EC.red(`${err.message} ${url}`));
+ return;
+ }
+
+ const content = res.data;
+
+ // could be string not json, if Content-Type is application/octet-stream
+ if (typeof content === 'string') {
+ return Util.jsonParse(content);
+ }
+
+ return content;
+};
+
+const loadSourceMap = async (url, options) => {
+ if (typeof options.sourceMapResolver === 'function') {
+ const content = await options.sourceMapResolver(url, defaultSourceMapResolver);
+ if (typeof content === 'string') {
+ return Util.jsonParse(content);
+ }
+ return content;
+ }
+ return defaultSourceMapResolver(url);
+};
+
+const getSourceMapUrl = (content, url) => {
+
+ const m = content.match(convertSourceMap.mapFileCommentRegex);
+ if (!m) {
+ return;
+ }
+
+ const comment = m.pop();
+ const r = convertSourceMap.mapFileCommentRegex.exec(comment);
+ // for some odd reason //# .. captures in 1 and /* .. */ in 2
+ const filename = r[1] || r[2];
+
+ const urlObj = Util.resolveUrl(filename, url);
+ if (urlObj) {
+ return urlObj.toString();
+ }
+
+ const mapUrl = Util.resolveUrl(filename, pathToFileURL(url).toString());
+ if (mapUrl) {
+ return mapUrl.toString();
+ }
+};
+
+const resolveSourcesContent = (data, url) => {
+
+ const { sources, sourcesContent } = data;
+
+ // sources [1,2,3]
+ // sourcesContent could be [null, null, "content"]
+ // some of contents could be missed
+
+ let hasSourceContent = false;
+ sources.forEach((file, i) => {
+ if (typeof sourcesContent[i] === 'string') {
+ hasSourceContent = true;
+ return;
+ }
+
+ const sourceUrl = Util.resolveUrl(file, url);
+ if (sourceUrl) {
+ let sourcePath = sourceUrl.toString();
+ // could be no `file:`
+ if (sourcePath.startsWith('file:')) {
+ sourcePath = fileURLToPath(sourcePath);
+ }
+ const content = Util.readFileSync(path.resolve(sourcePath));
+ if (typeof content === 'string') {
+ sourcesContent[i] = content;
+ hasSourceContent = true;
+ return;
+ }
+ }
+
+ sourcesContent[i] = '';
+ Util.logDebug(EC.red(`failed to load source content: ${file}`));
+
+ });
+
+
+ if (hasSourceContent) {
+ return data;
+ }
+};
+
+const checkSourcesContent = (data) => {
+ const { sourcesContent, sources } = data;
+
+ if (!sourcesContent) {
+ data.sourcesContent = [];
+ return false;
+ }
+
+ // all should be string, could be [null]
+ const contents = sourcesContent.filter((content) => typeof content === 'string');
+ if (contents.length === sources.length) {
+ return true;
+ }
+
+ return false;
+};
+
+const resolveSectionedSourceMap = (data, url, sections) => {
+
+ let hasSourceContent = false;
+ sections.forEach((item) => {
+ // offset: { line: 1, column: 0 },
+ // map: { sources, sourcesContent }
+
+ const map = item.map;
+
+ if (checkSourcesContent(map)) {
+ hasSourceContent = true;
+ return;
+ }
+
+ const done = resolveSourcesContent(map, url);
+ if (done) {
+ hasSourceContent = true;
+ }
+
+ });
+
+ if (hasSourceContent) {
+ return flattenSourceMaps(data);
+ }
+};
+
+const resolveSourceMap = (data, url) => {
+ if (!data) {
+ return;
+ }
+ const {
+ sections, sources, mappings
+ } = data;
+
+ if (sections) {
+ return resolveSectionedSourceMap(data, url, sections);
+ }
+
+ if (!sources || !mappings) {
+ return;
+ }
+
+ // check sources content
+ if (checkSourcesContent(data)) {
+ return data;
+ }
+
+ // load sources content by sources
+ return resolveSourcesContent(data, url);
+
+};
+
+const getInlineSourceMap = (content) => {
+ let smc;
+ try {
+ smc = convertSourceMap.fromSource(content);
+ } catch (e) {
+ // ignore "//# sourceMappingURL=" in a string
+ // console.log(e);
+ }
+ return smc;
+};
+
+const collectSourceMaps = async (v8list, options) => {
+
+ const sourceList = [];
+ const sourcemapList = [];
+ const concurrency = new Concurrency();
+ for (const item of v8list) {
+
+ const {
+ type, url, id, source, sourceMap
+ } = item;
+
+ // source and sourceMap will be saved as separated file (could be cached)
+ // just keep functions coverage ( could be multiple times, will be merged )
+ // so remove source and sourceMap
+ delete item.source;
+ delete item.sourceMap;
+
+ // source and sourceMap already saved
+ const { cachePath } = Util.getCacheFileInfo('source', id, options.cacheDir);
+ if (fs.existsSync(cachePath)) {
+ continue;
+ }
+
+ // save source and sourceMap to separated json file
+ const sourceData = {
+ id,
+ url,
+ source,
+ sourceMap
+ };
+
+ // remove comments if not debug
+ if (!Util.isDebug()) {
+ sourceData.source = convertSourceMap.removeComments(source);
+ }
+
+ // check sourceMap only for js
+ if (type === 'js' && !sourceData.sourceMap) {
+ // from inline sync
+ const smc = getInlineSourceMap(source);
+ if (smc) {
+ sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
+ sourcemapList.push({
+ url,
+ inline: true
+ });
+ sourceList.push(sourceData);
+ continue;
+ }
+ // from url async
+ const sourceMapUrl = getSourceMapUrl(source, item.url);
+ if (sourceMapUrl) {
+ concurrency.addItem({
+ sourceMapUrl,
+ sourceData
+ });
+ continue;
+ }
+ }
+
+ // no need check sourceMap
+ sourceList.push(sourceData);
+
+ }
+
+ // from url concurrency
+ await concurrency.start(async (item) => {
+ const { sourceMapUrl, sourceData } = item;
+ const { url } = sourceData;
+ const data = await loadSourceMap(sourceMapUrl, options);
+ if (data) {
+ sourceData.sourceMap = resolveSourceMap(data, url);
+ sourcemapList.push({
+ url,
+ sourceMapUrl
+ });
+ }
+
+ sourceList.push(sourceData);
+
+ });
+
+ return {
+ sourceList,
+ sourcemapList
+ };
+
+};
+
+module.exports = {
+ collectSourceMaps,
+ resolveSourceMap
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/converter.js b/node_modules/monocart-coverage-reports/lib/converter/converter.js
new file mode 100644
index 0000000..44eb155
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/converter.js
@@ -0,0 +1,1823 @@
+/**
+ * V8 Coverage Data Converter
+ * @copyright https://github.com/cenfun/monocart-coverage-reports
+ * @author cenfun@gmail.com
+ */
+
+const EC = require('eight-colors');
+
+const { Locator } = require('monocart-locator');
+const Util = require('../utils/util.js');
+
+const findOriginalRange = require('./find-original-range.js');
+const { getJsAstInfo, getCssAstInfo } = require('./ast.js');
+const { getIgnoredRanges } = require('./ignore.js');
+
+const { getUntestedList } = require('./untested.js');
+
+const {
+ sortRanges, dedupeCountRanges, mergeRangesWith
+} = require('../utils/dedupe.js');
+const { getSourceType, initSourceMapSourcesPath } = require('../utils/source-path.js');
+
+const { decode } = require('../packages/monocart-coverage-vendor.js');
+
+const InfoBranch = require('./info-branch.js');
+const InfoFunction = require('./info-function.js');
+const InfoStatement = require('./info-statement.js');
+
+// ========================================================================================================
+
+// debug info
+const logMappingErrors = (type, result) => {
+
+ // const {
+ // errors, start, end, sourcePath
+ // } = result;
+ // // if (!sourcePath.endsWith('monocart-v8.js') || type !== 'byte') {
+ // // return;
+ // // }
+ // const list = [
+ // Util.EC.red('[Mapping]'),
+ // Util.EC.magenta(type),
+ // errors.join(' -> '),
+ // Util.EC.blue(`${start} ~ ${end}`),
+ // sourcePath
+ // ];
+ // console.log(list.join(' '));
+
+};
+
+// ========================================================================================================
+
+const handleIgnoredRanges = (list, ignoredRanges) => {
+ list.forEach((item) => {
+ const range = Util.findInRanges(item.start, item.end, ignoredRanges);
+ if (range) {
+ // console.log(item, range);
+ item.ignored = true;
+ }
+ });
+};
+
+
+const applyBytesToLines = (bytes, locator, lineMap) => {
+ bytes.forEach((range) => {
+ const {
+ start, end, count, ignored
+ } = range;
+
+ // no need handle ignored byte
+ if (ignored) {
+ return;
+ }
+
+ const sLoc = locator.offsetToLocation(start);
+ const eLoc = locator.offsetToLocation(end);
+
+ // update lines coverage
+ const lines = Util.getRangeLines(sLoc, eLoc);
+ Util.updateLinesCoverage(lines, count, lineMap);
+
+ });
+
+ // no ignore items
+ lineMap.forEach((lineItem, line) => {
+
+ const {
+ uncoveredEntire, uncoveredPieces, coveredCount
+ } = lineItem;
+
+ // default count to 1, both js and css
+ let count = 1;
+ // full covered true/false for entire line
+ let covered = true;
+
+ if (uncoveredEntire) {
+ count = 0;
+ covered = false;
+ } else {
+ count = coveredCount;
+ const uncoveredLen = uncoveredPieces.length;
+ if (uncoveredLen > 0) {
+ covered = false;
+ // uncovered
+ count = `1/${uncoveredLen + 1}`;
+ }
+ }
+
+ lineItem.covered = covered;
+ lineItem.count = count;
+
+ });
+
+};
+
+const handleLinesCoverage = (bytes, locator, ignoredRanges) => {
+
+ // init lines
+ let blankCount = 0;
+ let commentCount = 0;
+ const dataExtras = {};
+ // line 1 based
+ const lineMap = new Map();
+ locator.lines.forEach((lineItem) => {
+ // line 1-base
+ const line = lineItem.line + 1;
+
+ // exclude blank and comment
+ if (lineItem.blank) {
+ blankCount += 1;
+ dataExtras[line] = 'b';
+ return;
+ }
+ if (lineItem.comment) {
+ commentCount += 1;
+ dataExtras[line] = 'c';
+ return;
+ }
+ const ignored = Util.findInRanges(lineItem.start, lineItem.end, ignoredRanges);
+ if (ignored) {
+ dataExtras[line] = 'i';
+ return;
+ }
+
+ Util.initLineCoverage(lineItem);
+
+ lineMap.set(line, lineItem);
+ });
+
+ applyBytesToLines(bytes, locator, lineMap);
+
+ const summaryLines = {
+ total: 0,
+ covered: 0,
+ blank: blankCount,
+ comment: commentCount
+ };
+ // data lines
+ const dataLines = {};
+
+ // no ignore items
+ lineMap.forEach((lineItem, line) => {
+ const { count, covered } = lineItem;
+ // data lines
+ dataLines[line] = count;
+
+ summaryLines.total += 1;
+ if (covered) {
+ summaryLines.covered += 1;
+ }
+ });
+
+ return {
+ dataLines,
+ dataExtras,
+ summaryLines
+ };
+};
+
+// ========================================================================================================
+
+const calculateV8Summary = (list) => {
+
+ const summary = {
+ total: 0,
+ covered: 0
+ };
+
+ list.forEach((item) => {
+ if (item.ignored) {
+ return;
+ }
+
+ summary.total += 1;
+ if (item.count > 0) {
+ summary.covered += 1;
+ }
+ });
+
+ return summary;
+};
+
+// ========================================================================================================
+// istanbul coverage format
+// https://github.com/istanbuljs/istanbuljs/blob/master/docs/raw-output.md
+/**
+ * * `path` - the file path for which coverage is being tracked
+ * * `statementMap` - map of statement locations keyed by statement index
+ * * `fnMap` - map of function metadata keyed by function index
+ * * `branchMap` - map of branch metadata keyed by branch index
+ * * `s` - hit counts for statements
+ * * `f` - hit count for functions
+ * * `b` - hit count for branches
+ */
+const collectFileCoverage = (v8Data, state, options) => {
+
+ const {
+ bytes,
+ functions,
+ branches,
+ statements,
+
+ sourcePath,
+ locator
+ } = state;
+
+ // ==========================================
+ // v8 data
+ const data = {
+ bytes: dedupeCountRanges(bytes),
+ functions: [],
+ branches: [],
+ statements: []
+ };
+
+ // ==========================================
+ // ignore
+ const ignoredRanges = getIgnoredRanges(locator, options);
+ if (ignoredRanges) {
+
+ data.ignores = ignoredRanges;
+
+ // data bytes is start/end/count object
+ handleIgnoredRanges(data.bytes, ignoredRanges);
+
+ // functions start/end/count instance
+ handleIgnoredRanges(functions, ignoredRanges);
+
+ // branches start/end/count instance
+ handleIgnoredRanges(branches, ignoredRanges);
+
+ // branch locations start/end/count object
+ branches.forEach((group) => {
+ if (group.ignored) {
+ // all branch group ignored
+ group.locations.forEach((it) => {
+ it.ignored = true;
+ });
+ } else {
+ handleIgnoredRanges(group.locations, ignoredRanges);
+ }
+ });
+
+ // statements start/end/count instance
+ handleIgnoredRanges(statements, ignoredRanges);
+
+ // console.log(ignoredRanges);
+
+ }
+
+ // ==========================================
+ // lines
+ // after bytes with ignored, before calculateV8Lines
+ const {
+ dataLines, dataExtras, summaryLines
+ } = handleLinesCoverage(data.bytes, locator, ignoredRanges);
+
+ data.lines = dataLines;
+ data.extras = dataExtras;
+
+ // console.log('statements', state.sourcePath, statements.length);
+
+ // ==========================================
+
+ data.functions = functions.map((info, i) => {
+ return info.getRange(i);
+ });
+ sortRanges(data.functions);
+
+ // branch group with locations to flat branches
+ data.branches = branches.map((info) => {
+ return info.getRanges();
+ }).flat();
+ sortRanges(data.branches);
+
+ data.statements = statements.map((info) => {
+ return info.getRange();
+ });
+ sortRanges(data.statements);
+
+ // ==========================================
+
+ const summary = {
+ functions: calculateV8Summary(data.functions),
+ branches: calculateV8Summary(data.branches),
+ statements: calculateV8Summary(data.statements),
+ lines: summaryLines
+ };
+
+ // ==========================================
+ // v8 data and summary
+ v8Data.data = data;
+ v8Data.summary = summary;
+
+ // ==========================================
+ // istanbul
+ const istanbulData = {
+ path: sourcePath,
+
+ statementMap: {},
+ fnMap: {},
+ branchMap: {},
+
+ s: {},
+ f: {},
+ b: {}
+ };
+
+ statements.filter((it) => !it.ignored).forEach((statement, index) => {
+ istanbulData.statementMap[`${index}`] = statement.generate(locator);
+ istanbulData.s[`${index}`] = statement.count;
+ });
+
+ functions.filter((it) => !it.ignored).forEach((fn, index) => {
+ istanbulData.fnMap[`${index}`] = fn.generate(locator, index);
+ istanbulData.f[`${index}`] = fn.count;
+ });
+
+ branches.filter((it) => !it.ignored).forEach((branch, index) => {
+ const { map, counts } = branch.generate(locator);
+ istanbulData.branchMap[`${index}`] = map;
+ istanbulData.b[`${index}`] = counts;
+ });
+
+ return istanbulData;
+
+};
+
+// ========================================================================================================
+
+const addJsBytesCoverage = (state, range) => {
+ const {
+ startOffset, endOffset, count
+ } = range;
+ // add bytes range
+ const byte = {
+ start: startOffset,
+ // the end could be > source.length
+ end: Math.min(endOffset, state.maxContentLength),
+ count
+ };
+ // for debug
+ if (Util.isDebug() && state.original) {
+ byte.generatedStart = range.generatedStart;
+ byte.generatedEnd = range.generatedEnd;
+ }
+ state.bytes.push(byte);
+};
+
+const addCssBytesCoverage = (state, range) => {
+ const {
+ start, end, count
+ } = range;
+ // add css bytes range, already start, end
+ state.bytes.push({
+ start,
+ end,
+ count
+ });
+};
+
+// ========================================================================================================
+
+const checkOriginalRangeCode = (range, state, startEndMap, type) => {
+
+ // only for original
+ if (!state.original) {
+ return true;
+ }
+
+ const { start, end } = range;
+ if (start >= end) {
+ // console.log('invalid branch', state.sourcePath, range);
+ return false;
+ }
+
+ // could be vue code
+ const text = state.locator.getSlice(start, end).trim();
+ if (!text) {
+ return false;
+ }
+
+ // do not check text for `bytes`
+ // it could be `} ` uncovered in `try { } catch`
+ // instead, use `dedupeCountRanges` to accumulate count
+ if (type === 'bytes') {
+ return true;
+ }
+
+ // invalid original code
+ // Matches any character that is not a word character from the basic Latin alphabet.
+ // Equivalent to [^A-Za-z0-9_]
+ if (text.length === 1 && (/\W/).test(text)) {
+ // ; } =
+ // console.log(text, type, state.sourcePath, range);
+ return false;
+ }
+
+ // type: bytes, functions, branches, statements
+
+ // check repeated range
+ const key = `${start}_${end}`;
+ if (startEndMap.has(key)) {
+ return false;
+ }
+ startEndMap.set(key, range);
+
+ return true;
+};
+
+const handleFunctionsCoverage = (state) => {
+
+ // functions only for js
+ if (!state.js) {
+ return;
+ }
+
+ const startEndMap = new Map();
+ const { functions, astInfo } = state;
+ astInfo.functions.forEach((it) => {
+
+ if (!checkOriginalRangeCode(it, state, startEndMap, 'functions')) {
+ return;
+ }
+
+ functions.push(new InfoFunction(it, state.original));
+ });
+
+};
+
+const handleBranchesCoverage = (state) => {
+
+ // functions only for js
+ if (!state.js) {
+ return;
+ }
+
+ const startEndMap = new Map();
+ const { branches, astInfo } = state;
+ astInfo.branches.forEach((it) => {
+
+ if (!checkOriginalRangeCode(it, state, startEndMap, 'branches')) {
+ return;
+ }
+
+ branches.push(new InfoBranch(it, state.original));
+ });
+
+};
+
+const handleStatementsCoverage = (state) => {
+
+ // statement only for js
+ if (!state.js) {
+ return;
+ }
+
+ const startEndMap = new Map();
+ const { statements, astInfo } = state;
+ astInfo.statements.forEach((it) => {
+
+ if (!checkOriginalRangeCode(it, state, startEndMap, 'statements')) {
+ return;
+ }
+
+ statements.push(new InfoStatement(it, state.original));
+ });
+
+};
+
+const handleOriginalBytesCoverage = (state) => {
+ const startEndMap = new Map();
+ state.bytes = state.bytes.filter((it) => {
+ return checkOriginalRangeCode(it, state, startEndMap, 'bytes');
+ });
+};
+
+const handleGeneratedBytesCoverage = (state) => {
+
+ // it could be a dist file, do not handle twice
+ if (state.addedGeneratedBytes) {
+ return;
+ }
+
+ const { js, coverageList } = state;
+
+ if (js) {
+ coverageList.forEach((block) => {
+ block.ranges.forEach((range) => {
+
+ const { fixedStart, fixedEnd } = Util.fixSourceRange(state.locator, range.startOffset, range.endOffset);
+ range.startOffset = fixedStart;
+ range.endOffset = fixedEnd;
+
+ addJsBytesCoverage(state, range);
+ });
+ });
+ } else {
+ coverageList.forEach((range) => {
+ addCssBytesCoverage(state, range);
+ });
+ }
+
+ state.addedGeneratedBytes = true;
+
+};
+
+// ========================================================================================================
+
+const handleOriginalFunctionsCoverage = (state, originalStateMap) => {
+
+ // functions only for js
+ if (!state.js) {
+ return;
+ }
+
+ // console.log(state.astInfo.functions);
+
+ const updateFunctionBodyRange = (start, end, bodyStart, bodyEnd, originalFunction) => {
+ if (bodyStart !== start || bodyEnd !== end) {
+ const result = findOriginalRange(bodyStart, bodyEnd, state, originalStateMap);
+ if (result.error) {
+ logMappingErrors('function body', result);
+ } else {
+ originalFunction.bodyStart = result.start;
+ originalFunction.bodyEnd = result.end;
+ }
+ }
+ };
+
+ // function count
+ state.astInfo.functions.forEach((it) => {
+
+ // remove webpack wrap functions for functions count, not for ranges here
+ if (it.generatedOnly) {
+ return;
+ }
+
+ const {
+ start, end, bodyStart, bodyEnd
+ } = it;
+
+ const result = findOriginalRange(start, end, state, originalStateMap, {
+ checkName: true
+ });
+ if (result.error) {
+ logMappingErrors('function', result);
+ return;
+ }
+
+ const originalFunction = {
+ ... it,
+ generatedStart: start,
+ generatedEnd: end,
+ start: result.start,
+ end: result.end,
+ bodyStart: result.start,
+ bodyEnd: result.end
+ };
+
+ if (result.name) {
+ originalFunction.functionName = result.name;
+ }
+
+ // body start and end
+ updateFunctionBodyRange(start, end, bodyStart, bodyEnd, originalFunction);
+
+ // add back to original ast
+ result.originalState.astInfo.functions.push(originalFunction);
+
+ });
+
+};
+
+const handleOriginalBranchesCoverage = (state, originalStateMap) => {
+
+ // branches only for js
+ if (!state.js) {
+ return;
+ }
+
+ // console.log(state.astInfo.branches);
+
+ // function count
+ state.astInfo.branches.forEach((group) => {
+
+ if (group.generatedOnly) {
+ return;
+ }
+
+ const { type, locations } = group;
+
+ // start
+ const result = findOriginalRange(group.start, group.end, state, originalStateMap);
+ if (result.error) {
+ logMappingErrors('branch group', result);
+ return;
+ }
+
+ // new group start and end
+ const groupStart = result.start;
+ const groupEnd = result.end;
+
+ let hasError;
+ const newLocations = locations.map((oLoc) => {
+
+ const newLoc = {
+ ... oLoc
+ };
+
+ if (newLoc.none) {
+ newLoc.start = groupStart;
+ newLoc.end = groupEnd;
+ return newLoc;
+ }
+
+ const locResult = findOriginalRange(newLoc.start, newLoc.end, state, originalStateMap);
+ if (locResult.error) {
+ // It should not happen unless it is minify files, the SourceMap has some order problems
+ logMappingErrors('branch', locResult);
+ hasError = true;
+ return newLoc;
+ }
+
+ // before new range
+ newLoc.generatedStart = newLoc.start;
+ newLoc.generatedEnd = newLoc.end;
+ // mapping to new range
+ newLoc.start = locResult.start;
+ newLoc.end = locResult.end;
+
+ return newLoc;
+ });
+
+ // ignored group when found error
+ if (hasError) {
+ return;
+ }
+
+ // add back to original ast
+ result.originalState.astInfo.branches.push({
+ type,
+ start: groupStart,
+ end: groupEnd,
+ locations: newLocations
+ });
+
+ });
+
+};
+
+const handleOriginalStatementsCoverage = (state, originalStateMap) => {
+
+ // statements only for js
+ if (!state.js) {
+ return;
+ }
+
+ // statement count
+ state.astInfo.statements.forEach((it) => {
+
+ if (it.generatedOnly) {
+ return;
+ }
+
+ const { start, end } = it;
+
+ const result = findOriginalRange(start, end, state, originalStateMap);
+ if (result.error) {
+ logMappingErrors('statement', result);
+ return;
+ }
+
+ // add back to original ast
+ result.originalState.astInfo.statements.push({
+ ... it,
+ generatedStart: start,
+ generatedEnd: end,
+ start: result.start,
+ end: result.end
+ });
+
+ });
+
+ originalStateMap.forEach((originalState) => {
+ const statements = originalState.astInfo.statements;
+ if (Util.isList(statements)) {
+ return;
+ }
+ // fake source nothing matched
+ statements.push({
+ start: 0,
+ end: originalState.source.length,
+ count: 1
+ });
+
+ });
+
+};
+
+// ========================================================================================================
+
+const handleOriginalEmptyBytesCoverage = (state, originalStateMap) => {
+ const checkList = [];
+
+ // check original bytes if fully in a wrapper range
+ originalStateMap.forEach((originalState) => {
+ const bytes = originalState.bytes;
+ if (Util.isList(bytes)) {
+ return;
+ }
+
+ const { decodedMappings } = originalState;
+ const len = decodedMappings.length;
+ if (len < 2) {
+ return;
+ }
+ // sort by original line/column
+ const startMapping = decodedMappings[0];
+ const endMapping = decodedMappings[len - 1];
+ // console.log(startMapping, endMapping);
+ const startOffset = startMapping.generatedOffset;
+
+ if (!endMapping.generatedEndOffset) {
+ // line last one
+ const line = state.locator.getLine(endMapping.generatedLine + 1);
+ // could be no line found
+ if (line) {
+ // last column
+ endMapping.generatedEndOffset = line.end;
+ } else {
+ endMapping.generatedEndOffset = endMapping.generatedOffset;
+ }
+ }
+ const endOffset = endMapping.generatedEndOffset;
+
+ // console.log('===========================================================', originalState.sourcePath);
+ // console.log(originalState.source.length, endMapping);
+
+ checkList.push({
+ originalState,
+ startOffset,
+ endOffset
+ });
+
+ });
+
+ // no file to handle
+ if (!checkList.length) {
+ return;
+ }
+
+ // there is no state.bytes if not in debug
+ // should using coverageList to generate bytes first
+ handleGeneratedBytesCoverage(state);
+
+ checkList.forEach((it) => {
+
+ const {
+ originalState, startOffset, endOffset
+ } = it;
+
+ // console.log('=============', 'no bytes', originalState.sourcePath);
+
+ // only check uncovered range
+ // because a uncovered range could be in a covered wrapper
+ // { start: 0, end: 12137, count: 1 }, could be { start: > 0, end: < 12137, count: 0 }
+ for (const range of state.bytes) {
+ if (range.count > 0) {
+ continue;
+ }
+ if (startOffset >= range.start && endOffset <= range.end) {
+ // console.log('------------', 'added');
+ originalState.bytes.push({
+ start: 0,
+ end: originalState.source.length,
+ count: 0
+ });
+ break;
+ }
+ }
+
+ });
+};
+
+
+const handleAllOriginalBytesCoverage = (state, originalStateMap) => {
+
+ const { js, coverageList } = state;
+
+ // only for js, no sourcemap for css for now
+ if (!js) {
+ return;
+ }
+
+ // v8 coverage
+ coverageList.forEach((block) => {
+ block.ranges.forEach((range) => {
+
+ // remove wrap functions for original files
+ if (range.generatedOnly) {
+ return;
+ }
+
+ const {
+ startOffset, endOffset, count
+ } = range;
+
+ const result = findOriginalRange(startOffset, endOffset, state, originalStateMap, {
+ fixOriginalRange: true
+ });
+ if (result.error) {
+ logMappingErrors('byte', result);
+ return;
+ }
+
+ addJsBytesCoverage(result.originalState, {
+ generatedStart: startOffset,
+ generatedEnd: endOffset,
+ startOffset: result.start,
+ endOffset: result.end,
+ count
+ });
+
+ });
+ });
+
+ handleOriginalEmptyBytesCoverage(state, originalStateMap);
+
+};
+
+// ========================================================================================================
+
+const decodeSourceMappings = (state, originalDecodedMap) => {
+
+ const generatedLocator = state.locator;
+
+ const {
+ sources, mappings, decodedMappings
+ } = state.sourceMap;
+
+ const decodedList = decodedMappings || decode(mappings);
+
+ // console.log(decodedList);
+
+ sources.forEach((source, i) => {
+ originalDecodedMap.set(i, []);
+ });
+
+ const allDecodedMappings = [];
+ decodedList.forEach((segments, generatedLine) => {
+
+ if (!segments.length) {
+ return;
+ }
+
+ // line segments
+ const lastIndex = segments.length - 1;
+ segments.forEach((segment, i) => {
+
+ // const COLUMN = 0;
+ // const SOURCES_INDEX = 1;
+ // const SOURCE_LINE = 2;
+ // const SOURCE_COLUMN = 3;
+ // const NAMES_INDEX = 4;
+ const [generatedColumn, sourceIndex, originalLine, originalColumn, nameIndex] = segment;
+ // the segment length could be 1, 4 or 5
+
+ if (typeof sourceIndex === 'undefined') {
+ // console.log('============================ sourceIndex undefined');
+ // console.log(segment);
+ return;
+ }
+
+ // 1-base
+ const generatedSN = generatedLine + 1;
+
+ const generatedOffset = generatedLocator.locationToOffset({
+ // 1-base
+ line: generatedSN,
+ column: generatedColumn
+ });
+
+ const info = {
+ generatedOffset,
+ generatedLine,
+ generatedColumn,
+
+ sourceIndex,
+ originalLine,
+ originalColumn,
+ nameIndex
+ };
+
+ // first and last column
+ if (i === 0) {
+ info.first = true;
+ }
+ if (i === lastIndex) {
+ info.last = true;
+ }
+
+ allDecodedMappings.push(info);
+ originalDecodedMap.get(sourceIndex).push(info);
+
+ // calculate line end column
+ if (!info.last) {
+ return;
+ }
+
+ const lineItem = generatedLocator.getLine(generatedSN);
+ if (!lineItem) {
+ // console.log('============================== not found line item');
+ return;
+ }
+
+ // console.log(generatedSN, generatedColumn, lineItem.length, lineItem.text);
+
+ if (generatedColumn >= lineItem.length) {
+ return;
+ }
+
+ const endColumn = {
+ generatedOffset: generatedOffset + (lineItem.length - generatedColumn),
+ generatedLine,
+ generatedColumn: lineItem.length,
+
+ end: true,
+
+ sourceIndex,
+ originalLine,
+ originalColumn,
+ nameIndex
+ };
+
+ // console.log(generatedSN, generatedColumn, info, endColumn);
+
+ allDecodedMappings.push(endColumn);
+ originalDecodedMap.get(sourceIndex).push(endColumn);
+
+ });
+
+
+ });
+
+ // defaults to sort by generated offset, no need sort
+ // allDecodedMappings.sort((a, b) => {
+ // return a.generatedOffset - b.generatedOffset;
+ // });
+
+ return allDecodedMappings;
+};
+
+const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) => {
+ // all mappings for the original file sorted
+ const decodedMappings = originalDecodedMap.get(sourceIndex);
+
+ if (!decodedMappings) {
+ return [];
+ }
+
+ // calculate line end column
+ decodedMappings.forEach((item) => {
+ if (!item.end) {
+ return;
+ }
+ const originalSN = item.originalLine + 1;
+ const lineItem = locator.getLine(originalSN);
+ // console.log(originalSN, item.originalColumn, lineItem.length, lineItem.text);
+ if (lineItem) {
+ item.originalColumn = lineItem.length;
+ }
+ });
+
+ // sort by original line/column
+ decodedMappings.sort((a, b) => {
+ if (a.originalLine === b.originalLine) {
+ return a.originalColumn - b.originalColumn;
+ }
+ return a.originalLine - b.originalLine;
+ });
+
+ // add offset and index
+ decodedMappings.forEach((item, i) => {
+ item.originalOffset = locator.locationToOffset({
+ line: item.originalLine + 1,
+ column: item.originalColumn
+ });
+ });
+
+ return decodedMappings;
+};
+
+// ========================================================================================================
+
+const initOriginalList = (state, originalDecodedMap, options) => {
+
+ // source filter
+ const sourceFilter = Util.getSourceFilter(options);
+
+ // create original content mappings
+ const originalStateMap = new Map();
+
+ const { sources, sourcesContent } = state.sourceMap;
+
+ const lengthBefore = sources.length;
+ let lengthAfter = 0;
+
+ sources.forEach((sourcePath, sourceIndex) => {
+
+ // filter
+ // do not change for sourceIndex
+ if (!sourceFilter(sourcePath)) {
+ // console.log('-', sourcePath);
+ return;
+ }
+ // console.log(sourcePath);
+
+ // console.log(`add source: ${k}`);
+ const sourceContent = sourcesContent?.[sourceIndex];
+ if (typeof sourceContent !== 'string') {
+ Util.logError(`not found source content: ${sourcePath}`);
+ return;
+ }
+
+ const locator = new Locator(sourceContent);
+ const maxContentLength = sourceContent.length;
+
+ const decodedMappings = getOriginalDecodedMappings(originalDecodedMap, sourceIndex, locator);
+
+ // unpacked file always is js
+
+ const type = getSourceType(sourcePath);
+
+ const originalState = {
+ original: true,
+ // original file is js
+ js: true,
+ type,
+ source: sourceContent,
+ sourcePath,
+ locator,
+ maxContentLength,
+ decodedMappings,
+ // coverage info
+ bytes: [],
+ functions: [],
+ branches: [],
+ statements: [],
+ astInfo: {
+ functions: [],
+ branches: [],
+ statements: []
+ },
+ // coverage data
+ v8Data: {}
+ };
+
+ originalStateMap.set(sourceIndex, originalState);
+ lengthAfter += 1;
+ });
+
+ Util.logFilter(`source filter (${state.sourcePath}):`, lengthBefore, lengthAfter);
+
+ return originalStateMap;
+};
+
+const collectOriginalList = (state, originalStateMap) => {
+
+ const { fileUrls } = state;
+ const distFile = state.sourcePath;
+
+ let added = 0;
+
+ // collect original files
+ originalStateMap.forEach((originalState) => {
+
+ const {
+ js, type, sourcePath, source
+ } = originalState;
+
+ // add file item
+ const url = fileUrls[sourcePath] || sourcePath;
+
+ // add dist for id
+ const id = Util.calculateSha1(sourcePath + source);
+
+ const sourceItem = {
+ url,
+ id,
+ js,
+ type,
+ sourcePath,
+ distFile,
+ source
+ };
+
+ // save v8 data and add to originalList
+ originalState.v8Data = sourceItem;
+ state.originalList.push(originalState);
+ added += 1;
+ });
+
+ Util.logDebug(`added source files: ${EC.yellow(added)}`);
+
+};
+
+// ========================================================================================================
+
+const generateCoverageForDist = (state) => {
+
+ handleFunctionsCoverage(state);
+ handleBranchesCoverage(state);
+ handleStatementsCoverage(state);
+ handleGeneratedBytesCoverage(state);
+
+};
+
+const handleUncoveredInCovered = (state, originalStateMap) => {
+ if (!state.fake) {
+ return;
+ }
+
+ // fake statements/branches/functions
+ originalStateMap.forEach((originalState) => {
+ const { bytes } = originalState;
+ const list = [];
+ originalState.bytes = bytes.filter((item) => {
+ if (item.count === 0) {
+ const range = bytes.find((it) => {
+ if (it.count === 0) {
+ return false;
+ }
+ if (it.start > item.end) {
+ return false;
+ }
+ if (it.end < item.start) {
+ return false;
+ }
+ return true;
+ });
+ if (range) {
+ list.push(item);
+ return false;
+ }
+ }
+ return true;
+ });
+
+ if (!list.length) {
+ return;
+ }
+
+ list.forEach((item) => {
+ ['statements', 'branches', 'functions'].forEach((key) => {
+ originalState[key] = originalState[key].filter((it) => {
+ if (it.start === item.start && it.end === item.end) {
+ return false;
+ }
+ return true;
+ });
+ });
+ });
+
+ });
+
+};
+
+const unpackSourceMap = (state, options) => {
+
+ const { sourceMap, sourcePath } = state;
+
+ // keep original urls
+ const fileUrls = {};
+ initSourceMapSourcesPath(fileUrls, sourceMap, sourcePath, options);
+ state.fileUrls = fileUrls;
+ // for function names
+ state.sourceMapNames = sourceMap.names || [];
+
+ // ===============================================
+ // decode mappings for each original file
+
+ const originalDecodedMap = new Map();
+ // for find-original-range
+ state.decodedMappings = decodeSourceMappings(state, originalDecodedMap);
+
+ // filter original list and init list
+ const originalStateMap = initOriginalList(state, originalDecodedMap, options);
+
+ originalDecodedMap.clear();
+
+ // ===============================================
+
+ // handle functions before handle original state functions
+ handleOriginalFunctionsCoverage(state, originalStateMap);
+ handleOriginalBranchesCoverage(state, originalStateMap);
+ handleOriginalStatementsCoverage(state, originalStateMap);
+
+ // handle lines info before handle ranges to update line count
+ originalStateMap.forEach((originalState) => {
+
+ handleFunctionsCoverage(originalState);
+ handleBranchesCoverage(originalState);
+ handleStatementsCoverage(originalState);
+
+ // if (originalState.sourcePath.endsWith('demo.js')) {
+ // console.log('=================================', originalState.sourcePath);
+ // }
+
+ });
+
+ // handle bytes ranges
+ handleAllOriginalBytesCoverage(state, originalStateMap);
+
+ originalStateMap.forEach((originalState) => {
+ handleOriginalBytesCoverage(originalState);
+ });
+
+ // remove uncovered in covered for fake
+ handleUncoveredInCovered(state, originalStateMap);
+
+ // collect coverage for original list
+ collectOriginalList(state, originalStateMap);
+
+};
+
+const unpackDistFile = (item, state, options) => {
+
+ if (state.sourceMap) {
+ if (Util.isDebug()) {
+ // js self
+ item.debug = true;
+ generateCoverageForDist(state);
+ } else {
+ item.dedupe = true;
+ }
+
+ // unpack source map
+ unpackSourceMap(state, options);
+
+ } else {
+
+ // css/js self
+ generateCoverageForDist(state);
+
+ }
+
+};
+
+// ========================================================================================================
+
+const filterCoverageList = (item) => {
+ const {
+ functions, scriptOffset, source
+ } = item;
+
+ // no script offset
+ if (!scriptOffset) {
+ return functions;
+ }
+
+ // vm script offset
+ const minOffset = scriptOffset;
+ // the inline sourcemap could be removed
+ const maxOffset = source.length;
+
+ const wrapperList = [];
+
+ const coverageList = functions.filter((block) => {
+
+ const { ranges } = block;
+
+ // first one is function coverage info
+ const functionRange = ranges[0];
+ const { startOffset, endOffset } = functionRange;
+ if (startOffset >= minOffset && endOffset <= maxOffset) {
+ return true;
+ }
+
+ wrapperList.push(block);
+
+ return false;
+ });
+
+ if (wrapperList.length) {
+ const rootBlock = wrapperList.pop();
+ rootBlock.root = true;
+ coverageList.unshift(rootBlock);
+ }
+
+ // if (item.sourcePath.includes('PlaceB.tsx')) {
+ // console.log(coverageList);
+ // }
+
+ return coverageList;
+};
+
+const initJsCoverageList = (item) => {
+ const coverageList = filterCoverageList(item);
+
+ // if (item.sourcePath.includes('PlaceB.tsx')) {
+ // console.log(coverageList[0]);
+ // }
+
+ // function could be covered even it is defined after an uncovered return, see case closures.js
+ // fix uncovered range if there are covered ranges in uncovered range
+
+ const uncoveredBlocks = [];
+ const uncoveredList = [];
+ coverageList.forEach((block) => {
+ block.ranges.forEach((range, i) => {
+ const {
+ count, startOffset, endOffset
+ } = range;
+
+ if (i === 0) {
+ // check only first level
+ if (count > 0) {
+ const inUncoveredRange = Util.findInRanges(startOffset, endOffset, uncoveredBlocks, 'startOffset', 'endOffset');
+ if (inUncoveredRange) {
+ if (!inUncoveredRange.coveredList) {
+ inUncoveredRange.coveredList = [];
+ uncoveredList.push(inUncoveredRange);
+ }
+ inUncoveredRange.coveredList.push(range);
+ }
+ }
+ return;
+ }
+
+ if (count === 0) {
+ uncoveredBlocks.push({
+ ... range,
+ index: i,
+ ranges: block.ranges
+ });
+ }
+
+ });
+ });
+
+ if (uncoveredList.length) {
+
+ uncoveredList.forEach((it) => {
+ const {
+ ranges, index, count, coveredList
+ } = it;
+
+ // remove previous range first
+ const args = [index, 1];
+
+ Util.sortOffsetRanges(coveredList);
+ let startOffset = it.startOffset;
+ coveredList.forEach((cov) => {
+ // ignore sub functions in the function
+ if (cov.startOffset > startOffset) {
+ args.push({
+ startOffset,
+ endOffset: cov.startOffset,
+ count
+ });
+ startOffset = cov.endOffset;
+ }
+ });
+
+ if (it.endOffset > startOffset) {
+ args.push({
+ startOffset,
+ endOffset: it.endOffset,
+ count
+ });
+ }
+
+ ranges.splice(... args);
+
+ });
+ }
+
+ return coverageList;
+};
+
+const logConvertTime = (msg, time_start, untested) => {
+ if (untested) {
+ return;
+ }
+ Util.logTime(msg, time_start);
+};
+
+const convertCoverages = (list, options, untested) => {
+ const stateList = [];
+
+ for (const item of list) {
+ // console.log([item.id]);
+
+ const time_start_ast = Date.now();
+
+ const {
+ type, source, fake, sourcePath
+ } = item;
+
+ // for source file, type could be ts or vue as extname, but js = true
+ const js = type === 'js';
+ item.js = js;
+
+ // source mapping
+ const locator = new Locator(source);
+ const maxContentLength = source.length;
+
+ // ============================
+ // move sourceMap
+ const sourceMap = item.sourceMap;
+ if (sourceMap) {
+ delete item.sourceMap;
+ }
+
+ // ============================
+ // move functions and ranges to coverageList
+ let coverageList = [];
+ let astInfo;
+ if (js) {
+ coverageList = initJsCoverageList(item);
+ // remove original functions
+ if (!Util.isDebug()) {
+ delete item.functions;
+ }
+ astInfo = getJsAstInfo(item, coverageList);
+ } else {
+ // convent css covered ranges to rules ranges and include uncovered ranges
+ astInfo = getCssAstInfo(item, coverageList);
+ // remove original ranges
+ if (!Util.isDebug()) {
+ delete item.ranges;
+ }
+ }
+
+ logConvertTime(`${EC.magenta('│ ')}${EC.cyan('├')} [convert] parsed ast: ${sourcePath} (${EC.cyan(Util.BSF(maxContentLength))})`, time_start_ast, untested);
+
+ // console.log(sourcePath, astInfo.statements.length);
+ // ============================
+
+ const time_start_unpack = Date.now();
+
+ // current file and it's sources from sourceMap
+ // see const originalState
+ const state = {
+ js,
+ type,
+ source,
+ fake,
+ sourcePath,
+ sourceMap,
+ locator,
+ maxContentLength,
+ decodedMappings: [],
+ rangeCache: new Map(),
+ diffCache: new Map(),
+
+ // alignTextList: [],
+
+ // coverage info
+ bytes: [],
+ functions: [],
+ branches: [],
+ statements: [],
+ astInfo,
+ // for sub source files
+ coverageList,
+ originalList: [],
+ // coverage data
+ v8Data: item
+ };
+
+ unpackDistFile(item, state, options);
+ const unpackedFiles = EC.cyan(`${state.originalList.length} files`);
+
+ stateList.push(state);
+
+ logConvertTime(`${EC.magenta('│ ')}${EC.cyan('├')} [convert] unpacked sourcemap: ${sourcePath} (${unpackedFiles})`, time_start_unpack, untested);
+
+ }
+
+ return stateList;
+};
+
+// ========================================================================================================
+
+const isUncoveredRange = (range, key, uncoveredGroups) => {
+
+ let uncovered = true;
+ uncoveredGroups.forEach((group) => {
+ const { groupMap, state } = group;
+
+ // ========================================================
+ // self key
+ if (groupMap.has(key)) {
+ return;
+ }
+
+ // ========================================================
+ // in all group
+ for (const item of groupMap.values()) {
+ if (range.start >= item.start && range.end <= item.end) {
+ return;
+ }
+ }
+
+ // ========================================================
+ // no sourcemap mappings in range
+ // check original range in decodedMappings
+ const { decodedMappings } = state;
+ const item = decodedMappings.find((it) => it.originalOffset >= range.start && it.originalOffset <= range.end);
+ if (!item) {
+ return;
+ }
+
+ // ========================================================
+ uncovered = false;
+
+ });
+
+ return uncovered;
+
+};
+
+const mergeV8Data = (state, stateList) => {
+ // console.log(stateList);
+
+ // let debug = false;
+ // if (state.sourcePath.endsWith('counter')) {
+ // debug = true;
+ // console.log('merge v8 data ===================================', state.sourcePath);
+ // console.log('bytes before ============', stateList.map((it) => it.bytes));
+ // // console.log('statements ==============', stateList.map((it) => it.statements));
+ // // console.log('functions ==============', stateList.map((it) => it.functions));
+ // // console.log('branches ======================', stateList.map((it) => it.branches.map((b) => [`${b.start}-${b.end}`, JSON.stringify(b.locations.map((l) => l.count))])));
+ // }
+
+ // ===========================================================
+ // bytes
+ const mergedBytes = [];
+
+ const coveredMap = new Map();
+ const uncoveredMap = new Map();
+ const uncoveredGroups = [];
+ stateList.forEach((st) => {
+ const groupMap = new Map();
+ const bytes = dedupeCountRanges(st.bytes);
+ bytes.forEach((range) => {
+ const key = `${range.start}_${range.end}`;
+ if (range.count) {
+ mergedBytes.push(range);
+ coveredMap.set(key, true);
+ } else {
+ uncoveredMap.set(key, range);
+ groupMap.set(key, range);
+ }
+ });
+
+ uncoveredGroups.push({
+ groupMap,
+ state: st
+ });
+ });
+
+ // if (debug) {
+ // console.log('coveredMap ============', coveredMap);
+ // console.log('uncoveredMap ============', uncoveredMap);
+ // console.log('uncoveredGroups ============', uncoveredGroups);
+ // }
+
+ uncoveredMap.forEach((range, key) => {
+
+ // in covered range
+ if (coveredMap.has(key)) {
+ return;
+ }
+
+ if (isUncoveredRange(range, key, uncoveredGroups)) {
+ mergedBytes.push(range);
+ }
+
+ });
+
+ // will be dedupeCountRanges in collectFileCoverage
+ state.bytes = mergedBytes;
+
+ // if (debug) {
+ // console.log('bytes after ============', mergedBytes);
+ // }
+
+ // ===========================================================
+ // functions
+ const allFunctions = stateList.map((it) => it.functions).flat();
+ const functionComparer = (lastRange, range) => {
+ // if (lastRange.start === range.start && lastRange.end === range.end) {
+ // return true;
+ // }
+
+ // function range could be from sourcemap, not exact matched
+
+ // end is same
+ // {start: 2017, end: 2315, count: 481}
+ // {start: 2018, end: 2315, count: 14}
+
+ // start is same
+ // {start: 10204, end: 10379, count: 0}
+ // {start: 10204, end: 10393, count: 5}
+
+ // only one position matched could be same
+ if (lastRange.start === range.start || lastRange.end === range.end) {
+ // console.log(lastRange.start, range.start, lastRange.end, range.end);
+
+ // if (lastRange.start === range.start) {
+ // console.log(range.end - lastRange.end, lastRange.start, lastRange.end, 'end', range.end, state.sourcePath);
+ // } else {
+ // console.log(range.start - lastRange.start, lastRange.start, lastRange.end, 'start', range.start, state.sourcePath);
+ // }
+
+ return true;
+ }
+
+ return false;
+ };
+ const functionHandler = (lastRange, range) => {
+ lastRange.count += range.count;
+ };
+ const mergedFunctions = mergeRangesWith(allFunctions, functionComparer, functionHandler);
+ state.functions = mergedFunctions;
+
+ // ===========================================================
+ // statements
+ const allStatements = stateList.map((it) => it.statements).flat();
+ const statementComparer = (lastRange, range) => {
+ // exact matched because the statement range is generated from ast
+ return lastRange.start === range.start && lastRange.end === range.end;
+ };
+ const statementHandler = (lastRange, range) => {
+ // merge statements count
+ lastRange.count += range.count;
+ };
+ const mergedStatements = mergeRangesWith(allStatements, statementComparer, statementHandler);
+ state.statements = mergedStatements;
+
+ // ===========================================================
+ // branches
+ const allBranches = stateList.map((it) => it.branches).flat();
+ const branchComparer = (lastRange, range) => {
+ // exact matched because the branch range is generated from ast
+ return lastRange.start === range.start && lastRange.end === range.end;
+ };
+ const branchHandler = (lastRange, range) => {
+ // merge locations count
+ lastRange.locations.forEach((item, i) => {
+ const loc = range.locations[i];
+ if (loc) {
+ item.count += loc.count;
+ }
+ });
+ };
+ const mergedBranches = mergeRangesWith(allBranches, branchComparer, branchHandler);
+ state.branches = mergedBranches;
+
+ // if (sourcePath.endsWith('scroll_zoom.ts')) {
+ // console.log(mergedBytes);
+ // console.log(mergedFunctions);
+ // console.log(mergedBranches.map((b) => [`${b.start}-${b.end}`, JSON.stringify(b.locations.map((l) => l.count))]));
+ // }
+
+
+};
+
+const addUntestedFiles = async (stateList, options) => {
+
+ const time_start_untested = Date.now();
+
+ const testedMap = new Map();
+ stateList.forEach((state) => {
+ const { v8Data, originalList } = state;
+ // dedupe dist file if not debug
+ if (!v8Data.dedupe) {
+ testedMap.set(state.sourcePath, true);
+ }
+ originalList.forEach((originalState) => {
+ testedMap.set(originalState.sourcePath, true);
+ });
+ });
+
+ // console.log('testedMap', testedMap);
+
+ const untestedList = await getUntestedList(testedMap, options, 'v8');
+ if (!untestedList) {
+ return;
+ }
+
+ const untestedStateList = convertCoverages(untestedList, options, true);
+
+ // console.log(untestedStateList);
+ untestedStateList.forEach((state) => {
+ stateList.push(state);
+ });
+
+ // console.log('untestedList', untestedList);
+
+ Util.logTime(`${EC.magenta('│ ')}${EC.cyan('├')} [convert] added untested files: ${EC.yellow(untestedList.length)}`, time_start_untested);
+
+
+};
+
+const generateV8DataList = (stateList, options) => {
+
+ const stateMap = new Map();
+
+ // all original files from dist
+ const allOriginalList = [];
+ stateList.forEach((state) => {
+ const { v8Data, originalList } = state;
+ // dedupe dist file if not debug
+ if (!v8Data.dedupe) {
+ stateMap.set(v8Data.id, state);
+ }
+ allOriginalList.push(originalList);
+ });
+
+ // merge istanbul and v8(converted)
+ const mergeMap = new Map();
+ allOriginalList.flat().forEach((originalState) => {
+ const { v8Data } = originalState;
+ const id = v8Data.id;
+ // exists item
+ const prevState = stateMap.get(id);
+ if (prevState) {
+ // ignore empty item, just override it
+ if (!prevState.v8Data.empty) {
+ if (mergeMap.has(id)) {
+ mergeMap.get(id).push(originalState);
+ } else {
+ mergeMap.set(id, [prevState, originalState]);
+ }
+ return;
+ }
+ }
+ stateMap.set(id, originalState);
+ });
+
+ const mergeIds = mergeMap.keys();
+ for (const id of mergeIds) {
+ const state = stateMap.get(id);
+ // for source the type could be ts, so just use js (boolean)
+ if (state.js) {
+ mergeV8Data(state, mergeMap.get(id));
+ } else {
+ // should no css here, css can not be in sources
+ }
+ }
+
+ // new v8 data list (includes sources)
+ const v8DataList = [];
+ // global file sources and istanbul coverage data
+ const fileSources = {};
+ const coverageData = {};
+ stateMap.forEach((state) => {
+ const { v8Data } = state;
+ const istanbulData = collectFileCoverage(v8Data, state, options);
+ const { sourcePath, source } = v8Data;
+ v8DataList.push(v8Data);
+ fileSources[sourcePath] = source;
+ coverageData[sourcePath] = istanbulData;
+ });
+
+ // sort v8DataList (files)
+ v8DataList.sort((a, b) => {
+ if (a.sourcePath > b.sourcePath) {
+ return 1;
+ }
+ return -1;
+ });
+
+ return {
+ v8DataList,
+ fileSources,
+ coverageData
+ };
+};
+
+const convertV8List = async (v8list, options) => {
+
+ // for tested files
+ const stateList = convertCoverages(v8list, options);
+
+ // empty coverage handler
+ await addUntestedFiles(stateList, options);
+
+ const time_start_convert = Date.now();
+ const dataList = generateV8DataList(stateList, options);
+ const dataFiles = EC.cyan(`${dataList.v8DataList.length} files`);
+ Util.logTime(`${EC.magenta('│ ')}${EC.cyan('├')} [convert] converted data list (${dataFiles})`, time_start_convert);
+
+ return dataList;
+};
+
+module.exports = {
+ convertV8List
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/find-original-range.js b/node_modules/monocart-coverage-reports/lib/converter/find-original-range.js
new file mode 100644
index 0000000..a1cf3a5
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/find-original-range.js
@@ -0,0 +1,980 @@
+const { diffSequence } = require('../packages/monocart-coverage-vendor.js');
+const Util = require('../utils/util.js');
+const EC = Util.EC;
+
+const findMapping = (list, offset) => {
+ let start = 0;
+ let end = list.length - 1;
+ while (end - start > 1) {
+ const i = Math.floor((start + end) * 0.5);
+ const item = list[i];
+ if (offset < item.generatedOffset) {
+ end = i;
+ continue;
+ }
+ if (offset > item.generatedOffset) {
+ start = i;
+ continue;
+ }
+ return list[i];
+ }
+ // last two items, less is start
+ const startItem = list[start];
+ if (offset === startItem.generatedOffset) {
+ return startItem;
+ }
+ const endItem = list[end];
+ if (offset === endItem.generatedOffset) {
+ return endItem;
+ }
+
+ // between two mappings
+ if (offset > startItem.generatedOffset && offset < endItem.generatedOffset) {
+ return [startItem, endItem];
+ }
+
+ // not found the mappings
+
+};
+
+// ========================================================================================================
+
+const alignText = (gt, ot, info) => {
+
+ const { diffCache } = info.state;
+
+ if (diffCache.has(gt)) {
+ const subMap = diffCache.get(gt);
+ if (subMap.has(ot)) {
+ return subMap.get(ot);
+ }
+ }
+
+ const toList = (s) => {
+ return s.split('').map((v, i) => {
+ return {
+ index: i,
+ value: v
+ };
+ });
+ };
+
+ const mergeList = (gList, oList) => {
+ const oLen = oList.length;
+ if (!oLen) {
+ return;
+ }
+ const gLen = gList.length;
+ if (gLen === oLen) {
+ gList.forEach((item, i) => {
+ item.original = oList[i];
+ });
+ }
+ };
+
+ const gl = toList(gt);
+ const ol = toList(ot);
+
+ // console.log(gl);
+ // console.log(ol);
+
+ const commonItems = [];
+ diffSequence(gl.length, ol.length, (gi, oi) => {
+ return gl[gi].value === ol[oi].value;
+ }, (len, gi, oi) => {
+ commonItems.push([gi, oi, len]);
+ });
+
+ // console.log(commonItems);
+ // console.log('================================================================');
+ // merge list
+ // previous index
+ let gpi = 0;
+ let opi = 0;
+ commonItems.forEach(([gi, oi, len]) => {
+
+ if (gpi !== gi) {
+ const gList = gl.slice(gpi, gi);
+ const oList = ol.slice(opi, oi);
+ mergeList(gList, oList);
+ }
+
+ for (let i = 0; i < len; i++) {
+
+ const gii = gi + i;
+ const oii = oi + i;
+ const gItem = gl[gii];
+ gItem.original = ol[oii];
+
+ // matching a word end
+ if (i === len - 1 && len > 1) {
+ gItem.wordEnd = true;
+ }
+
+ }
+ gpi = gi + len;
+ opi = oi + len;
+ });
+
+ // console.log(JSON.stringify(data.gt));
+ // console.log(JSON.stringify(data.ot));
+ if (gpi < gl.length && opi < ol.length) {
+ const gList = gl.slice(gpi);
+ const oList = ol.slice(opi);
+ mergeList(gList, oList);
+ }
+
+ if (diffCache.has(gt)) {
+ const subMap = diffCache.get(gt);
+ subMap.set(ot, gl);
+ } else {
+ const subMap = new Map();
+ diffCache.set(gt, subMap);
+ subMap.set(ot, gl);
+ }
+
+ // console.log(gl);
+ return gl;
+};
+
+const getWordEndPosition = (list, gp, direction) => {
+ // for end only
+ if (direction !== 'end') {
+ return;
+ }
+
+ const prev = list[gp - 1];
+ if (prev && prev.original && prev.wordEnd) {
+ const op = prev.original.index + 1;
+ // console.log(gp, JSON.stringify(gt));
+ // console.log(op, JSON.stringify(ot));
+ return {
+ pos: op
+ };
+ }
+};
+
+const getAlignPosition = (info, direction) => {
+
+ const gt = info.generatedText;
+ const gp = info.generatedPos;
+ const ot = info.originalText;
+
+ // const alignTextItem = {
+ // gt, ot, gp
+ // };
+ // info.state.alignTextList.push(it);
+
+ // there is no need to align for long text
+ const maxLength = 100;
+ if (gt.length > maxLength || ot.length > maxLength) {
+ // console.log(gt.length, ot.length, gp);
+ return;
+ }
+
+ // generatedText: '"false";\n',
+ // originalText: "'false';\r\n ",
+ // generatedPos: 7
+
+ // left matched
+
+ // originalText: '1;',
+ // generatedText: '1;else',
+ // generatedLeft: '1;'
+
+ // right matched
+ // only for original first line text
+
+ // exclusive
+ const list = alignText(gt, ot, info);
+ const item = list[gp];
+ if (item && item.original) {
+
+ // alignTextItem.op = item.original.index;
+
+ return {
+ pos: item.original.index
+ };
+ }
+
+ // inclusive, for end only
+ return getWordEndPosition(list, gp, direction);
+
+};
+
+// ========================================================================================================
+
+const getBlockStartPosition = (originalLineText) => {
+ // originalText: 'argument) {',
+ // generatedLeft: 'o',
+ // generatedRight: '&&'
+
+ // function/block could be started with {(
+ const startBlockIndex = originalLineText.search(/[<{(]/);
+ if (startBlockIndex !== -1) {
+ return {
+ pos: startBlockIndex
+ };
+ }
+
+ // end a block
+ const list = ['>', '}', ')'];
+ for (const s of list) {
+ const endBlockIndex = originalLineText.lastIndexOf(s);
+ if (endBlockIndex !== -1) {
+ return {
+ pos: endBlockIndex + 1
+ };
+ }
+ }
+
+ // ============================
+ // end characters
+
+ // ends with ">" in vue
+ //
+
+ // originalText: '">',
+ // generatedText: ' ? ((0,vue__.openBlock)(), '
+
+ // originalMethod?.apply
+ // originalText: '?.' no ?
+
+ const indexEndBlock = originalLineText.search(/(?<=[;,:"'\s])/);
+ if (indexEndBlock !== -1) {
+ return {
+ pos: indexEndBlock
+ };
+ }
+
+};
+
+const getBlockEndPosition = (originalLineText) => {
+
+ // generatedText: 'e),',
+ // generatedPos: 2,
+ // originalText: 'prop))'
+
+ // generatedText: ' = false)',
+ // generatedPos: 8,
+ // originalText: '=false"'
+
+ // generatedText: '), 1 /* TEXT */)])])) : (0,vue__...)("v-if", true)], 6 /* CLASS, STYLE */);',
+ // generatedPos: 17,
+ // originalText: ' }}'
+
+ // end marks
+ const list = ['>', '}', ')'];
+ for (const s of list) {
+ const endBlockIndex = originalLineText.lastIndexOf(s);
+ if (endBlockIndex !== -1) {
+ return {
+ pos: endBlockIndex + 1
+ };
+ }
+ }
+
+ const startBlockIndex = originalLineText.search(/[<{(]/);
+ if (startBlockIndex !== -1) {
+ return {
+ pos: startBlockIndex
+ };
+ }
+
+};
+
+// ========================================================================================================
+
+const getComparedPosition = (info, direction) => {
+
+ const gt = info.generatedText;
+ const gp = info.generatedPos;
+ const ot = info.originalText;
+ const olt = info.originalLineText;
+
+ // seems direction is start only
+ if (gp === 0) {
+ return;
+ }
+
+ // whole generated text
+ if (gp >= gt.length) {
+ // console.log(JSON.stringify(gt), gt.length, gp);
+ // console.log(JSON.stringify(ot));
+ return {
+ pos: olt.length
+ };
+ }
+
+ // =============================
+ // trim
+
+ const gtt = gt.trim();
+ // no generated content after trim
+ if (!gtt) {
+ return;
+ }
+
+ const ott = ot.trim();
+ // no original content after trim
+ if (!ott) {
+ return {
+ pos: direction === 'start' ? ot.length : 0
+ };
+ }
+
+ // same content
+ if (gtt === ott) {
+ // fix indent
+ const blankBlock = /\S/;
+ const gi = gt.search(blankBlock);
+ const oi = ot.search(blankBlock);
+
+ return {
+ pos: gp - gi + oi
+ };
+ }
+
+ // =============================
+
+ return getAlignPosition(info, direction);
+
+};
+
+
+const getSimilarPosition = (info, direction) => {
+
+ const originalText = info.originalText;
+ // never cross line, using first line of original text
+ // trim end remove \r\n and \n
+ const originalLines = originalText.split(/\n/);
+ if (originalLines.length === 1) {
+ // already single line
+ info.originalLineText = originalText.trimEnd();
+ } else {
+ // multiple liens
+ info.originalLineText = originalLines[0].trimEnd();
+ }
+
+ // no need comparison for fake source
+ if (info.state.fake) {
+ return;
+ }
+
+ const textPos = getComparedPosition(info, direction);
+ if (textPos) {
+ return textPos;
+ }
+
+ // console.log('====================================================================');
+ // console.log(`${EC.magenta(direction)} similar position can NOT be fixed`, info.originalState.sourcePath);
+ // console.log({
+ // direction,
+ // generatedText: info.generatedText,
+ // generatedPos: info.generatedPos,
+ // originalText
+ // });
+
+};
+
+const getFixedPosition = (info, direction) => {
+
+ const similarPos = getSimilarPosition(info, direction);
+ if (similarPos) {
+ return similarPos.pos;
+ }
+
+
+ // generatedText: never cross lines
+ // originalText: could be multiple lines
+ // originalLineText: single line originalText
+ const { originalLineText } = info;
+
+ if (direction === 'start') {
+ const blockPos = getBlockStartPosition(originalLineText);
+ if (blockPos) {
+ return blockPos.pos;
+ }
+ return 0;
+ }
+
+ const blockPos = getBlockEndPosition(originalLineText);
+ if (blockPos) {
+ return blockPos.pos;
+ }
+ return originalLineText.length;
+};
+
+// ========================================================================================================
+
+const getOriginalEndOffset = (m, originalState) => {
+ if (!m.originalEndOffset) {
+ const line = originalState.locator.getLine(m.originalLine + 1);
+ m.originalEndOffset = line ? line.end : m.originalOffset;
+ }
+ return m.originalEndOffset;
+};
+
+const getOriginalText = (m1, m2, originalState, startNextLine) => {
+ const originalLocator = originalState.locator;
+
+ const o1 = m1.originalOffset;
+ const o2 = m2.originalOffset;
+ const sameLine = m2.originalLine === m1.originalLine;
+
+ // o1 < o2: most of time
+ if (o1 < o2) {
+
+ // could be in comments
+ // it must be wrong sourcemap, do not fix it here
+ // if (originalLocator.lineParser.commentParser.isComment(o1, o2)) {
+ // console.log('in comments', originalLocator.getSlice(o1, o2), originalLocator.offsetToLocation(o1), originalLocator.offsetToLocation(o2), cache.originalState.sourcePath);
+ // return {
+ // originalOffset: o1,
+ // originalText: ''
+ // };
+ // }
+
+ if (sameLine) {
+ return {
+ originalOffset: o1,
+ originalText: originalLocator.getSlice(o1, o2)
+ };
+ }
+
+ // start from next line
+ if (startNextLine) {
+ const nextLine = originalState.locator.getLine(m1.originalLine + 2);
+ if (nextLine) {
+ // console.log(nextLine);
+ return {
+ originalOffset: nextLine.start + nextLine.indent,
+ originalText: nextLine.text.slice(nextLine.indent)
+ };
+ }
+ }
+
+ // could be multiple lines for original text
+ return {
+ originalOffset: o1,
+ originalText: originalLocator.getSlice(o1, getOriginalEndOffset(m1, originalState))
+ };
+ }
+
+ // esbuild fixing two mapping have same original
+ // m1 to end line
+ if (o1 === o2) {
+ return {
+ originalOffset: o1,
+ originalText: originalLocator.getSlice(o1, getOriginalEndOffset(m1, originalState))
+ };
+ }
+
+ // o1 > o2: should be wrong sourcemap
+
+ // just reverse offsets if same line
+ if (sameLine) {
+ return {
+ originalOffset: o2,
+ originalText: originalLocator.getSlice(o2, o1)
+ };
+ }
+
+ // if (direction === 'start') {
+ // should be m2 ?
+ // console.log(direction, cache.originalState.sourcePath);
+ // console.log(m1, m2);
+ // }
+
+ // should be wrong, ignore m2
+ return {
+ originalOffset: o1,
+ originalText: originalLocator.getSlice(o1, getOriginalEndOffset(m1, originalState))
+ };
+
+};
+
+const getGeneratedText = (m1, m2, state, offset) => {
+ const generatedLocator = state.locator;
+
+ const o1 = m1.generatedOffset;
+ const o2 = m2.generatedOffset;
+
+ // same line
+ if (m1.generatedLine === m2.generatedLine) {
+ return {
+ generatedText: generatedLocator.getSlice(o1, o2),
+ generatedPos: offset - o1
+ };
+ }
+
+ // =======================================
+ // different lines
+ // never cross lines
+
+ // 1-base
+ const lineInfo = generatedLocator.offsetToLocation(offset);
+ // 0-base
+ const targetLine = lineInfo.line - 1;
+
+ // m1 p (case 1, right to end)
+ // p (case 2, mid line)
+ // p (case 3) m2
+
+ // case 1
+ if (targetLine === m1.generatedLine) {
+ return {
+ generatedText: generatedLocator.getSlice(o1, lineInfo.end),
+ generatedPos: offset - o1
+ };
+ }
+
+ // case 3
+ if (targetLine === m2.generatedLine) {
+ return {
+ generatedText: generatedLocator.getSlice(lineInfo.start, o2),
+ generatedPos: offset - lineInfo.start
+ };
+ }
+
+ // case 2
+ return {
+ generatedText: lineInfo.text,
+ generatedPos: offset - lineInfo.start
+ };
+
+};
+
+// ========================================================================================================
+const getFunctionName = (mp, state, options) => {
+ if (options.checkName && typeof mp.nameIndex !== 'undefined') {
+ return state.sourceMapNames[mp.nameIndex];
+ }
+};
+
+const getFixedOriginalStart = (start, mappings, state, cache, options) => {
+
+ const [m1, m2] = mappings;
+ const { originalState, crossStart } = cache;
+
+ // cross file from start of line
+ if (crossStart) {
+ return {
+ originalStart: m2.originalOffset,
+ originalName: getFunctionName(m2, state, options)
+ };
+ }
+
+ // skip last and end column mapping
+ let startNextLine = false;
+ if (m1.end || m1.last) {
+ const locStart = state.locator.offsetToLocation(start);
+ // 1-base
+ const lineIndex = locStart.line - 1;
+ if (lineIndex > m1.generatedLine) {
+ startNextLine = true;
+ // console.log(originalState.sourcePath, start, m1, m2);
+ }
+ }
+
+ const { originalText, originalOffset } = getOriginalText(m1, m2, originalState, startNextLine);
+ const { generatedText, generatedPos } = getGeneratedText(m1, m2, state, start);
+ const direction = 'start';
+
+ const info = {
+ state,
+ generatedText,
+ generatedPos,
+ originalText,
+ originalState
+ };
+
+ const originalPos = getFixedPosition(info, direction);
+
+ // if (start === 2660) {
+ // console.log('=====================================================');
+ // console.log('fixed start', originalState.sourcePath);
+ // console.log({
+ // generatedText,
+ // generatedPos,
+ // originalText,
+ // originalPos
+ // });
+ // }
+
+ return {
+ originalStart: originalOffset + originalPos,
+ originalName: getFunctionName(m1, state, options)
+ };
+};
+
+const getFixedOriginalEnd = (end, mappings, state, cache) => {
+
+ const [m1, m2] = mappings;
+ const { originalState, crossEnd } = cache;
+
+ // cross file until e1 line end (not file end)
+ if (crossEnd) {
+ const originalEndOffset = getOriginalEndOffset(m1, originalState);
+ return {
+ originalEnd: originalEndOffset
+ };
+ }
+
+ // end is exclusive
+ // quick check previous one if exact match
+ // most of case is matching "}"
+ if (m1.generatedOffset === end - 1) {
+ return {
+ originalEnd: m1.originalOffset + 1
+ };
+ }
+
+
+ const { originalText, originalOffset } = getOriginalText(m1, m2, originalState);
+ const { generatedText, generatedPos } = getGeneratedText(m1, m2, state, end);
+ const direction = 'end';
+
+ const info = {
+ state,
+ offset: end,
+ generatedText,
+ generatedPos,
+ originalText,
+ originalState
+ };
+
+ const originalPos = getFixedPosition(info, direction);
+
+ // if (end === 1304578) {
+ // console.log('=====================================================');
+ // console.log('fixed end', originalState.sourcePath);
+ // console.log({
+ // generatedText,
+ // generatedPos,
+ // originalText,
+ // originalPos,
+ // originalLineText: info.originalLineText
+ // });
+ // }
+
+ return {
+ originalEnd: originalOffset + originalPos
+ };
+};
+
+// ========================================================================================================
+
+const getOriginalStartPosition = (cache, state, options) => {
+
+ const { start, startMappings } = cache;
+
+ if (Array.isArray(startMappings)) {
+ return getFixedOriginalStart(start, startMappings, state, cache, options);
+ }
+
+ // Exact match
+ return {
+ originalStart: startMappings.originalOffset,
+ originalName: getFunctionName(startMappings, state, options)
+ };
+};
+
+const getOriginalExclusiveEnd = (cache, state) => {
+ // end, exclusive mappings
+ const { end } = cache;
+ const { decodedMappings } = state;
+ const endMappings = findMapping(decodedMappings, end);
+ if (!endMappings) {
+ return {
+ error: true,
+ errors: ['not found end mappings']
+ };
+ }
+ if (Array.isArray(endMappings)) {
+ return getFixedOriginalEnd(end, endMappings, state, cache);
+ }
+
+ // Exact match end
+ return {
+ originalEnd: endMappings.originalOffset
+ };
+};
+
+const getOriginalEndPosition = (cache, state) => {
+
+ const { endMappings, originalState } = cache;
+
+ // (end - 1), inclusive
+ if (Array.isArray(endMappings)) {
+ return getOriginalExclusiveEnd(cache, state);
+ }
+
+ // Exact match (end - 1)
+
+ // check end char
+ const oi = endMappings.originalOffset;
+ const originalEndChar = originalState.locator.getSlice(oi, oi + 1);
+ // the char should never end with "{" or "("
+ if (['{', '('].includes(originalEndChar)) {
+ return getOriginalExclusiveEnd(cache, state);
+ }
+
+ // inclusive to exclusive
+ return {
+ originalEnd: oi + 1
+ };
+};
+
+// ========================================================================================================
+
+const checkSourceFileIndexes = (startIndexes, endIndexes) => {
+
+ const checkIndex11 = (s1, e1) => {
+ if (s1 === e1) {
+ return {
+ sourceIndex: s1
+ };
+ }
+ };
+
+ const checkIndex21 = (s1, s2, e1) => {
+ if (s1 === e1 || s2 === e1) {
+ if (s1 === s2) {
+ return {
+ sourceIndex: e1
+ };
+ }
+ return {
+ crossStart: true,
+ sourceIndex: e1
+ };
+ }
+ };
+
+ const checkIndex12 = (s1, e1, e2) => {
+ if (s1 === e1 || s1 === e2) {
+ if (e1 === e2) {
+ return {
+ sourceIndex: s1
+ };
+ }
+ return {
+ crossEnd: true,
+ sourceIndex: s1
+ };
+ }
+ };
+
+ const checkIndex22 = (s1, s2, e1, e2) => {
+ // 4 same
+ if (s1 === s2 && e1 === e2 && s1 === e1) {
+ return {
+ sourceIndex: s1
+ };
+ }
+
+ if (e1 === e2) {
+ return checkIndex21(s1, s2, e1);
+ }
+
+ if (s1 === s2) {
+ return checkIndex12(s1, e1, e2);
+ }
+
+ if (s2 === e1) {
+ return {
+ crossStart: true,
+ crossEnd: true,
+ sourceIndex: s2
+ };
+ }
+
+ // both between two mappings: 38,39 ~ 38,39
+ // if (s1 === e1 && s2 === e2) {
+ // }
+ };
+
+ // both exact matched
+ if (startIndexes.length === 1 && endIndexes.length === 1) {
+ return checkIndex11(startIndexes[0], endIndexes[0]);
+ }
+
+ if (startIndexes.length === 2 && endIndexes.length === 1) {
+ return checkIndex21(startIndexes[0], startIndexes[1], endIndexes[0]);
+ }
+
+ if (startIndexes.length === 1 && endIndexes.length === 2) {
+ return checkIndex12(startIndexes[0], endIndexes[0], endIndexes[1]);
+ }
+
+ // both 2 mappings
+ return checkIndex22(startIndexes[0], startIndexes[1], endIndexes[0], endIndexes[1]);
+
+};
+
+const getOriginalState = (start, end, state, originalMap) => {
+
+ const mappingInfo = getMappingInfo(start, end, state);
+ if (mappingInfo.error) {
+ return mappingInfo;
+ }
+
+ const { startMappings, endMappings } = mappingInfo;
+
+ // check source file indexes
+ const startIndexes = [].concat(startMappings).map((it) => it.sourceIndex);
+ const endIndexes = [].concat(endMappings).map((it) => it.sourceIndex);
+ const results = checkSourceFileIndexes(startIndexes, endIndexes);
+ if (!results) {
+ return {
+ error: true,
+ errors: [`invalid source indexes: ${EC.yellow(`${startIndexes} ~ ${endIndexes}`)}`]
+ };
+ }
+
+ const {
+ sourceIndex, crossStart, crossEnd
+ } = results;
+
+ // if (crossStart || crossEnd) {
+ // console.log(EC.magenta('cross file'), EC.yellow(`${startIndexes} ~ ${endIndexes}`));
+ // }
+
+ const originalState = originalMap.get(sourceIndex);
+ if (!originalState) {
+ return {
+ error: true,
+ errors: [`not found original file: ${EC.yellow(sourceIndex)}`]
+ };
+ }
+
+ // cache for original info
+ return {
+ // for debug
+ // startIndexes,
+ // endIndexes,
+
+ start,
+ end,
+ startMappings,
+ endMappings,
+
+ sourceIndex,
+ crossStart,
+ crossEnd,
+ originalState
+ };
+};
+
+const getMappingInfo = (start, end, state) => {
+
+ const { decodedMappings } = state;
+ // possible no length
+ if (decodedMappings.length < 2) {
+ return {
+ error: true,
+ errors: ['invalid decoded mappings (length < 2)']
+ };
+ }
+
+ // start: inclusive
+ const startMappings = findMapping(decodedMappings, start);
+ if (!startMappings) {
+ return {
+ error: true,
+ errors: ['not found start mappings']
+ };
+ }
+
+ // end: exclusive
+ const endMappings = findMapping(decodedMappings, end - 1);
+ if (!endMappings) {
+ return {
+ error: true,
+ errors: ['not found end mappings']
+ };
+ }
+
+ // could be 4 mappings found
+ return {
+ startMappings,
+ endMappings
+ };
+};
+
+
+const findOriginalRange = (start, end, state, originalMap, options = {}) => {
+
+ const { sourcePath, rangeCache } = state;
+
+ const key = `${start}_${end}_${Boolean(options.fixOriginalRange)}`;
+ if (rangeCache.has(key)) {
+ return rangeCache.get(key);
+ }
+
+ const createMappingError = (errors) => {
+ const res = {
+ error: true,
+ start,
+ end,
+ sourcePath,
+ errors
+ };
+
+ // cache error response
+ rangeCache.set(key, res);
+
+ return res;
+ };
+
+ const cache = getOriginalState(start, end, state, originalMap);
+ if (cache.error) {
+ return createMappingError(cache.errors);
+ }
+
+ const originalStartResult = getOriginalStartPosition(cache, state, options);
+ const { originalStart, originalName } = originalStartResult;
+ // could be used for end
+ cache.originalStart = originalStart;
+
+ const originalEndResult = getOriginalEndPosition(cache, state);
+ if (originalEndResult.error) {
+ return createMappingError(originalEndResult.errors);
+ }
+ const { originalEnd } = originalEndResult;
+
+ // range start > end
+ if (originalStart > originalEnd) {
+ return createMappingError([`invalid original start > end: ${EC.yellow(originalStart)} > ${EC.yellow(originalEnd)}`]);
+ }
+
+ const { originalState } = cache;
+ const locator = originalState.locator;
+
+ const inComment = locator.lineParser.commentParser.isComment(originalStart, originalEnd);
+ if (inComment) {
+ return createMappingError(['the range in a original comment']);
+ }
+
+ const res = {
+ start: originalStart,
+ end: originalEnd,
+ name: originalName,
+ originalState
+ };
+
+ if (options.fixOriginalRange) {
+ const { fixedStart, fixedEnd } = Util.fixSourceRange(locator, originalStart, originalEnd);
+ res.start = fixedStart;
+ res.end = fixedEnd;
+ }
+
+ // cache response
+ rangeCache.set(key, res);
+
+ return res;
+
+};
+
+module.exports = findOriginalRange;
diff --git a/node_modules/monocart-coverage-reports/lib/converter/flatten-source-maps.js b/node_modules/monocart-coverage-reports/lib/converter/flatten-source-maps.js
new file mode 100644
index 0000000..0a2b773
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/flatten-source-maps.js
@@ -0,0 +1,186 @@
+const { decode } = require('../packages/monocart-coverage-vendor.js');
+
+const COLUMN = 0;
+const SOURCES_INDEX = 1;
+const SOURCE_LINE = 2;
+const SOURCE_COLUMN = 3;
+const NAMES_INDEX = 4;
+
+function getLine(arr, index) {
+ for (let i = arr.length; i <= index; i++) {
+ arr[i] = [];
+ }
+ return arr[index];
+}
+
+function getMapping(seg, column, sourcesOffset, namesOffset) {
+
+ const sourcesIndex = sourcesOffset + seg[SOURCES_INDEX];
+ const sourceLine = seg[SOURCE_LINE];
+ const sourceColumn = seg[SOURCE_COLUMN];
+
+ if (seg.length === 4) {
+ return [column, sourcesIndex, sourceLine, sourceColumn];
+ }
+
+ return [column, sourcesIndex, sourceLine, sourceColumn, namesOffset + seg[NAMES_INDEX]];
+}
+
+function addMappings(options, sourcesOffset, namesOffset) {
+
+ const {
+ input,
+ lineOffset,
+ columnOffset,
+ stopLine,
+ stopColumn
+ } = options;
+
+ const decoded = decode(input.mappings);
+
+ for (let i = 0; i < decoded.length; i++) {
+ const lineI = lineOffset + i;
+
+ if (lineI > stopLine) {
+ return;
+ }
+
+ const out = getLine(options.decodedMappings, lineI);
+ const cOffset = i === 0 ? columnOffset : 0;
+
+ const line = decoded[i];
+ for (let j = 0; j < line.length; j++) {
+ const seg = line[j];
+ const column = cOffset + seg[COLUMN];
+
+ if (lineI === stopLine && column >= stopColumn) {
+ return;
+ }
+
+ if (seg.length === 1) {
+ out.push([column]);
+ continue;
+ }
+
+ out.push(getMapping(seg, column, sourcesOffset, namesOffset));
+ }
+ }
+}
+
+function addSection(options) {
+
+ const { input } = options;
+
+ const { sections } = input;
+ if (sections) {
+ return flatten(options);
+ }
+
+ const sourcesOffset = options.sources.length;
+ const namesOffset = options.names.length;
+
+ // sources and sourcesContent
+ if (!input.sourcesContent) {
+ input.sourcesContent = [];
+ }
+ input.sources.forEach((src, i) => {
+ options.sources.push(src);
+ options.sourcesContent.push(input.sourcesContent[i] || null);
+ });
+
+ // names
+ if (input.names) {
+ input.names.forEach((n) => {
+ options.names.push(n);
+ });
+ }
+
+ addMappings(options, sourcesOffset, namesOffset);
+
+}
+
+function flatten(options) {
+
+ const {
+ input,
+ lineOffset,
+ columnOffset,
+ stopLine,
+ stopColumn
+ } = options;
+
+ const { sections } = input;
+
+ for (let i = 0, l = sections.length; i < l; i++) {
+ const { map, offset } = sections[i];
+
+ let sl = stopLine;
+ let sc = stopColumn;
+ if (i + 1 < sections.length) {
+ const nextOffset = sections[i + 1].offset;
+ sl = Math.min(stopLine, lineOffset + nextOffset.line);
+
+ if (sl === stopLine) {
+ sc = Math.min(stopColumn, columnOffset + nextOffset.column);
+ } else if (sl < stopLine) {
+ sc = columnOffset + nextOffset.column;
+ }
+ }
+
+ options.input = map;
+ options.lineOffset = lineOffset + offset.line;
+ options.columnOffset = columnOffset + offset.column;
+ options.stopLine = sl;
+ options.stopColumn = sc;
+
+ addSection(options);
+ }
+}
+
+const flattenSourceMaps = function(indexedMap, mapUrl) {
+
+ const sections = indexedMap.sections;
+ if (!sections) {
+ return indexedMap;
+ }
+
+ const decodedMappings = [];
+ const sources = [];
+ const sourcesContent = [];
+ const names = [];
+
+ const lineOffset = 0;
+ const columnOffset = 0;
+ const stopLine = Infinity;
+ const stopColumn = Infinity;
+
+ flatten({
+ input: indexedMap,
+
+ mapUrl,
+
+ decodedMappings,
+ sources,
+ sourcesContent,
+
+ names,
+
+ lineOffset,
+ columnOffset,
+ stopLine,
+ stopColumn
+ });
+
+ indexedMap.sources = sources;
+ indexedMap.sourcesContent = sourcesContent;
+ indexedMap.names = names;
+ indexedMap.decodedMappings = decodedMappings;
+
+ // console.log(decodedMappings);
+
+ return indexedMap;
+};
+
+module.exports = {
+ flattenSourceMaps
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/ignore.js b/node_modules/monocart-coverage-reports/lib/converter/ignore.js
new file mode 100644
index 0000000..636d7bb
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/ignore.js
@@ -0,0 +1,225 @@
+const Util = require('../utils/util.js');
+
+const getNextN = (content, currentLine, item, locator) => {
+
+ // v-8 ignore next N
+ if (content) {
+ const n = parseInt(content);
+ if (n && n > 0) {
+ return n;
+ }
+ return 1;
+ }
+
+ // block comment not end of line, same line
+ if (item.block) {
+ // has code between comment and line end
+ const text = locator.getSlice(item.end, currentLine.end).trim();
+ if (text) {
+ return 0;
+ }
+ }
+
+ // v-8 ignore next
+ return 1;
+};
+
+const extendStart = (start, source) => {
+ // extend left, include
+ while (start > 0 && Util.isBlank(source[start - 1])) {
+ start -= 1;
+ }
+ return start;
+};
+
+const extendEnd = (end, source, maxLength) => {
+ // extend right, exclude
+ while (end < maxLength && Util.isBlank(source[end])) {
+ end += 1;
+ }
+ return end;
+};
+
+const addNextIgnore = (list, content, item, locator) => {
+
+ const { lineParser, source } = locator;
+ const maxLength = source.length;
+
+ const start = extendStart(item.start, source);
+
+ // findLine 0-base
+ const currentLine = lineParser.findLine(item.end);
+ const n = getNextN(content, currentLine, item, locator);
+
+ // console.log('current line', currentLine.line, 'lines', lines);
+
+ // getLine 1-base
+ const line = currentLine.line + 1;
+ const endLine = locator.getLine(line + n);
+ const end = extendEnd(endLine ? endLine.end : maxLength, source, maxLength);
+
+ const nextItem = {
+ type: 'next',
+ n,
+ start,
+ end
+ };
+
+ list.push(nextItem);
+};
+
+// both v8 or c8
+const getStartEndNextInfo = (content) => {
+ content = content.trim();
+
+ const start = 'start';
+ const stop = 'stop';
+ const next = 'next';
+ if (content.startsWith(start)) {
+ return {
+ type: 'range',
+ value: stop
+ };
+ }
+ if (content.startsWith(stop)) {
+ return {
+ type: 'range',
+ value: stop
+ };
+ }
+ if (content.startsWith(next)) {
+ return {
+ type: 'next',
+ value: content.slice(next.length)
+ };
+ }
+};
+
+const getDisableEnableNextInfo = (content) => {
+ content = content.trim();
+
+ const start = 'disable';
+ const stop = 'enable';
+ const next = 'ignore next';
+ if (content.startsWith(start)) {
+ return {
+ type: 'range',
+ value: stop
+ };
+ }
+ if (content.startsWith(stop)) {
+ return {
+ type: 'range',
+ value: stop
+ };
+ }
+ if (content.startsWith(next)) {
+ return {
+ type: 'next',
+ value: content.slice(next.length)
+ };
+ }
+};
+
+
+const getIgnoreInfo = (content) => {
+ content = content.trim();
+
+ const v8_ignore = 'v8 ignore';
+ if (content.startsWith(v8_ignore)) {
+ return getStartEndNextInfo(content.slice(v8_ignore.length));
+ }
+
+ const c8_ignore = 'c8 ignore';
+ if (content.startsWith(c8_ignore)) {
+ return getStartEndNextInfo(content.slice(c8_ignore.length));
+ }
+
+ const node_coverage = 'node:coverage';
+ if (content.startsWith(node_coverage)) {
+ return getDisableEnableNextInfo(content.slice(node_coverage.length));
+ }
+
+};
+
+const getIgnoredRanges = (locator, options) => {
+ if (!options.v8Ignore) {
+ return;
+ }
+
+ const { lineParser, source } = locator;
+ const maxLength = source.length;
+
+ const comments = lineParser.comments;
+ if (!Util.isList(comments)) {
+ return;
+ }
+
+ const list = [];
+ let ignoreStart = null;
+
+ comments.forEach((item) => {
+ const {
+ block, start, end, text
+ } = item;
+
+ const content = block ? text.slice(2, -2) : text.slice(2);
+ const ignoreInfo = getIgnoreInfo(content);
+ if (!ignoreInfo) {
+ return;
+ }
+
+ const { type, value } = ignoreInfo;
+
+ if (ignoreStart) {
+ // v-8 ignore stop
+ if (type === 'range' && value === ignoreStart.value) {
+ ignoreStart.ignoreData.end = extendEnd(end, source, maxLength);
+ ignoreStart = null;
+ }
+ return;
+ }
+
+ // v-8 ignore start
+ if (type === 'range') {
+ // add first for sort by start
+ const ignoreData = {
+ type,
+ start: extendStart(start, source),
+ end: start
+ };
+ list.push(ignoreData);
+ ignoreStart = {
+ ignoreData,
+ value
+ };
+ return;
+ }
+
+ // next N
+ if (type === 'next') {
+ addNextIgnore(list, value.trim(), item, locator);
+ }
+
+ });
+
+ // ignore not stop
+ if (ignoreStart) {
+ ignoreStart.end = locator.source.length;
+ ignoreStart = null;
+ }
+
+ if (!list.length) {
+ return;
+ }
+
+ // console.log('=============================================');
+ // console.log(list);
+
+ return list;
+};
+
+
+module.exports = {
+ getIgnoredRanges
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/info-branch.js b/node_modules/monocart-coverage-reports/lib/converter/info-branch.js
new file mode 100644
index 0000000..211bd00
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/info-branch.js
@@ -0,0 +1,93 @@
+const Util = require('../utils/util.js');
+module.exports = class InfoBranch {
+ constructor(data, original) {
+ const {
+ start, end, locations, type
+ } = data;
+
+ this.start = start;
+ this.end = end;
+ this.locations = locations;
+ this.type = type;
+ this.data = data;
+ this.original = original;
+ }
+
+ getRanges() {
+ return this.locations.map((item) => {
+ const range = {
+ start: item.start,
+ end: item.end,
+ count: item.count
+ };
+ if (item.none) {
+ range.none = true;
+ }
+ if (item.ignored) {
+ range.ignored = true;
+ }
+ // for debug
+ if (Util.isDebug() && this.original) {
+ range.generatedStart = item.generatedStart;
+ range.generatedEnd = item.generatedEnd;
+ }
+ return range;
+ });
+ }
+
+ generate(locator) {
+ const groupLoc = {
+ start: this.start,
+ end: this.end
+ };
+ Util.updateOffsetToLocation(locator, groupLoc);
+ const line = groupLoc.start.line;
+
+ // remove ignored
+ // do NOT change previous number type to object, need used for sourcemap
+ const newLocations = this.locations.filter((it) => !it.ignored).map((it) => {
+ const item = {
+ start: it.start,
+ end: it.end,
+ none: it.none,
+ count: it.count
+ };
+
+ // [ { start:{line,column}, end:{line,column}, count }, ...]
+ Util.updateOffsetToLocation(locator, item);
+
+ return item;
+ });
+
+ const map = {
+ loc: groupLoc,
+ type: this.type,
+ locations: newLocations.map((item) => {
+ const {
+ start, end, none
+ } = item;
+ if (none) {
+ // none with group start/end, should be empty for istanbul
+ return {
+ start: {},
+ end: {}
+ };
+ }
+ return {
+ start,
+ end
+ };
+
+ }),
+ line: line
+ };
+
+ const counts = newLocations.map((item) => item.count);
+
+ return {
+ map,
+ counts
+ };
+ }
+
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/info-function.js b/node_modules/monocart-coverage-reports/lib/converter/info-function.js
new file mode 100644
index 0000000..6cf96dc
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/info-function.js
@@ -0,0 +1,66 @@
+const Util = require('../utils/util.js');
+module.exports = class InfoFunction {
+ constructor(data, original) {
+
+ const {
+ start, end, bodyStart, bodyEnd, functionName, count
+ } = data;
+
+ this.start = start;
+ this.end = end;
+ this.bodyStart = bodyStart;
+ this.bodyEnd = bodyEnd;
+ this.count = count;
+ this.functionName = functionName;
+ this.data = data;
+ this.original = original;
+ }
+
+ getName(index) {
+ return this.functionName || `(anonymous_${index})`;
+ }
+
+ getRange(index) {
+ const range = {
+ name: this.getName(index),
+ start: this.start,
+ end: this.end,
+ count: this.count
+ };
+ if (this.ignored) {
+ range.ignored = true;
+ }
+ // for debug
+ if (Util.isDebug() && this.original) {
+ range.generatedStart = this.data.generatedStart;
+ range.generatedEnd = this.data.generatedEnd;
+ }
+ return range;
+ }
+
+ generate(locator, index) {
+
+ const decl = {
+ start: this.start,
+ end: this.bodyStart
+ };
+
+ Util.updateOffsetToLocation(locator, decl);
+
+ const loc = {
+ start: this.bodyStart,
+ end: this.end
+ };
+
+ Util.updateOffsetToLocation(locator, loc);
+
+ const line = loc.start.line;
+
+ return {
+ name: this.getName(index),
+ decl: decl,
+ loc: loc,
+ line: line
+ };
+ }
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/info-statement.js b/node_modules/monocart-coverage-reports/lib/converter/info-statement.js
new file mode 100644
index 0000000..24e3bee
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/info-statement.js
@@ -0,0 +1,43 @@
+const Util = require('../utils/util.js');
+module.exports = class InfoStatement {
+ constructor(data, original) {
+ const {
+ start, end, count
+ } = data;
+
+ this.start = start;
+ this.end = end;
+ this.count = count;
+ this.data = data;
+ this.original = original;
+ }
+
+ getRange() {
+ const range = {
+ start: this.start,
+ end: this.end,
+ count: this.count
+ };
+ if (this.ignored) {
+ range.ignored = true;
+ }
+ // for debug
+ if (Util.isDebug() && this.original) {
+ range.generatedStart = this.data.generatedStart;
+ range.generatedEnd = this.data.generatedEnd;
+ }
+ return range;
+ }
+
+ generate(locator) {
+
+ const loc = {
+ start: this.start,
+ end: this.end
+ };
+
+ Util.updateOffsetToLocation(locator, loc);
+
+ return loc;
+ }
+};
diff --git a/node_modules/monocart-coverage-reports/lib/converter/untested.js b/node_modules/monocart-coverage-reports/lib/converter/untested.js
new file mode 100644
index 0000000..cee9bad
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/converter/untested.js
@@ -0,0 +1,315 @@
+const fs = require('fs');
+const path = require('path');
+const Util = require('../utils/util.js');
+
+const { pathToFileURL, fileURLToPath } = require('url');
+
+const { normalizeSourcePath, getSourcePathReplacer } = require('../utils/source-path.js');
+
+// const EC = require('eight-colors');
+const { convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
+
+// ========================================================================================================
+
+const resolveAllDirList = (input) => {
+ const dirs = Util.toList(input, ',');
+ if (!dirs.length) {
+ return;
+ }
+ const dirList = dirs.filter((it) => fs.existsSync(it));
+ if (!dirList.length) {
+ return;
+ }
+ return dirList;
+};
+
+const resolveAllFilter = (input) => {
+ // for function handler
+ if (typeof input === 'function') {
+ return input;
+ }
+
+ // for single minimatch pattern
+ if (input && typeof input === 'string') {
+ // string to multiple patterns "{...}"
+ const obj = Util.strToObj(input);
+ if (obj) {
+ input = obj;
+ } else {
+ return (filePath) => {
+ return Util.betterMinimatch(filePath, input);
+ };
+ }
+ }
+
+ // for patterns
+ if (input && typeof input === 'object') {
+ const patterns = Object.keys(input);
+ return (filePath) => {
+ for (const pattern of patterns) {
+ if (Util.betterMinimatch(filePath, pattern)) {
+ return input[pattern];
+ }
+ }
+ // false if not matched
+ };
+ }
+
+ // default
+ return () => true;
+};
+
+const resolveAllOptions = (input) => {
+ if (!input) {
+ return;
+ }
+
+ let dir;
+ let filter;
+ let transformer;
+
+ if (typeof input === 'string') {
+ const obj = Util.strToObj(input);
+ if (obj) {
+ dir = obj.dir;
+ filter = obj.filter;
+ transformer = obj.transformer;
+ } else {
+ dir = input;
+ }
+ } else if (Array.isArray(input)) {
+ dir = input;
+ } else {
+ dir = input.dir;
+ filter = input.filter;
+ transformer = input.transformer;
+ }
+
+ const dirList = resolveAllDirList(dir);
+ if (!dirList) {
+ return;
+ }
+
+ const fileFilter = resolveAllFilter(filter);
+ const fileTransformer = typeof transformer === 'function' ? transformer : () => {};
+
+ return {
+ dirList,
+ fileFilter,
+ fileTransformer
+ };
+};
+
+const resolveFileType = (fileType, filePath) => {
+ if (fileType === 'js' || fileType === 'css') {
+ return fileType;
+ }
+ const extname = path.extname(filePath);
+ if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
+ return 'css';
+ }
+ return 'js';
+};
+
+const saveUntestedFileSource = async (entryFile, options) => {
+ const {
+ id,
+ url,
+ source,
+ sourceMap
+ } = entryFile;
+ // console.log('-', entry.sourcePath);
+
+ const { cachePath } = Util.getCacheFileInfo('source', id, options.cacheDir);
+ if (fs.existsSync(cachePath)) {
+ return;
+ }
+
+ // save source and sourceMap to separated json file
+ const sourceData = {
+ id,
+ url,
+ source,
+ sourceMap
+ };
+
+ // remove comments if not debug
+ if (!Util.isDebug()) {
+ sourceData.source = convertSourceMap.removeComments(source);
+ }
+
+ // console.log('save untested file', id, url);
+
+ await Util.saveSourceCacheFile(sourceData, options);
+
+};
+
+const getUntestedCoverageData = async (entryList, options, coverageType) => {
+
+ // save all empty coverage, 20 - 5(empty)
+ const dataId = Util.uid(15, 'empty');
+ const results = {
+ id: dataId
+ };
+
+ if (coverageType === 'istanbul') {
+ results.type = 'istanbul';
+ results.data = {};
+ } else {
+ results.type = 'v8';
+ results.data = [];
+ }
+
+ const emptyCoverageList = [];
+
+ // save all empty source and sourcemap
+ for (const entry of entryList) {
+
+ // for raw report: source file
+ // id, url, source, sourceMap
+ await saveUntestedFileSource(entry, options);
+
+ if (coverageType === 'istanbul') {
+
+ // ===============================================
+ const item = {
+ path: fileURLToPath(entry.url),
+ statementMap: {},
+ fnMap: {},
+ branchMap: {},
+ s: {},
+ f: {},
+ b: {}
+ };
+ // object
+ results.data[item.path] = item;
+ emptyCoverageList.push(item);
+
+ } else {
+
+ // ===============================================
+ Util.setEmptyV8Coverage(entry);
+
+ const item = {
+ ... entry
+ };
+ delete item.source;
+ delete item.sourceMap;
+
+ // array
+ results.data.push(item);
+ // will be parsed to AST and converted to V8 coverage
+ emptyCoverageList.push(entry);
+
+ }
+
+ }
+
+ // for raw report: coverage file
+ const { cachePath } = Util.getCacheFileInfo('coverage', dataId, options.cacheDir);
+ await Util.writeFile(cachePath, JSON.stringify(results));
+
+ return emptyCoverageList;
+};
+
+const getEmptyCoverages = async (testedMap, options, coverageType, fileList, fileTransformer) => {
+ const sourceFilter = Util.getSourceFilter(options);
+ const sourcePathReplacer = getSourcePathReplacer(options);
+ const baseDir = options.baseDir;
+
+ // console.log(fileList);
+
+ const entryList = [];
+ for (const item of fileList) {
+
+ const { fileType, filePath } = item;
+ const type = resolveFileType(fileType, filePath);
+ const url = pathToFileURL(filePath).toString();
+ const source = Util.readFileSync(filePath);
+ const entry = {
+ empty: true,
+ type,
+ url,
+ source
+ };
+
+ // normalize sourcePath here
+ let sourcePath = normalizeSourcePath(url, baseDir);
+ if (sourcePathReplacer) {
+ const newSourcePath = sourcePathReplacer(sourcePath, entry);
+ if (typeof newSourcePath === 'string' && newSourcePath) {
+ sourcePath = newSourcePath;
+ }
+ }
+
+ // console.log(sourcePath);
+
+ if (testedMap.has(sourcePath)) {
+ continue;
+ }
+
+ if (!sourceFilter(sourcePath)) {
+ continue;
+ }
+
+ entry.sourcePath = sourcePath;
+
+ await fileTransformer(entry, coverageType);
+ // after transformer
+ entry.id = Util.calculateSha1(entry.sourcePath + entry.source);
+
+ entryList.push(entry);
+ }
+
+
+ // console.log('fileList', fileList);
+ if (!entryList.length) {
+ return;
+ }
+
+
+ // save empty coverage for merging raw reports
+ return getUntestedCoverageData(entryList, options, coverageType);
+};
+
+const getUntestedList = (testedMap, options, coverageType = 'v8') => {
+ const allOptions = resolveAllOptions(options.all);
+ // console.log('allOptions', all, allOptions);
+ if (!allOptions) {
+ return;
+ }
+
+ const {
+ dirList, fileFilter, fileTransformer
+ } = allOptions;
+
+ const fileList = [];
+ dirList.forEach((dir) => {
+ Util.forEachFile(dir, [], (fileName, fileDir) => {
+ const filePath = path.resolve(fileDir, fileName);
+ // return file extname for file type
+ const fileType = fileFilter(filePath);
+ if (fileType) {
+ fileList.push({
+ filePath,
+ fileType,
+ fileDir,
+ fileName
+ });
+ }
+
+ });
+ });
+
+ if (!fileList.length) {
+ return;
+ }
+
+ return getEmptyCoverages(testedMap, options, coverageType, fileList, fileTransformer);
+
+};
+
+
+module.exports = {
+ getUntestedList
+};
diff --git a/node_modules/monocart-coverage-reports/lib/default/options.js b/node_modules/monocart-coverage-reports/lib/default/options.js
new file mode 100644
index 0000000..5c3280a
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/default/options.js
@@ -0,0 +1,107 @@
+module.exports = {
+
+ // {string} logging levels: off, error, info, debug
+ logging: 'info',
+
+ // {string} Report name. Defaults to "Coverage Report".
+ name: 'Coverage Report',
+
+ // {string} v8 or html for istanbul by default
+ // {array} multiple reports with options
+ // v8 report or istanbul supported reports
+ // reports: [
+ // ['v8'],
+ // ['html', {
+ // subdir: 'my-sub-dir'
+ // }],
+ // 'lcov'
+ // ],
+ reports: '',
+
+ // {string} output dir
+ outputDir: './coverage-reports',
+
+ // {string|string[]} input raw dir(s)
+ inputDir: null,
+
+ // {string} base dir for normalizing the relative source path, defaults to cwd
+ baseDir: null,
+
+ // {string} coverage data dir, alternative to method `addFromDir()`, defaults to null
+ dataDir: null,
+
+ // (V8 only) {function} A filter function to execute for each element in the V8 list.
+ // entryFilter: (entry) => {
+ // if (entry.url.indexOf('googleapis.com') !== -1) {
+ // return false;
+ // }
+ // return true;
+ // },
+ entryFilter: null,
+
+ // (V8 only) {function} A filter function to execute for each element in the sources which unpacked from the source map.
+ // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
+ sourceFilter: null,
+
+ // The combined filter for entryFilter and sourceFilter
+ filter: null,
+
+ // {function} Source path handler.
+ // sourcePath: (filePath) => `wwwroot/${filePath}`,
+ sourcePath: null,
+
+ // (V8 only) {string} Output [sub dir/]filename. Defaults to "index.html"
+ outputFile: 'index.html',
+
+ // (V8 only) {boolean} Inline all scripts to the single HTML file. Defaults to false.
+ inline: false,
+
+ // (V8 only) {string} Assets path if not inline. Defaults to "./assets"
+ assetsPath: './assets',
+
+ // (Istanbul only) defaultSummarizer, sourceFinder
+
+ // {boolean} Generate lcov.info file, same as lcovonly report. Defaults to false.
+ lcov: false,
+
+ // options for adding empty coverage for all files
+ // all: {
+ // dir: ['src'],
+ // filter: (sourcePath) => true,
+ // transformer: (entry) => {}
+ // },
+ all: null,
+
+ // (V8 only) {boolean} Enable/Disable ignoring uncovered codes with the special comments: v8 ignore next/next N/start/stop
+ v8Ignore: true,
+
+ // {string|function} Specify the report path, especially when there are multiple reports. Defaults to outputDir/index.html.
+ reportPath: null,
+
+ // {array} watermarks for low/medium/high. Defaults to [50, 80]
+ // {object} { bytes:[50,80], statements:[50,80], branches:[50,80], functions:[50,80], lines:[50,80] }
+ watermarks: [50, 80],
+
+ // {boolean} Indicates whether to clean previous files in output dir before generating report. Defaults to true.
+ clean: true,
+
+ // {boolean} Indicates whether to clean previous cache in output dir before generating report. Defaults to false.
+ cleanCache: false,
+
+ // {number} gc threshold
+ gc: null,
+
+ // {boolean} Indicates whether to save source and sourcemap file for debug (require logging="debug")
+ sourceMap: false,
+
+ // {function} Custom resolver for sourcemap content
+ sourceMapResolver: null,
+
+ // {function} onEntry hook
+ // onEntry: async (entry) => {}
+ onEntry: null,
+
+ // {function} onEnd hook
+ // onEnd: async (reportData) => {}
+ onEnd: null
+};
diff --git a/node_modules/monocart-coverage-reports/lib/generate.js b/node_modules/monocart-coverage-reports/lib/generate.js
new file mode 100644
index 0000000..a663513
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/generate.js
@@ -0,0 +1,494 @@
+const fs = require('fs');
+const path = require('path');
+const { fileURLToPath } = require('url');
+const EC = require('eight-colors');
+
+const Util = require('./utils/util.js');
+const { convertV8List } = require('./converter/converter.js');
+const { resolveSourceMap } = require('./converter/collect-source-maps.js');
+const { StreamZip } = require('./packages/monocart-coverage-vendor.js');
+
+// ========================================================================================================
+// built-in reports
+
+// istanbul
+const { mergeIstanbulCoverage, saveIstanbulReports } = require('./istanbul/istanbul.js');
+
+// v8
+const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
+
+// both
+const { codecovReport } = require('./reports/codecov.js');
+const { codacyReport } = require('./reports/codacy.js');
+
+const { consoleDetailsReport } = require('./reports/console-details.js');
+const { consoleSummaryReport } = require('./reports/console-summary.js');
+
+const { markdownDetailsReport } = require('./reports/markdown-details.js');
+const { markdownSummaryReport } = require('./reports/markdown-summary.js');
+
+const { rawReport } = require('./reports/raw.js');
+
+const { customReport } = require('./reports/custom.js');
+
+const allBuiltInReports = {
+ // v8
+ 'v8': 'v8',
+ 'v8-json': 'v8',
+
+ // istanbul
+ 'clover': 'istanbul',
+ 'cobertura': 'istanbul',
+ 'html': 'istanbul',
+ 'html-spa': 'istanbul',
+ 'json': 'istanbul',
+ 'json-summary': 'istanbul',
+ 'lcov': 'istanbul',
+ 'lcovonly': 'istanbul',
+ 'none': 'istanbul',
+ 'teamcity': 'istanbul',
+ 'text': 'istanbul',
+ 'text-lcov': 'istanbul',
+ 'text-summary': 'istanbul',
+
+ // both
+ 'codecov': 'both',
+ 'codacy': 'both',
+ 'console-details': 'both',
+ 'console-summary': 'both',
+ 'markdown-details': 'both',
+ 'markdown-summary': 'both',
+ 'raw': 'both'
+};
+
+const bothBuiltInReports = {
+ 'codecov': codecovReport,
+ 'codacy': codacyReport,
+
+ 'console-details': consoleDetailsReport,
+ 'console-summary': consoleSummaryReport,
+
+ 'markdown-details': markdownDetailsReport,
+ 'markdown-summary': markdownSummaryReport,
+
+ 'raw': rawReport
+};
+
+// ========================================================================================================
+
+const getReportGroup = (reports, lcov, dataType) => {
+
+ const reportMap = {};
+
+ const reportList = Util.toList(reports, ',');
+ reportList.forEach((it) => {
+ if (Util.isList(it)) {
+ // ["v8"], ["v8", {}]
+ const id = it[0];
+ if (typeof id === 'string' && id) {
+ reportMap[id] = {
+ ... it[1]
+ };
+ }
+ return;
+ }
+ if (typeof it === 'string' && it) {
+ reportMap[it] = {};
+ }
+ });
+
+ // using default report if no reports
+ if (!Object.keys(reportMap).length) {
+ const defaultReport = dataType === 'v8' ? 'v8' : 'html';
+ reportMap[defaultReport] = {};
+ }
+
+ // add lcovonly report after default report
+ if (lcov && !reportMap.lcovonly) {
+ reportMap.lcovonly = {};
+ }
+
+ // group v8 and istanbul
+ const groupMap = new Map();
+ Object.keys(reportMap).forEach((k) => {
+ const options = reportMap[k];
+
+ let type = allBuiltInReports[k];
+ if (!type) {
+ // for custom reporter
+ type = options.type || 'v8';
+ }
+
+ let group = groupMap.get(type);
+ if (!group) {
+ group = new Map();
+ groupMap.set(type, group);
+ }
+
+ group.set(k, options);
+
+ });
+
+ // requires a default istanbul report if data is istanbul
+ if (dataType === 'istanbul' && !groupMap.has('istanbul')) {
+ const istanbulGroup = new Map();
+ istanbulGroup.set('html', {});
+ groupMap.set('istanbul', istanbulGroup);
+ }
+
+ return groupMap;
+};
+
+// ========================================================================================================
+
+// maybe v8 or to istanbul reports
+const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
+ let istanbulReportPath;
+ // v8 to istanbul reports
+ if (options.reportGroup.has('istanbul')) {
+ const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options);
+ istanbulReportPath = istanbulCoverageResults.reportPath;
+ }
+
+ // v8 reports and v8 coverage results
+ // could be no v8 or v8-json, but requires v8 coverage results
+ const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath);
+ return v8CoverageResults;
+};
+
+const getCoverageResults = async (dataList, sourceCache, options) => {
+ // get first and check v8list or istanbul data
+ const firstData = dataList[0];
+ const dataType = firstData.type;
+ // console.log('data type', dataType);
+
+ // init reports
+ options.reportGroup = getReportGroup(options.reports, options.lcov, dataType);
+ // console.log('reportGroup', options.reportGroup);
+
+ // v8list
+ if (dataType === 'v8') {
+ // merge v8list first
+ const t1 = Date.now();
+ const v8list = await mergeV8Coverage(dataList, sourceCache, options);
+ Util.logTime(`${EC.magenta('├')} [generate] merged v8 coverage data`, t1);
+ // console.log('after merge', v8list.map((it) => it.url));
+
+ const t2 = Date.now();
+ const results = await convertV8List(v8list, options);
+ Util.logTime(`${EC.magenta('├')} [generate] converted coverage data`, t2);
+
+ const {
+ v8DataList, coverageData, fileSources
+ } = results;
+ return generateV8ListReports(v8DataList, coverageData, fileSources, options);
+ }
+
+ // istanbul data
+ const t3 = Date.now();
+ const istanbulData = await mergeIstanbulCoverage(dataList, options);
+ Util.logTime(`${EC.magenta('├')} [generate] prepared istanbul coverage data`, t3);
+ const fileSources = options.fileSources || {};
+ const results = await saveIstanbulReports(istanbulData, fileSources, options);
+ return results;
+};
+
+const generateCoverageReports = async (dataList, sourceCache, options) => {
+ const coverageResults = await getCoverageResults(dataList, sourceCache, options);
+
+ // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ]
+ // console.log(Object.keys(coverageResults));
+
+ if (options.reportGroup.has('both')) {
+ const bothGroup = options.reportGroup.get('both');
+ for (const [reportName, reportOptions] of bothGroup) {
+ const builtInHandler = bothBuiltInReports[reportName];
+ const t1 = Date.now();
+ if (builtInHandler) {
+ await builtInHandler(coverageResults, reportOptions, options);
+ } else {
+ await customReport(reportName, coverageResults, reportOptions, options);
+ }
+ Util.logTime(`${EC.magenta('├')} [generate] saved report: ${reportName}`, t1);
+ }
+ }
+
+ return coverageResults;
+};
+
+
+// ========================================================================================================
+
+const getInputList = (mcr) => {
+ // get input dirs
+ const { inputDir, cacheDir } = mcr.options;
+
+ const inputDirs = Util.toList(inputDir, ',');
+
+ const inputList = inputDirs.filter((dir) => {
+ const hasDir = fs.existsSync(dir);
+ if (!hasDir) {
+ // could be empty
+ Util.logInfo(`Input coverage not exists: ${Util.relativePath(dir)}`);
+ }
+ return hasDir;
+ });
+
+ if (mcr.hasCache()) {
+ inputList.push(cacheDir);
+ }
+
+ return inputList;
+};
+
+const addJsonData = async (mcr, dataList, sourceCache, input, filename) => {
+ const isCoverage = filename.startsWith('coverage-');
+ const isSource = filename.startsWith('source-');
+ if (isCoverage || isSource) {
+ let json = input;
+ if (typeof input === 'string') {
+ if (mcr.fileCache.has(filename)) {
+ json = mcr.fileCache.get(filename);
+ } else {
+ json = await Util.readJson(path.resolve(input, filename));
+ }
+ }
+ if (json) {
+ if (isCoverage) {
+ dataList.push(json);
+ } else {
+ sourceCache.set(json.id, json);
+ }
+ }
+ }
+};
+
+const addDirData = async (mcr, dataList, sourceCache, dir) => {
+ const allFiles = fs.readdirSync(dir);
+ if (!allFiles.length) {
+ return;
+ }
+ for (const filename of allFiles) {
+ // only json file
+ if (filename.endsWith('.json')) {
+ await addJsonData(mcr, dataList, sourceCache, dir, filename);
+ }
+ }
+};
+
+const addZipData = async (mcr, dataList, sourceCache, dir) => {
+ const zip = new StreamZip({
+ file: dir
+ });
+ const entries = await zip.entries();
+ for (const entry of Object.values(entries)) {
+ if (entry.isDirectory) {
+ continue;
+ }
+ const entryName = entry.name;
+ const filename = path.basename(entryName);
+ // console.log('============================', filename);
+ if (filename.endsWith('.json')) {
+ const buf = await zip.entryData(entryName);
+ const json = JSON.parse(buf.toString('utf-8'));
+ await addJsonData(mcr, dataList, sourceCache, json, filename);
+ }
+
+ }
+ // Do not forget to close the file once you're done
+ await zip.close();
+};
+
+const getInputData = async (mcr) => {
+
+ const inputList = getInputList(mcr);
+ // console.log('input list', inputList);
+
+ const dataList = [];
+ const sourceCache = new Map();
+
+ for (const dir of inputList) {
+
+ const info = fs.statSync(dir);
+ if (info.isDirectory()) {
+ await addDirData(mcr, dataList, sourceCache, dir);
+ } else if (info.isFile()) {
+ await addZipData(mcr, dataList, sourceCache, dir);
+ } else {
+ Util.logError(`Invalid input: ${dir}`);
+ }
+
+ }
+
+ if (!dataList.length) {
+ const dirs = inputList.map((dir) => Util.relativePath(dir));
+ Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
+ return;
+ }
+
+ return {
+ dataList,
+ sourceCache
+ };
+};
+
+// ========================================================================================================
+
+const resolveEntrySource = (entry, sourceMapCache, sourceCache) => {
+
+ const url = entry.url;
+
+ // source from `source-id.json`
+ const sourceData = sourceCache.get(url);
+ if (sourceData) {
+ entry.source = sourceData.source;
+ return;
+ }
+
+ // source for typescript file from source map cache
+ // Note: no runtime code but lineLengths
+ const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)($|\?)/;
+ if (tsExtensionsPattern.test(url)) {
+ const sourcemapData = sourceMapCache[url];
+ const lineLengths = sourcemapData && sourcemapData.lineLengths;
+
+ // for fake source file (can not parse to AST)
+ if (lineLengths) {
+ let fakeSource = '';
+ sourcemapData.lineLengths.forEach((length) => {
+ fakeSource += `${''.padEnd(length, '*')}\n`;
+ });
+ entry.fake = true;
+ entry.source = fakeSource;
+ return;
+ }
+
+ }
+
+ // Note: it could be jsx format even extname is `.js`
+ const filePath = fileURLToPath(url);
+ if (fs.existsSync(filePath)) {
+ entry.source = fs.readFileSync(filePath).toString('utf8');
+ }
+
+};
+
+const resolveEntrySourceMap = (entry, sourceMapCache) => {
+ // sourcemap data
+ const sourcemapData = sourceMapCache[entry.url];
+ if (sourcemapData) {
+ if (sourcemapData.data) {
+ entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
+ }
+ }
+};
+
+const readCoverageData = async (dir, filename, entryFilter, sourceCache) => {
+
+ const content = await Util.readFile(path.resolve(dir, filename));
+ if (!content) {
+ return;
+ }
+ const json = JSON.parse(content);
+ if (!json) {
+ return;
+ }
+
+ // raw v8 json
+ let coverageData = json.result;
+ if (!Util.isList(coverageData)) {
+ return;
+ }
+
+ // filter node internal files, should no anonymous for nodejs
+ coverageData = coverageData.filter((entry) => entry.url && entry.url.startsWith('file:'));
+
+ const lengthBefore = coverageData.length;
+ coverageData = coverageData.filter(entryFilter);
+ const lengthAfter = coverageData.length;
+ Util.logFilter('entry filter (addFromDir):', lengthBefore, lengthAfter);
+
+ if (!Util.isList(coverageData)) {
+ // Util.logDebug('No coverage data after filter');
+ return;
+ }
+
+ const sourceMapCache = json['source-map-cache'] || {};
+ for (const entry of coverageData) {
+ resolveEntrySource(entry, sourceMapCache, sourceCache);
+ resolveEntrySourceMap(entry, sourceMapCache);
+ }
+
+ return coverageData;
+};
+
+const readSourceList = async (dir, sourceList) => {
+ const sourceCache = new Map();
+
+ for (const filename of sourceList) {
+ const content = await Util.readFile(path.resolve(dir, filename));
+ if (!content) {
+ continue;
+ }
+ const json = JSON.parse(content);
+ if (!json) {
+ continue;
+ }
+ if (json.url) {
+ sourceCache.set(json.url, json);
+ }
+ }
+
+ return sourceCache;
+};
+
+const readFromDir = async (mcr, dir) => {
+
+ if (!dir || !fs.existsSync(dir)) {
+ Util.logInfo(`Not found V8 coverage dir: ${dir}`);
+ return;
+ }
+
+ const files = fs.readdirSync(dir);
+
+ const coverageList = [];
+ const sourceList = [];
+
+ files.forEach((filename) => {
+ // read all json files
+ if (filename.endsWith('.json')) {
+ // could be source files generated by register hooks
+ if (filename.startsWith('source-')) {
+ sourceList.push(filename);
+ } else {
+ coverageList.push(filename);
+ }
+ }
+ });
+
+ if (!coverageList.length) {
+ Util.logInfo(`No coverage files in the dir: ${dir}`);
+ return;
+ }
+
+ const sourceCache = await readSourceList(dir, sourceList);
+
+ const entryFilter = mcr.getEntryFilter();
+
+ for (const filename of coverageList) {
+ const coverageData = await readCoverageData(dir, filename, entryFilter, sourceCache);
+ if (coverageData) {
+ await mcr.add(coverageData);
+ }
+ }
+
+ // GC
+ sourceCache.clear();
+
+};
+
+module.exports = {
+ getInputData,
+ readFromDir,
+ generateCoverageReports
+};
diff --git a/node_modules/monocart-coverage-reports/lib/index.d.ts b/node_modules/monocart-coverage-reports/lib/index.d.ts
new file mode 100644
index 0000000..dc1d83c
--- /dev/null
+++ b/node_modules/monocart-coverage-reports/lib/index.d.ts
@@ -0,0 +1,606 @@
+declare namespace MCR {
+
+ export interface V8CoverageEntry {
+ url: string;
+
+ /** entry type */
+ type?: "js" | "css";
+
+ /** css only */
+ text?: string;
+ /** css only */
+ ranges?: any[];
+
+ /** js only */
+ source?: string;
+ /** js only */
+ scriptId?: string;
+ /** js only */
+ functions?: any[];
+
+ /** js only */
+ sourceMap?: any;
+ /** js only */
+ scriptOffset?: number;
+ /** js only */
+ distFile?: string;
+
+ /** empty coverage */
+ empty?: boolean;
+ /** fake source */
+ fake?: boolean;
+
+ [key: string]: any;
+ }
+
+ export type Watermarks = [number, number] | {
+ /** V8 only */
+ bytes?: [number, number];
+ statements: [number, number];
+ branches?: [number, number];
+ functions?: [number, number];
+ lines?: [number, number];
+ }
+
+ export type ReportDescription =
+ ['v8'] | ["v8", {
+ /**
+ * defaults to `index.html`
+ */
+ outputFile?: string;
+ inline?: boolean;
+ assetsPath?: string;
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ }] |
+ ['v8-json'] | ["v8-json", {
+ /**
+ * defaults to `coverage-report.json`
+ */
+ outputFile?: string;
+ }] |
+ ['clover'] | ['clover', {
+ file?: string;
+ }] |
+ ['cobertura'] | ['cobertura', {
+ file?: string;
+ timestamp?: string;
+ projectRoot?: string;
+ }] |
+ ['html'] | ['html', {
+ subdir?: string;
+ verbose?: boolean;
+ linkMapper?: any;
+ skipEmpty?: boolean;
+ }] |
+ ['html-spa'] | ['html-spa', {
+ subdir?: string;
+ verbose?: boolean;
+ linkMapper?: any;
+ skipEmpty?: boolean;
+ metricsToShow?: Array<"statements" | "branches" | "functions" | "lines">;
+ }] |
+ ['json'] | ['json', {
+ file?: string;
+ }] |
+ ['json-summary'] | ['json-summary', {
+ file?: string;
+ }] |
+ ['lcov'] | ['lcov', {
+ file?: string;
+ projectRoot?: string;
+ }] |
+ ['lcovonly'] | ['lcovonly', {
+ file?: string;
+ projectRoot?: string;
+ }] |
+ ['none'] |
+ ['teamcity'] | ['teamcity', {
+ file?: string;
+ blockName?: string;
+ }] |
+ ['text'] | ['text', {
+ file?: string;
+ maxCols?: number;
+ skipEmpty?: boolean;
+ skipFull?: boolean;
+ }] |
+ ['text-lcov'] | ['text-lcov', {
+ projectRoot?: string;
+ }] |
+ ['text-summary'] | ['text-summary', {
+ file?: string;
+ }] |
+ ['codecov'] | ["codecov", {
+ /**
+ * defaults to `codecov.json`
+ */
+ outputFile?: string;
+ }] |
+ ['codacy'] | ["codacy", {
+ /**
+ * defaults to `codacy.json`
+ */
+ outputFile?: string;
+ }] |
+ ['console-summary'] | ['console-summary', {
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ }] |
+ ['console-details'] | ['console-details', {
+ maxCols?: number;
+ skipPercent?: number;
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ filter?: string | {
+ [pattern: string]: boolean;
+ } | ((file: CoverageFile) => boolean);
+ }] |
+ ['markdown-summary'] | ['markdown-summary', {
+ color: 'unicode' | 'html' | 'tex' | string;
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ /**
+ * defaults to `coverage-summary.md`
+ */
+ outputFile?: string;
+ }] |
+ ['markdown-details'] | ['markdown-details', {
+ baseUrl?: string;
+ color: 'unicode' | 'html' | 'tex' | string;
+ maxCols?: number;
+ skipPercent?: number;
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ filter?: string | {
+ [pattern: string]: boolean;
+ } | ((file: CoverageFile) => boolean);
+ /**
+ * defaults to `coverage-details.md`
+ */
+ outputFile?: string;
+ }] |
+ ['raw'] | ['raw', {
+ merge?: boolean;
+ zip?: boolean;
+ outputDir?: string;
+ }] |
+ [string] | [string, {
+ type?: "v8" | "istanbul" | "both" | string;
+ [key: string]: any;
+ }];
+
+ export type AddedResults = {
+ id: string;
+ path: string;
+ type: "v8" | "istanbul";
+ data: any;
+ };
+
+
+ export interface MetricsSummary {
+ covered: number;
+ uncovered?: number;
+ total: number;
+ pct: number | "";
+ status: "low" | "medium" | "high" | "unknown";
+ /** V8 lines only */
+ blank?: number;
+ /** V8 lines only */
+ comment?: number;
+ }
+
+ export interface CoverageSummary {
+ /** V8 only */
+ bytes?: MetricsSummary;
+ statements: MetricsSummary;
+ branches: MetricsSummary;
+ functions: MetricsSummary;
+ lines: MetricsSummary;
+ }
+
+ /** V8 only */
+ export interface CoverageRange {
+ start: number;
+ end: number;
+ count: number;
+ /** ignored by special comment which starts with `v8 ignore` */
+ ignored?: boolean;
+ /**
+ * branch only, for example:
+ * there is only `if` branch but no `else` branch, then `none` will be true, it shows `else path uncovered`
+ */
+ none?: boolean;
+ /** function only, function name */
+ name?: boolean;
+ }
+
+ export interface IgnoredRange {
+ start: number;
+ end: number;
+ /** ignore type: `next` or `range` */
+ type: string;
+ /** n lines for `next` type */
+ n?: number;
+ }
+
+ export interface CoverageFile {
+ sourcePath: string;
+ summary: CoverageSummary;
+ /** V8 only */
+ url?: string;
+ /** V8 only */
+ id?: string;
+ /** V8 only */
+ type?: string;
+ /** V8 only */
+ source?: string;
+ /** V8 only */
+ distFile?: string;
+ /** V8 only */
+ js?: boolean;
+ /** V8 only */
+ data?: {
+ bytes?: CoverageRange[];
+ statements?: CoverageRange[];
+ branches?: CoverageRange[];
+ functions?: CoverageRange[];
+ ignores?: IgnoredRange[];
+ lines?: {
+ /**
+ * key: line number;
+ * number: hits (0 means uncovered);
+ * string: partial covered
+ */
+ [key: string]: number | string;
+ };
+ extras?: {
+ /**
+ * key: line number;
+ * b: blank;
+ * c: comment;
+ * i: ignored;
+ */
+ [key: string]: "b" | "c" | "i";
+ }
+ }
+ }
+
+ export type CoverageResults = {
+ type: "v8" | "istanbul";
+ reportPath: string;
+ version: string;
+ name: string;
+ watermarks: Watermarks;
+ summary: CoverageSummary;
+ files: CoverageFile[];
+ };
+
+ export type LoggingType = "off" | "error" | "info" | "debug";
+ export interface CoverageReportOptions {
+
+ /** {string} logging levels: off, error, info, debug */
+ logging?: LoggingType;
+
+ /** {string} Report name. Defaults to "Coverage Report". */
+ name?: string;
+
+ /**
+ *
+ * {string} 'v8', 'v8,console-details'
+ *
+ * {array} ['v8'], ['v8', ['console-details', { skipPercent: 80 }]]
+ *
+ * By default, `v8` for V8 data, `html` for Istanbul data
+ */
+ reports?: string | (string | ReportDescription)[];
+
+ /** {string} output dir */
+ outputDir?: string;
+
+ /** {string|string[]} input raw dir(s) */
+ inputDir?: string | string[];
+
+ /** {string} base dir for normalizing the relative source path, defaults to cwd */
+ baseDir?: string;
+
+ /** {string} coverage data dir, alternative to method `addFromDir()`, defaults to null */
+ dataDir?: string;
+
+ /** (V8 only)
+ *
+ * {string} `minimatch` pattern for entry url;
+ * {object} multiple patterns;
+ * {function} A filter function for each entry file in the V8 list.
+ */
+ entryFilter?: string | {
+ [pattern: string]: boolean;
+ } | ((entry: V8CoverageEntry) => boolean);
+
+ /** (V8 only)
+ *
+ * {string} `minimatch` pattern for source path;
+ * {object} multiple patterns;
+ * {function} A filter function for each source path when the source is unpacked from the source map.
+ */
+ sourceFilter?: string | {
+ [pattern: string]: boolean;
+ } | ((sourcePath: string) => boolean);
+
+ /**
+ * The combined filter for entryFilter and sourceFilter
+ */
+ filter?: string | {
+ [pattern: string]: boolean;
+ } | ((input: string | V8CoverageEntry) => boolean);
+
+ /**
+ * {function} Source path handler.
+ *
+ * {object} Replace key with value.
+ * */
+ sourcePath?: ((filePath: string, info: {
+ /** the related dist file of current source file */
+ distFile?: string;
+ [key: string]: any;
+ }) => string) | {
+ [key: string]: string;
+ };
+
+
+ /** (V8 only) {string} Output [sub dir/]filename. Defaults to "index.html" */
+ outputFile?: string;
+ /** (V8 only) {boolean} Inline all scripts to the single HTML file. Defaults to false. */
+ inline?: boolean;
+ /** (V8 only) {string} Assets path if not inline. Defaults to "./assets" */
+ assetsPath?: string;
+
+ /** (Istanbul only) defaultSummarizer, sourceFinder */
+
+ /** {boolean} Generate lcov.info file, same as lcovonly report. Defaults to false. */
+ lcov?: boolean;
+
+ /**
+ * options for adding empty coverage for all files
+ */
+ all?: string | string[] | {
+ /** the dir(s) of all files */
+ dir: string | string[];
+
+ /**
+ * the file filter is triggered before `sourceFilter`, no need to use it in normal case
+ * the filter could return the file type, if true, defaults to css if ".css" otherwise is js
+ * {string} `minimatch` pattern for file path; {object} multiple patterns; {function} A filter function for file path;
+ */
+ filter?: string | {
+ [pattern: string]: "js" | "css" | boolean;
+ } | ((filePath: string) => "js" | "css" | boolean);
+
+ /**
+ * the file transformer for source and sourceMap
+ * some of untested files like .ts/.jsx/.vue can not be parsed to AST directly by acorn
+ * so this is the function which can transform the original source to generated source and sourceMap
+ */
+ transformer?: (entry: any) => Promise;
+
+ };
+
+ /** (V8 only) {boolean} Enable/Disable ignoring uncovered codes with the special comments: v8 ignore next/next N/start/stop */
+ v8Ignore?: boolean;
+
+ /** {string|function} Specify the report path, especially when there are multiple reports. Defaults to outputDir/index.html. */
+ reportPath?: string | (() => string);
+
+ /** {array} watermarks for low/medium/high. Defaults to [50, 80]
+ * {object} { bytes:[50,80], statements:[50,80], branches:[50,80], functions:[50,80], lines:[50,80] } */
+ watermarks?: Watermarks;
+
+ /**
+ * {boolean} Indicates whether to clean previous reports in output dir before generating new reports. Defaults to true.
+ *
+ * If true, the API `clean()` will execute automatically.
+ * */
+ clean?: boolean;
+
+ /**
+ * {boolean} Indicates whether to clean previous cache in output dir on start. Defaults to false.
+ *
+ * If true, the API `cleanCache()` will execute automatically.
+ */
+ cleanCache?: boolean;
+
+ /**
+ * {number} gc threshold
+ * for example: sets gc to 1024 means that force gc when the memory > 1024M at certain critical stages
+ * https://nodejs.org/docs/latest/api/v8.html#v8setflagsfromstringflags
+ */
+ gc?: number;
+
+ /**
+ * {boolean} Indicates whether to save source and sourcemap file for debug (require logging="debug")
+ */
+ sourceMap?: boolean;
+
+ /**
+ * {function} Custom resolver for sourcemap content
+ */
+ sourceMapResolver?: (url: string, defaultResolver: Function) => Promise;
+
+ /** (V8 only) {function} onEntry hook */
+ onEntry?: (entry: V8CoverageEntry) => Promise;
+
+ /** {function} onEnd hook */
+ onEnd?: (coverageResults: CoverageResults | undefined) => Promise;
+
+ [key: string]: any;
+ }
+
+ export interface McrCliOptions extends CoverageReportOptions {
+
+ /** (CLI only) {function} onStart hook */
+ onStart?: (coverageReport: CoverageReport) => Promise;
+
+ /** (CLI only) {function} onReady hook before adding coverage data.
+ *
+ * Sometimes, the child process has not yet finished writing the coverage data, and it needs to wait here.
+ */
+ onReady?: (coverageReport: CoverageReport, nodeV8CoverageDir: string, subprocess: any) => Promise;
+
+ }
+
+ export class CoverageReport {
+
+ /**
+ * @param options coverage report options
+ */
+ constructor(options?: CoverageReportOptions);
+
+ /** add coverage data
+ *
+ * @param coverageData {array} V8 format, {object} Istanbul format */
+ add: (coverageData: any[] | any) => Promise;
+
+ /**
+ * add V8 coverage from a dir
+ * @param dir node v8 coverage dir
+ */
+ addFromDir: (dir: string) => Promise;
+
+ /** generate report */
+ generate: () => Promise;
+
+ /** check if cache exists */
+ hasCache: () => boolean;
+
+ /** clean previous cache, return `false` if no cache */
+ cleanCache: () => boolean;
+
+ /** clean previous reports except cache dir and v8 coverage dir */
+ clean: () => void;
+
+ /** get entry filter handler, it can be used to filter the coverage data list before adding it. */
+ getEntryFilter: () => ((entry: V8CoverageEntry) => boolean);
+
+ /** load config file.
+ *
+ * @param configFile custom config file path
+ *
+ * Supports loading default config file if no custom config specified:
+ 'mcr.config.js',
+ 'mcr.config.cjs',
+ 'mcr.config.mjs',
+ 'mcr.config.json',
+ 'mcr.config.ts'
+ */
+ loadConfig: (configFile?: string) => Promise;
+ }
+
+ //=====================================================================================================
+
+ export interface CoverageSnapshot {
+ type: "v8" | "istanbul";
+ summary: {
+ bytes?: string;
+ statements: string;
+ branches: string;
+ functions: string;
+ lines: string;
+ },
+ files: {
+ [sourcePath: string]: {
+ bytes?: string;
+ statements: string;
+ branches: string;
+ functions: string;
+ lines: string;
+ uncoveredLines: string;
+ extras: string;
+ }
+ }
+ }
+
+ /** get snapshot from coverage report data */
+ export function getSnapshot(coverageResults: CoverageResults): CoverageSnapshot;
+
+ /** diff two snapshots */
+ export function diffSnapshot(oldData: CoverageSnapshot, newData: CoverageSnapshot, diffOptions: {
+ skipEqual?: boolean;
+ showSummary?: boolean;
+ maxCols?: number;
+ metrics?: Array<"bytes" | "statements" | "branches" | "functions" | "lines">;
+ }): {
+ change: boolean;
+ results: any[];
+ message: string;
+ };
+
+ //=====================================================================================================
+
+ export class CoverageClient {
+ /** start js coverage */
+ startJSCoverage: () => Promise;
+ /** stop and return js coverage */
+ stopJSCoverage: () => Promise;
+
+ /** start css coverage */
+ startCSSCoverage: () => Promise;
+ /** stop and return css coverage */
+ stopCSSCoverage: () => Promise;
+
+ /** start both js and css coverage */
+ startCoverage: () => Promise;
+ /** stop and return both js and css coverage */
+ stopCoverage: () => Promise;
+
+ /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
+ writeCoverage: () => Promise;
+
+ /** get istanbul coverage data
+ * @param coverageKey defaults to `__coverage__`
+ */
+ getIstanbulCoverage: (coverageKey?: string) => Promise;
+
+ close: () => Promise
+ }
+
+ /** Adapt to the CDPSession of Playwright or Puppeteer */
+ export interface CDPSession {
+ send: (method: string, params?: any) => Promise;
+ on: (type: string, handler: (e: any) => void) => void;
+ detach: () => Promise;
+ }
+
+ /** custom websocket CDPSession */
+ export class WSSession implements CDPSession {
+ constructor(ws: any);
+ send: (method: string, params?: any) => Promise;
+ on: (type: string, handler: (e: any) => void) => void;
+ detach: () => Promise