gurkencheck 0.0.6 → 0.0.9

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.
Files changed (120) hide show
  1. package/README.md +134 -3
  2. package/dist/config-parser.d.ts.map +1 -1
  3. package/dist/config-parser.js +7 -1
  4. package/dist/config-parser.js.map +1 -1
  5. package/dist/diagnostics.d.ts +34 -0
  6. package/dist/diagnostics.d.ts.map +1 -0
  7. package/dist/diagnostics.js +53 -0
  8. package/dist/diagnostics.js.map +1 -0
  9. package/dist/exit-codes.d.ts +10 -0
  10. package/dist/exit-codes.d.ts.map +1 -0
  11. package/dist/exit-codes.js +10 -0
  12. package/dist/exit-codes.js.map +1 -0
  13. package/dist/feature-finder.d.ts +38 -0
  14. package/dist/feature-finder.d.ts.map +1 -1
  15. package/dist/feature-finder.js +86 -8
  16. package/dist/feature-finder.js.map +1 -1
  17. package/dist/formatters/index.d.ts +44 -4
  18. package/dist/formatters/index.d.ts.map +1 -1
  19. package/dist/formatters/index.js +52 -15
  20. package/dist/formatters/index.js.map +1 -1
  21. package/dist/formatters/stylish.d.ts +3 -0
  22. package/dist/formatters/stylish.d.ts.map +1 -1
  23. package/dist/formatters/stylish.js +32 -1
  24. package/dist/formatters/stylish.js.map +1 -1
  25. package/dist/formatters/tap.d.ts +14 -0
  26. package/dist/formatters/tap.d.ts.map +1 -1
  27. package/dist/formatters/tap.js +22 -7
  28. package/dist/formatters/tap.js.map +1 -1
  29. package/dist/gherkin/parse.d.ts +42 -1
  30. package/dist/gherkin/parse.d.ts.map +1 -1
  31. package/dist/gherkin/parse.js +68 -3
  32. package/dist/gherkin/parse.js.map +1 -1
  33. package/dist/index.d.ts +27 -9
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +15 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/linter.d.ts +31 -3
  38. package/dist/linter.d.ts.map +1 -1
  39. package/dist/linter.js +82 -15
  40. package/dist/linter.js.map +1 -1
  41. package/dist/logger.d.ts +2 -0
  42. package/dist/logger.d.ts.map +1 -1
  43. package/dist/logger.js +4 -0
  44. package/dist/logger.js.map +1 -1
  45. package/dist/main.d.ts +9 -1
  46. package/dist/main.d.ts.map +1 -1
  47. package/dist/main.js +158 -31
  48. package/dist/main.js.map +1 -1
  49. package/dist/rules/no-dupe-feature-names.d.ts.map +1 -1
  50. package/dist/rules/no-dupe-feature-names.js +26 -13
  51. package/dist/rules/no-dupe-feature-names.js.map +1 -1
  52. package/dist/rules/no-dupe-file-names.d.ts.map +1 -1
  53. package/dist/rules/no-dupe-file-names.js +32 -17
  54. package/dist/rules/no-dupe-file-names.js.map +1 -1
  55. package/dist/rules/no-dupe-scenario-names.d.ts.map +1 -1
  56. package/dist/rules/no-dupe-scenario-names.js +34 -22
  57. package/dist/rules/no-dupe-scenario-names.js.map +1 -1
  58. package/dist/rules.d.ts +41 -3
  59. package/dist/rules.d.ts.map +1 -1
  60. package/dist/rules.js +112 -4
  61. package/dist/rules.js.map +1 -1
  62. package/dist/stats/collect.d.ts +31 -0
  63. package/dist/stats/collect.d.ts.map +1 -0
  64. package/dist/stats/collect.js +249 -0
  65. package/dist/stats/collect.js.map +1 -0
  66. package/dist/stats/command.d.ts +4 -0
  67. package/dist/stats/command.d.ts.map +1 -0
  68. package/dist/stats/command.js +113 -0
  69. package/dist/stats/command.js.map +1 -0
  70. package/dist/stats/format/index.d.ts +25 -0
  71. package/dist/stats/format/index.d.ts.map +1 -0
  72. package/dist/stats/format/index.js +25 -0
  73. package/dist/stats/format/index.js.map +1 -0
  74. package/dist/stats/format/json.d.ts +10 -0
  75. package/dist/stats/format/json.d.ts.map +1 -0
  76. package/dist/stats/format/json.js +5 -0
  77. package/dist/stats/format/json.js.map +1 -0
  78. package/dist/stats/format/markdown.d.ts +8 -0
  79. package/dist/stats/format/markdown.d.ts.map +1 -0
  80. package/dist/stats/format/markdown.js +123 -0
  81. package/dist/stats/format/markdown.js.map +1 -0
  82. package/dist/stats/format/shared.d.ts +34 -0
  83. package/dist/stats/format/shared.d.ts.map +1 -0
  84. package/dist/stats/format/shared.js +37 -0
  85. package/dist/stats/format/shared.js.map +1 -0
  86. package/dist/stats/format/text.d.ts +5 -0
  87. package/dist/stats/format/text.d.ts.map +1 -0
  88. package/dist/stats/format/text.js +175 -0
  89. package/dist/stats/format/text.js.map +1 -0
  90. package/dist/stats/index.d.ts +21 -0
  91. package/dist/stats/index.d.ts.map +1 -0
  92. package/dist/stats/index.js +17 -0
  93. package/dist/stats/index.js.map +1 -0
  94. package/dist/stats/normalise.d.ts +38 -0
  95. package/dist/stats/normalise.d.ts.map +1 -0
  96. package/dist/stats/normalise.js +65 -0
  97. package/dist/stats/normalise.js.map +1 -0
  98. package/dist/stats/similar.d.ts +61 -0
  99. package/dist/stats/similar.d.ts.map +1 -0
  100. package/dist/stats/similar.js +173 -0
  101. package/dist/stats/similar.js.map +1 -0
  102. package/dist/stats/types.d.ts +159 -0
  103. package/dist/stats/types.d.ts.map +1 -0
  104. package/dist/stats/types.js +10 -0
  105. package/dist/stats/types.js.map +1 -0
  106. package/dist/types.d.ts +47 -3
  107. package/dist/types.d.ts.map +1 -1
  108. package/dist/util/glob.d.ts +18 -0
  109. package/dist/util/glob.d.ts.map +1 -1
  110. package/dist/util/glob.js +126 -22
  111. package/dist/util/glob.js.map +1 -1
  112. package/dist/util/stream.d.ts +31 -0
  113. package/dist/util/stream.d.ts.map +1 -0
  114. package/dist/util/stream.js +67 -0
  115. package/dist/util/stream.js.map +1 -0
  116. package/dist/watch.d.ts +36 -0
  117. package/dist/watch.d.ts.map +1 -0
  118. package/dist/watch.js +203 -0
  119. package/dist/watch.js.map +1 -0
  120. package/package.json +1 -1
@@ -0,0 +1,249 @@
1
+ import { getNeutralKeyword, resolvedStepKeywords } from '../gherkin/keywords.js';
2
+ import { backgroundsOf, rulesOf, scenariosOf, stepContainersOf, taggedNodesOf, } from '../gherkin/traverse.js';
3
+ import { countWords, normaliseStepText } from './normalise.js';
4
+ import { DEFAULT_SIMILARITY, groupSimilar } from './similar.js';
5
+ /**
6
+ * A summary of a set of counts.
7
+ *
8
+ * The quantiles are nearest-rank: the median of an even set is the lower of
9
+ * the two middle values rather than the average of them, so every number in
10
+ * the summary is a number some scenario actually has.
11
+ */
12
+ export function distribution(values) {
13
+ if (values.length === 0) {
14
+ return { count: 0, min: 0, median: 0, p90: 0, max: 0, mean: 0 };
15
+ }
16
+ const sorted = [...values].sort((a, b) => a - b);
17
+ const rank = (quantile) => sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(quantile * sorted.length) - 1))] ?? 0;
18
+ const sum = sorted.reduce((total, value) => total + value, 0);
19
+ return {
20
+ count: sorted.length,
21
+ min: sorted[0] ?? 0,
22
+ median: rank(0.5),
23
+ p90: rank(0.9),
24
+ max: sorted[sorted.length - 1] ?? 0,
25
+ mean: sum / sorted.length,
26
+ };
27
+ }
28
+ /** True when the node is a Scenario Outline rather than a plain Scenario. */
29
+ function isOutline(scenario, language) {
30
+ return getNeutralKeyword(scenario, language) === 'scenariooutline';
31
+ }
32
+ /**
33
+ * How many test cases a scenario stands for: one, or one per row of its
34
+ * Examples tables. This is how `max-scenarios-per-file` counts too, so the
35
+ * two never disagree about the size of a suite.
36
+ */
37
+ function testCases(scenario) {
38
+ if (scenario.examples.length === 0) {
39
+ return 1;
40
+ }
41
+ return scenario.examples.reduce((total, examples) => total + examples.tableBody.length, 0);
42
+ }
43
+ function tallyOne(counts, key) {
44
+ counts.set(key, (counts.get(key) ?? 0) + 1);
45
+ }
46
+ /** Most used first, and alphabetically within an equal count. */
47
+ function byCountThenName(name) {
48
+ return (a, b) => {
49
+ if (a.count !== b.count) {
50
+ return b.count - a.count;
51
+ }
52
+ const left = name(a);
53
+ const right = name(b);
54
+ return left < right ? -1 : left > right ? 1 : 0;
55
+ };
56
+ }
57
+ /** Counts everything in one already parsed feature file. */
58
+ function collectFile(feature, file, into) {
59
+ const language = feature.language;
60
+ const { inventory } = into;
61
+ inventory.features += 1;
62
+ inventory.rules += [...rulesOf(feature)].length;
63
+ inventory.backgrounds += [...backgroundsOf(feature)].length;
64
+ for (const { scenario, rule } of scenariosOf(feature)) {
65
+ if (isOutline(scenario, language)) {
66
+ inventory.scenarioOutlines += 1;
67
+ }
68
+ else {
69
+ inventory.scenarios += 1;
70
+ }
71
+ for (const examples of scenario.examples) {
72
+ inventory.examplesTables += 1;
73
+ inventory.examplesRows += examples.tableBody.length;
74
+ }
75
+ into.effective += testCases(scenario);
76
+ into.stepsPerScenario.push(scenario.steps.length);
77
+ into.scenarios.push({
78
+ name: scenario.name,
79
+ file,
80
+ line: scenario.location.line,
81
+ steps: scenario.steps.length,
82
+ });
83
+ // A scenario with no tag anywhere on it and none to inherit cannot be
84
+ // picked out by any tag expression, however carefully the run is
85
+ // filtered. A tag on an Examples table counts: Cucumber puts it on every
86
+ // test case that table generates, so the scenario is reachable after all.
87
+ const selectable = scenario.tags.length > 0 ||
88
+ feature.tags.length > 0 ||
89
+ (rule?.tags.length ?? 0) > 0 ||
90
+ scenario.examples.some((examples) => examples.tags.length > 0);
91
+ if (!selectable) {
92
+ into.untagged += 1;
93
+ }
94
+ }
95
+ for (const { node } of stepContainersOf(feature)) {
96
+ const keywords = resolvedStepKeywords(node.steps, language);
97
+ node.steps.forEach((step, index) => {
98
+ inventory.steps += 1;
99
+ if (step.dataTable !== undefined) {
100
+ inventory.dataTables += 1;
101
+ }
102
+ if (step.docString !== undefined) {
103
+ inventory.docStrings += 1;
104
+ }
105
+ into.stepWords.push(countWords(step.text));
106
+ const keyword = keywords[index];
107
+ if (keyword === 'given' || keyword === 'when' || keyword === 'then') {
108
+ into.keywords[keyword] += 1;
109
+ }
110
+ else {
111
+ into.keywords.other += 1;
112
+ }
113
+ const text = normaliseStepText(step.text);
114
+ const seen = into.steps.get(text);
115
+ if (seen === undefined) {
116
+ into.steps.set(text, { count: 1, example: step.text.trim(), file, line: step.location.line });
117
+ }
118
+ else {
119
+ seen.count += 1;
120
+ }
121
+ });
122
+ }
123
+ for (const { node } of taggedNodesOf(feature)) {
124
+ for (const tag of node.tags) {
125
+ tallyOne(into.tags, tag.name);
126
+ }
127
+ }
128
+ }
129
+ function newTally() {
130
+ const inventory = {
131
+ features: 0,
132
+ rules: 0,
133
+ backgrounds: 0,
134
+ scenarios: 0,
135
+ scenarioOutlines: 0,
136
+ examplesTables: 0,
137
+ examplesRows: 0,
138
+ steps: 0,
139
+ dataTables: 0,
140
+ docStrings: 0,
141
+ };
142
+ return {
143
+ into: {
144
+ inventory,
145
+ steps: new Map(),
146
+ keywords: { given: 0, when: 0, then: 0, other: 0 },
147
+ stepWords: [],
148
+ stepsPerScenario: [],
149
+ scenarios: [],
150
+ tags: new Map(),
151
+ effective: 0,
152
+ untagged: 0,
153
+ },
154
+ unreadable: [],
155
+ languages: new Map(),
156
+ readable: 0,
157
+ total: 0,
158
+ };
159
+ }
160
+ /** Counts one file into the tally. */
161
+ function absorb(tally, result) {
162
+ tally.total += 1;
163
+ const file = result.file.relativePath;
164
+ const failure = result.errors[0];
165
+ if (failure !== undefined) {
166
+ tally.unreadable.push({ file, reason: failure.message, line: failure.line });
167
+ return;
168
+ }
169
+ tally.readable += 1;
170
+ // An empty file parses without complaint and holds nothing to count.
171
+ if (result.feature === undefined) {
172
+ return;
173
+ }
174
+ tallyOne(tally.languages, result.feature.language);
175
+ collectFile(result.feature, file, tally.into);
176
+ }
177
+ /** Turns the running totals into the report. */
178
+ function summarise(tally, options) {
179
+ const { into, unreadable, languages } = tally;
180
+ const { inventory } = into;
181
+ const vocabulary = [...into.steps]
182
+ .map(([text, sighting]) => ({ text, ...sighting }))
183
+ .sort(byCountThenName((entry) => entry.text));
184
+ const similar = groupSimilar(vocabulary, options.similarity ?? DEFAULT_SIMILARITY)
185
+ .map((members) => ({
186
+ members,
187
+ total: members.reduce((sum, member) => sum + member.count, 0),
188
+ }))
189
+ .sort((a, b) => b.total - a.total || b.members.length - a.members.length);
190
+ const tagVocabulary = [...into.tags]
191
+ .map(([name, count]) => ({ name, count }))
192
+ .sort(byCountThenName((entry) => entry.name));
193
+ return {
194
+ files: { total: tally.total, parsed: tally.readable, unreadable },
195
+ inventory,
196
+ scenarios: {
197
+ effective: into.effective,
198
+ stepsPerScenario: distribution(into.stepsPerScenario),
199
+ largest: [...into.scenarios].sort((a, b) => b.steps - a.steps),
200
+ },
201
+ steps: {
202
+ total: inventory.steps,
203
+ unique: vocabulary.length,
204
+ uniqueRatio: inventory.steps === 0 ? 0 : vocabulary.length / inventory.steps,
205
+ usedOnce: vocabulary.filter((entry) => entry.count === 1).length,
206
+ vocabulary,
207
+ similar,
208
+ keywords: into.keywords,
209
+ wordsPerStep: distribution(into.stepWords),
210
+ },
211
+ tags: {
212
+ total: tagVocabulary.reduce((sum, entry) => sum + entry.count, 0),
213
+ unique: tagVocabulary.length,
214
+ vocabulary: tagVocabulary,
215
+ usedOnce: tagVocabulary.filter((entry) => entry.count === 1).map((entry) => entry.name),
216
+ untaggedScenarios: into.untagged,
217
+ },
218
+ languages: [...languages]
219
+ .map(([code, files]) => ({ code, files }))
220
+ .sort((a, b) => b.files - a.files || (a.code < b.code ? -1 : 1)),
221
+ };
222
+ }
223
+ /**
224
+ * Counts a set of parsed feature files.
225
+ *
226
+ * Files the parser refused are listed rather than counted: half a broken file
227
+ * is worse than none of it, because the numbers would quietly be wrong.
228
+ */
229
+ export function collectStatistics(parsed, options = {}) {
230
+ const tally = newTally();
231
+ for (const result of parsed) {
232
+ absorb(tally, result);
233
+ }
234
+ return summarise(tally, options);
235
+ }
236
+ /**
237
+ * The same count, over files arriving one at a time.
238
+ *
239
+ * Nothing here holds on to a parsed document once it has been counted, so a
240
+ * suite of any size costs the same as the report it produces.
241
+ */
242
+ export async function collectStatisticsFrom(parsed, options = {}) {
243
+ const tally = newTally();
244
+ for await (const result of parsed) {
245
+ absorb(tally, result);
246
+ }
247
+ return summarise(tally, options);
248
+ }
249
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/stats/collect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Diagnostics } from '../diagnostics.ts';
2
+ export declare function statsUsage(): string;
3
+ export declare function runStats(argv: readonly string[], diagnostics?: Diagnostics): Promise<number>;
4
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/stats/command.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AASnD,wBAAgB,UAAU,IAAI,MAAM,CAmBnC;AAED,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,WAAW,GAAE,WAAoB,GAChC,OAAO,CAAC,MAAM,CAAC,CAmFjB"}
@@ -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, findFeatureFileStream } from '../feature-finder.js';
12
+ import { isKnownLanguage } from '../gherkin/dialects.js';
13
+ import { readAndParseFiles } from '../gherkin/parse.js';
14
+ import { SILENT } from '../diagnostics.js';
15
+ import { collectStatisticsFrom } 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
+ export async function runStats(argv, diagnostics = SILENT) {
38
+ let parsed;
39
+ try {
40
+ parsed = parseArgs({
41
+ args: [...argv],
42
+ options: {
43
+ format: { type: 'string', short: 'f' },
44
+ ignore: { type: 'string', short: 'i' },
45
+ language: { type: 'string', short: 'l' },
46
+ top: { type: 'string' },
47
+ help: { type: 'boolean', short: 'h' },
48
+ },
49
+ allowPositionals: true,
50
+ });
51
+ }
52
+ catch (thrown) {
53
+ diagnostics.report({
54
+ level: 'error',
55
+ message: thrown instanceof Error ? thrown.message : String(thrown),
56
+ });
57
+ diagnostics.report({ level: 'notice', message: statsUsage() });
58
+ return EXIT_USAGE;
59
+ }
60
+ const { values, positionals } = parsed;
61
+ if (values.help === true) {
62
+ console.log(statsUsage());
63
+ return EXIT_OK;
64
+ }
65
+ const formatter = getStatsFormatter(values.format);
66
+ if (formatter === undefined) {
67
+ diagnostics.report({
68
+ level: 'error',
69
+ message: `Unsupported format "${values.format}". Use one of ${Object.keys(STATS_FORMATTERS).join(', ')}.`,
70
+ });
71
+ return EXIT_USAGE;
72
+ }
73
+ let top = DEFAULT_TOP;
74
+ if (values.top !== undefined) {
75
+ top = Number(values.top);
76
+ if (!Number.isInteger(top) || top < 1) {
77
+ diagnostics.report({
78
+ level: 'error',
79
+ message: `--top needs a whole number of at least 1, not "${values.top}".`,
80
+ });
81
+ return EXIT_USAGE;
82
+ }
83
+ }
84
+ const language = values.language;
85
+ if (language !== undefined && !isKnownLanguage(language)) {
86
+ diagnostics.report({
87
+ level: 'error',
88
+ message: `Unknown language "${language}". Use a Gherkin language code, such as "fr".`,
89
+ });
90
+ return EXIT_USAGE;
91
+ }
92
+ const ignore = values.ignore?.split(',').map((pattern) => pattern.trim());
93
+ const { files, invalidPatterns } = findFeatureFileStream(positionals, ignore);
94
+ if (invalidPatterns.length > 0) {
95
+ for (const pattern of invalidPatterns) {
96
+ diagnostics.report({
97
+ level: 'error',
98
+ message: `Invalid format of the feature file path/pattern: "${pattern}".`,
99
+ });
100
+ }
101
+ diagnostics.report({
102
+ level: 'detail',
103
+ message: 'To run the linter please specify an existing feature file, directory or glob.',
104
+ });
105
+ return EXIT_USAGE;
106
+ }
107
+ // Counted as they arrive, so a suite of any size costs what the report
108
+ // costs rather than what the suite does.
109
+ const statistics = await collectStatisticsFrom(readAndParseFiles(files, { language }));
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,5 @@
1
+ /** The whole report, indented so a diff of two runs is readable. */
2
+ export function toJson(statistics) {
3
+ return JSON.stringify(statistics, null, 2);
4
+ }
5
+ //# sourceMappingURL=json.js.map
@@ -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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"}