@escover/formatter-responsive 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/lib/build-grouped-table.js +78 -0
- package/lib/format-lines.js +60 -0
- package/lib/group-by-folder.js +28 -0
- package/lib/responsive.js +122 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @escover/formatter-responsive [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Coverage Status][CoverageIMGURL]][CoverageURL]
|
|
2
|
+
|
|
3
|
+
[NPMIMGURL]: https://img.shields.io/npm/v/@escover/formatter-responsive.svg?style=flat
|
|
4
|
+
[BuildStatusURL]: https://github.com/coderaiser/@escover/formatter-responsive/actions?query=workflow%3A%22Node+CI%22 "Build Status"
|
|
5
|
+
[BuildStatusIMGURL]: https://github.com/coderaiser/@escover/formatter-responsive/workflows/Node%20CI/badge.svg
|
|
6
|
+
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
|
7
|
+
[NPMURL]: https://npmjs.org/package/@escover/formatter-responsive "npm"
|
|
8
|
+
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
|
9
|
+
[CoverageURL]: https://coveralls.io/github/coderaiser/@escover/formatter-responsive?branch=master
|
|
10
|
+
[CoverageIMGURL]: https://coveralls.io/repos/coderaiser/@escover/formatter-responsive/badge.svg?branch=master&service=github
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
npm i @formatter-responsive -D
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage Example
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
ESCOVER_FORMAT='responsive' escover npm test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
MIT
|
|
26
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {formatLines} from './format-lines.js';
|
|
3
|
+
import {groupByFolder} from './group-by-folder.js';
|
|
4
|
+
|
|
5
|
+
const makeGreen = chalk.hex('#4caf50');
|
|
6
|
+
const makeRed = chalk.hex('#f44336');
|
|
7
|
+
|
|
8
|
+
export const getColor = (value) => value === 100 ? makeGreen : makeRed;
|
|
9
|
+
|
|
10
|
+
function truncateLeft(str, maxLength) {
|
|
11
|
+
if (str.length <= maxLength)
|
|
12
|
+
return str;
|
|
13
|
+
|
|
14
|
+
return `...${str.slice(str.length - maxLength + 5)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildGroupedTable({files, showPercent, linesColWidth, fileColWidth}) {
|
|
18
|
+
const tableData = [];
|
|
19
|
+
|
|
20
|
+
if (showPercent)
|
|
21
|
+
tableData.push([
|
|
22
|
+
'File',
|
|
23
|
+
'% Lines',
|
|
24
|
+
'Uncovered Lines #s',
|
|
25
|
+
]);
|
|
26
|
+
else
|
|
27
|
+
tableData.push([
|
|
28
|
+
'File',
|
|
29
|
+
'Uncovered Lines #s',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const groups = groupByFolder(files);
|
|
33
|
+
const hideFolders = groups.size === 1;
|
|
34
|
+
|
|
35
|
+
for (const [folder, group] of groups) {
|
|
36
|
+
let sum = 0;
|
|
37
|
+
|
|
38
|
+
for (const f of group.files)
|
|
39
|
+
sum += f.percentLines;
|
|
40
|
+
|
|
41
|
+
const coverage = Math.round(sum / group.files.length);
|
|
42
|
+
|
|
43
|
+
if (!hideFolders) {
|
|
44
|
+
const row = [];
|
|
45
|
+
|
|
46
|
+
const shortFolder = truncateLeft(folder, fileColWidth);
|
|
47
|
+
row.push(getColor(coverage)(shortFolder));
|
|
48
|
+
|
|
49
|
+
if (showPercent)
|
|
50
|
+
row.push(getColor(coverage)(`${coverage}%`));
|
|
51
|
+
|
|
52
|
+
row.push('');
|
|
53
|
+
tableData.push(row);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const {covered, lines, fileName, percentLines} of group.files) {
|
|
57
|
+
const row = [];
|
|
58
|
+
const shortName = truncateLeft(fileName, fileColWidth);
|
|
59
|
+
const coloredShortName = covered ? makeGreen(shortName) : makeRed(shortName);
|
|
60
|
+
|
|
61
|
+
row.push(` ${coloredShortName}`);
|
|
62
|
+
|
|
63
|
+
if (showPercent) {
|
|
64
|
+
const percent = percentLines === 100 ? makeGreen(`${percentLines}%`) : makeRed(`${percentLines}%`);
|
|
65
|
+
row.push(percent);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
row.push(covered ? '' : makeRed(formatLines(
|
|
69
|
+
lines,
|
|
70
|
+
linesColWidth,
|
|
71
|
+
)));
|
|
72
|
+
|
|
73
|
+
tableData.push(row);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return tableData;
|
|
78
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function formatLines(nums, maxLen = 20) {
|
|
2
|
+
const ranges = [];
|
|
3
|
+
|
|
4
|
+
for (let i = 0; i < nums.length; i++) {
|
|
5
|
+
const [j, range] = maybeRange(nums, i);
|
|
6
|
+
ranges.push(range);
|
|
7
|
+
i = j - 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const joined = ranges.join(', ');
|
|
11
|
+
|
|
12
|
+
if (joined.length <= maxLen)
|
|
13
|
+
return joined;
|
|
14
|
+
|
|
15
|
+
return getShortenedRange(ranges, maxLen);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getShortenedRange(ranges, maxLen) {
|
|
19
|
+
const last = ranges
|
|
20
|
+
.at(-1)
|
|
21
|
+
.toString();
|
|
22
|
+
|
|
23
|
+
for (let i = 1; i < ranges.length - 1; i++) {
|
|
24
|
+
const joined = `${ranges
|
|
25
|
+
.slice(0, -i)
|
|
26
|
+
.join(', ')} ... ${last}`;
|
|
27
|
+
|
|
28
|
+
if (joined.length <= maxLen)
|
|
29
|
+
return joined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const first = ranges.at(0);
|
|
33
|
+
const firstLast = `${first} ... ${last}`;
|
|
34
|
+
|
|
35
|
+
if (firstLast.length <= maxLen)
|
|
36
|
+
return firstLast;
|
|
37
|
+
|
|
38
|
+
return `${ranges.at(0)} ...`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function maybeRange(nums, i) {
|
|
42
|
+
const start = nums.at(i);
|
|
43
|
+
let prev = nums.at(i);
|
|
44
|
+
|
|
45
|
+
for (++i; i < nums.length; i++) {
|
|
46
|
+
const n = nums[i];
|
|
47
|
+
|
|
48
|
+
if (n === prev + 1) {
|
|
49
|
+
prev = n;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (start === prev)
|
|
57
|
+
return [i, start];
|
|
58
|
+
|
|
59
|
+
return [i, `${start}..${prev}`];
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function groupByFolder(files) {
|
|
2
|
+
const groups = new Map();
|
|
3
|
+
|
|
4
|
+
for (const f of files) {
|
|
5
|
+
const parts = f.filename.split('/');
|
|
6
|
+
const folder = parts.length > 1 ? parts
|
|
7
|
+
.slice(0, -1)
|
|
8
|
+
.join('/') : '';
|
|
9
|
+
|
|
10
|
+
const fileName = parts.at(-1);
|
|
11
|
+
|
|
12
|
+
let group = groups.get(folder);
|
|
13
|
+
|
|
14
|
+
if (!group) {
|
|
15
|
+
group = {
|
|
16
|
+
files: [],
|
|
17
|
+
};
|
|
18
|
+
groups.set(folder, group);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
group.files.push({
|
|
22
|
+
...f,
|
|
23
|
+
fileName,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return groups;
|
|
28
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import {
|
|
3
|
+
table,
|
|
4
|
+
getBorderCharacters,
|
|
5
|
+
} from 'table';
|
|
6
|
+
import {buildGroupedTable} from './build-grouped-table.js';
|
|
7
|
+
|
|
8
|
+
const CWD = process.cwd();
|
|
9
|
+
const {entries, keys} = Object;
|
|
10
|
+
|
|
11
|
+
export default (coverageFile, {skipFull = false} = {}) => {
|
|
12
|
+
const files = parseCoverageFile(coverageFile, skipFull);
|
|
13
|
+
|
|
14
|
+
if (skipFull && !files.length)
|
|
15
|
+
return '💪 coverage 100% Good Job!\n';
|
|
16
|
+
|
|
17
|
+
const totalWidth = process.stdout.columns || 80;
|
|
18
|
+
const showPercent = totalWidth >= 70;
|
|
19
|
+
|
|
20
|
+
const linesColWidth = Math.floor(totalWidth / 2);
|
|
21
|
+
const percentColWidth = showPercent ? 7 : 0;
|
|
22
|
+
|
|
23
|
+
const fileColWidth = Math.max(10, totalWidth - linesColWidth - percentColWidth - 6 - 4);
|
|
24
|
+
const tableData = buildGroupedTable({
|
|
25
|
+
files,
|
|
26
|
+
showPercent,
|
|
27
|
+
linesColWidth,
|
|
28
|
+
fileColWidth,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const options = createTableOptions({
|
|
32
|
+
showPercent,
|
|
33
|
+
tableData,
|
|
34
|
+
fileColWidth,
|
|
35
|
+
percentColWidth,
|
|
36
|
+
linesColWidth,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return table(tableData, options);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function createTableOptions({showPercent, tableData, fileColWidth, percentColWidth, linesColWidth}) {
|
|
43
|
+
const columns = [{
|
|
44
|
+
paddingLeft: 1,
|
|
45
|
+
paddingRight: 1,
|
|
46
|
+
width: fileColWidth,
|
|
47
|
+
wrapWord: false,
|
|
48
|
+
}];
|
|
49
|
+
|
|
50
|
+
if (showPercent)
|
|
51
|
+
columns.push({
|
|
52
|
+
alignment: 'center',
|
|
53
|
+
paddingLeft: 1,
|
|
54
|
+
paddingRight: 1,
|
|
55
|
+
width: percentColWidth,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
columns.push({
|
|
59
|
+
paddingLeft: 1,
|
|
60
|
+
paddingRight: 1,
|
|
61
|
+
width: linesColWidth,
|
|
62
|
+
wrapWord: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
drawHorizontalLine: (i) => !i || i === 1 || i === tableData.length,
|
|
67
|
+
columns,
|
|
68
|
+
border: {
|
|
69
|
+
...getBorderCharacters('void'),
|
|
70
|
+
topBody: '-',
|
|
71
|
+
bottomBody: '-',
|
|
72
|
+
joinBody: '-',
|
|
73
|
+
topJoin: '|',
|
|
74
|
+
bottomJoin: '|',
|
|
75
|
+
joinJoin: '|',
|
|
76
|
+
bodyJoin: '|',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function parseCoverageFile(coverageFile, skipFull) {
|
|
82
|
+
const files = [];
|
|
83
|
+
|
|
84
|
+
for (const {name, lines} of coverageFile) {
|
|
85
|
+
const uncovered = parseUncoveredLines(lines);
|
|
86
|
+
const linesCount = keys(lines).length;
|
|
87
|
+
const uncoveredLinesCount = uncovered.length;
|
|
88
|
+
const percentLines = getLinesPercent(linesCount, uncoveredLinesCount);
|
|
89
|
+
const covered = uncoveredLinesCount === 0;
|
|
90
|
+
|
|
91
|
+
if (skipFull && covered)
|
|
92
|
+
continue;
|
|
93
|
+
|
|
94
|
+
files.push({
|
|
95
|
+
filename: name.replace(`${CWD}/`, ''),
|
|
96
|
+
covered,
|
|
97
|
+
lines: uncovered,
|
|
98
|
+
percentLines,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function parseUncoveredLines(lines) {
|
|
106
|
+
const out = [];
|
|
107
|
+
|
|
108
|
+
for (const [line, covered] of entries(lines))
|
|
109
|
+
if (!covered)
|
|
110
|
+
out.push(Number(line));
|
|
111
|
+
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getLinesPercent(linesCount, uncoveredLinesCount) {
|
|
116
|
+
if (!linesCount)
|
|
117
|
+
return 100;
|
|
118
|
+
|
|
119
|
+
const ratio = 100 / linesCount * uncoveredLinesCount;
|
|
120
|
+
|
|
121
|
+
return 100 - Math.round(ratio);
|
|
122
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@escover/formatter-responsive",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
|
|
5
|
+
"description": "Istanbul converter",
|
|
6
|
+
"exports": "./lib/responsive.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/coderaiser/escover.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"coverage",
|
|
14
|
+
"putout",
|
|
15
|
+
"loader"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "madrun test",
|
|
19
|
+
"coverage": "madrun coverage",
|
|
20
|
+
"lint": "madrun lint",
|
|
21
|
+
"fresh:lint": "madrun fresh:lint",
|
|
22
|
+
"lint:fresh": "madrun lint:fresh",
|
|
23
|
+
"fix:lint": "madrun fix:lint",
|
|
24
|
+
"report": "madrun report",
|
|
25
|
+
"watcher": "madrun watcher",
|
|
26
|
+
"watch:test": "madrun watch:test",
|
|
27
|
+
"watch:lint": "madrun watch:lint",
|
|
28
|
+
"watch:tape": "madrun watch:tape",
|
|
29
|
+
"prewisdom": "madrun prewisdom"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"chalk": "^5.0.0",
|
|
33
|
+
"find-cache-dir": "^5.0.0",
|
|
34
|
+
"find-up": "^8.0.0",
|
|
35
|
+
"montag": "^2.0.1",
|
|
36
|
+
"once": "^1.4.0",
|
|
37
|
+
"picomatch": "^4.0.2",
|
|
38
|
+
"putout": "^42.0.1",
|
|
39
|
+
"strip-ansi": "^7.0.1",
|
|
40
|
+
"table": "^6.8.0",
|
|
41
|
+
"try-catch": "^4.0.9",
|
|
42
|
+
"yargs-parser": "^22.0.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@putout/eslint-flat": "^4.0.0",
|
|
50
|
+
"@putout/test": "^15.1.1",
|
|
51
|
+
"c8": "^11.0.0",
|
|
52
|
+
"escover": "^6.2.9",
|
|
53
|
+
"eslint": "^10.0.0",
|
|
54
|
+
"eslint-plugin-putout": "^31.0.0",
|
|
55
|
+
"madrun": "^13.0.0",
|
|
56
|
+
"supertape": "^13.1.1"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|