@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RuleMetadata } from './rule-metadata.js';
|
|
1
2
|
/**
|
|
2
3
|
* Structured description of a rule, returned by `.describe()`.
|
|
3
4
|
* Used by the `explain` CLI subcommand to dump active rules as JSON.
|
|
@@ -8,7 +9,88 @@ export interface RuleDescription {
|
|
|
8
9
|
because?: string;
|
|
9
10
|
suggestion?: string;
|
|
10
11
|
docs?: string;
|
|
11
|
-
/**
|
|
12
|
+
/** Imperative "Do NOT … / MUST …" sentence for agent system prompts. */
|
|
12
13
|
imperative?: string;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A rule that can describe itself.
|
|
17
|
+
*
|
|
18
|
+
* `RuleBuilderLike` declares only `violations()` — deliberately, so a caller can
|
|
19
|
+
* pass a plain object — while every builder in `src/` implements `describeRule()`.
|
|
20
|
+
* So reading a rule's id or remedy needs a narrowing, and that narrowing existed
|
|
21
|
+
* in **three** places with two different implementations: `cli/commands/explain.ts`
|
|
22
|
+
* and two test files.
|
|
23
|
+
*
|
|
24
|
+
* One owner, because a duplicated predicate is not a style problem here:
|
|
25
|
+
* [ts-archunit bug 0044](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0044-an-inline-exclusion-comment-has-no-feedback-channel.md)
|
|
26
|
+
* was a measurement error caused by exactly that, and the fix was to delete the
|
|
27
|
+
* duplicate rather than to test both copies.
|
|
28
|
+
*
|
|
29
|
+
* `DiagnosableRule` and `orphanExclusions` are NOT copies of this: they declare
|
|
30
|
+
* `describeRule?` as an optional structural member of their own input type, which
|
|
31
|
+
* is a different mechanism and correct where it is used.
|
|
32
|
+
*/
|
|
33
|
+
export interface Describable {
|
|
34
|
+
describeRule: () => RuleDescription;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Does this value describe itself?
|
|
38
|
+
*
|
|
39
|
+
* Cast-free, and that is the point: the version in `explain.ts` read
|
|
40
|
+
* `(value as Record<string, unknown>)['describeRule']`, an ADR-005 breach in
|
|
41
|
+
* shipped source with no `eslint-disable` and no interop boundary to justify it.
|
|
42
|
+
* It was also unnecessary — once `value` is narrowed to `object`, `'describeRule'
|
|
43
|
+
* in value` narrows enough for the property access to compile, which is what the
|
|
44
|
+
* two test copies already did.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isDescribable(value: unknown): value is Describable;
|
|
47
|
+
/**
|
|
48
|
+
* The metadata half of a rule's description — one projection, four callers.
|
|
49
|
+
*
|
|
50
|
+
* `id`, `because`, `suggestion` and `docs` are read off `RuleMetadata` the same
|
|
51
|
+
* way everywhere. `rule` and `imperative` are the two axes that genuinely vary,
|
|
52
|
+
* so they are parameters rather than something this function guesses:
|
|
53
|
+
*
|
|
54
|
+
* - `rule` — the base `TerminalBuilder` says `id ?? 'unnamed'` (a SENTINEL that
|
|
55
|
+
* `dedupe-config-findings.ts` keys on, not an identity), a `RuleBuilder` says
|
|
56
|
+
* the assembled `ruleDescriptionOf(...)` sentence, and several `eess-ts`
|
|
57
|
+
* subclasses override it again on top of `...super.describeRule()`.
|
|
58
|
+
* - `imperativeFallback` — what to say for an agent prompt when the author
|
|
59
|
+
* declared no `imperative`. `eess-ts`'s `RuleDeclaration` falls back to the
|
|
60
|
+
* reason and its `describeRuleOf` to a derived Do-NOT/MUST sentence; the
|
|
61
|
+
* kernel has no fallback, and that is not an oversight this extraction
|
|
62
|
+
* quietly fixed — `imperative` is read only by `eess-ts`'s
|
|
63
|
+
* `explain --format agent`, and `eess-mermaid`'s `explain` does not project
|
|
64
|
+
* the field at all. The kernel has no consumer to be wrong for.
|
|
65
|
+
*
|
|
66
|
+
* The four bodies were otherwise identical, which `no-copy-paste` reported as a
|
|
67
|
+
* 93% cluster.
|
|
68
|
+
*/
|
|
69
|
+
export declare function ruleDescriptionFrom(opts: {
|
|
70
|
+
metadata: RuleMetadata | undefined;
|
|
71
|
+
reason: string | undefined;
|
|
72
|
+
rule: string;
|
|
73
|
+
imperativeFallback?: string;
|
|
74
|
+
}): RuleDescription;
|
|
75
|
+
/**
|
|
76
|
+
* A rule's English description, assembled from what it declared.
|
|
77
|
+
*
|
|
78
|
+
* `subject` is the noun the rule opens with when its builder has one — the
|
|
79
|
+
* GraphQL builders say `schema that ... should ...` and
|
|
80
|
+
* `resolvers that ... should ...`; the general builders open with `that`. That
|
|
81
|
+
* one word was the only difference between four copies of this function, which
|
|
82
|
+
* `no-copy-paste` reported as a cluster at 100%.
|
|
83
|
+
*
|
|
84
|
+
* An empty predicate or condition list contributes nothing rather than an empty
|
|
85
|
+
* clause, so a rule that never reached `.should()` reads `that X` and not
|
|
86
|
+
* `that X should ` — the missing half is what `assertionAdvice` then reports.
|
|
87
|
+
*
|
|
88
|
+
* `slice-rule-builder.ts` is deliberately NOT a caller: its description is a
|
|
89
|
+
* different sentence (`slices [a, b] should ...`), not this one with a prefix.
|
|
90
|
+
*/
|
|
91
|
+
export declare function ruleDescriptionOf(predicates: readonly {
|
|
92
|
+
description: string;
|
|
93
|
+
}[], conditions: readonly {
|
|
94
|
+
description: string;
|
|
95
|
+
}[], subject?: string): string;
|
|
14
96
|
//# 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,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD;;;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;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAA;IAClC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,GAAG,eAAe,CASlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,EAC9C,UAAU,EAAE,SAAS;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,EAC9C,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAOR"}
|
package/dist/rule-description.js
CHANGED
|
@@ -1,2 +1,76 @@
|
|
|
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
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The metadata half of a rule's description — one projection, four callers.
|
|
20
|
+
*
|
|
21
|
+
* `id`, `because`, `suggestion` and `docs` are read off `RuleMetadata` the same
|
|
22
|
+
* way everywhere. `rule` and `imperative` are the two axes that genuinely vary,
|
|
23
|
+
* so they are parameters rather than something this function guesses:
|
|
24
|
+
*
|
|
25
|
+
* - `rule` — the base `TerminalBuilder` says `id ?? 'unnamed'` (a SENTINEL that
|
|
26
|
+
* `dedupe-config-findings.ts` keys on, not an identity), a `RuleBuilder` says
|
|
27
|
+
* the assembled `ruleDescriptionOf(...)` sentence, and several `eess-ts`
|
|
28
|
+
* subclasses override it again on top of `...super.describeRule()`.
|
|
29
|
+
* - `imperativeFallback` — what to say for an agent prompt when the author
|
|
30
|
+
* declared no `imperative`. `eess-ts`'s `RuleDeclaration` falls back to the
|
|
31
|
+
* reason and its `describeRuleOf` to a derived Do-NOT/MUST sentence; the
|
|
32
|
+
* kernel has no fallback, and that is not an oversight this extraction
|
|
33
|
+
* quietly fixed — `imperative` is read only by `eess-ts`'s
|
|
34
|
+
* `explain --format agent`, and `eess-mermaid`'s `explain` does not project
|
|
35
|
+
* the field at all. The kernel has no consumer to be wrong for.
|
|
36
|
+
*
|
|
37
|
+
* The four bodies were otherwise identical, which `no-copy-paste` reported as a
|
|
38
|
+
* 93% cluster.
|
|
39
|
+
*/
|
|
40
|
+
export function ruleDescriptionFrom(opts) {
|
|
41
|
+
return {
|
|
42
|
+
rule: opts.rule,
|
|
43
|
+
id: opts.metadata?.id,
|
|
44
|
+
because: opts.reason,
|
|
45
|
+
suggestion: opts.metadata?.suggestion,
|
|
46
|
+
docs: opts.metadata?.docs,
|
|
47
|
+
imperative: opts.metadata?.imperative ?? opts.imperativeFallback,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A rule's English description, assembled from what it declared.
|
|
52
|
+
*
|
|
53
|
+
* `subject` is the noun the rule opens with when its builder has one — the
|
|
54
|
+
* GraphQL builders say `schema that ... should ...` and
|
|
55
|
+
* `resolvers that ... should ...`; the general builders open with `that`. That
|
|
56
|
+
* one word was the only difference between four copies of this function, which
|
|
57
|
+
* `no-copy-paste` reported as a cluster at 100%.
|
|
58
|
+
*
|
|
59
|
+
* An empty predicate or condition list contributes nothing rather than an empty
|
|
60
|
+
* clause, so a rule that never reached `.should()` reads `that X` and not
|
|
61
|
+
* `that X should ` — the missing half is what `assertionAdvice` then reports.
|
|
62
|
+
*
|
|
63
|
+
* `slice-rule-builder.ts` is deliberately NOT a caller: its description is a
|
|
64
|
+
* different sentence (`slices [a, b] should ...`), not this one with a prefix.
|
|
65
|
+
*/
|
|
66
|
+
export function ruleDescriptionOf(predicates, conditions, subject) {
|
|
67
|
+
const predicateDesc = predicates.map((p) => p.description).join(' and ');
|
|
68
|
+
const conditionDesc = conditions.map((c) => c.description).join(' and ');
|
|
69
|
+
const parts = subject === undefined ? [] : [subject];
|
|
70
|
+
if (predicateDesc)
|
|
71
|
+
parts.push(`that ${predicateDesc}`);
|
|
72
|
+
if (conditionDesc)
|
|
73
|
+
parts.push(`should ${conditionDesc}`);
|
|
74
|
+
return parts.join(' ');
|
|
75
|
+
}
|
|
2
76
|
//# 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":"AAsCA;;;;;;;;;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;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAKnC;IACC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE;QACrB,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU;QACrC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI;QACzB,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,IAAI,IAAI,CAAC,kBAAkB;KACjE,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAA8C,EAC9C,UAA8C,EAC9C,OAAgB;IAEhB,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxE,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxE,MAAM,KAAK,GAAa,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IAC9D,IAAI,aAAa;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,aAAa,EAAE,CAAC,CAAA;IACtD,IAAI,aAAa;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,aAAa,EAAE,CAAC,CAAA;IACxD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memoize a builder's materialized selection — plan 0096.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* Plan 0096 makes every family expose the set its conditions receive, so that
|
|
7
|
+
* `diagnose()`'s preview and (in plan 0098) the floor read the **same
|
|
8
|
+
* computation** the gate reads rather than a parallel one. Two readers of one
|
|
9
|
+
* method means the walk would run twice per rule — and `doctor` calls
|
|
10
|
+
* `diagnose()` once per rule file, so on a 600-file project a rule file with ten
|
|
11
|
+
* smell rules paid seconds to compute ten integers.
|
|
12
|
+
*
|
|
13
|
+
* ## Why a WeakMap and not an instance field
|
|
14
|
+
*
|
|
15
|
+
* Because a field would be **wrong**, not merely different. These builders are
|
|
16
|
+
* copy-on-write: every chain method returns `this.copy()`, which is
|
|
17
|
+
* `shallowClone` — `Object.assign(clone, source)` over own enumerable
|
|
18
|
+
* properties. A memo stored on the instance is therefore copied onto a builder
|
|
19
|
+
* that has just been given a **different predicate**, and the clone answers with
|
|
20
|
+
* its parent's selection. That is a stale-evidence bug of exactly the kind this
|
|
21
|
+
* plan exists to prevent, and it would have been hard to see: the number is
|
|
22
|
+
* plausible, and only a test that narrows *after* materializing would catch it.
|
|
23
|
+
*
|
|
24
|
+
* A `WeakMap` keyed on the builder has no such hazard by construction. A clone
|
|
25
|
+
* is a different object, so a different key, so no entry. Nothing to clear, no
|
|
26
|
+
* `copy()` override to remember in five families, and the entry is collected
|
|
27
|
+
* with the builder.
|
|
28
|
+
*
|
|
29
|
+
* ## Why a factory rather than one shared map
|
|
30
|
+
*
|
|
31
|
+
* So the element type survives without an assertion. One `WeakMap<object,
|
|
32
|
+
* unknown[]>` shared by every family would need a cast on the way out, which
|
|
33
|
+
* [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids — and the
|
|
34
|
+
* honest alternatives (a `filter` with an always-true type predicate) are worse:
|
|
35
|
+
* they compile, allocate, and lie.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const selectionOf = selectionMemo<ArchFunction>()
|
|
39
|
+
*
|
|
40
|
+
* private selected(): ArchFunction[] {
|
|
41
|
+
* return selectionOf(this, () => matchingElements(this.getElements(), this._predicates))
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
export declare function selectionMemo<T>(): (owner: object, compute: () => T[]) => T[];
|
|
45
|
+
//# sourceMappingURL=selection-memo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-memo.d.ts","sourceRoot":"","sources":["../src/selection-memo.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,aAAa,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,CAmB7E"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { registerCacheReset } from './cache-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Memoize a builder's materialized selection — plan 0096.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Plan 0096 makes every family expose the set its conditions receive, so that
|
|
8
|
+
* `diagnose()`'s preview and (in plan 0098) the floor read the **same
|
|
9
|
+
* computation** the gate reads rather than a parallel one. Two readers of one
|
|
10
|
+
* method means the walk would run twice per rule — and `doctor` calls
|
|
11
|
+
* `diagnose()` once per rule file, so on a 600-file project a rule file with ten
|
|
12
|
+
* smell rules paid seconds to compute ten integers.
|
|
13
|
+
*
|
|
14
|
+
* ## Why a WeakMap and not an instance field
|
|
15
|
+
*
|
|
16
|
+
* Because a field would be **wrong**, not merely different. These builders are
|
|
17
|
+
* copy-on-write: every chain method returns `this.copy()`, which is
|
|
18
|
+
* `shallowClone` — `Object.assign(clone, source)` over own enumerable
|
|
19
|
+
* properties. A memo stored on the instance is therefore copied onto a builder
|
|
20
|
+
* that has just been given a **different predicate**, and the clone answers with
|
|
21
|
+
* its parent's selection. That is a stale-evidence bug of exactly the kind this
|
|
22
|
+
* plan exists to prevent, and it would have been hard to see: the number is
|
|
23
|
+
* plausible, and only a test that narrows *after* materializing would catch it.
|
|
24
|
+
*
|
|
25
|
+
* A `WeakMap` keyed on the builder has no such hazard by construction. A clone
|
|
26
|
+
* is a different object, so a different key, so no entry. Nothing to clear, no
|
|
27
|
+
* `copy()` override to remember in five families, and the entry is collected
|
|
28
|
+
* with the builder.
|
|
29
|
+
*
|
|
30
|
+
* ## Why a factory rather than one shared map
|
|
31
|
+
*
|
|
32
|
+
* So the element type survives without an assertion. One `WeakMap<object,
|
|
33
|
+
* unknown[]>` shared by every family would need a cast on the way out, which
|
|
34
|
+
* [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids — and the
|
|
35
|
+
* honest alternatives (a `filter` with an always-true type predicate) are worse:
|
|
36
|
+
* they compile, allocate, and lie.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const selectionOf = selectionMemo<ArchFunction>()
|
|
40
|
+
*
|
|
41
|
+
* private selected(): ArchFunction[] {
|
|
42
|
+
* return selectionOf(this, () => matchingElements(this.getElements(), this._predicates))
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
export function selectionMemo() {
|
|
46
|
+
let cache = new WeakMap();
|
|
47
|
+
// Same staleness profile as `element-cache.ts`, so the same escape hatch — a
|
|
48
|
+
// consumer holding a builder across a mutation of the underlying ts-morph
|
|
49
|
+
// project gets the pre-mutation selection back, and identity has not changed
|
|
50
|
+
// so nothing else can notice. That is worse here than in the element cache:
|
|
51
|
+
// BOTH readers go through this memo, so a stale entry is a stale VERDICT and
|
|
52
|
+
// a stale count that agree with each other. Reasoning about the clone hazard
|
|
53
|
+
// and stopping one short of this one would read as "considered and safe".
|
|
54
|
+
registerCacheReset(() => {
|
|
55
|
+
cache = new WeakMap();
|
|
56
|
+
});
|
|
57
|
+
return (owner, compute) => {
|
|
58
|
+
const cached = cache.get(owner);
|
|
59
|
+
if (cached !== undefined)
|
|
60
|
+
return cached;
|
|
61
|
+
const computed = compute();
|
|
62
|
+
cache.set(owner, computed);
|
|
63
|
+
return computed;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=selection-memo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-memo.js","sourceRoot":"","sources":["../src/selection-memo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,KAAK,GAAG,IAAI,OAAO,EAAe,CAAA;IACtC,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,kBAAkB,CAAC,GAAG,EAAE;QACtB,KAAK,GAAG,IAAI,OAAO,EAAe,CAAA;IACpC,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAA;QACvC,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAA;QAC1B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QAC1B,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A same-prototype shallow copy of a builder.
|
|
3
|
+
*
|
|
4
|
+
* The one place in the codebase that reaches for `Object.create` /
|
|
5
|
+
* `Object.getPrototypeOf`, which are the JS-interop boundary ADR-005's
|
|
6
|
+
* `eslint-disable` carve-out exists for. Every copy-on-write builder goes
|
|
7
|
+
* through here so that carve-out is written once and reviewed once.
|
|
8
|
+
*
|
|
9
|
+
* Shallow is deliberate. It gives the clone its own *slots*, which is what
|
|
10
|
+
* makes `next._x = ...` safe; a field holding a mutable container still needs
|
|
11
|
+
* the owning class to replace it, which is what the `copy()` overrides do.
|
|
12
|
+
* A deep clone would instead copy ts-morph `Project` graphs — the opposite of
|
|
13
|
+
* cheap, and it would break identity comparisons on `SourceFile`.
|
|
14
|
+
*
|
|
15
|
+
* Class fields declared with a constructor parameter property (`private
|
|
16
|
+
* readonly project`) are ordinary instance properties at runtime, so they come
|
|
17
|
+
* across too: a clone is built against the same project, without re-loading it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function shallowClone<T extends object>(source: T): T;
|
|
20
|
+
//# sourceMappingURL=shallow-clone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shallow-clone.d.ts","sourceRoot":"","sources":["../src/shallow-clone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAO3D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A same-prototype shallow copy of a builder.
|
|
3
|
+
*
|
|
4
|
+
* The one place in the codebase that reaches for `Object.create` /
|
|
5
|
+
* `Object.getPrototypeOf`, which are the JS-interop boundary ADR-005's
|
|
6
|
+
* `eslint-disable` carve-out exists for. Every copy-on-write builder goes
|
|
7
|
+
* through here so that carve-out is written once and reviewed once.
|
|
8
|
+
*
|
|
9
|
+
* Shallow is deliberate. It gives the clone its own *slots*, which is what
|
|
10
|
+
* makes `next._x = ...` safe; a field holding a mutable container still needs
|
|
11
|
+
* the owning class to replace it, which is what the `copy()` overrides do.
|
|
12
|
+
* A deep clone would instead copy ts-morph `Project` graphs — the opposite of
|
|
13
|
+
* cheap, and it would break identity comparisons on `SourceFile`.
|
|
14
|
+
*
|
|
15
|
+
* Class fields declared with a constructor parameter property (`private
|
|
16
|
+
* readonly project`) are ordinary instance properties at runtime, so they come
|
|
17
|
+
* across too: a clone is built against the same project, without re-loading it.
|
|
18
|
+
*/
|
|
19
|
+
export function shallowClone(source) {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Object.getPrototypeOf returns `any`
|
|
21
|
+
const proto = Object.getPrototypeOf(source);
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Object.create returns `any`
|
|
23
|
+
const clone = Object.create(proto);
|
|
24
|
+
Object.assign(clone, source);
|
|
25
|
+
return clone;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=shallow-clone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shallow-clone.js","sourceRoot":"","sources":["../src/shallow-clone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAmB,MAAS;IACtD,0GAA0G;IAC1G,MAAM,KAAK,GAAW,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IACnD,kGAAkG;IAClG,MAAM,KAAK,GAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACrC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -34,5 +34,17 @@ export declare function silent(pattern: string | RegExp): SilentExclusion;
|
|
|
34
34
|
* Check whether a value is a SilentExclusion.
|
|
35
35
|
*/
|
|
36
36
|
export declare function isSilent(value: unknown): value is SilentExclusion;
|
|
37
|
+
/**
|
|
38
|
+
* Record `patterns` into a builder's exclusion state, tracking which are silent.
|
|
39
|
+
*
|
|
40
|
+
* A free function over the two collections rather than a method, because the two
|
|
41
|
+
* builders that need it sit in incompatible class hierarchies — `eess-ts` has
|
|
42
|
+
* `RuleDeclaration -> TerminalBuilder -> RuleBuilder` where the kernel has two
|
|
43
|
+
* levels — so inheritance cannot share it. It mutates only what it is handed,
|
|
44
|
+
* so no private state crosses a package boundary.
|
|
45
|
+
*
|
|
46
|
+
* The two copies were byte-identical (`no-copy-paste` reported 100%).
|
|
47
|
+
*/
|
|
48
|
+
export declare function recordExclusions(patterns: readonly (string | RegExp | SilentExclusion)[], exclusions: (string | RegExp)[], silentIndices: Set<number>): void;
|
|
37
49
|
export {};
|
|
38
50
|
//# sourceMappingURL=silent-exclusion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"silent-exclusion.d.ts","sourceRoot":"","sources":["../src/silent-exclusion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,QAAA,MAAM,MAAM,EAAE,OAAO,MAAmC,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACjC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAA;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,eAAe,CAEhE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAEjE"}
|
|
1
|
+
{"version":3,"file":"silent-exclusion.d.ts","sourceRoot":"","sources":["../src/silent-exclusion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,QAAA,MAAM,MAAM,EAAE,OAAO,MAAmC,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACjC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAA;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,eAAe,CAEhE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAEjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC,EAAE,EACxD,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAC/B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,IAAI,CASN"}
|
package/dist/silent-exclusion.js
CHANGED
|
@@ -27,4 +27,26 @@ export function silent(pattern) {
|
|
|
27
27
|
export function isSilent(value) {
|
|
28
28
|
return typeof value === 'object' && value !== null && SILENT in value;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Record `patterns` into a builder's exclusion state, tracking which are silent.
|
|
32
|
+
*
|
|
33
|
+
* A free function over the two collections rather than a method, because the two
|
|
34
|
+
* builders that need it sit in incompatible class hierarchies — `eess-ts` has
|
|
35
|
+
* `RuleDeclaration -> TerminalBuilder -> RuleBuilder` where the kernel has two
|
|
36
|
+
* levels — so inheritance cannot share it. It mutates only what it is handed,
|
|
37
|
+
* so no private state crosses a package boundary.
|
|
38
|
+
*
|
|
39
|
+
* The two copies were byte-identical (`no-copy-paste` reported 100%).
|
|
40
|
+
*/
|
|
41
|
+
export function recordExclusions(patterns, exclusions, silentIndices) {
|
|
42
|
+
for (const p of patterns) {
|
|
43
|
+
if (isSilent(p)) {
|
|
44
|
+
exclusions.push(p.pattern);
|
|
45
|
+
silentIndices.add(exclusions.length - 1);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
exclusions.push(p);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
30
52
|
//# sourceMappingURL=silent-exclusion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"silent-exclusion.js","sourceRoot":"","sources":["../src/silent-exclusion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,GAAkB,MAAM,CAAC,kBAAkB,CAAC,CAAA;AAcxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,OAAwB;IAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAA;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAA;AACvE,CAAC"}
|
|
1
|
+
{"version":3,"file":"silent-exclusion.js","sourceRoot":"","sources":["../src/silent-exclusion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,GAAkB,MAAM,CAAC,kBAAkB,CAAC,CAAA;AAcxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,OAAwB;IAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAA;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAA;AACvE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAwD,EACxD,UAA+B,EAC/B,aAA0B;IAE1B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;YAC1B,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAC1C,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/stderr.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write one message to stderr so it survives a test runner.
|
|
3
|
+
*
|
|
4
|
+
* A trailing newline is added when absent, because the call sites this replaced
|
|
5
|
+
* used `console.warn`, which appends one — omitting it at ten call sites is a
|
|
6
|
+
* mistake waiting to happen, and a message run onto the next is the defect this
|
|
7
|
+
* channel exists to avoid.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeStderr(message: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Reset the lazily-attached listener. **Tests only** — a suite that asserts on
|
|
12
|
+
* the attachment needs to observe it happening rather than inherit it from
|
|
13
|
+
* whichever earlier test wrote first.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resetStderrGuardForTests(): void;
|
|
16
|
+
//# sourceMappingURL=stderr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stderr.d.ts","sourceRoot":"","sources":["../src/stderr.ts"],"names":[],"mappings":"AA8CA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQjD;AAED;;;;GAIG;AAEH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C"}
|
package/dist/stderr.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The library's single stderr channel.
|
|
3
|
+
*
|
|
4
|
+
* [ts-archunit Bug 0024](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0024-warn-terminal-is-invisible-inside-a-test-runner.md):
|
|
5
|
+
* every library-originated message went through `console.warn`, and vitest's
|
|
6
|
+
* default reporter **intercepts** console output and replays it only for
|
|
7
|
+
* **failing** tests. `.warn()` never fails a test, so its output was always
|
|
8
|
+
* dropped. Measured on a real child `vitest run` with the default reporter: a
|
|
9
|
+
* passing test whose rule has 4 real violations printed **nothing** — zero
|
|
10
|
+
* occurrences of the violation text.
|
|
11
|
+
*
|
|
12
|
+
* That silence was not confined to `.warn()`. The stale-exclusion warning, the
|
|
13
|
+
* unused-exclusion warning, the exclusion-comment parse warnings, the
|
|
14
|
+
* invalid-baseline warning, the diff-aware fallback warning and `expression()`'s
|
|
15
|
+
* escape-hatch warning were all invisible in a passing test — five files' worth.
|
|
16
|
+
* A finding nobody reads has not been reported (ADR-009 rule 1).
|
|
17
|
+
*
|
|
18
|
+
* **The EPIPE guard is not optional, and it is why this is a function rather
|
|
19
|
+
* than a bare write.** Node's `Console` is constructed with `ignoreErrors: true`
|
|
20
|
+
* and swallows write errors; `process.stderr.write` does not. With a closed
|
|
21
|
+
* downstream pipe — `eess-ts check 2>&1 | head` — the error arrives
|
|
22
|
+
* **asynchronously**, so neither `try`/`catch` nor the write callback can see
|
|
23
|
+
* it, and the process dies with an uncaught EPIPE. Measured over 20 000 lines:
|
|
24
|
+
*
|
|
25
|
+
* bare process.stderr.write node exits 1
|
|
26
|
+
* with an 'error' listener attached node exits 0
|
|
27
|
+
* console.warn node exits 0
|
|
28
|
+
*
|
|
29
|
+
* `writeReport` already wrote to `process.stderr` unguarded, so that exit-1 was
|
|
30
|
+
* a live defect before this existed: a piped `check` could fail for EPIPE rather
|
|
31
|
+
* than for findings, and the two are indistinguishable from the exit code.
|
|
32
|
+
*
|
|
33
|
+
* A persistent listener rather than `once`: `once` removes itself after the
|
|
34
|
+
* first error, leaving a second EPIPE uncaught, and re-adding per write leaks
|
|
35
|
+
* listeners. One listener, attached lazily, never removed — the same trade
|
|
36
|
+
* `ignoreErrors: true` makes, and the reason it is documented here rather than
|
|
37
|
+
* hidden.
|
|
38
|
+
*
|
|
39
|
+
* **The accepted cost:** vitest annotates intercepted console output with the
|
|
40
|
+
* test that produced it (`stderr | file > test name`), and a direct write loses
|
|
41
|
+
* that. For a violation report the rule's own identity is in the message, so the
|
|
42
|
+
* loss is real but small — and being attributed to a test that never printed is
|
|
43
|
+
* worse than being unattributed.
|
|
44
|
+
*/
|
|
45
|
+
let listenerAttached = false;
|
|
46
|
+
/**
|
|
47
|
+
* Write one message to stderr so it survives a test runner.
|
|
48
|
+
*
|
|
49
|
+
* A trailing newline is added when absent, because the call sites this replaced
|
|
50
|
+
* used `console.warn`, which appends one — omitting it at ten call sites is a
|
|
51
|
+
* mistake waiting to happen, and a message run onto the next is the defect this
|
|
52
|
+
* channel exists to avoid.
|
|
53
|
+
*/
|
|
54
|
+
export function writeStderr(message) {
|
|
55
|
+
if (!listenerAttached) {
|
|
56
|
+
// See the module docstring: the EPIPE is asynchronous, so this listener is
|
|
57
|
+
// the only thing that can catch it.
|
|
58
|
+
process.stderr.on('error', () => { });
|
|
59
|
+
listenerAttached = true;
|
|
60
|
+
}
|
|
61
|
+
process.stderr.write(message.endsWith('\n') ? message : `${message}\n`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reset the lazily-attached listener. **Tests only** — a suite that asserts on
|
|
65
|
+
* the attachment needs to observe it happening rather than inherit it from
|
|
66
|
+
* whichever earlier test wrote first.
|
|
67
|
+
*/
|
|
68
|
+
// eess-exclude eess/no-unused-exports: re-exported from `src/index.ts`; this gate does not count a barrel `export … from` re-export as usage — see work/bugs/0168
|
|
69
|
+
export function resetStderrGuardForTests() {
|
|
70
|
+
process.stderr.removeAllListeners('error');
|
|
71
|
+
listenerAttached = false;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=stderr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stderr.js","sourceRoot":"","sources":["../src/stderr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,IAAI,gBAAgB,GAAG,KAAK,CAAA;AAE5B;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,2EAA2E;QAC3E,oCAAoC;QACpC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACpC,gBAAgB,GAAG,IAAI,CAAA;IACzB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAA;AACzE,CAAC;AAED;;;;GAIG;AACH,kKAAkK;AAClK,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC1C,gBAAgB,GAAG,KAAK,CAAA;AAC1B,CAAC"}
|