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.
- package/README.md +134 -3
- package/dist/config-parser.d.ts.map +1 -1
- package/dist/config-parser.js +7 -1
- package/dist/config-parser.js.map +1 -1
- package/dist/diagnostics.d.ts +34 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- 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/feature-finder.d.ts +38 -0
- package/dist/feature-finder.d.ts.map +1 -1
- package/dist/feature-finder.js +86 -8
- package/dist/feature-finder.js.map +1 -1
- package/dist/formatters/index.d.ts +44 -4
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +52 -15
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/stylish.d.ts +3 -0
- package/dist/formatters/stylish.d.ts.map +1 -1
- package/dist/formatters/stylish.js +32 -1
- package/dist/formatters/stylish.js.map +1 -1
- package/dist/formatters/tap.d.ts +14 -0
- package/dist/formatters/tap.d.ts.map +1 -1
- package/dist/formatters/tap.js +22 -7
- package/dist/formatters/tap.js.map +1 -1
- package/dist/gherkin/parse.d.ts +42 -1
- package/dist/gherkin/parse.d.ts.map +1 -1
- package/dist/gherkin/parse.js +68 -3
- package/dist/gherkin/parse.js.map +1 -1
- package/dist/index.d.ts +27 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/linter.d.ts +31 -3
- package/dist/linter.d.ts.map +1 -1
- package/dist/linter.js +82 -15
- package/dist/linter.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/main.d.ts +9 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +158 -31
- package/dist/main.js.map +1 -1
- package/dist/rules/no-dupe-feature-names.d.ts.map +1 -1
- package/dist/rules/no-dupe-feature-names.js +26 -13
- package/dist/rules/no-dupe-feature-names.js.map +1 -1
- package/dist/rules/no-dupe-file-names.d.ts.map +1 -1
- package/dist/rules/no-dupe-file-names.js +32 -17
- package/dist/rules/no-dupe-file-names.js.map +1 -1
- package/dist/rules/no-dupe-scenario-names.d.ts.map +1 -1
- package/dist/rules/no-dupe-scenario-names.js +34 -22
- package/dist/rules/no-dupe-scenario-names.js.map +1 -1
- package/dist/rules.d.ts +41 -3
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +112 -4
- package/dist/rules.js.map +1 -1
- package/dist/stats/collect.d.ts +31 -0
- package/dist/stats/collect.d.ts.map +1 -0
- package/dist/stats/collect.js +249 -0
- package/dist/stats/collect.js.map +1 -0
- package/dist/stats/command.d.ts +4 -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/dist/types.d.ts +47 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/util/glob.d.ts +18 -0
- package/dist/util/glob.d.ts.map +1 -1
- package/dist/util/glob.js +126 -22
- package/dist/util/glob.js.map +1 -1
- package/dist/util/stream.d.ts +31 -0
- package/dist/util/stream.d.ts.map +1 -0
- package/dist/util/stream.js +67 -0
- package/dist/util/stream.js.map +1 -0
- package/dist/watch.d.ts +36 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +203 -0
- package/dist/watch.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding steps that are nearly, but not quite, the same step.
|
|
3
|
+
*
|
|
4
|
+
* `I am logged in` and `I'm logged in` are one behaviour written twice, and
|
|
5
|
+
* they cost two step definitions. Normalisation cannot join them - they
|
|
6
|
+
* really are different sentences - so they are grouped by how many single
|
|
7
|
+
* character edits it takes to turn one into the other.
|
|
8
|
+
*
|
|
9
|
+
* Edit distance is quadratic in the number of distinct steps, which on a
|
|
10
|
+
* large suite is millions of comparisons, so two cheap tests run first. Both
|
|
11
|
+
* are lower bounds on the distance, never overestimates, so a pair they
|
|
12
|
+
* reject could not have been close enough anyway:
|
|
13
|
+
*
|
|
14
|
+
* - two strings are at least as far apart as their lengths differ;
|
|
15
|
+
* - every edit changes the tally of characters by at most two, so half the
|
|
16
|
+
* difference between two tallies is a floor on the distance as well.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_SIMILARITY = { ratio: 0.85, minLength: 8, maxEdits: 3 };
|
|
19
|
+
/** How many buckets the character tally uses. */
|
|
20
|
+
const TALLY_SIZE = 32;
|
|
21
|
+
/**
|
|
22
|
+
* A rough tally of the characters in a string.
|
|
23
|
+
*
|
|
24
|
+
* Characters share buckets, which can only make two tallies look more alike
|
|
25
|
+
* than they are - and since the tally is used to rule pairs out, looking more
|
|
26
|
+
* alike is the safe direction to be wrong in.
|
|
27
|
+
*/
|
|
28
|
+
function tally(text) {
|
|
29
|
+
const counts = new Array(TALLY_SIZE).fill(0);
|
|
30
|
+
for (let index = 0; index < text.length; index++) {
|
|
31
|
+
const bucket = text.charCodeAt(index) % TALLY_SIZE;
|
|
32
|
+
counts[bucket] = (counts[bucket] ?? 0) + 1;
|
|
33
|
+
}
|
|
34
|
+
return counts;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when two tallies are far enough apart that no run of `limit` edits
|
|
38
|
+
* could join the strings behind them.
|
|
39
|
+
*
|
|
40
|
+
* This is the test almost every pair fails, so it stops as soon as the answer
|
|
41
|
+
* is settled rather than finishing the count.
|
|
42
|
+
*/
|
|
43
|
+
function tallyRulesOut(left, right, limit) {
|
|
44
|
+
const budget = limit * 2;
|
|
45
|
+
let sum = 0;
|
|
46
|
+
for (let bucket = 0; bucket < TALLY_SIZE; bucket++) {
|
|
47
|
+
sum += Math.abs((left[bucket] ?? 0) - (right[bucket] ?? 0));
|
|
48
|
+
if (sum > budget) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The Levenshtein distance between two strings, giving up as soon as it is
|
|
56
|
+
* certain to exceed `limit` and returning `limit + 1` in that case.
|
|
57
|
+
*
|
|
58
|
+
* Every value in a row of the table is a lower bound on everything below it,
|
|
59
|
+
* so once the whole row is past the limit there is no way back.
|
|
60
|
+
*/
|
|
61
|
+
export function boundedEditDistance(left, right, limit) {
|
|
62
|
+
if (left === right) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
if (Math.abs(left.length - right.length) > limit) {
|
|
66
|
+
return limit + 1;
|
|
67
|
+
}
|
|
68
|
+
const width = right.length;
|
|
69
|
+
let previous = Array.from({ length: width + 1 }, (_, index) => index);
|
|
70
|
+
let current = new Array(width + 1).fill(0);
|
|
71
|
+
for (let row = 1; row <= left.length; row++) {
|
|
72
|
+
current[0] = row;
|
|
73
|
+
let rowMinimum = row;
|
|
74
|
+
const leftCharacter = left.charCodeAt(row - 1);
|
|
75
|
+
for (let column = 1; column <= width; column++) {
|
|
76
|
+
const substitution = (previous[column - 1] ?? 0) + (leftCharacter === right.charCodeAt(column - 1) ? 0 : 1);
|
|
77
|
+
const deletion = (previous[column] ?? 0) + 1;
|
|
78
|
+
const insertion = (current[column - 1] ?? 0) + 1;
|
|
79
|
+
const best = Math.min(substitution, deletion, insertion);
|
|
80
|
+
current[column] = best;
|
|
81
|
+
if (best < rowMinimum) {
|
|
82
|
+
rowMinimum = best;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (rowMinimum > limit) {
|
|
86
|
+
return limit + 1;
|
|
87
|
+
}
|
|
88
|
+
const finished = previous;
|
|
89
|
+
previous = current;
|
|
90
|
+
current = finished;
|
|
91
|
+
}
|
|
92
|
+
return previous[width] ?? limit + 1;
|
|
93
|
+
}
|
|
94
|
+
/** Disjoint sets, used to join pairs of close steps into groups. */
|
|
95
|
+
function makeSets(size) {
|
|
96
|
+
const parent = Array.from({ length: size }, (_, index) => index);
|
|
97
|
+
function find(item) {
|
|
98
|
+
let root = item;
|
|
99
|
+
while ((parent[root] ?? root) !== root) {
|
|
100
|
+
root = parent[root] ?? root;
|
|
101
|
+
}
|
|
102
|
+
let walk = item;
|
|
103
|
+
while ((parent[walk] ?? walk) !== root) {
|
|
104
|
+
const next = parent[walk] ?? walk;
|
|
105
|
+
parent[walk] = root;
|
|
106
|
+
walk = next;
|
|
107
|
+
}
|
|
108
|
+
return root;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
find,
|
|
112
|
+
union(a, b) {
|
|
113
|
+
const rootA = find(a);
|
|
114
|
+
const rootB = find(b);
|
|
115
|
+
if (rootA !== rootB) {
|
|
116
|
+
parent[rootB] = rootA;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Groups entries whose text is close enough to be the same step written two
|
|
123
|
+
* ways. Entries that are close to nothing are left out, so every group
|
|
124
|
+
* returned has at least two members, in the order they were given in.
|
|
125
|
+
*/
|
|
126
|
+
export function groupSimilar(entries, options = DEFAULT_SIMILARITY) {
|
|
127
|
+
// Sorting by length lets the search stop early: once two steps are too
|
|
128
|
+
// different in length, every step after this one is longer still.
|
|
129
|
+
const candidates = entries
|
|
130
|
+
.map((entry, index) => ({ index, text: entry.text, tally: tally(entry.text) }))
|
|
131
|
+
.filter((candidate) => candidate.text.length >= options.minLength)
|
|
132
|
+
.sort((a, b) => a.text.length - b.text.length);
|
|
133
|
+
const sets = makeSets(entries.length);
|
|
134
|
+
let joined = false;
|
|
135
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
136
|
+
const left = candidates[i];
|
|
137
|
+
for (let j = i + 1; j < candidates.length; j++) {
|
|
138
|
+
const right = candidates[j];
|
|
139
|
+
const limit = Math.min(options.maxEdits, Math.floor((1 - options.ratio) * right.text.length));
|
|
140
|
+
// Sorted by length, so once a step is too much longer than this one,
|
|
141
|
+
// every step after it is longer still.
|
|
142
|
+
if (right.text.length - left.text.length > limit) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if (limit < 1 || tallyRulesOut(left.tally, right.tally, limit)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (boundedEditDistance(left.text, right.text, limit) <= limit) {
|
|
149
|
+
sets.union(left.index, right.index);
|
|
150
|
+
joined = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!joined) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
const groups = new Map();
|
|
158
|
+
for (const candidate of candidates) {
|
|
159
|
+
const root = sets.find(candidate.index);
|
|
160
|
+
const member = { index: candidate.index, entry: entries[candidate.index] };
|
|
161
|
+
const group = groups.get(root);
|
|
162
|
+
if (group === undefined) {
|
|
163
|
+
groups.set(root, [member]);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
group.push(member);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return [...groups.values()]
|
|
170
|
+
.filter((group) => group.length > 1)
|
|
171
|
+
.map((group) => group.sort((a, b) => a.index - b.index).map((member) => member.entry));
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=similar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similar.js","sourceRoot":"","sources":["../../src/stats/similar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a statistics run.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is complete: no list is cut short, no number is rounded to
|
|
5
|
+
* fit a screen. Trimming is the formatters' job, so that the JSON output is a
|
|
6
|
+
* full dataset - one a later run can be compared against - while the text
|
|
7
|
+
* report stays short enough to read.
|
|
8
|
+
*/
|
|
9
|
+
/** A summary of a set of counts, for numbers whose average hides the shape. */
|
|
10
|
+
export interface Distribution {
|
|
11
|
+
/** How many values went into the summary. */
|
|
12
|
+
count: number;
|
|
13
|
+
min: number;
|
|
14
|
+
median: number;
|
|
15
|
+
/** The value nine tenths of the set are at or below. */
|
|
16
|
+
p90: number;
|
|
17
|
+
max: number;
|
|
18
|
+
mean: number;
|
|
19
|
+
}
|
|
20
|
+
/** One scenario, named well enough to go and find it. */
|
|
21
|
+
export interface ScenarioRef {
|
|
22
|
+
name: string;
|
|
23
|
+
file: string;
|
|
24
|
+
line: number;
|
|
25
|
+
/** Steps written in the scenario itself, Background steps excluded. */
|
|
26
|
+
steps: number;
|
|
27
|
+
}
|
|
28
|
+
/** One entry of the step vocabulary: a normalised step and where it is used. */
|
|
29
|
+
export interface StepEntry {
|
|
30
|
+
/** The step text after normalisation - what makes two steps "the same". */
|
|
31
|
+
text: string;
|
|
32
|
+
/** How many written steps normalise to this text. */
|
|
33
|
+
count: number;
|
|
34
|
+
/** The step text as somebody actually wrote it, the first time it appears. */
|
|
35
|
+
example: string;
|
|
36
|
+
/** Where that first sighting is. */
|
|
37
|
+
file: string;
|
|
38
|
+
line: number;
|
|
39
|
+
}
|
|
40
|
+
/** Normalised steps that differ only slightly from each other. */
|
|
41
|
+
export interface SimilarGroup {
|
|
42
|
+
/** The steps in the group, most used first. */
|
|
43
|
+
members: StepEntry[];
|
|
44
|
+
/** Written steps covered by the whole group. */
|
|
45
|
+
total: number;
|
|
46
|
+
}
|
|
47
|
+
/** How often a tag is written. */
|
|
48
|
+
export interface TagEntry {
|
|
49
|
+
name: string;
|
|
50
|
+
count: number;
|
|
51
|
+
}
|
|
52
|
+
/** A file the Gherkin parser refused, and so could not be counted. */
|
|
53
|
+
export interface UnreadableFile {
|
|
54
|
+
file: string;
|
|
55
|
+
/** The parser's complaint, or the rule name it maps onto. */
|
|
56
|
+
reason: string;
|
|
57
|
+
line: number;
|
|
58
|
+
}
|
|
59
|
+
/** How many files were written in each Gherkin dialect. */
|
|
60
|
+
export interface LanguageEntry {
|
|
61
|
+
/** The Gherkin language code, such as `en` or `de`. */
|
|
62
|
+
code: string;
|
|
63
|
+
files: number;
|
|
64
|
+
}
|
|
65
|
+
/** What is in the feature files, counted. */
|
|
66
|
+
export interface Inventory {
|
|
67
|
+
features: number;
|
|
68
|
+
rules: number;
|
|
69
|
+
backgrounds: number;
|
|
70
|
+
/** Scenarios that are not Scenario Outlines. */
|
|
71
|
+
scenarios: number;
|
|
72
|
+
scenarioOutlines: number;
|
|
73
|
+
examplesTables: number;
|
|
74
|
+
/** Body rows across every Examples table; header rows are not counted. */
|
|
75
|
+
examplesRows: number;
|
|
76
|
+
/** Steps as written, Background steps included. */
|
|
77
|
+
steps: number;
|
|
78
|
+
dataTables: number;
|
|
79
|
+
docStrings: number;
|
|
80
|
+
}
|
|
81
|
+
/** How the steps of the suite divide between Given, When and Then. */
|
|
82
|
+
export interface KeywordMix {
|
|
83
|
+
given: number;
|
|
84
|
+
when: number;
|
|
85
|
+
then: number;
|
|
86
|
+
/**
|
|
87
|
+
* Steps whose keyword resolves to nothing: an `And` or `But` written before
|
|
88
|
+
* anything for it to carry on from.
|
|
89
|
+
*/
|
|
90
|
+
other: number;
|
|
91
|
+
}
|
|
92
|
+
/** What the steps of the suite look like taken together. */
|
|
93
|
+
export interface StepStats {
|
|
94
|
+
/** Steps as written, Background steps included. */
|
|
95
|
+
total: number;
|
|
96
|
+
/** Distinct steps after normalisation. */
|
|
97
|
+
unique: number;
|
|
98
|
+
/**
|
|
99
|
+
* `unique / total`. Low means the team shares a vocabulary; high means
|
|
100
|
+
* everybody invents their own phrasing, and the step definitions rot.
|
|
101
|
+
*/
|
|
102
|
+
uniqueRatio: number;
|
|
103
|
+
/** Distinct steps written exactly once - usually accidental one-offs. */
|
|
104
|
+
usedOnce: number;
|
|
105
|
+
/** Every distinct step, most used first. */
|
|
106
|
+
vocabulary: StepEntry[];
|
|
107
|
+
/** Steps that are nearly, but not quite, each other. */
|
|
108
|
+
similar: SimilarGroup[];
|
|
109
|
+
/** Given/When/Then mix, with `And` and `But` resolved to what they follow. */
|
|
110
|
+
keywords: KeywordMix;
|
|
111
|
+
/** Words per step, as a proxy for how detailed the steps have become. */
|
|
112
|
+
wordsPerStep: Distribution;
|
|
113
|
+
}
|
|
114
|
+
/** What the scenarios of the suite look like taken together. */
|
|
115
|
+
export interface ScenarioStats {
|
|
116
|
+
/**
|
|
117
|
+
* The number of test cases: one per Scenario, and one per row of every
|
|
118
|
+
* Examples table. This is the number that predicts how long a suite takes
|
|
119
|
+
* to run, and almost nobody's mental count of it is right.
|
|
120
|
+
*/
|
|
121
|
+
effective: number;
|
|
122
|
+
/** Steps per scenario, Background steps excluded. */
|
|
123
|
+
stepsPerScenario: Distribution;
|
|
124
|
+
/** Every scenario, longest first. */
|
|
125
|
+
largest: ScenarioRef[];
|
|
126
|
+
}
|
|
127
|
+
/** What the tags of the suite look like taken together. */
|
|
128
|
+
export interface TagStats {
|
|
129
|
+
/** Tags as written, counting a tag once for every node carrying it. */
|
|
130
|
+
total: number;
|
|
131
|
+
unique: number;
|
|
132
|
+
/** Every distinct tag, most used first. */
|
|
133
|
+
vocabulary: TagEntry[];
|
|
134
|
+
/** Tags written exactly once - very often a typo of one written often. */
|
|
135
|
+
usedOnce: string[];
|
|
136
|
+
/**
|
|
137
|
+
* Scenarios carrying no tag of their own and inheriting none, and so
|
|
138
|
+
* reachable by no tag expression.
|
|
139
|
+
*/
|
|
140
|
+
untaggedScenarios: number;
|
|
141
|
+
}
|
|
142
|
+
/** Everything one statistics run found. */
|
|
143
|
+
export interface Statistics {
|
|
144
|
+
files: {
|
|
145
|
+
/** Files the search matched. */
|
|
146
|
+
total: number;
|
|
147
|
+
/** Files the parser accepted, and so the ones every other number is from. */
|
|
148
|
+
parsed: number;
|
|
149
|
+
/** Files the parser refused. Nothing in them is counted. */
|
|
150
|
+
unreadable: UnreadableFile[];
|
|
151
|
+
};
|
|
152
|
+
inventory: Inventory;
|
|
153
|
+
scenarios: ScenarioStats;
|
|
154
|
+
steps: StepStats;
|
|
155
|
+
tags: TagStats;
|
|
156
|
+
/** Dialects in use, most common first. A single stray entry is a bug. */
|
|
157
|
+
languages: LanguageEntry[];
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/stats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kCAAkC;AAClC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,wDAAwD;IACxD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,EAAE,UAAU,CAAC;IACrB,yEAAyE;IACzE,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,gBAAgB,EAAE,YAAY,CAAC;IAC/B,qCAAqC;IACrC,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,2DAA2D;AAC3D,MAAM,WAAW,QAAQ;IACvB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QACL,gCAAgC;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,6EAA6E;QAC7E,MAAM,EAAE,MAAM,CAAC;QACf,4DAA4D;QAC5D,UAAU,EAAE,cAAc,EAAE,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,aAAa,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,yEAAyE;IACzE,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a statistics run.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is complete: no list is cut short, no number is rounded to
|
|
5
|
+
* fit a screen. Trimming is the formatters' job, so that the JSON output is a
|
|
6
|
+
* full dataset - one a later run can be compared against - while the text
|
|
7
|
+
* report stays short enough to read.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/stats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
package/dist/types.d.ts
CHANGED
|
@@ -50,6 +50,30 @@ export type Severity = 'error' | 'warning';
|
|
|
50
50
|
export type RuleConfig = RuleState | readonly [RuleState, unknown];
|
|
51
51
|
/** The parsed contents of a `.gurkencheckrc` file. */
|
|
52
52
|
export type Configuration = Record<string, RuleConfig>;
|
|
53
|
+
/**
|
|
54
|
+
* Somewhere for a rule to keep what it learns as a run goes on.
|
|
55
|
+
*
|
|
56
|
+
* A rule looking for duplicates has to remember what it has already seen.
|
|
57
|
+
* Keeping that in the module makes it process-global, so two runs at once - a
|
|
58
|
+
* language server checking two folders, a test suite running cases side by
|
|
59
|
+
* side - quietly corrupt each other's state. A context belongs to one rule in
|
|
60
|
+
* one run, so there is nothing shared and nothing to reset.
|
|
61
|
+
*/
|
|
62
|
+
export interface RunContext {
|
|
63
|
+
/** This rule's state for this run, made the first time it is asked for. */
|
|
64
|
+
state<T>(create: () => T): T;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A finding that could only be worked out once every file had been seen, so
|
|
68
|
+
* it has to say which file it is about.
|
|
69
|
+
*/
|
|
70
|
+
export interface RunFinding extends RuleError {
|
|
71
|
+
/**
|
|
72
|
+
* The file this is about, as it was given to the linter. Left out for a
|
|
73
|
+
* finding about the run as a whole rather than about any one file.
|
|
74
|
+
*/
|
|
75
|
+
filePath?: string;
|
|
76
|
+
}
|
|
53
77
|
/**
|
|
54
78
|
* A lint rule.
|
|
55
79
|
*
|
|
@@ -68,11 +92,31 @@ export interface LintRule {
|
|
|
68
92
|
/**
|
|
69
93
|
* Returns the violations found in this file. A rule that needs to wait for
|
|
70
94
|
* something - reading a file, asking a service - may return a promise.
|
|
95
|
+
*
|
|
96
|
+
* `context` is the same object for every file of a run, and a different one
|
|
97
|
+
* for every other run, so it is where anything remembered between files
|
|
98
|
+
* belongs.
|
|
99
|
+
*/
|
|
100
|
+
run(feature: Feature | undefined, file: FeatureFile, configuration: unknown, context: RunContext): RuleError[] | Promise<RuleError[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Called once before the first file, for a rule with something to set up.
|
|
103
|
+
* Rules that only accumulate as they go need nothing here: the context
|
|
104
|
+
* makes their state the first time they ask for it.
|
|
105
|
+
*/
|
|
106
|
+
onRunStart?(configuration: unknown, context: RunContext): void;
|
|
107
|
+
/**
|
|
108
|
+
* The findings that could only be worked out once every file had been seen,
|
|
109
|
+
* such as two files sharing a name. Called once after the last file, and
|
|
110
|
+
* only for a rule the configuration switched on.
|
|
71
111
|
*/
|
|
72
|
-
|
|
112
|
+
onRunEnd?(configuration: unknown, context: RunContext): RunFinding[] | Promise<RunFinding[]>;
|
|
73
113
|
/**
|
|
74
|
-
* Clears any state kept between files.
|
|
75
|
-
*
|
|
114
|
+
* Clears any state kept between files.
|
|
115
|
+
*
|
|
116
|
+
* @deprecated Keep state in the `RunContext` handed to `run` instead. State
|
|
117
|
+
* in the module is shared by every run in the process, so two at once tread
|
|
118
|
+
* on each other. Still called before each run, for rules written before
|
|
119
|
+
* contexts existed.
|
|
76
120
|
*/
|
|
77
121
|
reset?(): void;
|
|
78
122
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAEhD,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,CAAC;AAE9C,kCAAkC;AAClC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAEnE,sDAAsD;AACtD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAEhD,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,CAAC;AAE9C,kCAAkC;AAClC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAEnE,sDAAsD;AACtD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;OAOG;IACH,GAAG,CACD,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,OAAO,EACtB,OAAO,EAAE,UAAU,GAClB,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtC;;;;OAIG;IACH,UAAU,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,CACP,aAAa,EAAE,OAAO,EACtB,OAAO,EAAE,UAAU,GAClB,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACxC;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,IAAI,CAAC;CAChB;AAED,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC"}
|
package/dist/util/glob.d.ts
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
* slash-separated paths.
|
|
4
4
|
*/
|
|
5
5
|
export declare function globToRegExp(pattern: string): RegExp;
|
|
6
|
+
/**
|
|
7
|
+
* The directory a pattern's search starts from - the leading run of literal
|
|
8
|
+
* segments, resolved against `cwd`. What you would watch to be told about
|
|
9
|
+
* anything the pattern could match.
|
|
10
|
+
*/
|
|
11
|
+
export declare function globRoot(pattern: string, cwd?: string): string;
|
|
6
12
|
export interface GlobOptions {
|
|
7
13
|
/** Directory that relative patterns and returned paths are relative to. */
|
|
8
14
|
cwd?: string;
|
|
@@ -17,4 +23,16 @@ export interface GlobOptions {
|
|
|
17
23
|
* forward slashes. Directories are never returned. Results are sorted.
|
|
18
24
|
*/
|
|
19
25
|
export declare function globSync(pattern: string, options?: GlobOptions): string[];
|
|
26
|
+
/**
|
|
27
|
+
* The same search, handing each match over as it is found.
|
|
28
|
+
*
|
|
29
|
+
* Collecting a whole tree before anything else starts is time in which
|
|
30
|
+
* nothing is read, parsed or checked. This hands each match over as it is
|
|
31
|
+
* reached, so the work after it starts on the first file rather than on the
|
|
32
|
+
* last.
|
|
33
|
+
*
|
|
34
|
+
* The order is the order `globSync` returns, without anything being collected
|
|
35
|
+
* to sort: `byPathOrder` is what makes the two agree.
|
|
36
|
+
*/
|
|
37
|
+
export declare function globStream(pattern: string, options?: GlobOptions): AsyncGenerator<string>;
|
|
20
38
|
//# sourceMappingURL=glob.d.ts.map
|
package/dist/util/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/util/glob.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA8DpD;
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/util/glob.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA8DpD;AAgCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,CAE7E;AAsFD,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AA0DD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,CAsB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAuB,UAAU,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,WAAgB,GACxB,cAAc,CAAC,MAAM,CAAC,CAsBxB"}
|
package/dist/util/glob.js
CHANGED
|
@@ -109,19 +109,44 @@ function staticPrefix(pattern) {
|
|
|
109
109
|
}
|
|
110
110
|
return literal.join('/');
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* The directory a pattern's search starts from - the leading run of literal
|
|
114
|
+
* segments, resolved against `cwd`. What you would watch to be told about
|
|
115
|
+
* anything the pattern could match.
|
|
116
|
+
*/
|
|
117
|
+
export function globRoot(pattern, cwd = process.cwd()) {
|
|
118
|
+
return path.resolve(cwd, staticPrefix(pattern.split(path.sep).join('/')));
|
|
119
|
+
}
|
|
112
120
|
function isHidden(name) {
|
|
113
121
|
return name.startsWith('.');
|
|
114
122
|
}
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Orders the entries of a directory so that walking depth-first reaches files
|
|
125
|
+
* in the same order that sorting the finished paths would.
|
|
126
|
+
*
|
|
127
|
+
* Everything inside a directory `d` has a path starting `d/`, so `d` has to
|
|
128
|
+
* be ordered as `d/` rather than as `d`. Otherwise a sibling file called
|
|
129
|
+
* `d-1.feature` comes after the directory here and before it in a sorted
|
|
130
|
+
* list, because `-` sorts before `/` - and a walk that hands files over as it
|
|
131
|
+
* finds them would quietly report them in a different order from one that
|
|
132
|
+
* collects and sorts.
|
|
133
|
+
*/
|
|
134
|
+
function byPathOrder(a, b) {
|
|
135
|
+
const left = a.isDirectory() ? `${a.name}/` : a.name;
|
|
136
|
+
const right = b.isDirectory() ? `${b.name}/` : b.name;
|
|
137
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
138
|
+
}
|
|
139
|
+
function readEntries(directory) {
|
|
117
140
|
try {
|
|
118
|
-
|
|
141
|
+
return fs.readdirSync(directory, { withFileTypes: true }).sort(byPathOrder);
|
|
119
142
|
}
|
|
120
143
|
catch {
|
|
121
144
|
// A missing or unreadable directory simply contributes no matches.
|
|
122
|
-
return;
|
|
145
|
+
return [];
|
|
123
146
|
}
|
|
124
|
-
|
|
147
|
+
}
|
|
148
|
+
function walk(directory, onFile) {
|
|
149
|
+
for (const entry of readEntries(directory)) {
|
|
125
150
|
if (isHidden(entry.name)) {
|
|
126
151
|
continue;
|
|
127
152
|
}
|
|
@@ -134,6 +159,43 @@ function walk(directory, onFile) {
|
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* The same walk, handing each file over as it is reached.
|
|
164
|
+
*
|
|
165
|
+
* Each directory is still read synchronously. `fs.promises.readdir` was tried
|
|
166
|
+
* and is about 40% slower over a tree of a couple of thousand directories -
|
|
167
|
+
* a promise for each directory costs more than reading it does - and it
|
|
168
|
+
* bought nothing, because the only thing waiting on this walk is the work
|
|
169
|
+
* being fed by it. What matters is that the walk is no longer one long block:
|
|
170
|
+
* it gives way at every file, and the reading and checking downstream happen
|
|
171
|
+
* in the gaps.
|
|
172
|
+
*/
|
|
173
|
+
async function* walkStream(root) {
|
|
174
|
+
// An explicit stack rather than recursion: a recursive async generator
|
|
175
|
+
// delegates through one `yield*` per level of nesting for every file it
|
|
176
|
+
// hands over, so a deep tree pays for its depth on every single file.
|
|
177
|
+
const stack = [{ directory: root, entries: readEntries(root), index: 0 }];
|
|
178
|
+
while (stack.length > 0) {
|
|
179
|
+
const top = stack[stack.length - 1];
|
|
180
|
+
if (top.index >= top.entries.length) {
|
|
181
|
+
stack.pop();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const entry = top.entries[top.index++];
|
|
185
|
+
if (isHidden(entry.name)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const absolutePath = path.join(top.directory, entry.name);
|
|
189
|
+
if (entry.isDirectory()) {
|
|
190
|
+
// Pushed so that its contents come before the entries after it here,
|
|
191
|
+
// which is what a depth-first walk means.
|
|
192
|
+
stack.push({ directory: absolutePath, entries: readEntries(absolutePath), index: 0 });
|
|
193
|
+
}
|
|
194
|
+
else if (entry.isFile()) {
|
|
195
|
+
yield absolutePath;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
137
199
|
/**
|
|
138
200
|
* True when the path, or any directory leading to it, matches one of the
|
|
139
201
|
* patterns.
|
|
@@ -156,42 +218,84 @@ function isIgnored(relativePath, ignores) {
|
|
|
156
218
|
}
|
|
157
219
|
return false;
|
|
158
220
|
}
|
|
159
|
-
|
|
160
|
-
* Returns the files matching `pattern`, as paths relative to `cwd` using
|
|
161
|
-
* forward slashes. Directories are never returned. Results are sorted.
|
|
162
|
-
*/
|
|
163
|
-
export function globSync(pattern, options = {}) {
|
|
221
|
+
function newSearch(pattern, options) {
|
|
164
222
|
const cwd = options.cwd ?? process.cwd();
|
|
165
223
|
const normalisedPattern = pattern.split(path.sep).join('/');
|
|
166
224
|
const matches = globToRegExp(normalisedPattern);
|
|
167
225
|
const ignores = (options.ignore ?? []).map((ignorePattern) => globToRegExp(ignorePattern.split(path.sep).join('/')));
|
|
168
|
-
const searchRoot = path.resolve(cwd, staticPrefix(normalisedPattern));
|
|
169
|
-
const found = [];
|
|
170
226
|
// An absolute pattern is matched against absolute paths: comparing it with
|
|
171
227
|
// a path relative to cwd can never match, and the search would come back
|
|
172
228
|
// empty rather than wrong, which reads as "nothing to report".
|
|
173
229
|
const absolutePattern = path.isAbsolute(normalisedPattern);
|
|
230
|
+
return {
|
|
231
|
+
searchRoot: path.resolve(cwd, staticPrefix(normalisedPattern)),
|
|
232
|
+
match(absolutePath) {
|
|
233
|
+
const relativePath = path.relative(cwd, absolutePath).split(path.sep).join('/');
|
|
234
|
+
const candidate = absolutePattern ? absolutePath.split(path.sep).join('/') : relativePath;
|
|
235
|
+
if (!matches.test(candidate))
|
|
236
|
+
return undefined;
|
|
237
|
+
if (isIgnored(relativePath, ignores))
|
|
238
|
+
return undefined;
|
|
239
|
+
return relativePath;
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Returns the files matching `pattern`, as paths relative to `cwd` using
|
|
245
|
+
* forward slashes. Directories are never returned. Results are sorted.
|
|
246
|
+
*/
|
|
247
|
+
export function globSync(pattern, options = {}) {
|
|
248
|
+
const search = newSearch(pattern, options);
|
|
249
|
+
const found = [];
|
|
174
250
|
const consider = (absolutePath) => {
|
|
175
|
-
const relativePath =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (isIgnored(relativePath, ignores))
|
|
180
|
-
return;
|
|
181
|
-
found.push(relativePath);
|
|
251
|
+
const relativePath = search.match(absolutePath);
|
|
252
|
+
if (relativePath !== undefined) {
|
|
253
|
+
found.push(relativePath);
|
|
254
|
+
}
|
|
182
255
|
};
|
|
183
|
-
const stats = statOrUndefined(searchRoot);
|
|
256
|
+
const stats = statOrUndefined(search.searchRoot);
|
|
184
257
|
if (stats === undefined) {
|
|
185
258
|
return [];
|
|
186
259
|
}
|
|
187
260
|
if (stats.isFile()) {
|
|
188
|
-
consider(searchRoot);
|
|
261
|
+
consider(search.searchRoot);
|
|
189
262
|
}
|
|
190
263
|
else {
|
|
191
|
-
walk(searchRoot, consider);
|
|
264
|
+
walk(search.searchRoot, consider);
|
|
192
265
|
}
|
|
193
266
|
return found.sort();
|
|
194
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* The same search, handing each match over as it is found.
|
|
270
|
+
*
|
|
271
|
+
* Collecting a whole tree before anything else starts is time in which
|
|
272
|
+
* nothing is read, parsed or checked. This hands each match over as it is
|
|
273
|
+
* reached, so the work after it starts on the first file rather than on the
|
|
274
|
+
* last.
|
|
275
|
+
*
|
|
276
|
+
* The order is the order `globSync` returns, without anything being collected
|
|
277
|
+
* to sort: `byPathOrder` is what makes the two agree.
|
|
278
|
+
*/
|
|
279
|
+
export async function* globStream(pattern, options = {}) {
|
|
280
|
+
const search = newSearch(pattern, options);
|
|
281
|
+
const stats = statOrUndefined(search.searchRoot);
|
|
282
|
+
if (stats === undefined) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (stats.isFile()) {
|
|
286
|
+
const relativePath = search.match(search.searchRoot);
|
|
287
|
+
if (relativePath !== undefined) {
|
|
288
|
+
yield relativePath;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
for await (const absolutePath of walkStream(search.searchRoot)) {
|
|
293
|
+
const relativePath = search.match(absolutePath);
|
|
294
|
+
if (relativePath !== undefined) {
|
|
295
|
+
yield relativePath;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
195
299
|
function statOrUndefined(target) {
|
|
196
300
|
try {
|
|
197
301
|
return fs.statSync(target);
|