@nielspeter/eess 0.2.1 → 0.4.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 +3 -3
- 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.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 +65 -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/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 +6 -0
- package/dist/condition.d.ts.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 +27 -5
- package/dist/correspondence.d.ts.map +1 -1
- package/dist/correspondence.js +39 -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 +74 -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 -0
- package/dist/diff-aware.d.ts.map +1 -1
- package/dist/diff-aware.js +11 -2
- 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/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute-rule.d.ts +11 -5
- package/dist/execute-rule.d.ts.map +1 -1
- package/dist/execute-rule.js +87 -19
- package/dist/execute-rule.js.map +1 -1
- 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 +83 -1
- package/dist/format-json.d.ts.map +1 -1
- package/dist/format-json.js +70 -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 +40 -8
- 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 +41 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- 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 +77 -0
- package/dist/owns-empty-discovery.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 +33 -0
- package/dist/predicate.d.ts.map +1 -1
- package/dist/preset-dispatch.d.ts +19 -0
- package/dist/preset-dispatch.d.ts.map +1 -1
- package/dist/preset-dispatch.js +43 -3
- 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 +11 -5
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +17 -0
- package/dist/report.js.map +1 -1
- package/dist/rule-builder-like.d.ts +11 -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 +109 -84
- package/dist/rule-builder.d.ts.map +1 -1
- package/dist/rule-builder.js +202 -158
- package/dist/rule-builder.js.map +1 -1
- package/dist/rule-description.d.ts +33 -1
- package/dist/rule-description.d.ts.map +1 -1
- package/dist/rule-description.js +17 -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 +47 -0
- package/dist/selection-memo.d.ts.map +1 -0
- package/dist/selection-memo.js +68 -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/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 +209 -9
- package/dist/terminal-builder.d.ts.map +1 -1
- package/dist/terminal-builder.js +264 -20
- 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 +55 -0
- package/dist/vacuity-findings.d.ts.map +1 -0
- package/dist/vacuity-findings.js +124 -0
- package/dist/vacuity-findings.js.map +1 -0
- package/dist/violation.d.ts +152 -4
- package/dist/violation.d.ts.map +1 -1
- package/dist/violation.js +262 -1
- package/dist/violation.js.map +1 -1
- package/package.json +1 -1
package/dist/rule-builder.js
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { assertionAdviceFor } from './assertion-advice.js';
|
|
2
|
+
import { countDeclaredGlobs, stampGlobs } from './glob-site.js';
|
|
3
|
+
import { TerminalBuilder, assertionLessViolation } from './terminal-builder.js';
|
|
4
|
+
import { assertsCardinality as conditionAssertsCardinality } from './cardinality.js';
|
|
5
|
+
/**
|
|
6
|
+
* A declared glob's own label in a dead-glob finding — the predicate/
|
|
7
|
+
* condition's description, disambiguated with the literal glob only when
|
|
8
|
+
* more than one site shares the description (a variadic predicate like
|
|
9
|
+
* `importFrom(...globs)` already spells every glob into its own
|
|
10
|
+
* description, so a substring test would collapse the one case this exists
|
|
11
|
+
* to separate — keyed on the site COUNT instead).
|
|
12
|
+
*/
|
|
13
|
+
function describeOrigin(description, glob, siteCount) {
|
|
14
|
+
return siteCount > 1 ? `${description} ("${glob.glob}")` : description;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The actual walk `RuleBuilder.globs()` delegates to — a free function
|
|
18
|
+
* rather than a method body, to keep the class itself under this repo's own
|
|
19
|
+
* 300-line class-length gate (`arch.internal.rules.ts`).
|
|
20
|
+
*/
|
|
21
|
+
function declaredGlobsOf(predicates, conditions) {
|
|
22
|
+
const trees = [];
|
|
23
|
+
for (const predicate of predicates) {
|
|
24
|
+
if (predicate.globs) {
|
|
25
|
+
const count = countDeclaredGlobs(predicate.globs);
|
|
26
|
+
trees.push(stampGlobs(predicate.globs, 'selector', (g) => describeOrigin(predicate.description, g, count)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const condition of conditions) {
|
|
30
|
+
if (condition.globs) {
|
|
31
|
+
const count = countDeclaredGlobs(condition.globs);
|
|
32
|
+
trees.push(stampGlobs(condition.globs, 'condition', (g) => describeOrigin(condition.description, g, count)));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return trees;
|
|
36
|
+
}
|
|
3
37
|
/**
|
|
4
38
|
* Abstract base class for all rule builders.
|
|
5
39
|
*
|
|
@@ -9,29 +43,46 @@ import { executeCheck, executeWarn, applyFilters } from './execute-rule.js';
|
|
|
9
43
|
* 3. Add condition methods that call `addCondition()`
|
|
10
44
|
*
|
|
11
45
|
* The builder accumulates predicates and conditions. Nothing executes
|
|
12
|
-
* until a terminal method (`.check()`, `.warn()`, `.severity()`) is called
|
|
46
|
+
* until a terminal method (`.check()`, `.warn()`, `.severity()`) is called —
|
|
47
|
+
* inherited from {@link TerminalBuilder} (plan 0088 Phase 4), which also
|
|
48
|
+
* supplies `because`/`rule`/`excluding`/`violations`/`expectEmpty`/
|
|
49
|
+
* `expectNonEmpty` and the ADR-010 evidence gate. `RuleBuilder` keeps its own
|
|
50
|
+
* `<T, P>` two-param generic — `P` is the dialect's project type, kept
|
|
51
|
+
* separate from `TerminalBuilder` (which has no project concept, since
|
|
52
|
+
* `correspondence()`, `SmellBuilder`, and the graphql builders don't share a
|
|
53
|
+
* uniform notion of "one project") — every builder across all five dialects
|
|
54
|
+
* depends on this exact two-param signature, so it is not replaced by the
|
|
55
|
+
* fold, only extended.
|
|
13
56
|
*/
|
|
14
|
-
export class RuleBuilder {
|
|
57
|
+
export class RuleBuilder extends TerminalBuilder {
|
|
15
58
|
project;
|
|
16
59
|
_predicates = [];
|
|
17
60
|
_conditions = [];
|
|
18
|
-
_reason;
|
|
19
|
-
_metadata;
|
|
20
|
-
_exclusions = [];
|
|
21
|
-
_silentIndices = new Set();
|
|
22
61
|
_phase = 'predicate';
|
|
62
|
+
/** Whether `.should()` was ever reached — bug 0155 state 3/4 vs state 1. */
|
|
63
|
+
_reachedShould = false;
|
|
64
|
+
/**
|
|
65
|
+
* Descriptions of predicate-only methods used AFTER `.should()` — bug 0155
|
|
66
|
+
* state 2. A dual-use method dispatches to a condition in that phase and
|
|
67
|
+
* never lands here, so anything recorded is genuinely a filter written where
|
|
68
|
+
* an assertion was meant.
|
|
69
|
+
*/
|
|
70
|
+
_misplaced = [];
|
|
23
71
|
constructor(project) {
|
|
72
|
+
super();
|
|
24
73
|
this.project = project;
|
|
25
74
|
}
|
|
26
75
|
// --- Chain methods (grammar transitions) ---
|
|
27
76
|
/**
|
|
28
|
-
* Begin the predicate phase. Returns
|
|
77
|
+
* Begin the predicate phase. Returns a COPY — a held selection is never
|
|
78
|
+
* edited by narrowing it (see `TerminalBuilder.copy()`).
|
|
29
79
|
* Purely a readability marker — `.that().haveNameMatching(...)` reads like English.
|
|
30
80
|
* Explicitly resets phase to 'predicate' — defensive against `.should().that()` misuse.
|
|
31
81
|
*/
|
|
32
82
|
that() {
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
const next = this.copy();
|
|
84
|
+
next._phase = 'predicate';
|
|
85
|
+
return next;
|
|
35
86
|
}
|
|
36
87
|
/**
|
|
37
88
|
* Add another predicate (AND). Returns `this` for chaining.
|
|
@@ -48,6 +99,7 @@ export class RuleBuilder {
|
|
|
48
99
|
should() {
|
|
49
100
|
const fork = this.fork();
|
|
50
101
|
fork._phase = 'condition';
|
|
102
|
+
fork._reachedShould = true;
|
|
51
103
|
return fork;
|
|
52
104
|
}
|
|
53
105
|
/**
|
|
@@ -72,70 +124,11 @@ export class RuleBuilder {
|
|
|
72
124
|
}
|
|
73
125
|
return this.addCondition(custom);
|
|
74
126
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this._reason = reason;
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Attach rich metadata to the rule.
|
|
85
|
-
* Provides educational context in violation output: why, how to fix, docs link.
|
|
86
|
-
*
|
|
87
|
-
* If `metadata.because` is set, it also sets the reason (same as `.because()`).
|
|
88
|
-
*/
|
|
89
|
-
rule(metadata) {
|
|
90
|
-
this._metadata = metadata;
|
|
91
|
-
if (metadata.because) {
|
|
92
|
-
this._reason = metadata.because;
|
|
93
|
-
}
|
|
94
|
-
return this;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Exclude specific violations from reporting by matching against
|
|
98
|
-
* the violation's `element`, `file`, or `message` fields.
|
|
99
|
-
*
|
|
100
|
-
* Matched violations are silently suppressed. Use for permanent,
|
|
101
|
-
* intentional exceptions — not for temporary violations (use baseline for those).
|
|
102
|
-
*
|
|
103
|
-
* Patterns are matched against all three fields. Prefer anchored regexes
|
|
104
|
-
* or full string matches over short substrings, especially for `message`
|
|
105
|
-
* matching, to avoid accidentally suppressing unrelated violations whose
|
|
106
|
-
* messages happen to contain the same text.
|
|
107
|
-
*
|
|
108
|
-
* Emits a warning if an exclusion matches zero violations — so renamed
|
|
109
|
-
* or deleted exceptions don't silently stay in the rule.
|
|
110
|
-
*
|
|
111
|
-
* For narrowing a rule's scope at the predicate phase (so the rule never
|
|
112
|
-
* evaluates the excluded element), use `satisfy(not(<predicate>))` instead.
|
|
113
|
-
* See the "Excluding a file from a rule's scope" recipe in docs/recipes.md.
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* // Exclude by element name (fully qualified)
|
|
117
|
-
* .excluding('Asset.getImageUrl')
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* // Exclude by file path (regex anchored to suffix)
|
|
121
|
-
* .excluding(/repositories\/index\.ts$/)
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* // Multiple exclusions, mixed forms
|
|
125
|
-
* .excluding('Asset.getImageUrl', /\/legacy\//, /generated/)
|
|
126
|
-
*/
|
|
127
|
-
excluding(...patterns) {
|
|
128
|
-
for (const p of patterns) {
|
|
129
|
-
if (isSilent(p)) {
|
|
130
|
-
this._exclusions.push(p.pattern);
|
|
131
|
-
this._silentIndices.add(this._exclusions.length - 1);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
this._exclusions.push(p);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return this;
|
|
138
|
-
}
|
|
127
|
+
// `because`, `rule`, `excluding`, `violations`, `check`, `warn`, `severity`,
|
|
128
|
+
// `expectEmpty`, `expectNonEmpty` are inherited from `TerminalBuilder`
|
|
129
|
+
// (plan 0088 Phase 4) — this class used to duplicate all of them, and the
|
|
130
|
+
// two copies had already drifted (the exact hazard `ts-archunit`'s own
|
|
131
|
+
// `RuleBuilder`/`TerminalBuilder` merge commit documents).
|
|
139
132
|
// --- Terminal methods ---
|
|
140
133
|
/**
|
|
141
134
|
* Return the predicate-filtered elements as a labelled `Selection<T>` for
|
|
@@ -164,96 +157,133 @@ export class RuleBuilder {
|
|
|
164
157
|
};
|
|
165
158
|
}
|
|
166
159
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
* Execute the rule and throw `ArchRuleError` if any violations are found.
|
|
181
|
-
* This is the primary terminal method — use in test assertions.
|
|
160
|
+
* ADR-010: is this rule's assertion satisfied by an empty selection, as a
|
|
161
|
+
* property of what its conditions assert (`.notExist()`) rather than a
|
|
162
|
+
* caller's `.expectEmpty()` declaration?
|
|
163
|
+
*
|
|
164
|
+
* `.every()`, not `.some()` — `.andShould()` ANDs, so a rule reading
|
|
165
|
+
* `.should().notExist().andShould().beExported()` still asserts something
|
|
166
|
+
* about subjects that exist, and exempting it on the strength of the one
|
|
167
|
+
* cardinality condition would silence the other. An empty condition list is
|
|
168
|
+
* NOT exempt: `[].every()` is vacuously `true`, and a rule with zero
|
|
169
|
+
* conditions is the assertion-less case `collectViolations()` now reports as
|
|
170
|
+
* a configuration finding (bug 0155; it used to be an unreachable stderr
|
|
171
|
+
* warning, which this comment named until that fix landed) — this override
|
|
172
|
+
* must not paper over it by also declaring it cardinality-satisfied.
|
|
182
173
|
*
|
|
183
|
-
*
|
|
174
|
+
* **Load-bearing:** for a DEAD selector this early return is the only thing
|
|
175
|
+
* between an assertion-less rule and a silent pass (the gate runs after the
|
|
176
|
+
* zero-examined branch). Pinned in `assertion-less-rules.test.ts`.
|
|
184
177
|
*/
|
|
185
|
-
check(options) {
|
|
186
|
-
const violations = this.evaluate();
|
|
187
|
-
executeCheck(violations, {
|
|
188
|
-
reason: this._reason,
|
|
189
|
-
metadata: this._metadata,
|
|
190
|
-
exclusions: this._exclusions,
|
|
191
|
-
silentIndices: this._silentIndices,
|
|
192
|
-
}, options);
|
|
193
|
-
}
|
|
194
178
|
/**
|
|
195
|
-
*
|
|
196
|
-
* Use for rules that should warn but not fail CI.
|
|
179
|
+
* Does this rule assert anything at all? — bug 0155.
|
|
197
180
|
*
|
|
198
|
-
*
|
|
181
|
+
* Conditions alone are not enough: a predicate-only method written *after*
|
|
182
|
+
* `.should()` silently shrank the set the conditions run over, possibly to
|
|
183
|
+
* empty, in which case they hold vacuously. That rule asserts nothing
|
|
184
|
+
* meaningful even though `_conditions` is non-empty.
|
|
199
185
|
*/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
186
|
+
assertsSomething() {
|
|
187
|
+
return this._conditions.length > 0 && this._misplaced.length === 0;
|
|
188
|
+
}
|
|
189
|
+
assertionAdvice() {
|
|
190
|
+
return assertionAdviceFor({
|
|
191
|
+
reachedShould: this._reachedShould,
|
|
192
|
+
misplaced: this._misplaced,
|
|
193
|
+
conditionCount: this._conditions.length,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
assertsCardinality() {
|
|
197
|
+
if (this._conditions.length === 0)
|
|
198
|
+
return false;
|
|
199
|
+
return this._conditions.every((condition) => conditionAssertsCardinality(condition));
|
|
208
200
|
}
|
|
209
201
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
202
|
+
* ADR-010 part 3: did this rule's own underlying source (the project, a
|
|
203
|
+
* diagram, an SDL string — whatever `P` loads) produce nothing at all,
|
|
204
|
+
* before `getElements()`'s own domain-specific extraction or any
|
|
205
|
+
* predicate ran? Default `false` — the kernel has no way to know, since
|
|
206
|
+
* `P` is opaque here. A dialect's concrete builder, which knows `P`'s real
|
|
207
|
+
* shape (e.g. `ArchProject`), overrides this to check its own project's
|
|
208
|
+
* file count directly — never `getElements().length`, which conflates "the
|
|
209
|
+
* source is empty" with "this domain legitimately has nothing in a
|
|
210
|
+
* healthy project" (e.g. zero JSX elements in a backend-only codebase).
|
|
213
211
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
212
|
+
sourceEmpty() {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
/** Declared globs, position derived from `_phase`. Pure — no `P` needed. */
|
|
216
|
+
globs() {
|
|
217
|
+
return declaredGlobsOf(this._predicates, this._conditions);
|
|
218
|
+
}
|
|
219
|
+
/** This rule's own project, for `diagnose()`/`doctor` outside `.check()`. */
|
|
220
|
+
getProject() {
|
|
221
|
+
return this.project;
|
|
222
|
+
}
|
|
223
|
+
/** A `globs()` tree diagnosably dead, when zero examined — same shape as `sourceEmpty()`. */
|
|
224
|
+
deadGlobDiagnosis() {
|
|
225
|
+
return undefined;
|
|
221
226
|
}
|
|
222
227
|
// --- Protected: for subclasses ---
|
|
223
228
|
/**
|
|
224
229
|
* Register a predicate. Called by concrete builder methods like
|
|
225
|
-
* `.haveNameMatching()`, `.extend()`, etc.
|
|
230
|
+
* `.haveNameMatching()`, `.extend()`, etc. Returns a COPY — see `copy()`.
|
|
226
231
|
*/
|
|
227
232
|
addPredicate(predicate) {
|
|
228
|
-
this.
|
|
229
|
-
|
|
233
|
+
const next = this.copy();
|
|
234
|
+
next._predicates.push(predicate);
|
|
235
|
+
// Bug 0155 state 2: a predicate-only method used after `.should()`. Dual-use
|
|
236
|
+
// methods dispatch to conditions in that phase and never reach here, so this
|
|
237
|
+
// is a filter written where an assertion was meant — the one state whose fix
|
|
238
|
+
// is "move it before .should()", not "add a condition".
|
|
239
|
+
if (next._phase === 'condition')
|
|
240
|
+
next._misplaced.push(predicate.description);
|
|
241
|
+
return next;
|
|
230
242
|
}
|
|
231
243
|
/**
|
|
232
244
|
* Register a condition. Called by concrete builder methods like
|
|
233
|
-
* `.notContain()`, `.notExist()`, etc.
|
|
245
|
+
* `.notContain()`, `.notExist()`, etc. Returns a COPY — see `copy()`.
|
|
234
246
|
*/
|
|
235
247
|
addCondition(condition) {
|
|
236
|
-
this.
|
|
237
|
-
|
|
248
|
+
const next = this.copy();
|
|
249
|
+
next._conditions.push(condition);
|
|
250
|
+
return next;
|
|
238
251
|
}
|
|
239
252
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
253
|
+
* An independent copy, carrying **both** predicates and conditions.
|
|
254
|
+
* Extends `TerminalBuilder.copy()` with the fields this class adds.
|
|
242
255
|
*
|
|
243
|
-
* Subclasses with additional
|
|
256
|
+
* Subclasses with additional mutable-reference fields of their own (arrays,
|
|
257
|
+
* sets, maps a chain method pushes onto) MUST override this and call
|
|
258
|
+
* `super.copy()` first, the same reason `TerminalBuilder.copy()`'s own
|
|
259
|
+
* docstring gives — `Object.assign` shallow-copies a field, it does not
|
|
260
|
+
* give it independent identity.
|
|
261
|
+
*/
|
|
262
|
+
copy() {
|
|
263
|
+
const clone = super.copy();
|
|
264
|
+
clone._predicates = [...this._predicates];
|
|
265
|
+
clone._conditions = [...this._conditions];
|
|
266
|
+
clone._misplaced = [...this._misplaced];
|
|
267
|
+
return clone;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* A fork of this builder, carrying BOTH lists. Used by `.should()` to support
|
|
271
|
+
* named selections without mutation.
|
|
272
|
+
*
|
|
273
|
+
* **Nothing here clears the conditions** (bug 0156). It used to, and a second
|
|
274
|
+
* `.should()` therefore silently discarded the first assertion — a rule that
|
|
275
|
+
* asserted something turned into one that asserted less, by a chain method.
|
|
276
|
+
* `.should().X().should().Y()` now accumulates exactly as `.andShould()` does.
|
|
277
|
+
*
|
|
278
|
+
* Ported from `packages/ts/src/core/rule-builder.ts`, where the engine copy
|
|
279
|
+
* landed the fix and left the kernel behind. Found by the architect review of
|
|
280
|
+
* PR #72: `eess-md`, `eess-mermaid` and `eess-gherkin` all extend THIS class,
|
|
281
|
+
* so they carried the defect — and `check:corpus`, `check:ledger` and
|
|
282
|
+
* `check:diagram` are md/mermaid gates, meaning this repo's own corpus
|
|
283
|
+
* enforcement ran on the broken copy.
|
|
244
284
|
*/
|
|
245
285
|
fork() {
|
|
246
|
-
|
|
247
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
248
|
-
const proto = Object.getPrototypeOf(this);
|
|
249
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
250
|
-
const fork = Object.create(proto);
|
|
251
|
-
Object.assign(fork, this);
|
|
252
|
-
fork._predicates = [...this._predicates];
|
|
253
|
-
fork._conditions = [];
|
|
254
|
-
fork._exclusions = [...this._exclusions];
|
|
255
|
-
fork._silentIndices = new Set(this._silentIndices);
|
|
256
|
-
fork._metadata = this._metadata ? { ...this._metadata } : undefined;
|
|
286
|
+
const fork = this.copy();
|
|
257
287
|
fork._reason = fork._metadata?.because ?? this._reason;
|
|
258
288
|
return fork;
|
|
259
289
|
}
|
|
@@ -272,28 +302,42 @@ export class RuleBuilder {
|
|
|
272
302
|
return parts.join(' ');
|
|
273
303
|
}
|
|
274
304
|
/**
|
|
275
|
-
* Execute the full pipeline: filter elements with predicates,
|
|
276
|
-
*
|
|
305
|
+
* Execute the full pipeline: filter elements with predicates, evaluate
|
|
306
|
+
* conditions, return violations plus the ADR-010 evidence — the
|
|
307
|
+
* post-predicate subject count, whatever it is. `TerminalBuilder.check()` /
|
|
308
|
+
* `.warn()` / `.violations()` decide what a zero count means (a
|
|
309
|
+
* configuration finding, unless `.expectEmpty()` was declared); this method
|
|
310
|
+
* only reports the count, never interprets it.
|
|
311
|
+
*
|
|
312
|
+
* `sourceEmpty` (ADR-010 part 3) is deliberately not `allElements.length
|
|
313
|
+
* === 0` — a domain's own `getElements()` legitimately returning nothing in
|
|
314
|
+
* a healthy, fully-loaded project (e.g. zero JSX elements in a
|
|
315
|
+
* backend-only codebase) is not brokenness, and conflating the two broke
|
|
316
|
+
* `.notExist()`'s cardinality exemption for exactly that case. Subclasses
|
|
317
|
+
* that can answer "did my underlying source load anything at all" override
|
|
318
|
+
* `sourceEmpty()`; the default assumes it did.
|
|
277
319
|
*/
|
|
278
|
-
|
|
279
|
-
// Step 1: Get all elements from the concrete builder
|
|
320
|
+
collectViolations() {
|
|
280
321
|
const allElements = this.getElements();
|
|
281
|
-
// Step 2: Filter with predicates (AND — all predicates must match)
|
|
282
322
|
const filtered = allElements.filter((element) => this._predicates.every((predicate) => predicate.test(element)));
|
|
283
|
-
|
|
323
|
+
const examined = filtered.length;
|
|
284
324
|
if (filtered.length === 0) {
|
|
285
|
-
|
|
325
|
+
const sourceEmpty = this.sourceEmpty();
|
|
326
|
+
// Skip the diagnosis (a real filesystem walk, in a dialect's
|
|
327
|
+
// override) when the project itself is already known empty — nothing
|
|
328
|
+
// useful to say about one glob's fate against zero loaded files, and
|
|
329
|
+
// `sourceEmpty` outranks it in `evidencedViolations()` regardless.
|
|
330
|
+
const deadGlob = sourceEmpty ? undefined : this.deadGlobDiagnosis();
|
|
331
|
+
return { violations: [], examined, sourceEmpty, deadGlob };
|
|
286
332
|
}
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
333
|
+
// Bug 0155 — gate-first, before the conditions run. See
|
|
334
|
+
// `assertionLessViolation` for why this is a finding and not a warning,
|
|
335
|
+
// and why there is no `_phase` term.
|
|
336
|
+
// `_expectEmpty === undefined`: a declared emptiness expectation is an
|
|
337
|
+
// assertion too — see `assertionLessViolation`.
|
|
338
|
+
if (!this.assertsSomething() && this._expectEmpty === undefined) {
|
|
291
339
|
const ruleId = this._metadata?.id ?? (this.buildRuleDescription() || 'unnamed');
|
|
292
|
-
|
|
293
|
-
`Did you use a predicate-only method after .should()? ` +
|
|
294
|
-
`Predicate-only methods (e.g. areExported, areAsync) filter elements; ` +
|
|
295
|
-
`use a condition method or .satisfy() after .should().`);
|
|
296
|
-
return [];
|
|
340
|
+
return { violations: [assertionLessViolation(ruleId, this.assertionAdvice())], examined };
|
|
297
341
|
}
|
|
298
342
|
// Step 4: Build context for conditions
|
|
299
343
|
const context = this.buildConditionContext();
|
|
@@ -302,7 +346,7 @@ export class RuleBuilder {
|
|
|
302
346
|
for (const condition of this._conditions) {
|
|
303
347
|
violations.push(...condition.evaluate(filtered, context));
|
|
304
348
|
}
|
|
305
|
-
return violations;
|
|
349
|
+
return { violations, examined };
|
|
306
350
|
}
|
|
307
351
|
/**
|
|
308
352
|
* Build the `ConditionContext` passed to each condition.
|
package/dist/rule-builder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-builder.js","sourceRoot":"","sources":["../src/rule-builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rule-builder.js","sourceRoot":"","sources":["../src/rule-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,39 @@ export interface RuleDescription {
|
|
|
8
8
|
because?: string;
|
|
9
9
|
suggestion?: string;
|
|
10
10
|
docs?: string;
|
|
11
|
-
/**
|
|
11
|
+
/** Imperative "Do NOT … / MUST …" sentence for agent system prompts. */
|
|
12
12
|
imperative?: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* A rule that can describe itself.
|
|
16
|
+
*
|
|
17
|
+
* `RuleBuilderLike` declares only `violations()` — deliberately, so a caller can
|
|
18
|
+
* pass a plain object — while every builder in `src/` implements `describeRule()`.
|
|
19
|
+
* So reading a rule's id or remedy needs a narrowing, and that narrowing existed
|
|
20
|
+
* in **three** places with two different implementations: `cli/commands/explain.ts`
|
|
21
|
+
* and two test files.
|
|
22
|
+
*
|
|
23
|
+
* One owner, because a duplicated predicate is not a style problem here:
|
|
24
|
+
* [ts-archunit bug 0044](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0044-an-inline-exclusion-comment-has-no-feedback-channel.md)
|
|
25
|
+
* was a measurement error caused by exactly that, and the fix was to delete the
|
|
26
|
+
* duplicate rather than to test both copies.
|
|
27
|
+
*
|
|
28
|
+
* `DiagnosableRule` and `orphanExclusions` are NOT copies of this: they declare
|
|
29
|
+
* `describeRule?` as an optional structural member of their own input type, which
|
|
30
|
+
* is a different mechanism and correct where it is used.
|
|
31
|
+
*/
|
|
32
|
+
export interface Describable {
|
|
33
|
+
describeRule: () => RuleDescription;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Does this value describe itself?
|
|
37
|
+
*
|
|
38
|
+
* Cast-free, and that is the point: the version in `explain.ts` read
|
|
39
|
+
* `(value as Record<string, unknown>)['describeRule']`, an ADR-005 breach in
|
|
40
|
+
* shipped source with no `eslint-disable` and no interop boundary to justify it.
|
|
41
|
+
* It was also unnecessary — once `value` is narrowed to `object`, `'describeRule'
|
|
42
|
+
* in value` narrows enough for the property access to compile, which is what the
|
|
43
|
+
* two test copies already did.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isDescribable(value: unknown): value is Describable;
|
|
14
46
|
//# sourceMappingURL=rule-description.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-description.d.ts","sourceRoot":"","sources":["../src/rule-description.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,
|
|
1
|
+
{"version":3,"file":"rule-description.d.ts","sourceRoot":"","sources":["../src/rule-description.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,eAAe,CAAA;CACpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAIlE"}
|
package/dist/rule-description.js
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Does this value describe itself?
|
|
3
|
+
*
|
|
4
|
+
* Cast-free, and that is the point: the version in `explain.ts` read
|
|
5
|
+
* `(value as Record<string, unknown>)['describeRule']`, an ADR-005 breach in
|
|
6
|
+
* shipped source with no `eslint-disable` and no interop boundary to justify it.
|
|
7
|
+
* It was also unnecessary — once `value` is narrowed to `object`, `'describeRule'
|
|
8
|
+
* in value` narrows enough for the property access to compile, which is what the
|
|
9
|
+
* two test copies already did.
|
|
10
|
+
*/
|
|
11
|
+
export function isDescribable(value) {
|
|
12
|
+
if (typeof value !== 'object' || value === null)
|
|
13
|
+
return false;
|
|
14
|
+
if (!('describeRule' in value))
|
|
15
|
+
return false;
|
|
16
|
+
return typeof value.describeRule === 'function';
|
|
17
|
+
}
|
|
2
18
|
//# sourceMappingURL=rule-description.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-description.js","sourceRoot":"","sources":["../src/rule-description.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"rule-description.js","sourceRoot":"","sources":["../src/rule-description.ts"],"names":[],"mappings":"AAoCA;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7D,IAAI,CAAC,CAAC,cAAc,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC5C,OAAO,OAAO,KAAK,CAAC,YAAY,KAAK,UAAU,CAAA;AACjD,CAAC"}
|
package/dist/rule-metadata.d.ts
CHANGED
|
@@ -14,10 +14,27 @@ export interface RuleMetadata {
|
|
|
14
14
|
/** Link to documentation — ADR, wiki, style guide */
|
|
15
15
|
docs?: string;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Imperative one-liner for AI-agent system prompts, e.g.
|
|
18
|
+
* "Do NOT throw new Error() in repositories". Surfaced by
|
|
19
|
+
* `explain --format agent`. When unset, a heuristic is derived from the
|
|
20
|
+
* rule's predicate/condition description.
|
|
20
21
|
*/
|
|
21
22
|
imperative?: string;
|
|
23
|
+
/**
|
|
24
|
+
* How the AUTHOR of this rule spells "I declare this empty" — plan 0099.
|
|
25
|
+
*
|
|
26
|
+
* Set by whoever constructs the rule, because only they know. A preset sets
|
|
27
|
+
* `expectEmpty: ['<id>']` because that is what its user can type; a builder
|
|
28
|
+
* author leaves it unset and gets `.expectEmpty()`.
|
|
29
|
+
*
|
|
30
|
+
* Core used to derive this from the rule id starting with `preset/`, which it
|
|
31
|
+
* cannot verify: that is false for a hand-written `.rule({ id: 'preset/...' })`,
|
|
32
|
+
* for a third-party preset that never extended `PresetBaseOptions`, and for a
|
|
33
|
+
* preset that forwards `overrides` but not `expectEmpty`. A prefix is a naming
|
|
34
|
+
* convention, not a capability, and the remedy has to name a call the reader
|
|
35
|
+
* can actually make ([ADR-009](../../../adr/009-agent-first-failure-surfaces.md)
|
|
36
|
+
* rule 2).
|
|
37
|
+
*/
|
|
38
|
+
declarationSpelling?: string;
|
|
22
39
|
}
|
|
23
40
|
//# sourceMappingURL=rule-metadata.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-metadata.d.ts","sourceRoot":"","sources":["../src/rule-metadata.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,EAAE,CAAC,EAAE,MAAM,CAAA;IAEX,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb
|
|
1
|
+
{"version":3,"file":"rule-metadata.d.ts","sourceRoot":"","sources":["../src/rule-metadata.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,EAAE,CAAC,EAAE,MAAM,CAAA;IAEX,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B"}
|