@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 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
+ }