gurkencheck 0.0.6 → 0.0.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/README.md +115 -3
- package/dist/exit-codes.d.ts +10 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +10 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -6
- package/dist/main.js.map +1 -1
- package/dist/stats/collect.d.ts +23 -0
- package/dist/stats/collect.d.ts.map +1 -0
- package/dist/stats/collect.js +219 -0
- package/dist/stats/collect.js.map +1 -0
- package/dist/stats/command.d.ts +3 -0
- package/dist/stats/command.d.ts.map +1 -0
- package/dist/stats/command.js +113 -0
- package/dist/stats/command.js.map +1 -0
- package/dist/stats/format/index.d.ts +25 -0
- package/dist/stats/format/index.d.ts.map +1 -0
- package/dist/stats/format/index.js +25 -0
- package/dist/stats/format/index.js.map +1 -0
- package/dist/stats/format/json.d.ts +10 -0
- package/dist/stats/format/json.d.ts.map +1 -0
- package/dist/stats/format/json.js +5 -0
- package/dist/stats/format/json.js.map +1 -0
- package/dist/stats/format/markdown.d.ts +8 -0
- package/dist/stats/format/markdown.d.ts.map +1 -0
- package/dist/stats/format/markdown.js +123 -0
- package/dist/stats/format/markdown.js.map +1 -0
- package/dist/stats/format/shared.d.ts +34 -0
- package/dist/stats/format/shared.d.ts.map +1 -0
- package/dist/stats/format/shared.js +37 -0
- package/dist/stats/format/shared.js.map +1 -0
- package/dist/stats/format/text.d.ts +5 -0
- package/dist/stats/format/text.d.ts.map +1 -0
- package/dist/stats/format/text.js +175 -0
- package/dist/stats/format/text.js.map +1 -0
- package/dist/stats/index.d.ts +21 -0
- package/dist/stats/index.d.ts.map +1 -0
- package/dist/stats/index.js +17 -0
- package/dist/stats/index.js.map +1 -0
- package/dist/stats/normalise.d.ts +38 -0
- package/dist/stats/normalise.d.ts.map +1 -0
- package/dist/stats/normalise.js +65 -0
- package/dist/stats/normalise.js.map +1 -0
- package/dist/stats/similar.d.ts +61 -0
- package/dist/stats/similar.d.ts.map +1 -0
- package/dist/stats/similar.js +173 -0
- package/dist/stats/similar.js.map +1 -0
- package/dist/stats/types.d.ts +159 -0
- package/dist/stats/types.d.ts.map +1 -0
- package/dist/stats/types.js +10 -0
- package/dist/stats/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `gurkencheck stats` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Statistics describe a suite rather than judge it, so this always exits 0
|
|
5
|
+
* once it has run: there is no threshold to fail, and nothing here should
|
|
6
|
+
* ever be the reason a build goes red. Only being unable to run at all -
|
|
7
|
+
* an option that makes no sense, a path that names nothing - fails.
|
|
8
|
+
*/
|
|
9
|
+
import { parseArgs } from 'node:util';
|
|
10
|
+
import { EXIT_OK, EXIT_USAGE } from '../exit-codes.js';
|
|
11
|
+
import { DEFAULT_IGNORE_FILE_NAME, findFeatureFiles } from '../feature-finder.js';
|
|
12
|
+
import { isKnownLanguage } from '../gherkin/dialects.js';
|
|
13
|
+
import { readAndParseFile } from '../gherkin/parse.js';
|
|
14
|
+
import * as logger from '../logger.js';
|
|
15
|
+
import { collectStatistics } from './collect.js';
|
|
16
|
+
import { DEFAULT_STATS_FORMAT, DEFAULT_TOP, STATS_FORMATTERS, getStatsFormatter, } from './format/index.js';
|
|
17
|
+
export function statsUsage() {
|
|
18
|
+
return [
|
|
19
|
+
'Usage: gurkencheck stats [options] <feature-files>',
|
|
20
|
+
'',
|
|
21
|
+
'Reports on the shape of your feature files: what is in them, how much of',
|
|
22
|
+
'the step vocabulary is shared, and where it has drifted apart.',
|
|
23
|
+
'',
|
|
24
|
+
'Options:',
|
|
25
|
+
` -f, --format <format> output format: ${Object.keys(STATS_FORMATTERS).join(', ')}`,
|
|
26
|
+
` (default: ${DEFAULT_STATS_FORMAT})`,
|
|
27
|
+
` -i, --ignore <globs> comma separated globs to skip, overriding ${DEFAULT_IGNORE_FILE_NAME}`,
|
|
28
|
+
' -l, --language <code> dialect for files with no "# language:" header',
|
|
29
|
+
` --top <n> how many entries each list shows (default: ${DEFAULT_TOP})`,
|
|
30
|
+
' -h, --help show this message',
|
|
31
|
+
'',
|
|
32
|
+
'With no files given, the working directory is searched recursively.',
|
|
33
|
+
'The report goes to stdout, and always exits 0: it describes the files',
|
|
34
|
+
'rather than judging them. Use the linter itself to fail a build.',
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
37
|
+
/** Reads and parses a file, turning a file that cannot be read into a result. */
|
|
38
|
+
async function read(file, language) {
|
|
39
|
+
try {
|
|
40
|
+
return await readAndParseFile(file, language);
|
|
41
|
+
}
|
|
42
|
+
catch (thrown) {
|
|
43
|
+
return {
|
|
44
|
+
file: { relativePath: file, lines: [] },
|
|
45
|
+
feature: undefined,
|
|
46
|
+
errors: [
|
|
47
|
+
{
|
|
48
|
+
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
49
|
+
rule: 'unexpected-error',
|
|
50
|
+
line: 0,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function runStats(argv) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = parseArgs({
|
|
60
|
+
args: [...argv],
|
|
61
|
+
options: {
|
|
62
|
+
format: { type: 'string', short: 'f' },
|
|
63
|
+
ignore: { type: 'string', short: 'i' },
|
|
64
|
+
language: { type: 'string', short: 'l' },
|
|
65
|
+
top: { type: 'string' },
|
|
66
|
+
help: { type: 'boolean', short: 'h' },
|
|
67
|
+
},
|
|
68
|
+
allowPositionals: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (thrown) {
|
|
72
|
+
logger.boldError(thrown instanceof Error ? thrown.message : String(thrown));
|
|
73
|
+
console.error(statsUsage());
|
|
74
|
+
return EXIT_USAGE;
|
|
75
|
+
}
|
|
76
|
+
const { values, positionals } = parsed;
|
|
77
|
+
if (values.help === true) {
|
|
78
|
+
console.log(statsUsage());
|
|
79
|
+
return EXIT_OK;
|
|
80
|
+
}
|
|
81
|
+
const formatter = getStatsFormatter(values.format);
|
|
82
|
+
if (formatter === undefined) {
|
|
83
|
+
logger.boldError(`Unsupported format "${values.format}". Use one of ${Object.keys(STATS_FORMATTERS).join(', ')}.`);
|
|
84
|
+
return EXIT_USAGE;
|
|
85
|
+
}
|
|
86
|
+
let top = DEFAULT_TOP;
|
|
87
|
+
if (values.top !== undefined) {
|
|
88
|
+
top = Number(values.top);
|
|
89
|
+
if (!Number.isInteger(top) || top < 1) {
|
|
90
|
+
logger.boldError(`--top needs a whole number of at least 1, not "${values.top}".`);
|
|
91
|
+
return EXIT_USAGE;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const language = values.language;
|
|
95
|
+
if (language !== undefined && !isKnownLanguage(language)) {
|
|
96
|
+
logger.boldError(`Unknown language "${language}". Use a Gherkin language code, such as "fr".`);
|
|
97
|
+
return EXIT_USAGE;
|
|
98
|
+
}
|
|
99
|
+
const ignore = values.ignore?.split(',').map((pattern) => pattern.trim());
|
|
100
|
+
const { files, invalidPatterns } = findFeatureFiles(positionals, ignore);
|
|
101
|
+
if (invalidPatterns.length > 0) {
|
|
102
|
+
for (const pattern of invalidPatterns) {
|
|
103
|
+
logger.boldError(`Invalid format of the feature file path/pattern: "${pattern}".`);
|
|
104
|
+
}
|
|
105
|
+
logger.error('To run the linter please specify an existing feature file, directory or glob.');
|
|
106
|
+
return EXIT_USAGE;
|
|
107
|
+
}
|
|
108
|
+
const results = await Promise.all(files.map((file) => read(file, language)));
|
|
109
|
+
const statistics = collectStatistics(results);
|
|
110
|
+
console.log(formatter(statistics, { top }));
|
|
111
|
+
return EXIT_OK;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/stats/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The formats `gurkencheck stats --format` accepts.
|
|
3
|
+
*/
|
|
4
|
+
import type { Statistics } from '../types.ts';
|
|
5
|
+
import { toJson } from './json.ts';
|
|
6
|
+
import { toMarkdown } from './markdown.ts';
|
|
7
|
+
import { toText } from './text.ts';
|
|
8
|
+
import type { StatsFormatOptions } from './shared.ts';
|
|
9
|
+
/** Turns a finished statistics run into the text of a report. */
|
|
10
|
+
export type StatsFormatter = (statistics: Statistics, options: StatsFormatOptions) => string;
|
|
11
|
+
export declare const DEFAULT_STATS_FORMAT = "text";
|
|
12
|
+
export declare const STATS_FORMATTERS: Record<string, StatsFormatter>;
|
|
13
|
+
/**
|
|
14
|
+
* The formatter for a format name, or `undefined` when there is no such format.
|
|
15
|
+
*
|
|
16
|
+
* Asking the record for a key it does not have is not enough: every object
|
|
17
|
+
* inherits `constructor`, `toString` and the rest from its prototype, so
|
|
18
|
+
* `--format constructor` would come back with a function and be run as if it
|
|
19
|
+
* were a formatter.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getStatsFormatter(format: string | undefined): StatsFormatter | undefined;
|
|
22
|
+
export { toJson, toMarkdown, toText };
|
|
23
|
+
export { DEFAULT_TOP } from './shared.ts';
|
|
24
|
+
export type { StatsFormatOptions } from './shared.ts';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stats/format/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,aAAa,CAAC;AAEpD,iEAAiE;AACjE,MAAM,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC;AAE7F,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAK3D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAGxF;AAED,OAAO,EAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAC,CAAC;AACpC,OAAO,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AACxC,YAAY,EAAC,kBAAkB,EAAC,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { toJson } from './json.js';
|
|
2
|
+
import { toMarkdown } from './markdown.js';
|
|
3
|
+
import { toText } from './text.js';
|
|
4
|
+
export const DEFAULT_STATS_FORMAT = 'text';
|
|
5
|
+
export const STATS_FORMATTERS = {
|
|
6
|
+
text: toText,
|
|
7
|
+
json: toJson,
|
|
8
|
+
md: toMarkdown,
|
|
9
|
+
markdown: toMarkdown,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The formatter for a format name, or `undefined` when there is no such format.
|
|
13
|
+
*
|
|
14
|
+
* Asking the record for a key it does not have is not enough: every object
|
|
15
|
+
* inherits `constructor`, `toString` and the rest from its prototype, so
|
|
16
|
+
* `--format constructor` would come back with a function and be run as if it
|
|
17
|
+
* were a formatter.
|
|
18
|
+
*/
|
|
19
|
+
export function getStatsFormatter(format) {
|
|
20
|
+
const name = format ?? DEFAULT_STATS_FORMAT;
|
|
21
|
+
return Object.hasOwn(STATS_FORMATTERS, name) ? STATS_FORMATTERS[name] : undefined;
|
|
22
|
+
}
|
|
23
|
+
export { toJson, toMarkdown, toText };
|
|
24
|
+
export { DEFAULT_TOP } from './shared.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/stats/format/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAMjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,EAAE,EAAE,UAAU;IACd,QAAQ,EAAE,UAAU;CACrB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,oBAAoB,CAAC;IAC5C,OAAO,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpF,CAAC;AAED,OAAO,EAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAC,CAAC;AACpC,OAAO,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report as JSON.
|
|
3
|
+
*
|
|
4
|
+
* Nothing is cut short here: this is the format to keep alongside a build, so
|
|
5
|
+
* that a later run has something to be compared against.
|
|
6
|
+
*/
|
|
7
|
+
import type { Statistics } from '../types.ts';
|
|
8
|
+
/** The whole report, indented so a diff of two runs is readable. */
|
|
9
|
+
export declare function toJson(statistics: Statistics): string;
|
|
10
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/stats/format/json.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,oEAAoE;AACpE,wBAAgB,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/stats/format/json.ts"],"names":[],"mappings":"AAQA,oEAAoE;AACpE,MAAM,UAAU,MAAM,CAAC,UAAsB;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report as Markdown, for pasting into a pull request or a wiki.
|
|
3
|
+
*/
|
|
4
|
+
import type { Statistics } from '../types.ts';
|
|
5
|
+
import type { StatsFormatOptions } from './shared.ts';
|
|
6
|
+
/** The report, as Markdown. */
|
|
7
|
+
export declare function toMarkdown(statistics: Statistics, options: StatsFormatOptions): string;
|
|
8
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/stats/format/markdown.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAA0B,UAAU,EAAC,MAAM,aAAa,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,aAAa,CAAC;AA2CpD,+BAA+B;AAC/B,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAmLtF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { andMore, head, location, percent, plural, summarise } from './shared.js';
|
|
2
|
+
/** A pipe inside a cell would start a column of its own. */
|
|
3
|
+
function cell(text) {
|
|
4
|
+
return text.replace(/\|/gu, '\\|');
|
|
5
|
+
}
|
|
6
|
+
/** ``` `text` ```, so step text keeps its quotes and angle brackets. */
|
|
7
|
+
function code(text) {
|
|
8
|
+
return `\`${cell(text)}\``;
|
|
9
|
+
}
|
|
10
|
+
function table(headings, alignments, rows) {
|
|
11
|
+
return [
|
|
12
|
+
`| ${headings.join(' | ')} |`,
|
|
13
|
+
`|${alignments.map((alignment) => (alignment === 'right' ? '---:' : '---')).join('|')}|`,
|
|
14
|
+
...rows.map((row) => `| ${row.join(' | ')} |`),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
function more(hidden, singular) {
|
|
18
|
+
return hidden === 0 ? [] : ['', `_${andMore(hidden, singular)}._`];
|
|
19
|
+
}
|
|
20
|
+
function stepRows(entries, counted = true) {
|
|
21
|
+
return entries.map((entry) => [
|
|
22
|
+
code(entry.text),
|
|
23
|
+
...(counted ? [String(entry.count)] : []),
|
|
24
|
+
cell(location(entry.file, entry.line)),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
function similarRows(groups) {
|
|
28
|
+
return groups.flatMap((group, index) => group.members.map((member, position) => [
|
|
29
|
+
position === 0 ? String(index + 1) : '',
|
|
30
|
+
code(member.text),
|
|
31
|
+
String(member.count),
|
|
32
|
+
cell(location(member.file, member.line)),
|
|
33
|
+
]));
|
|
34
|
+
}
|
|
35
|
+
/** The report, as Markdown. */
|
|
36
|
+
export function toMarkdown(statistics, options) {
|
|
37
|
+
const { files, inventory, scenarios, steps, tags, languages } = statistics;
|
|
38
|
+
if (files.total === 0) {
|
|
39
|
+
return '# Feature file statistics\n\nNo feature files found.';
|
|
40
|
+
}
|
|
41
|
+
const counted = files.unreadable.length === 0
|
|
42
|
+
? plural(files.parsed, 'file')
|
|
43
|
+
: `${files.parsed} of ${plural(files.total, 'file')}`;
|
|
44
|
+
const lines = [
|
|
45
|
+
'# Feature file statistics',
|
|
46
|
+
'',
|
|
47
|
+
[counted, plural(inventory.features, 'feature'), plural(scenarios.effective, 'test case')].join(', '),
|
|
48
|
+
];
|
|
49
|
+
lines.push('', '## Inventory', '', ...table(['', 'Count'], ['left', 'right'], [
|
|
50
|
+
['Features', String(inventory.features)],
|
|
51
|
+
['Rules', String(inventory.rules)],
|
|
52
|
+
['Backgrounds', String(inventory.backgrounds)],
|
|
53
|
+
['Scenarios', String(inventory.scenarios)],
|
|
54
|
+
['Scenario Outlines', String(inventory.scenarioOutlines)],
|
|
55
|
+
['Examples tables', String(inventory.examplesTables)],
|
|
56
|
+
['Examples rows', String(inventory.examplesRows)],
|
|
57
|
+
['Steps, as written', String(inventory.steps)],
|
|
58
|
+
['Data tables', String(inventory.dataTables)],
|
|
59
|
+
['Doc strings', String(inventory.docStrings)],
|
|
60
|
+
]));
|
|
61
|
+
lines.push('', '## Scenarios', '', `**${scenarios.effective}** test cases: one per Scenario, one per Examples row.`, '', `Steps per scenario, Background steps excluded: ${summarise(scenarios.stepsPerScenario)}`);
|
|
62
|
+
const largest = head(scenarios.largest, options.top);
|
|
63
|
+
if (largest.shown.length > 0) {
|
|
64
|
+
lines.push('', '### Longest scenarios', '', ...table(['Scenario', 'Steps', 'Where'], ['left', 'right', 'left'], largest.shown.map((scenario) => [
|
|
65
|
+
cell(scenario.name === '' ? '(unnamed)' : scenario.name),
|
|
66
|
+
String(scenario.steps),
|
|
67
|
+
cell(location(scenario.file, scenario.line)),
|
|
68
|
+
])), ...more(largest.hidden, 'scenario'));
|
|
69
|
+
}
|
|
70
|
+
const { given, when, then, other } = steps.keywords;
|
|
71
|
+
lines.push('', '## Steps', '', ...table(['', 'Count', 'Share'], ['left', 'right', 'right'], [
|
|
72
|
+
['Written, Background steps included', String(steps.total), ''],
|
|
73
|
+
['Distinct after normalisation', String(steps.unique), percent(steps.unique, steps.total)],
|
|
74
|
+
['Written exactly once', String(steps.usedOnce), percent(steps.usedOnce, steps.unique)],
|
|
75
|
+
['Given', String(given), percent(given, steps.total)],
|
|
76
|
+
['When', String(when), percent(when, steps.total)],
|
|
77
|
+
['Then', String(then), percent(then, steps.total)],
|
|
78
|
+
...(other === 0 ? [] : [['Unresolved And or But', String(other), percent(other, steps.total)]]),
|
|
79
|
+
]));
|
|
80
|
+
lines.push('', `Words per step: ${summarise(steps.wordsPerStep)}`);
|
|
81
|
+
const mostUsed = head(steps.vocabulary.filter((entry) => entry.count > 1), options.top);
|
|
82
|
+
if (mostUsed.shown.length > 0) {
|
|
83
|
+
lines.push('', '### Most used steps', '', ...table(['Step', 'Uses', 'First seen'], ['left', 'right', 'left'], stepRows(mostUsed.shown)), ...more(mostUsed.hidden, 'step'));
|
|
84
|
+
}
|
|
85
|
+
const once = head(steps.vocabulary.filter((entry) => entry.count === 1).sort((a, b) => (a.text < b.text ? -1 : 1)), options.top);
|
|
86
|
+
if (once.shown.length > 0) {
|
|
87
|
+
lines.push('', '### Steps written exactly once', '', ...table(['Step', 'Where'], ['left', 'left'], stepRows(once.shown, false)), ...more(once.hidden, 'step'));
|
|
88
|
+
}
|
|
89
|
+
const similar = head(steps.similar, options.top);
|
|
90
|
+
if (similar.shown.length > 0) {
|
|
91
|
+
lines.push('', '### Nearly the same step, written more than one way', '', ...table(['Group', 'Step', 'Uses', 'Where'], ['right', 'left', 'right', 'left'], similarRows(similar.shown)), ...more(similar.hidden, 'group'));
|
|
92
|
+
}
|
|
93
|
+
const untagged = inventory.scenarios + inventory.scenarioOutlines;
|
|
94
|
+
lines.push('', '## Tags', '', ...table(['', 'Count'], ['left', 'right'], [
|
|
95
|
+
['Written', String(tags.total)],
|
|
96
|
+
['Distinct', String(tags.unique)],
|
|
97
|
+
['Written exactly once', String(tags.usedOnce.length)],
|
|
98
|
+
[
|
|
99
|
+
'Scenarios with no tag, inherited or their own',
|
|
100
|
+
`${tags.untaggedScenarios} (${percent(tags.untaggedScenarios, untagged)})`,
|
|
101
|
+
],
|
|
102
|
+
]));
|
|
103
|
+
const tagsUsed = head(tags.vocabulary.filter((entry) => entry.count > 1), options.top);
|
|
104
|
+
if (tagsUsed.shown.length > 0) {
|
|
105
|
+
lines.push('', '### Most used tags', '', ...table(['Tag', 'Uses'], ['left', 'right'], tagsUsed.shown.map((tag) => [code(tag.name), String(tag.count)])), ...more(tagsUsed.hidden, 'tag'));
|
|
106
|
+
}
|
|
107
|
+
if (tags.usedOnce.length > 0) {
|
|
108
|
+
const tagsOnce = head(tags.usedOnce, options.top);
|
|
109
|
+
lines.push('', '### Tags written exactly once', '', tagsOnce.shown.map(code).join(', '), ...more(tagsOnce.hidden, 'tag'));
|
|
110
|
+
}
|
|
111
|
+
if (languages.length > 1) {
|
|
112
|
+
lines.push('', '## Languages', '', ...table(['Dialect', 'Files'], ['left', 'right'], languages.map((language) => [code(language.code), String(language.files)])));
|
|
113
|
+
}
|
|
114
|
+
if (files.unreadable.length > 0) {
|
|
115
|
+
const unreadable = head(files.unreadable, options.top);
|
|
116
|
+
lines.push('', `## Could not be read (${plural(files.unreadable.length, 'file')})`, '', 'Nothing in these files is counted above.', '', ...table(['File', 'Why'], ['left', 'left'], unreadable.shown.map((file) => [
|
|
117
|
+
cell(location(file.file, file.line)),
|
|
118
|
+
cell(file.reason),
|
|
119
|
+
])), ...more(unreadable.hidden, 'file'));
|
|
120
|
+
}
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/stats/format/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bits of formatting the reports have in common.
|
|
3
|
+
*/
|
|
4
|
+
import type { Distribution } from '../types.ts';
|
|
5
|
+
/** How many entries a list in a report shows before it is cut short. */
|
|
6
|
+
export declare const DEFAULT_TOP = 10;
|
|
7
|
+
/** What a report needs to know beyond the numbers themselves. */
|
|
8
|
+
export interface StatsFormatOptions {
|
|
9
|
+
/** How many entries each list shows. */
|
|
10
|
+
top: number;
|
|
11
|
+
}
|
|
12
|
+
/** `64%`, and `0%` rather than `NaN%` when there is nothing to divide. */
|
|
13
|
+
export declare function percent(part: number, whole: number): string;
|
|
14
|
+
/** A number with one decimal, and none at all when it is a round one. */
|
|
15
|
+
export declare function decimal(value: number): string;
|
|
16
|
+
/** `min 1 median 5 p90 11 max 27 mean 5.5` */
|
|
17
|
+
export declare function summarise(distribution: Distribution): string;
|
|
18
|
+
/** `features/login.feature:12` */
|
|
19
|
+
export declare function location(file: string, line: number): string;
|
|
20
|
+
/** `1 file` / `3 files` */
|
|
21
|
+
export declare function plural(count: number, singular: string, many?: string): string;
|
|
22
|
+
/** `… and 1 more scenario` / `… and 3 more scenarios` */
|
|
23
|
+
export declare function andMore(hidden: number, singular: string, many?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* The first `top` entries, and how many were left out.
|
|
26
|
+
*
|
|
27
|
+
* Every list in a report is the head of a longer one, and a reader who is not
|
|
28
|
+
* told that draws conclusions from a list they think is complete.
|
|
29
|
+
*/
|
|
30
|
+
export declare function head<T>(values: readonly T[], top: number): {
|
|
31
|
+
shown: T[];
|
|
32
|
+
hidden: number;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/stats/format/shared.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAE9C,wEAAwE;AACxE,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,kDAAkD;AAClD,wBAAgB,SAAS,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAG5D;AAED,kCAAkC;AAClC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,2BAA2B;AAC3B,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAiB,GAAG,MAAM,CAErF;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAiB,GAAG,MAAM,CAEvF;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG;IAAC,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAEvF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** How many entries a list in a report shows before it is cut short. */
|
|
2
|
+
export const DEFAULT_TOP = 10;
|
|
3
|
+
/** `64%`, and `0%` rather than `NaN%` when there is nothing to divide. */
|
|
4
|
+
export function percent(part, whole) {
|
|
5
|
+
return `${whole === 0 ? 0 : Math.round((part / whole) * 100)}%`;
|
|
6
|
+
}
|
|
7
|
+
/** A number with one decimal, and none at all when it is a round one. */
|
|
8
|
+
export function decimal(value) {
|
|
9
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
10
|
+
}
|
|
11
|
+
/** `min 1 median 5 p90 11 max 27 mean 5.5` */
|
|
12
|
+
export function summarise(distribution) {
|
|
13
|
+
const { min, median, p90, max, mean } = distribution;
|
|
14
|
+
return `min ${min} median ${median} p90 ${p90} max ${max} mean ${decimal(mean)}`;
|
|
15
|
+
}
|
|
16
|
+
/** `features/login.feature:12` */
|
|
17
|
+
export function location(file, line) {
|
|
18
|
+
return `${file}:${line}`;
|
|
19
|
+
}
|
|
20
|
+
/** `1 file` / `3 files` */
|
|
21
|
+
export function plural(count, singular, many = `${singular}s`) {
|
|
22
|
+
return `${count} ${count === 1 ? singular : many}`;
|
|
23
|
+
}
|
|
24
|
+
/** `… and 1 more scenario` / `… and 3 more scenarios` */
|
|
25
|
+
export function andMore(hidden, singular, many = `${singular}s`) {
|
|
26
|
+
return `… and ${hidden} more ${hidden === 1 ? singular : many}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The first `top` entries, and how many were left out.
|
|
30
|
+
*
|
|
31
|
+
* Every list in a report is the head of a longer one, and a reader who is not
|
|
32
|
+
* told that draws conclusions from a list they think is complete.
|
|
33
|
+
*/
|
|
34
|
+
export function head(values, top) {
|
|
35
|
+
return { shown: values.slice(0, top), hidden: Math.max(0, values.length - top) };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/stats/format/shared.ts"],"names":[],"mappings":"AAKA,wEAAwE;AACxE,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAQ9B,0EAA0E;AAC1E,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,KAAa;IACjD,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;AAClE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,SAAS,CAAC,YAA0B;IAClD,MAAM,EAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAC,GAAG,YAAY,CAAC;IACnD,OAAO,OAAO,GAAG,aAAa,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;AAC3F,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,IAAY;IACjD,OAAO,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,QAAgB,EAAE,IAAI,GAAG,GAAG,QAAQ,GAAG;IAC3E,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,QAAgB,EAAE,IAAI,GAAG,GAAG,QAAQ,GAAG;IAC7E,OAAO,SAAS,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAI,MAAoB,EAAE,GAAW;IACvD,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,EAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Statistics } from '../types.ts';
|
|
2
|
+
import type { StatsFormatOptions } from './shared.ts';
|
|
3
|
+
/** The report, as a block of text. */
|
|
4
|
+
export declare function toText(statistics: Statistics, options: StatsFormatOptions): string;
|
|
5
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/stats/format/text.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0B,UAAU,EAAC,MAAM,aAAa,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,aAAa,CAAC;AAuFpD,sCAAsC;AACtC,wBAAgB,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAqLlF"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report as something to read in a terminal.
|
|
3
|
+
*
|
|
4
|
+
* Every list here is the head of a longer one held in the JSON report, cut to
|
|
5
|
+
* `--top` entries and marked with how many were left out.
|
|
6
|
+
*/
|
|
7
|
+
import { style } from '../../logger.js';
|
|
8
|
+
import { andMore, head, location, percent, plural, summarise } from './shared.js';
|
|
9
|
+
/** Longer than this and a step is a paragraph; the report shows the start. */
|
|
10
|
+
const TEXT_WIDTH = 90;
|
|
11
|
+
function truncate(text, width = TEXT_WIDTH) {
|
|
12
|
+
return text.length <= width ? text : `${text.slice(0, width - 1)}…`;
|
|
13
|
+
}
|
|
14
|
+
/** Lays out a block so the numbers line up under each other. */
|
|
15
|
+
function block(entries) {
|
|
16
|
+
const labelWidth = entries.reduce((widest, row) => Math.max(widest, row.label.length), 0);
|
|
17
|
+
const valueWidth = entries.reduce((widest, row) => (row.wide === true ? widest : Math.max(widest, row.value.length)), 0);
|
|
18
|
+
return entries.map((row) => {
|
|
19
|
+
const value = row.wide === true ? row.value : row.value.padStart(valueWidth);
|
|
20
|
+
const line = ` ${row.label.padEnd(labelWidth)} ${value}`;
|
|
21
|
+
return row.note === undefined ? line : `${line} ${style.gray(row.note)}`;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** A list of `count text where` lines, with each column lined up. */
|
|
25
|
+
function listing(entries, indent = ' ') {
|
|
26
|
+
const countWidth = entries.reduce((widest, entry) => Math.max(widest, entry.count === undefined ? 0 : String(entry.count).length), 0);
|
|
27
|
+
const prefix = countWidth === 0 ? indent : `${indent}${' '.repeat(countWidth + 2)}`;
|
|
28
|
+
const textWidth = entries.reduce((widest, entry) => Math.max(widest, truncate(entry.text).length), 0);
|
|
29
|
+
return entries.map((entry) => {
|
|
30
|
+
const counted = entry.count === undefined
|
|
31
|
+
? `${prefix}${truncate(entry.text)}`
|
|
32
|
+
: `${indent}${String(entry.count).padStart(countWidth)} ${truncate(entry.text)}`;
|
|
33
|
+
if (entry.where === undefined) {
|
|
34
|
+
return counted;
|
|
35
|
+
}
|
|
36
|
+
return `${counted.padEnd(prefix.length + textWidth)} ${style.gray(entry.where)}`;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function more(hidden, singular) {
|
|
40
|
+
return hidden === 0 ? [] : [style.gray(` ${andMore(hidden, singular)}`)];
|
|
41
|
+
}
|
|
42
|
+
function stepLines(entries, counted = true) {
|
|
43
|
+
return listing(entries.map((entry) => ({
|
|
44
|
+
...(counted ? { count: entry.count } : {}),
|
|
45
|
+
text: entry.text,
|
|
46
|
+
where: location(entry.file, entry.line),
|
|
47
|
+
})));
|
|
48
|
+
}
|
|
49
|
+
function similarLines(groups) {
|
|
50
|
+
return groups.flatMap((group, index) => [...(index === 0 ? [] : ['']), ...stepLines(group.members)]);
|
|
51
|
+
}
|
|
52
|
+
/** The report, as a block of text. */
|
|
53
|
+
export function toText(statistics, options) {
|
|
54
|
+
const { files, inventory, scenarios, steps, tags, languages } = statistics;
|
|
55
|
+
if (files.total === 0) {
|
|
56
|
+
return 'No feature files found.';
|
|
57
|
+
}
|
|
58
|
+
const counted = files.unreadable.length === 0
|
|
59
|
+
? plural(files.parsed, 'file')
|
|
60
|
+
: `${files.parsed} of ${plural(files.total, 'file')}`;
|
|
61
|
+
const lines = [
|
|
62
|
+
[counted, plural(inventory.features, 'feature'), plural(scenarios.effective, 'test case')].join(', '),
|
|
63
|
+
];
|
|
64
|
+
lines.push('', 'Inventory', ...block([
|
|
65
|
+
{ label: 'Features', value: String(inventory.features) },
|
|
66
|
+
{ label: 'Rules', value: String(inventory.rules) },
|
|
67
|
+
{ label: 'Backgrounds', value: String(inventory.backgrounds) },
|
|
68
|
+
{ label: 'Scenarios', value: String(inventory.scenarios) },
|
|
69
|
+
{ label: 'Scenario Outlines', value: String(inventory.scenarioOutlines) },
|
|
70
|
+
{
|
|
71
|
+
label: 'Examples tables',
|
|
72
|
+
value: String(inventory.examplesTables),
|
|
73
|
+
note: plural(inventory.examplesRows, 'row'),
|
|
74
|
+
},
|
|
75
|
+
{ label: 'Steps', value: String(inventory.steps), note: 'as written' },
|
|
76
|
+
{ label: 'Data tables', value: String(inventory.dataTables) },
|
|
77
|
+
{ label: 'Doc strings', value: String(inventory.docStrings) },
|
|
78
|
+
]));
|
|
79
|
+
const largest = head(scenarios.largest, options.top);
|
|
80
|
+
lines.push('', 'Scenarios', ...block([
|
|
81
|
+
{
|
|
82
|
+
label: 'Test cases',
|
|
83
|
+
value: String(scenarios.effective),
|
|
84
|
+
note: 'one per Scenario, one per Examples row',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: 'Steps per scenario',
|
|
88
|
+
value: summarise(scenarios.stepsPerScenario),
|
|
89
|
+
note: 'Background steps excluded',
|
|
90
|
+
wide: true,
|
|
91
|
+
},
|
|
92
|
+
]));
|
|
93
|
+
if (largest.shown.length > 0) {
|
|
94
|
+
lines.push('', ' Longest', ...listing(largest.shown.map((scenario) => ({
|
|
95
|
+
count: scenario.steps,
|
|
96
|
+
text: scenario.name === '' ? '(unnamed)' : scenario.name,
|
|
97
|
+
where: location(scenario.file, scenario.line),
|
|
98
|
+
}))), ...more(largest.hidden, 'scenario'));
|
|
99
|
+
}
|
|
100
|
+
const { given, when, then, other } = steps.keywords;
|
|
101
|
+
const mix = [
|
|
102
|
+
`Given ${given} (${percent(given, steps.total)})`,
|
|
103
|
+
`When ${when} (${percent(when, steps.total)})`,
|
|
104
|
+
`Then ${then} (${percent(then, steps.total)})`,
|
|
105
|
+
...(other === 0 ? [] : [`unresolved ${other}`]),
|
|
106
|
+
].join(' ');
|
|
107
|
+
lines.push('', 'Steps', ...block([
|
|
108
|
+
{ label: 'Written', value: String(steps.total), note: 'Background steps included' },
|
|
109
|
+
{
|
|
110
|
+
label: 'Distinct',
|
|
111
|
+
value: String(steps.unique),
|
|
112
|
+
note: `${percent(steps.unique, steps.total)} of all steps - lower is more reuse`,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
label: 'Written once',
|
|
116
|
+
value: String(steps.usedOnce),
|
|
117
|
+
note: `${percent(steps.usedOnce, steps.unique)} of distinct steps`,
|
|
118
|
+
},
|
|
119
|
+
{ label: 'Words per step', value: summarise(steps.wordsPerStep), wide: true },
|
|
120
|
+
{
|
|
121
|
+
label: 'Keywords',
|
|
122
|
+
value: mix,
|
|
123
|
+
note: 'And and But resolved to what they follow',
|
|
124
|
+
wide: true,
|
|
125
|
+
},
|
|
126
|
+
]));
|
|
127
|
+
const mostUsed = head(steps.vocabulary.filter((entry) => entry.count > 1), options.top);
|
|
128
|
+
if (mostUsed.shown.length > 0) {
|
|
129
|
+
lines.push('', ' Most used', ...stepLines(mostUsed.shown), ...more(mostUsed.hidden, 'step'));
|
|
130
|
+
}
|
|
131
|
+
const once = head([...steps.vocabulary].filter((entry) => entry.count === 1).sort((a, b) => (a.text < b.text ? -1 : 1)), options.top);
|
|
132
|
+
if (once.shown.length > 0) {
|
|
133
|
+
lines.push('', ' Written once', ...stepLines(once.shown, false), ...more(once.hidden, 'step'));
|
|
134
|
+
}
|
|
135
|
+
const similar = head(steps.similar, options.top);
|
|
136
|
+
if (similar.shown.length > 0) {
|
|
137
|
+
lines.push('', ` Nearly the same (${plural(steps.similar.length, 'group')})`, ...similarLines(similar.shown), ...more(similar.hidden, 'group'));
|
|
138
|
+
}
|
|
139
|
+
const untagged = inventory.scenarios + inventory.scenarioOutlines;
|
|
140
|
+
lines.push('', 'Tags', ...block([
|
|
141
|
+
{ label: 'Written', value: String(tags.total) },
|
|
142
|
+
{ label: 'Distinct', value: String(tags.unique) },
|
|
143
|
+
{
|
|
144
|
+
label: 'Written once',
|
|
145
|
+
value: String(tags.usedOnce.length),
|
|
146
|
+
note: tags.usedOnce.length === 0 ? undefined : 'a tag written once is often a typo of one written often',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: 'Untagged scenarios',
|
|
150
|
+
value: String(tags.untaggedScenarios),
|
|
151
|
+
note: `${percent(tags.untaggedScenarios, untagged)} of scenarios, counting inherited tags`,
|
|
152
|
+
},
|
|
153
|
+
]));
|
|
154
|
+
const tagsUsed = head(tags.vocabulary.filter((entry) => entry.count > 1), options.top);
|
|
155
|
+
if (tagsUsed.shown.length > 0) {
|
|
156
|
+
lines.push('', ' Most used', ...listing(tagsUsed.shown.map((tag) => ({ count: tag.count, text: tag.name }))), ...more(tagsUsed.hidden, 'tag'));
|
|
157
|
+
}
|
|
158
|
+
const tagsOnce = head(tags.usedOnce, options.top);
|
|
159
|
+
if (tagsOnce.shown.length > 0) {
|
|
160
|
+
lines.push('', ' Written once', ` ${tagsOnce.shown.join(', ')}`, ...more(tagsOnce.hidden, 'tag'));
|
|
161
|
+
}
|
|
162
|
+
if (languages.length > 1) {
|
|
163
|
+
lines.push('', 'Languages', ...block(languages.map((language) => ({
|
|
164
|
+
label: language.code,
|
|
165
|
+
value: String(language.files),
|
|
166
|
+
note: language.files === 1 ? 'file' : 'files',
|
|
167
|
+
}))));
|
|
168
|
+
}
|
|
169
|
+
if (files.unreadable.length > 0) {
|
|
170
|
+
const unreadable = head(files.unreadable, options.top);
|
|
171
|
+
lines.push('', `Could not be read (${plural(files.unreadable.length, 'file')}, not counted above)`, ...unreadable.shown.map((file) => ` ${location(file.file, file.line)} ${style.gray(file.reason)}`), ...more(unreadable.hidden, 'file'));
|
|
172
|
+
}
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=text.js.map
|