escover 6.2.6 → 6.2.7

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/ChangeLog CHANGED
@@ -1,3 +1,11 @@
1
+ 2026.05.09, v6.2.7
2
+
3
+ feature:
4
+ - 47c8893 responsive: format-lines: shorten on small screen
5
+ - 2f91bbb escover: responsive: format-lines
6
+ - 25b9645 responsive: move out build-grouped-table
7
+ - 2a054d6 formatters: responsive: simplify
8
+
1
9
  2026.05.08, v6.2.6
2
10
 
3
11
  feature:
package/lib/cli/cli.js CHANGED
@@ -5,14 +5,11 @@ import yargsParser from 'yargs-parser';
5
5
  import {version} from './version.js';
6
6
  import reportLines from '../formatters/lines.js';
7
7
  import reportFiles from '../formatters/files.js';
8
- import reportResponsive from '../formatters/responsive.js';
8
+ import reportResponsive from '../formatters/responsive/responsive.js';
9
9
  import {readConfig} from '../config.js';
10
10
  import {help} from './help.js';
11
11
 
12
12
  const {env} = process;
13
-
14
- const noop = () => {};
15
-
16
13
  const {NODE_OPTIONS, ESCOVER_FORMAT} = env;
17
14
 
18
15
  export const createNodeOptions = (options = NODE_OPTIONS || '') => {
@@ -56,7 +53,7 @@ export const cli = ({argv, exit, readCoverage}) => {
56
53
  const cmd = argv.slice(2);
57
54
 
58
55
  if (cmd.length)
59
- execute(`"${cmd.join('" "')}"`);
56
+ execute(`"${cmd.join('" "')}"`, exit);
60
57
 
61
58
  const coverage = readCoverage();
62
59
 
@@ -76,7 +73,7 @@ export const cli = ({argv, exit, readCoverage}) => {
76
73
 
77
74
  export const isSuccess = (error) => !error || error?.status === Number(process.env.ESCOVER_SUCCESS_EXIT_CODE);
78
75
 
79
- function execute(cmd, exit = noop) {
76
+ function execute(cmd, exit) {
80
77
  const [error] = tryCatch(execSync, cmd, {
81
78
  stdio: [0, 1, 2],
82
79
  env: {
@@ -86,7 +83,7 @@ function execute(cmd, exit = noop) {
86
83
  });
87
84
 
88
85
  if (isSuccess(error))
89
- return exit(0);
86
+ return;
90
87
 
91
88
  if (error) {
92
89
  console.error(error.message);
@@ -0,0 +1,64 @@
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
+ export function buildGroupedTable(files, showPercent, linesColWidth) {
11
+ const tableData = [];
12
+
13
+ if (showPercent)
14
+ tableData.push([
15
+ 'File',
16
+ '% Lines',
17
+ 'Uncovered Lines #s',
18
+ ]);
19
+ else
20
+ tableData.push([
21
+ 'File',
22
+ 'Uncovered Lines #s',
23
+ ]);
24
+
25
+ const groups = groupByFolder(files);
26
+ const hideFolders = groups.size === 1;
27
+
28
+ for (const [folder, group] of groups) {
29
+ let sum = 0;
30
+
31
+ for (const f of group.files)
32
+ sum += f.percentLines;
33
+
34
+ const coverage = Math.round(sum / group.files.length);
35
+
36
+ if (!hideFolders) {
37
+ const row = [];
38
+ row.push(getColor(coverage)(folder));
39
+
40
+ if (showPercent)
41
+ row.push(getColor(coverage)(`${coverage}%`));
42
+
43
+ row.push('');
44
+ tableData.push(row);
45
+ }
46
+
47
+ for (const f of group.files) {
48
+ const row = [];
49
+ row.push(f.covered ? makeGreen(' ' + f.shortName) : makeRed(' ' + f.shortName));
50
+
51
+ if (showPercent)
52
+ row.push(f.percentLines === 100 ? makeGreen(`${f.percentLines}%`) : makeRed(`${f.percentLines}%`));
53
+
54
+ row.push(f.covered ? '' : makeRed(formatLines(
55
+ f.lines,
56
+ linesColWidth,
57
+ )));
58
+
59
+ tableData.push(row);
60
+ }
61
+ }
62
+
63
+ return tableData;
64
+ }
@@ -0,0 +1,56 @@
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.at(-1).toString();
20
+
21
+ for (let i = 1; i < ranges.length - 1; i++) {
22
+ const joined = `${ranges.slice(0, -i).join(', ')} ... ${last}`;
23
+
24
+ if (joined.length <= maxLen)
25
+ return joined;
26
+ }
27
+
28
+ const first = ranges.at(0);
29
+ const firstLast = `${first} ... ${last}`;
30
+
31
+ if (firstLast.length <= maxLen)
32
+ return firstLast;
33
+
34
+ return `${ranges.at(0)} ...`;
35
+ }
36
+
37
+ function maybeRange(nums, i) {
38
+ const start = nums.at(i);
39
+ let prev = nums.at(i);
40
+
41
+ for (++i; i < nums.length; i++) {
42
+ const n = nums[i];
43
+
44
+ if (n === prev + 1) {
45
+ prev = n;
46
+ continue;
47
+ }
48
+
49
+ break;
50
+ }
51
+
52
+ if (start === prev)
53
+ return [i, start];
54
+
55
+ return [i, `${start}..${prev}`];
56
+ }
@@ -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
+ shortName: fileName,
24
+ });
25
+ }
26
+
27
+ return groups;
28
+ }
@@ -0,0 +1,117 @@
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(files, showPercent, linesColWidth);
25
+
26
+ const options = createTableOptions({
27
+ showPercent,
28
+ tableData,
29
+ fileColWidth,
30
+ percentColWidth,
31
+ linesColWidth,
32
+ });
33
+
34
+ return table(tableData, options);
35
+ };
36
+
37
+ export function createTableOptions({showPercent, tableData, fileColWidth, percentColWidth, linesColWidth}) {
38
+ const columns = [{
39
+ paddingLeft: 1,
40
+ paddingRight: 1,
41
+ width: fileColWidth,
42
+ wrapWord: false,
43
+ }];
44
+
45
+ if (showPercent)
46
+ columns.push({
47
+ alignment: 'center',
48
+ paddingLeft: 1,
49
+ paddingRight: 1,
50
+ width: percentColWidth,
51
+ });
52
+
53
+ columns.push({
54
+ paddingLeft: 1,
55
+ paddingRight: 1,
56
+ width: linesColWidth,
57
+ wrapWord: false,
58
+ });
59
+
60
+ return {
61
+ drawHorizontalLine: (i) => !i || i === 1 || i === tableData.length,
62
+ columns,
63
+ border: {
64
+ ...getBorderCharacters('void'),
65
+ topBody: '-',
66
+ bottomBody: '-',
67
+ joinBody: '-',
68
+ topJoin: '|',
69
+ bottomJoin: '|',
70
+ joinJoin: '|',
71
+ bodyJoin: '|',
72
+ },
73
+ };
74
+ }
75
+
76
+ export function parseCoverageFile(coverageFile, skipFull) {
77
+ const files = [];
78
+
79
+ for (const {name, lines} of coverageFile) {
80
+ const uncovered = parseUncoveredLines(lines);
81
+ const linesCount = keys(lines).length;
82
+ const uncoveredLinesCount = uncovered.length;
83
+ const percentLines = getLinesPercent(linesCount, uncoveredLinesCount);
84
+ const covered = uncoveredLinesCount === 0;
85
+
86
+ if (skipFull && covered)
87
+ continue;
88
+
89
+ files.push({
90
+ filename: name.replace(`${CWD}/`, ''),
91
+ covered,
92
+ lines: uncovered,
93
+ percentLines,
94
+ });
95
+ }
96
+
97
+ return files;
98
+ }
99
+
100
+ export function parseUncoveredLines(lines) {
101
+ const out = [];
102
+
103
+ for (const [line, covered] of entries(lines))
104
+ if (!covered)
105
+ out.push(Number(line));
106
+
107
+ return out;
108
+ }
109
+
110
+ export function getLinesPercent(linesCount, uncoveredLinesCount) {
111
+ if (!linesCount)
112
+ return 100;
113
+
114
+ const ratio = 100 / linesCount * uncoveredLinesCount;
115
+
116
+ return 100 - Math.round(ratio);
117
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "escover",
3
- "version": "6.2.6",
3
+ "version": "6.2.7",
4
4
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
5
5
  "description": "Coverage for EcmaScript Modules",
6
6
  "main": "lib/escover.js",
@@ -1,249 +0,0 @@
1
- import process from 'node:process';
2
- import {
3
- table,
4
- getBorderCharacters,
5
- } from 'table';
6
- import chalk from 'chalk';
7
-
8
- const isNumber = (a) => !Number.isNaN(a) && typeof a === 'number';
9
- const CWD = process.cwd();
10
- const {entries, keys} = Object;
11
-
12
- const makeGreen = chalk.hex('#4caf50');
13
- const makeRed = chalk.hex('#f44336');
14
-
15
- export default (coverageFile, {skipFull = false} = {}) => {
16
- const files = parseCoverageFile(coverageFile, skipFull);
17
-
18
- if (skipFull && !files.length)
19
- return '💪 coverage 100% Good Job!\n';
20
-
21
- const totalWidth = process.stdout.columns || 80;
22
- const showPercent = totalWidth >= 70;
23
-
24
- const linesColWidth = 20;
25
- const percentColWidth = showPercent ? 7 : 0;
26
-
27
- // резерв на бордеры и padding + дополнительный запас
28
- const fileColWidth = Math.max(10, totalWidth - linesColWidth - percentColWidth - 6 - 4);
29
-
30
- const tableData = buildGroupedTable(files, showPercent, linesColWidth);
31
-
32
- const options = createTableOptions({
33
- showPercent,
34
- tableData,
35
- fileColWidth,
36
- percentColWidth,
37
- linesColWidth,
38
- });
39
-
40
- return table(tableData, options);
41
- };
42
-
43
- export function createTableOptions({showPercent, tableData, fileColWidth, percentColWidth, linesColWidth}) {
44
- const columns = [{
45
- paddingLeft: 1,
46
- paddingRight: 1,
47
- width: fileColWidth,
48
- wrapWord: false,
49
- }];
50
-
51
- if (showPercent)
52
- columns.push({
53
- alignment: 'center',
54
- paddingLeft: 1,
55
- paddingRight: 1,
56
- width: percentColWidth,
57
- });
58
-
59
- columns.push({
60
- paddingLeft: 1,
61
- paddingRight: 1,
62
- width: linesColWidth,
63
- wrapWord: false,
64
- });
65
-
66
- return {
67
- drawHorizontalLine: (i) => !i || i === 1 || i === tableData.length,
68
- columns,
69
- border: {
70
- ...getBorderCharacters('void'),
71
- topBody: '-',
72
- bottomBody: '-',
73
- joinBody: '-',
74
- topJoin: '|',
75
- bottomJoin: '|',
76
- joinJoin: '|',
77
- bodyJoin: '|',
78
- },
79
- };
80
- }
81
-
82
- export function groupByFolder(files) {
83
- const groups = new Map();
84
-
85
- for (const f of files) {
86
- const parts = f.filename.split('/');
87
- const folder = parts.length > 1 ? parts
88
- .slice(0, -1)
89
- .join('/') : '';
90
-
91
- const fileName = parts.at(-1);
92
-
93
- let group = groups.get(folder);
94
-
95
- if (!group) {
96
- group = {
97
- files: [],
98
- };
99
- groups.set(folder, group);
100
- }
101
-
102
- group.files.push({
103
- ...f,
104
- shortName: fileName,
105
- });
106
- }
107
-
108
- return groups;
109
- }
110
-
111
- export function parseCoverageFile(coverageFile, skipFull) {
112
- const files = [];
113
-
114
- for (const {name, lines} of coverageFile) {
115
- const uncovered = parseUncoveredLines(lines);
116
- const linesCount = keys(lines).length;
117
- const uncoveredLinesCount = uncovered.length;
118
- const percentLines = getLinesPercent(linesCount, uncoveredLinesCount);
119
- const covered = uncoveredLinesCount === 0;
120
-
121
- if (skipFull && covered)
122
- continue;
123
-
124
- files.push({
125
- filename: name.replace(`${CWD}/`, ''),
126
- covered,
127
- lines: uncovered,
128
- percentLines,
129
- });
130
- }
131
-
132
- return files;
133
- }
134
-
135
- export const getColor = (value) => value === 100 ? makeGreen : makeRed;
136
-
137
- export function buildGroupedTable(files, showPercent, linesColWidth) {
138
- const tableData = [];
139
-
140
- if (showPercent)
141
- tableData.push([
142
- 'File',
143
- '% Lines',
144
- 'Uncovered Lines #s',
145
- ]);
146
- else
147
- tableData.push([
148
- 'File',
149
- 'Uncovered Lines #s',
150
- ]);
151
-
152
- const groups = groupByFolder(files);
153
- const hideFolders = groups.size === 1;
154
-
155
- for (const [folder, group] of groups) {
156
- let sum = 0;
157
-
158
- for (const f of group.files)
159
- sum += f.percentLines;
160
-
161
- const coverage = Math.round(sum / group.files.length);
162
-
163
- if (!hideFolders) {
164
- const row = [];
165
- row.push(getColor(coverage)(folder));
166
-
167
- if (showPercent)
168
- row.push(getColor(coverage)(`${coverage}%`));
169
-
170
- row.push('');
171
- tableData.push(row);
172
- }
173
-
174
- for (const f of group.files) {
175
- const row = [];
176
- row.push(f.covered ? makeGreen(' ' + f.shortName) : makeRed(' ' + f.shortName));
177
-
178
- if (showPercent)
179
- row.push(f.percentLines === 100 ? makeGreen(`${f.percentLines}%`) : makeRed(`${f.percentLines}%`));
180
-
181
- row.push(f.covered ? '' : makeRed(formatLines(
182
- f.lines,
183
- linesColWidth,
184
- )));
185
- tableData.push(row);
186
- }
187
- }
188
-
189
- return tableData;
190
- }
191
-
192
- export function formatLines(array, maxLen = 20) {
193
- if (array.every(isNumber)) {
194
- const nums = array
195
- .slice()
196
- .sort((a, b) => a - b);
197
- const ranges = [];
198
- let [start] = nums;
199
- let [prev] = nums;
200
-
201
- for (let i = 1; i < nums.length; i++) {
202
- const n = nums[i];
203
-
204
- if (n === prev + 1) {
205
- prev = n;
206
- } else {
207
- ranges.push(start === prev ? String(start) : `${start}..${prev}`);
208
- start = n;
209
- prev = n;
210
- }
211
- }
212
-
213
- ranges.push(start === prev ? String(start) : `${start}..${prev}`);
214
-
215
- const joined = ranges.join(', ');
216
-
217
- if (joined.length <= maxLen)
218
- return joined;
219
-
220
- return ranges[0];
221
- }
222
-
223
- // если элементы строки — просто объединяем через ', ' и усечем
224
- const joined = array.join(', ');
225
-
226
- if (joined.length <= maxLen)
227
- return joined;
228
-
229
- return array[0];
230
- }
231
-
232
- export function parseUncoveredLines(lines) {
233
- const out = [];
234
-
235
- for (const [line, covered] of entries(lines))
236
- if (!covered)
237
- out.push(Number(line));
238
-
239
- return out;
240
- }
241
-
242
- export function getLinesPercent(linesCount, uncoveredLinesCount) {
243
- if (!linesCount)
244
- return 100;
245
-
246
- const ratio = 100 / linesCount * uncoveredLinesCount;
247
-
248
- return 100 - Math.round(ratio);
249
- }