@nielspeter/eess 0.2.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -4
- package/dist/assertion-advice.d.ts +26 -0
- package/dist/assertion-advice.d.ts.map +1 -0
- package/dist/assertion-advice.js +29 -0
- package/dist/assertion-advice.js.map +1 -0
- package/dist/baseline-generator.d.ts +9 -0
- package/dist/baseline-generator.d.ts.map +1 -1
- package/dist/baseline-generator.js +9 -0
- package/dist/baseline-generator.js.map +1 -1
- package/dist/baseline.d.ts +69 -4
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +119 -13
- package/dist/baseline.js.map +1 -1
- package/dist/cache-registry.d.ts +14 -0
- package/dist/cache-registry.d.ts.map +1 -0
- package/dist/cache-registry.js +37 -0
- package/dist/cache-registry.js.map +1 -0
- package/dist/cardinality.d.ts +35 -0
- package/dist/cardinality.d.ts.map +1 -0
- package/dist/cardinality.js +80 -0
- package/dist/cardinality.js.map +1 -0
- package/dist/check-options.d.ts +14 -0
- package/dist/check-options.d.ts.map +1 -1
- package/dist/cli-config.d.ts +59 -0
- package/dist/cli-config.d.ts.map +1 -0
- package/dist/cli-config.js +84 -0
- package/dist/cli-config.js.map +1 -0
- package/dist/collect-result.d.ts +94 -0
- package/dist/collect-result.d.ts.map +1 -0
- package/dist/collect-result.js +142 -0
- package/dist/collect-result.js.map +1 -0
- package/dist/combinators.d.ts +14 -0
- package/dist/combinators.d.ts.map +1 -1
- package/dist/combinators.js +53 -16
- package/dist/combinators.js.map +1 -1
- package/dist/comment-suppression.d.ts +60 -0
- package/dist/comment-suppression.d.ts.map +1 -0
- package/dist/comment-suppression.js +89 -0
- package/dist/comment-suppression.js.map +1 -0
- package/dist/condition.d.ts +43 -0
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +48 -1
- package/dist/condition.js.map +1 -1
- package/dist/correspondence-core.d.ts +35 -0
- package/dist/correspondence-core.d.ts.map +1 -0
- package/dist/correspondence-core.js +38 -0
- package/dist/correspondence-core.js.map +1 -0
- package/dist/correspondence.d.ts +57 -5
- package/dist/correspondence.d.ts.map +1 -1
- package/dist/correspondence.js +74 -5
- package/dist/correspondence.js.map +1 -1
- package/dist/dedupe-config-findings.d.ts +56 -0
- package/dist/dedupe-config-findings.d.ts.map +1 -0
- package/dist/dedupe-config-findings.js +101 -0
- package/dist/dedupe-config-findings.js.map +1 -0
- package/dist/define.d.ts +44 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +45 -4
- package/dist/define.js.map +1 -1
- package/dist/diff-aware.d.ts +8 -1
- package/dist/diff-aware.d.ts.map +1 -1
- package/dist/diff-aware.js +39 -9
- package/dist/diff-aware.js.map +1 -1
- package/dist/diff-disclosure.d.ts +72 -0
- package/dist/diff-disclosure.d.ts.map +1 -0
- package/dist/diff-disclosure.js +110 -0
- package/dist/diff-disclosure.js.map +1 -0
- package/dist/disk-set.d.ts +27 -0
- package/dist/disk-set.d.ts.map +1 -0
- package/dist/disk-set.js +12 -0
- package/dist/disk-set.js.map +1 -0
- package/dist/edge-coverage.d.ts +96 -0
- package/dist/edge-coverage.d.ts.map +1 -0
- package/dist/edge-coverage.js +108 -0
- package/dist/edge-coverage.js.map +1 -0
- package/dist/emitter-findings.d.ts +167 -0
- package/dist/emitter-findings.d.ts.map +1 -0
- package/dist/emitter-findings.js +237 -0
- package/dist/emitter-findings.js.map +1 -0
- package/dist/errors.d.ts +73 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +93 -0
- package/dist/errors.js.map +1 -1
- package/dist/exclusion-comments.d.ts +36 -1
- package/dist/exclusion-comments.d.ts.map +1 -1
- package/dist/exclusion-comments.js +185 -37
- package/dist/exclusion-comments.js.map +1 -1
- package/dist/execute-rule.d.ts +11 -1
- package/dist/execute-rule.d.ts.map +1 -1
- package/dist/execute-rule.js +220 -11
- package/dist/execute-rule.js.map +1 -1
- package/dist/file-walk.d.ts +22 -0
- package/dist/file-walk.d.ts.map +1 -0
- package/dist/file-walk.js +51 -0
- package/dist/file-walk.js.map +1 -0
- package/dist/format-github.d.ts.map +1 -1
- package/dist/format-github.js +37 -5
- package/dist/format-github.js.map +1 -1
- package/dist/format-json.d.ts +96 -1
- package/dist/format-json.d.ts.map +1 -1
- package/dist/format-json.js +94 -9
- package/dist/format-json.js.map +1 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +41 -19
- package/dist/format.js.map +1 -1
- package/dist/glob-site.d.ts +232 -0
- package/dist/glob-site.d.ts.map +1 -0
- package/dist/glob-site.js +156 -0
- package/dist/glob-site.js.map +1 -0
- package/dist/identity-root.d.ts +55 -0
- package/dist/identity-root.d.ts.map +1 -0
- package/dist/identity-root.js +170 -0
- package/dist/identity-root.js.map +1 -0
- package/dist/index.d.ts +18 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +55 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +92 -0
- package/dist/internal.js.map +1 -0
- package/dist/mask-non-comment.d.ts +49 -0
- package/dist/mask-non-comment.d.ts.map +1 -0
- package/dist/mask-non-comment.js +261 -0
- package/dist/mask-non-comment.js.map +1 -0
- package/dist/owns-empty-discovery.d.ts +5 -0
- package/dist/owns-empty-discovery.d.ts.map +1 -0
- package/dist/owns-empty-discovery.js +85 -0
- package/dist/owns-empty-discovery.js.map +1 -0
- package/dist/path-suffix.d.ts +57 -0
- package/dist/path-suffix.d.ts.map +1 -0
- package/dist/path-suffix.js +73 -0
- package/dist/path-suffix.js.map +1 -0
- package/dist/path-universe.d.ts +44 -0
- package/dist/path-universe.d.ts.map +1 -0
- package/dist/path-universe.js +21 -0
- package/dist/path-universe.js.map +1 -0
- package/dist/predicate.d.ts +45 -0
- package/dist/predicate.d.ts.map +1 -1
- package/dist/predicate.js +20 -1
- package/dist/predicate.js.map +1 -1
- package/dist/preset-dispatch.d.ts +6 -15
- package/dist/preset-dispatch.d.ts.map +1 -1
- package/dist/preset-dispatch.js +35 -17
- package/dist/preset-dispatch.js.map +1 -1
- package/dist/project-relative.d.ts +25 -0
- package/dist/project-relative.d.ts.map +1 -0
- package/dist/project-relative.js +54 -0
- package/dist/project-relative.js.map +1 -0
- package/dist/report.d.ts +42 -12
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +190 -14
- package/dist/report.js.map +1 -1
- package/dist/rule-builder-like.d.ts +17 -0
- package/dist/rule-builder-like.d.ts.map +1 -0
- package/dist/rule-builder-like.js +2 -0
- package/dist/rule-builder-like.js.map +1 -0
- package/dist/rule-builder.d.ts +124 -84
- package/dist/rule-builder.d.ts.map +1 -1
- package/dist/rule-builder.js +230 -181
- package/dist/rule-builder.js.map +1 -1
- package/dist/rule-description.d.ts +83 -1
- package/dist/rule-description.d.ts.map +1 -1
- package/dist/rule-description.js +75 -1
- package/dist/rule-description.js.map +1 -1
- package/dist/rule-metadata.d.ts +20 -3
- package/dist/rule-metadata.d.ts.map +1 -1
- package/dist/selection-memo.d.ts +45 -0
- package/dist/selection-memo.d.ts.map +1 -0
- package/dist/selection-memo.js +66 -0
- package/dist/selection-memo.js.map +1 -0
- package/dist/shallow-clone.d.ts +20 -0
- package/dist/shallow-clone.d.ts.map +1 -0
- package/dist/shallow-clone.js +27 -0
- package/dist/shallow-clone.js.map +1 -0
- package/dist/silent-exclusion.d.ts +12 -0
- package/dist/silent-exclusion.d.ts.map +1 -1
- package/dist/silent-exclusion.js +22 -0
- package/dist/silent-exclusion.js.map +1 -1
- package/dist/stderr.d.ts +16 -0
- package/dist/stderr.d.ts.map +1 -0
- package/dist/stderr.js +73 -0
- package/dist/stderr.js.map +1 -0
- package/dist/terminal-builder.d.ts +224 -10
- package/dist/terminal-builder.d.ts.map +1 -1
- package/dist/terminal-builder.js +336 -46
- package/dist/terminal-builder.js.map +1 -1
- package/dist/type-guards.d.ts +38 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +42 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/unsuppressable.d.ts +54 -0
- package/dist/unsuppressable.d.ts.map +1 -0
- package/dist/unsuppressable.js +76 -0
- package/dist/unsuppressable.js.map +1 -0
- package/dist/vacuity-findings.d.ts +49 -0
- package/dist/vacuity-findings.d.ts.map +1 -0
- package/dist/vacuity-findings.js +94 -0
- package/dist/vacuity-findings.js.map +1 -0
- package/dist/violation.d.ts +173 -4
- package/dist/violation.d.ts.map +1 -1
- package/dist/violation.js +262 -1
- package/dist/violation.js.map +1 -1
- package/dist/watch.d.ts +71 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +129 -0
- package/dist/watch.js.map +1 -0
- package/package.json +6 -1
package/dist/correspondence.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { collectResult } from './collect-result.js';
|
|
1
2
|
import { TerminalBuilder } from './terminal-builder.js';
|
|
2
3
|
import { matchSelections } from './matching.js';
|
|
4
|
+
import { marksAssertsCardinality, assertsCardinality as checkAssertsCardinality, } from './cardinality.js';
|
|
3
5
|
function violationFor(info, message, ruleId) {
|
|
4
6
|
return {
|
|
5
7
|
rule: 'correspondence',
|
|
@@ -33,19 +35,52 @@ export class CorrespondenceBuilder extends TerminalBuilder {
|
|
|
33
35
|
andShould() {
|
|
34
36
|
return this;
|
|
35
37
|
}
|
|
36
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Every element on the named side(s) has a counterpart on the other. An
|
|
40
|
+
* absence assertion — "no element lacks a counterpart" — so it's marked
|
|
41
|
+
* cardinality-exempt (see `assertsCardinality()`).
|
|
42
|
+
*/
|
|
37
43
|
beComplete(opts = {}) {
|
|
38
|
-
this._checks.push((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId));
|
|
44
|
+
this._checks.push(marksAssertsCardinality((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId)));
|
|
39
45
|
return this;
|
|
40
46
|
}
|
|
41
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Related endpoints on one side have matching relations on the other. Also
|
|
49
|
+
* an absence assertion — "no matched pair disagrees" — cardinality-exempt
|
|
50
|
+
* for the same reason `beComplete()` is.
|
|
51
|
+
*/
|
|
42
52
|
preserveRelations(spec) {
|
|
43
|
-
this._checks.push((o, ruleId) => relations(o, spec, ruleId));
|
|
53
|
+
this._checks.push(marksAssertsCardinality((o, ruleId) => relations(o, spec, ruleId)));
|
|
44
54
|
return this;
|
|
45
55
|
}
|
|
46
56
|
collectViolations() {
|
|
47
57
|
const ruleId = this._metadata?.id;
|
|
48
|
-
|
|
58
|
+
const violations = this._checks.flatMap((check) => check(this.opts, ruleId));
|
|
59
|
+
// ADR-010: the unit is the key sets of both sides — a correspondence over
|
|
60
|
+
// an empty left AND an empty right examined nothing, regardless of how
|
|
61
|
+
// many checks were chained; one side alone being empty is still real
|
|
62
|
+
// examination of the other.
|
|
63
|
+
const examined = this.opts.left.elements.length + this.opts.right.elements.length;
|
|
64
|
+
return collectResult(violations, { examined });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* ADR-010: mirrors `RuleBuilder.assertsCardinality()` — per-check, not a
|
|
68
|
+
* blanket class-wide exemption. `beComplete()`/`preserveRelations()` mark
|
|
69
|
+
* the function they push via `marksAssertsCardinality()`, the same
|
|
70
|
+
* unforgeable registry `.notExist()` uses, because both are absence
|
|
71
|
+
* assertions ("no element lacks a counterpart", "no matched pair
|
|
72
|
+
* disagrees") that can never produce a violation from an empty side — an
|
|
73
|
+
* empty selection here is the correctly computed input (e.g. release-gate's
|
|
74
|
+
* "no packages changed this diff" on a clean tree), not a broken instrument.
|
|
75
|
+
* `.every()`, not `.some()`, for the same reason `RuleBuilder` uses it: a
|
|
76
|
+
* future check type that isn't an absence assertion must not silently
|
|
77
|
+
* inherit this exemption just because it's chained alongside one that is.
|
|
78
|
+
* An empty `_checks` list is not exempt — nothing was asserted yet.
|
|
79
|
+
*/
|
|
80
|
+
assertsCardinality() {
|
|
81
|
+
if (this._checks.length === 0)
|
|
82
|
+
return false;
|
|
83
|
+
return this._checks.every((check) => checkAssertsCardinality(check));
|
|
49
84
|
}
|
|
50
85
|
}
|
|
51
86
|
/** Bind two selections into a correspondence. */
|
|
@@ -124,4 +159,38 @@ function relations(o, spec, ruleId) {
|
|
|
124
159
|
}
|
|
125
160
|
return out;
|
|
126
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* The elements a rule's predicates admit, as a `Selection`.
|
|
164
|
+
*
|
|
165
|
+
* A free function over the elements and the predicates rather than a method,
|
|
166
|
+
* for the same reason as `recordExclusions`: the two builders that need it sit
|
|
167
|
+
* in incompatible hierarchies — `eess-ts` has three levels where the kernel has
|
|
168
|
+
* two — so there is no common base to hold it. The two copies were
|
|
169
|
+
* byte-identical.
|
|
170
|
+
*/
|
|
171
|
+
export function selectMatching(elements, predicates, opts) {
|
|
172
|
+
return {
|
|
173
|
+
elements: matchingElements(elements, predicates),
|
|
174
|
+
label: opts.label,
|
|
175
|
+
identify: opts.identify,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The elements every predicate admits.
|
|
180
|
+
*
|
|
181
|
+
* The same conjunction as `selectMatching`, without a `Selection` around it —
|
|
182
|
+
* split out because `eess-ts`'s two GraphQL builders want the ARRAY, to memoise
|
|
183
|
+
* it as the one set both `check()` and `diagnose()` read (plan 0096). They each
|
|
184
|
+
* carried this filter inline, byte for byte, which `no-copy-paste` reported at
|
|
185
|
+
* 100%; folding them onto `selectMatching` was not open, since a `Selection`
|
|
186
|
+
* carries a label and an `identify` neither of them has at that point.
|
|
187
|
+
*
|
|
188
|
+
* Sharing it with `selectMatching` matters beyond the duplicate count: "the
|
|
189
|
+
* elements a rule selected" must be ONE answer, or a builder's `.select()` side
|
|
190
|
+
* and its condition side can disagree about which elements the predicates
|
|
191
|
+
* admitted — the divergence ADR-010 exists to make impossible.
|
|
192
|
+
*/
|
|
193
|
+
export function matchingElements(elements, predicates) {
|
|
194
|
+
return elements.filter((element) => predicates.every((predicate) => predicate.test(element)));
|
|
195
|
+
}
|
|
127
196
|
//# sourceMappingURL=correspondence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One option, one finding — plan 0074, deciding 0069's appendix item 4.
|
|
3
|
+
*
|
|
4
|
+
* A preset generates rules combinatorially. `strictBoundaries` emits one
|
|
5
|
+
* `shared-isolation` rule per (shared glob × boundary folder) pair, so one
|
|
6
|
+
* wrong character in one option produces a finding per pair — same id, same
|
|
7
|
+
* glob, same cause, same remedy.
|
|
8
|
+
*
|
|
9
|
+
* **Measured on this repository**, `strictBoundaries({ folders: '**\/src/*',
|
|
10
|
+
* shared: ['**\/src/not-built-yet/**'] })`: 84 rules, **83 configuration
|
|
11
|
+
* findings, 2 distinct messages**. (0069's appendix recorded 38 for the same
|
|
12
|
+
* shape; the repo has grown more boundary folders since.) Every one is *true*,
|
|
13
|
+
* which is why exempting them is wrong — bug 0018 is precisely a preset
|
|
14
|
+
* silently enforcing nothing. But a report shaped like that makes a one-line
|
|
15
|
+
* fix look like an 83-line disaster, and it contradicts this project's own rule
|
|
16
|
+
* that findings are **identities, never totals**.
|
|
17
|
+
*
|
|
18
|
+
* So the fan-out collapses to the identity that matches the edit: the option
|
|
19
|
+
* the user actually wrote. The count survives as *context on that finding*
|
|
20
|
+
* rather than as 83 rows — which is not the snapshot ADR-009 rule 4 bars,
|
|
21
|
+
* because it is attached to a named identity rather than standing in for one.
|
|
22
|
+
*
|
|
23
|
+
* ## What the key is, and why each part is in it
|
|
24
|
+
*
|
|
25
|
+
* `(rule file, rule id, offending glob)`.
|
|
26
|
+
*
|
|
27
|
+
* - **The glob, not the message.** 0069's appendix is explicit: two different
|
|
28
|
+
* `shared` entries that both match nothing are **two findings, because they
|
|
29
|
+
* are two edits**. Keying on the message would merge them whenever the
|
|
30
|
+
* wording happened to coincide, and split them whenever it did not.
|
|
31
|
+
* - **The rule id**, so `shared-discovery` and `shared-isolation` stay
|
|
32
|
+
* distinct: they are different faults about the same glob and have different
|
|
33
|
+
* remedies.
|
|
34
|
+
* - **The rule file.** Two rule files that instantiate the same preset with the
|
|
35
|
+
* same bad glob are two edits in two places, and collapsing them would hide
|
|
36
|
+
* one. Empty in a test-file context, where every rule shares one origin, so
|
|
37
|
+
* the key degrades to `(id, glob)` there rather than merging across files
|
|
38
|
+
* that were never distinguished.
|
|
39
|
+
*
|
|
40
|
+
* ## What is deliberately NOT deduplicated
|
|
41
|
+
*
|
|
42
|
+
* Ordinary violations. Each names a distinct element at a distinct position and
|
|
43
|
+
* every one is a separate edit — collapsing them is the snapshot rule 4 bars,
|
|
44
|
+
* and it would hide real work. Only **configuration findings** fan out from a
|
|
45
|
+
* single authored mistake, and `bypassFilters` is exactly the flag that marks
|
|
46
|
+
* one.
|
|
47
|
+
*/
|
|
48
|
+
import type { ArchViolation } from './violation.js';
|
|
49
|
+
/**
|
|
50
|
+
* Collapse each configuration finding that a preset fanned out.
|
|
51
|
+
*
|
|
52
|
+
* Order-preserving, keeping the **first** occurrence, so the report reads in
|
|
53
|
+
* the order the rules were declared rather than in hash order.
|
|
54
|
+
*/
|
|
55
|
+
export declare function dedupeConfigFindings(violations: readonly ArchViolation[]): ArchViolation[];
|
|
56
|
+
//# sourceMappingURL=dedupe-config-findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-config-findings.d.ts","sourceRoot":"","sources":["../src/dedupe-config-findings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAenD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAsC1F"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The ids that are never collapsed — see `keyFor`. Imported rather than
|
|
2
|
+
// re-listed: `emitter-findings.ts` declares them and owns the set, so the next
|
|
3
|
+
// id is added in one place. A second local list here is how this guard would
|
|
4
|
+
// silently stop covering a fifth id.
|
|
5
|
+
import { EMITTER_IDS } from './emitter-findings.js';
|
|
6
|
+
/** How the surviving finding states the fan-out it stands for. */
|
|
7
|
+
function affectedNote(count) {
|
|
8
|
+
return (` This one option generated ${String(count)} rules that cannot enforce anything; they are ` +
|
|
9
|
+
`not listed separately because they are one edit.`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Collapse each configuration finding that a preset fanned out.
|
|
13
|
+
*
|
|
14
|
+
* Order-preserving, keeping the **first** occurrence, so the report reads in
|
|
15
|
+
* the order the rules were declared rather than in hash order.
|
|
16
|
+
*/
|
|
17
|
+
export function dedupeConfigFindings(violations) {
|
|
18
|
+
// Count first, then emit. A single pass that appends the note on first sight
|
|
19
|
+
// cannot know the total yet, and appending it on the last sight would reorder
|
|
20
|
+
// the report.
|
|
21
|
+
const counts = new Map();
|
|
22
|
+
for (const violation of violations) {
|
|
23
|
+
const key = keyFor(violation);
|
|
24
|
+
if (key === undefined)
|
|
25
|
+
continue;
|
|
26
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
27
|
+
}
|
|
28
|
+
const emitted = new Set();
|
|
29
|
+
const result = [];
|
|
30
|
+
for (const violation of violations) {
|
|
31
|
+
const key = keyFor(violation);
|
|
32
|
+
if (key === undefined) {
|
|
33
|
+
result.push(violation);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (emitted.has(key))
|
|
37
|
+
continue;
|
|
38
|
+
emitted.add(key);
|
|
39
|
+
const total = counts.get(key) ?? 1;
|
|
40
|
+
if (total === 1) {
|
|
41
|
+
result.push(violation);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// The note goes on `message` AND `suggestion`. They are separate fields with
|
|
45
|
+
// separate consumers — the terminal prints `Fix:` from `suggestion`, and
|
|
46
|
+
// `--format json` carries both — and a reader who sees only one of them
|
|
47
|
+
// otherwise learns a different number of affected rules from each.
|
|
48
|
+
result.push({
|
|
49
|
+
...violation,
|
|
50
|
+
message: violation.message + affectedNote(total),
|
|
51
|
+
suggestion: violation.suggestion === undefined ? undefined : violation.suggestion + affectedNote(total),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The dedupe key, or `undefined` for a finding that must never be collapsed.
|
|
58
|
+
*
|
|
59
|
+
* `undefined` for anything that is not a configuration finding, and for a
|
|
60
|
+
* configuration finding with no `element` to key on — a missing key must mean
|
|
61
|
+
* "keep it", never "merge everything that lacks one".
|
|
62
|
+
*/
|
|
63
|
+
function keyFor(violation) {
|
|
64
|
+
if (violation.bypassFilters !== true)
|
|
65
|
+
return undefined;
|
|
66
|
+
const identity = violation.ruleId ?? violation.rule;
|
|
67
|
+
// `'unnamed'` is `TerminalBuilder.describeRule()`'s SENTINEL, not an identity.
|
|
68
|
+
// A family that never overrides it gives every one of its rules the same key,
|
|
69
|
+
// so distinct rules merged and the survivor claimed they were "one edit" —
|
|
70
|
+
// measured on three different `duplicateBodies` detectors in plan 0099. A
|
|
71
|
+
// missing key must mean "keep it", never "merge everything that lacks one",
|
|
72
|
+
// which is the rule the empty-string guard beside this already follows.
|
|
73
|
+
if (identity === '' || identity === 'unnamed')
|
|
74
|
+
return undefined;
|
|
75
|
+
if (violation.element === '' || violation.element === 'unnamed')
|
|
76
|
+
return undefined;
|
|
77
|
+
// An emitter finding is about a VERDICT, not about a rule's narrowing, so the
|
|
78
|
+
// collapse's premise — one authored option fanned out by a preset — never
|
|
79
|
+
// holds for it. Each one stands for a different builder that hands back no
|
|
80
|
+
// evidence, and each is its own edit in its own place.
|
|
81
|
+
//
|
|
82
|
+
// It cannot be told apart by shape: `emitter-findings.ts` sets
|
|
83
|
+
// `element: ruleId` (the fault is the verdict, so there is no place in anyone's
|
|
84
|
+
// code to point at) and `file: ''`, so the key degenerates to `('' , id, id)`
|
|
85
|
+
// and every occurrence in a run merges. Measured before this guard: three
|
|
86
|
+
// hand-rolled builders in one rule file reported as ONE finding whose note said
|
|
87
|
+
// they were "one edit". They are three edits — the case 0069's appendix decides
|
|
88
|
+
// the other way ("two different `shared` entries that both match nothing are
|
|
89
|
+
// two findings, because they are two edits").
|
|
90
|
+
//
|
|
91
|
+
// **Keyed on the id set, not on the `element === identity` shape.** A first cut
|
|
92
|
+
// refused a key whenever the element repeated the identity, and that is too
|
|
93
|
+
// broad: a real rule with a real narrowing and no glob to name does the same,
|
|
94
|
+
// and two instances of it genuinely ARE one edit — `the-floor.test.ts`'s
|
|
95
|
+
// "CONTROL: genuinely identical findings still collapse" reds on it, over
|
|
96
|
+
// `smells.duplicateBodies(p).minLines(500)`.
|
|
97
|
+
if (EMITTER_IDS.has(identity))
|
|
98
|
+
return undefined;
|
|
99
|
+
return `${violation.file} ${identity} ${violation.element}`;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=dedupe-config-findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-config-findings.js","sourceRoot":"","sources":["../src/dedupe-config-findings.ts"],"names":[],"mappings":"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"}
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Predicate } from './predicate.js';
|
|
2
|
+
import type { DeclaredGlobs } from './glob-site.js';
|
|
2
3
|
import type { Condition, ConditionContext } from './condition.js';
|
|
3
4
|
import type { ArchViolation } from './violation.js';
|
|
4
5
|
/**
|
|
@@ -15,8 +16,40 @@ import type { ArchViolation } from './violation.js';
|
|
|
15
16
|
*
|
|
16
17
|
* classes(p).that().satisfy(isAbstract).should().beExported().check()
|
|
17
18
|
* ```
|
|
19
|
+
*
|
|
20
|
+
* ## Declaring a glob
|
|
21
|
+
*
|
|
22
|
+
* If the predicate matches paths against a glob, pass it as `globs` — otherwise
|
|
23
|
+
* `doctor` cannot see it, and a typo'd glob narrows the selection to nothing
|
|
24
|
+
* while reporting a clean bill of health. This is a **selector**-position glob,
|
|
25
|
+
* the one position that is acted on today, which is why the omission was bug
|
|
26
|
+
* 0030's higher-severity half.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const inGeneratedFolder = definePredicate<SourceFile>(
|
|
31
|
+
* "reside in '**\/generated/**'",
|
|
32
|
+
* (file) => picomatch('**\/generated/**')(file.getFilePath()),
|
|
33
|
+
* globNode({ glob: '**\/generated/**', kind: 'file-path' }),
|
|
34
|
+
* )
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* The `kind` must say what the glob is really matched against, because it
|
|
38
|
+
* selects which paths the glob is checked for satisfiability:
|
|
39
|
+
*
|
|
40
|
+
* - `file-path` — an **absolute** file path. Anchor the glob (`'**\/src/**'`,
|
|
41
|
+
* not `'src/**'`), or it matches nothing.
|
|
42
|
+
* - `parent-dir` — the immediate parent directory of a file.
|
|
43
|
+
* - `import-target` — a resolved module path **or a bare specifier**.
|
|
44
|
+
* Deliberately has no path universe, so `'fastify'` is never reported dead.
|
|
45
|
+
* - `specifier` / `literal` — a string in the source rather than a path.
|
|
46
|
+
*
|
|
47
|
+
* A wrong `kind` is believed: declaring a bare specifier as `file-path` earns a
|
|
48
|
+
* false dead-glob report, and declaring a real path as `import-target` silently
|
|
49
|
+
* exempts it from checking. When unsure, the honest choice is to declare
|
|
50
|
+
* nothing — the same position as before this parameter existed.
|
|
18
51
|
*/
|
|
19
|
-
export declare function definePredicate<T>(description: string, test: (element: T) => boolean): Predicate<T>;
|
|
52
|
+
export declare function definePredicate<T>(description: string, test: (element: T) => boolean, globs?: DeclaredGlobs): Predicate<T>;
|
|
20
53
|
/**
|
|
21
54
|
* Create a custom condition for use in `.should().satisfy()` chains.
|
|
22
55
|
*
|
|
@@ -36,6 +69,15 @@ export declare function definePredicate<T>(description: string, test: (element:
|
|
|
36
69
|
*
|
|
37
70
|
* classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
|
|
38
71
|
* ```
|
|
72
|
+
*
|
|
73
|
+
* ## Declaring a glob
|
|
74
|
+
*
|
|
75
|
+
* As with `definePredicate`, pass `globs` if the condition matches paths against
|
|
76
|
+
* one — see that function for the `kind` values and what a wrong one costs. The
|
|
77
|
+
* difference is what happens next: a **condition**-position glob is deliberately
|
|
78
|
+
* *not* reported when it matches nothing, because a denylist glob matching
|
|
79
|
+
* nothing is indistinguishable from a ban being respected. Declaring it makes it
|
|
80
|
+
* visible to `globs()` and `explain`; it does not make it a finding.
|
|
39
81
|
*/
|
|
40
|
-
export declare function defineCondition<T>(description: string, evaluate: (elements: T[], context: ConditionContext) => ArchViolation[]): Condition<T>;
|
|
82
|
+
export declare function defineCondition<T>(description: string, evaluate: (elements: T[], context: ConditionContext) => ArchViolation[], globs?: DeclaredGlobs): Condition<T>;
|
|
41
83
|
//# sourceMappingURL=define.d.ts.map
|
package/dist/define.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,EAC7B,KAAK,CAAC,EAAE,aAAa,GACpB,SAAS,CAAC,CAAC,CAAC,CAEd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,KAAK,aAAa,EAAE,EACvE,KAAK,CAAC,EAAE,aAAa,GACpB,SAAS,CAAC,CAAC,CAAC,CAEd"}
|
package/dist/define.js
CHANGED
|
@@ -12,9 +12,41 @@
|
|
|
12
12
|
*
|
|
13
13
|
* classes(p).that().satisfy(isAbstract).should().beExported().check()
|
|
14
14
|
* ```
|
|
15
|
+
*
|
|
16
|
+
* ## Declaring a glob
|
|
17
|
+
*
|
|
18
|
+
* If the predicate matches paths against a glob, pass it as `globs` — otherwise
|
|
19
|
+
* `doctor` cannot see it, and a typo'd glob narrows the selection to nothing
|
|
20
|
+
* while reporting a clean bill of health. This is a **selector**-position glob,
|
|
21
|
+
* the one position that is acted on today, which is why the omission was bug
|
|
22
|
+
* 0030's higher-severity half.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const inGeneratedFolder = definePredicate<SourceFile>(
|
|
27
|
+
* "reside in '**\/generated/**'",
|
|
28
|
+
* (file) => picomatch('**\/generated/**')(file.getFilePath()),
|
|
29
|
+
* globNode({ glob: '**\/generated/**', kind: 'file-path' }),
|
|
30
|
+
* )
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* The `kind` must say what the glob is really matched against, because it
|
|
34
|
+
* selects which paths the glob is checked for satisfiability:
|
|
35
|
+
*
|
|
36
|
+
* - `file-path` — an **absolute** file path. Anchor the glob (`'**\/src/**'`,
|
|
37
|
+
* not `'src/**'`), or it matches nothing.
|
|
38
|
+
* - `parent-dir` — the immediate parent directory of a file.
|
|
39
|
+
* - `import-target` — a resolved module path **or a bare specifier**.
|
|
40
|
+
* Deliberately has no path universe, so `'fastify'` is never reported dead.
|
|
41
|
+
* - `specifier` / `literal` — a string in the source rather than a path.
|
|
42
|
+
*
|
|
43
|
+
* A wrong `kind` is believed: declaring a bare specifier as `file-path` earns a
|
|
44
|
+
* false dead-glob report, and declaring a real path as `import-target` silently
|
|
45
|
+
* exempts it from checking. When unsure, the honest choice is to declare
|
|
46
|
+
* nothing — the same position as before this parameter existed.
|
|
15
47
|
*/
|
|
16
|
-
export function definePredicate(description, test) {
|
|
17
|
-
return { description, test };
|
|
48
|
+
export function definePredicate(description, test, globs) {
|
|
49
|
+
return { description, test, globs };
|
|
18
50
|
}
|
|
19
51
|
/**
|
|
20
52
|
* Create a custom condition for use in `.should().satisfy()` chains.
|
|
@@ -35,8 +67,17 @@ export function definePredicate(description, test) {
|
|
|
35
67
|
*
|
|
36
68
|
* classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
|
|
37
69
|
* ```
|
|
70
|
+
*
|
|
71
|
+
* ## Declaring a glob
|
|
72
|
+
*
|
|
73
|
+
* As with `definePredicate`, pass `globs` if the condition matches paths against
|
|
74
|
+
* one — see that function for the `kind` values and what a wrong one costs. The
|
|
75
|
+
* difference is what happens next: a **condition**-position glob is deliberately
|
|
76
|
+
* *not* reported when it matches nothing, because a denylist glob matching
|
|
77
|
+
* nothing is indistinguishable from a ban being respected. Declaring it makes it
|
|
78
|
+
* visible to `globs()` and `explain`; it does not make it a finding.
|
|
38
79
|
*/
|
|
39
|
-
export function defineCondition(description, evaluate) {
|
|
40
|
-
return { description, evaluate };
|
|
80
|
+
export function defineCondition(description, evaluate, globs) {
|
|
81
|
+
return { description, evaluate, globs };
|
|
41
82
|
}
|
|
42
83
|
//# sourceMappingURL=define.js.map
|
package/dist/define.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,IAA6B,EAC7B,KAAqB;IAErB,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,QAAuE,EACvE,KAAqB;IAErB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;AACzC,CAAC"}
|
package/dist/diff-aware.d.ts
CHANGED
|
@@ -10,7 +10,14 @@ import type { ArchViolation } from './violation.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class DiffFilter {
|
|
12
12
|
private readonly changedFiles;
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The branch this filter diffed against, for the suppression notice plan
|
|
15
|
+
* 0071 added. Defaulted rather than required: the constructor is public API
|
|
16
|
+
* (`src/index.ts` exports `DiffFilter`), so an existing
|
|
17
|
+
* `new DiffFilter(files)` must keep compiling.
|
|
18
|
+
*/
|
|
19
|
+
readonly baseBranch: string;
|
|
20
|
+
constructor(changedFiles: Set<string> | null, baseBranch?: string);
|
|
14
21
|
/**
|
|
15
22
|
* Filter violations to only those in changed files.
|
|
16
23
|
* If changedFiles is null (git error), returns all violations unfiltered.
|
package/dist/diff-aware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-aware.d.ts","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"diff-aware.d.ts","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEf,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,GAAE,MAA0B;IAKpF;;;OAGG;IACH,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE;IA0B7D,kEAAkE;IAClE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,UAAU,GAAE,MAAe,GAAG,UAAU,CAiCjE"}
|
package/dist/diff-aware.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { writeStderr } from './stderr.js';
|
|
3
4
|
/**
|
|
4
5
|
* A diff filter that restricts violation reporting to files
|
|
5
6
|
* changed since a base branch.
|
|
@@ -11,8 +12,16 @@ import path from 'node:path';
|
|
|
11
12
|
*/
|
|
12
13
|
export class DiffFilter {
|
|
13
14
|
changedFiles;
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The branch this filter diffed against, for the suppression notice plan
|
|
17
|
+
* 0071 added. Defaulted rather than required: the constructor is public API
|
|
18
|
+
* (`src/index.ts` exports `DiffFilter`), so an existing
|
|
19
|
+
* `new DiffFilter(files)` must keep compiling.
|
|
20
|
+
*/
|
|
21
|
+
baseBranch;
|
|
22
|
+
constructor(changedFiles, baseBranch = 'the base branch') {
|
|
15
23
|
this.changedFiles = changedFiles;
|
|
24
|
+
this.baseBranch = baseBranch;
|
|
16
25
|
}
|
|
17
26
|
/**
|
|
18
27
|
* Filter violations to only those in changed files.
|
|
@@ -22,7 +31,24 @@ export class DiffFilter {
|
|
|
22
31
|
const files = this.changedFiles;
|
|
23
32
|
if (files === null)
|
|
24
33
|
return violations;
|
|
25
|
-
|
|
34
|
+
// Config-level meta-findings (empty selector/discovery) have no changed file
|
|
35
|
+
// to attribute to — never filter them out (ADR-008; plan 0067).
|
|
36
|
+
//
|
|
37
|
+
// Kept from the kernel's copy when the two were unified, because it is the
|
|
38
|
+
// half that says WHY: a `bypassFilters` configuration finding (ADR-010's
|
|
39
|
+
// zero-examined / expired-`.expectEmpty()` violations) has `file: ''` and can
|
|
40
|
+
// never be a member of `changedFiles`. Without this branch, "unsuppressable"
|
|
41
|
+
// was false for the most realistic adoption path — a rule whose instrument
|
|
42
|
+
// silently broke reported nothing at all under `--changed`, with no
|
|
43
|
+
// diagnostic that anything had been hidden.
|
|
44
|
+
// `relatedFiles` (bug 0239): a finding about a RELATIONSHIP concerns several
|
|
45
|
+
// files and can only be reported at one of them. Keeping it when any of them
|
|
46
|
+
// changed is what makes `--changed` honest for it — the alternative measured
|
|
47
|
+
// in 0239 was that the developer who introduced a duplicate saw nothing,
|
|
48
|
+
// because the finding was anchored on the file they had not edited.
|
|
49
|
+
return violations.filter((v) => v.bypassFilters === true ||
|
|
50
|
+
files.has(v.file) ||
|
|
51
|
+
(v.relatedFiles?.some((f) => files.has(f)) ?? false));
|
|
26
52
|
}
|
|
27
53
|
/** Number of changed files detected, or -1 if diff unavailable */
|
|
28
54
|
get size() {
|
|
@@ -53,17 +79,21 @@ export function diffAware(baseBranch = 'main') {
|
|
|
53
79
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
54
80
|
}).trim();
|
|
55
81
|
}
|
|
56
|
-
catch (
|
|
57
|
-
// Not a git repo, or base branch doesn't exist — skip filtering (report
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
catch (error) {
|
|
83
|
+
// Not a git repo, or the base branch doesn't exist — skip filtering (report
|
|
84
|
+
// all violations). The cause is named: "could not run git diff" alone sends
|
|
85
|
+
// a reader looking for a missing branch when the real answer may be that git
|
|
86
|
+
// is not installed, or that the cwd is not a repository at all.
|
|
87
|
+
writeStderr(`[eess] Could not run git diff against '${baseBranch}' ` +
|
|
88
|
+
`(${error instanceof Error ? error.message.split('\n')[0] : String(error)}). ` +
|
|
89
|
+
'All violations will be reported.');
|
|
90
|
+
return new DiffFilter(null, baseBranch);
|
|
61
91
|
}
|
|
62
92
|
if (output === '') {
|
|
63
93
|
// No changes — empty set means nothing is "changed", so all violations are filtered out
|
|
64
|
-
return new DiffFilter(new Set());
|
|
94
|
+
return new DiffFilter(new Set(), baseBranch);
|
|
65
95
|
}
|
|
66
96
|
const changedFiles = new Set(output.split('\n').map((relativePath) => path.resolve(cwd, relativePath)));
|
|
67
|
-
return new DiffFilter(changedFiles);
|
|
97
|
+
return new DiffFilter(changedFiles, baseBranch);
|
|
68
98
|
}
|
|
69
99
|
//# sourceMappingURL=diff-aware.js.map
|
package/dist/diff-aware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-aware.js","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"diff-aware.js","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What diff-aware mode suppressed, said out loud.
|
|
3
|
+
*
|
|
4
|
+
* Plan 0071's first instrument. `--changed` (and `check({ diff })`) filter
|
|
5
|
+
* **reporting**, not evaluation — so a run with every finding suppressed is
|
|
6
|
+
* indistinguishable from a clean run: exit 0, no output, `total: 0`. That is
|
|
7
|
+
* the false green ADR-008 exists to prevent, and it is worse here than
|
|
8
|
+
* elsewhere because the reader chose the flag and then forgot it: CI configures
|
|
9
|
+
* `--changed` once and every subsequent run reads as clean.
|
|
10
|
+
*
|
|
11
|
+
* **The count is derived by the caller, never self-reported.** Each call site
|
|
12
|
+
* computes `before.length - after.length` around its own `filterToChanged`
|
|
13
|
+
* call. `DiffFilter` could keep that tally itself, and it deliberately does
|
|
14
|
+
* not: `CheckOptions.diff` is a structural interface (`DiffFilterLike`), so a
|
|
15
|
+
* consumer can pass any object with a `filterToChanged` method, and a filter
|
|
16
|
+
* that under-reports its own suppression would produce exactly the silence
|
|
17
|
+
* this module exists to break. Caller-side subtraction is a differently-derived
|
|
18
|
+
* value (ADR-009 rule 5) and it works for filters we did not write.
|
|
19
|
+
*
|
|
20
|
+
* **Two surfaces, because only one of them can count.**
|
|
21
|
+
*
|
|
22
|
+
* | surface | disclosure |
|
|
23
|
+
* | ---------------------------------------- | -------------------- |
|
|
24
|
+
* | CLI `check --changed` | `suppressionNotice` |
|
|
25
|
+
* | `checkAll([...])` | `suppressionNotice` |
|
|
26
|
+
* | `.check({ diff })` / `.warn({ diff })` | `activeNotice`, once |
|
|
27
|
+
*
|
|
28
|
+
* The first two filter once over every collected violation, so they know the
|
|
29
|
+
* whole number. The per-rule terminals do not: `filterToChanged` is called
|
|
30
|
+
* once per rule (`execute-rule.ts`), and a diff-aware suite with 79 rules
|
|
31
|
+
* would print 79 lines — on the channel v0.26.0 made unconditionally visible.
|
|
32
|
+
* There is no per-run aggregation point on that path, so those terminals state
|
|
33
|
+
* the **configuration** instead, once per process. A configuration statement
|
|
34
|
+
* cannot be wrong; a per-rule count presented as a run total would be.
|
|
35
|
+
*
|
|
36
|
+
* A `process.on('exit')` flush was considered and rejected: it would let the
|
|
37
|
+
* per-rule path print one complete total, but a library that installs an exit
|
|
38
|
+
* hook to deliver a correctness-relevant message has made that message
|
|
39
|
+
* dependent on stderr surviving process teardown — untestable in the shape the
|
|
40
|
+
* rest of this project asserts (a real child `vitest run`, bug 0024) without
|
|
41
|
+
* asserting a race. Stating the configuration is weaker and honest.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* One line naming what was suppressed, or `undefined` when nothing was.
|
|
45
|
+
*
|
|
46
|
+
* `undefined` rather than an empty string so a call site cannot print a blank
|
|
47
|
+
* line by forgetting to check — the two callers both feed this straight into
|
|
48
|
+
* `writeStderr`.
|
|
49
|
+
*
|
|
50
|
+
* @param suppressed - `before.length - after.length`, computed by the caller.
|
|
51
|
+
* @param changedFiles - How many files the filter considered changed, when the
|
|
52
|
+
* filter exposes it. A user-supplied `DiffFilterLike` need not, in which case
|
|
53
|
+
* the sentence drops that clause rather than guessing.
|
|
54
|
+
* @param baseBranch - The branch diffed against, when known.
|
|
55
|
+
*/
|
|
56
|
+
export declare function suppressionNotice(suppressed: number, changedFiles?: number, baseBranch?: string): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* State that diff-aware filtering is on, once per process. Returns `undefined`
|
|
59
|
+
* on every call after the first, and whenever nothing was suppressed.
|
|
60
|
+
*
|
|
61
|
+
* Keyed on a call that actually suppressed something: a diff-aware run whose
|
|
62
|
+
* findings all happen to be in changed files has hidden nothing, and a notice
|
|
63
|
+
* there would train the reader to skip the line that matters.
|
|
64
|
+
*/
|
|
65
|
+
export declare function activeNotice(suppressed: number, changedFiles?: number, baseBranch?: string): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Forget that {@link activeNotice} has spoken. **Tests only** — a suite
|
|
68
|
+
* asserting the once-per-process contract has to observe the first call, and
|
|
69
|
+
* whichever earlier test filtered first would otherwise have consumed it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resetDiffDisclosureForTests(): void;
|
|
72
|
+
//# sourceMappingURL=diff-disclosure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-disclosure.d.ts","sourceRoot":"","sources":["../src/diff-disclosure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAkBpB;AAYD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAepB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|