@shrkcrft/boundaries 0.1.0-alpha.26 → 0.1.0-alpha.28
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/dist/baseline/canonicalize.d.ts +35 -0
- package/dist/baseline/canonicalize.d.ts.map +1 -0
- package/dist/baseline/canonicalize.js +87 -0
- package/dist/baseline/compute-baseline.d.ts +23 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -0
- package/dist/baseline/compute-baseline.js +30 -0
- package/dist/baseline/diff-baseline.d.ts +68 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -0
- package/dist/baseline/diff-baseline.js +110 -0
- package/dist/baseline/json-path-keys.d.ts +10 -0
- package/dist/baseline/json-path-keys.d.ts.map +1 -0
- package/dist/baseline/json-path-keys.js +13 -0
- package/dist/extract/code-zones.d.ts +32 -0
- package/dist/extract/code-zones.d.ts.map +1 -0
- package/dist/extract/code-zones.js +59 -0
- package/dist/extract/extract-tokens.d.ts +30 -0
- package/dist/extract/extract-tokens.d.ts.map +1 -0
- package/dist/extract/extract-tokens.js +328 -0
- package/dist/extract/inspect-source.d.ts +24 -0
- package/dist/extract/inspect-source.d.ts.map +1 -0
- package/dist/extract/inspect-source.js +27 -0
- package/dist/extract/scan-literals.d.ts +54 -0
- package/dist/extract/scan-literals.d.ts.map +1 -0
- package/dist/extract/scan-literals.js +177 -0
- package/dist/generated/check-provenance.d.ts +37 -0
- package/dist/generated/check-provenance.d.ts.map +1 -0
- package/dist/generated/check-provenance.js +89 -0
- package/dist/generated/compare-trees.d.ts +26 -0
- package/dist/generated/compare-trees.d.ts.map +1 -0
- package/dist/generated/compare-trees.js +41 -0
- package/dist/generated/scan-generated.d.ts +17 -0
- package/dist/generated/scan-generated.d.ts.map +1 -0
- package/dist/generated/scan-generated.js +27 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/policy/evaluate-policy.d.ts +41 -5
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +112 -10
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +24 -3
- package/dist/wiring/evaluate-wiring.d.ts +81 -28
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +0 -0
- package/dist/wiring/explain-wiring.d.ts +15 -7
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +28 -27
- package/dist/wiring/registration-graph.d.ts +27 -2
- package/dist/wiring/registration-graph.d.ts.map +1 -1
- package/dist/wiring/registration-graph.js +51 -2
- package/dist/wiring/registry-query.d.ts +9 -0
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +11 -0
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +4 -7
- package/dist/wiring/trace-literal.d.ts +6 -0
- package/dist/wiring/trace-literal.d.ts.map +1 -1
- package/dist/wiring/trace-literal.js +22 -0
- package/package.json +2 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import { type IExtractFileEntry, type IExtractedSite } from '../extract/extract-tokens.js';
|
|
2
3
|
export declare const WIRING_SCHEMA: "sharkcraft.wiring/v1";
|
|
3
4
|
/** A file made available to the engine. */
|
|
4
|
-
export
|
|
5
|
-
/** Project-relative POSIX path. */
|
|
6
|
-
readonly path: string;
|
|
7
|
-
readonly content: string;
|
|
8
|
-
}
|
|
5
|
+
export type IWiringFileEntry = IExtractFileEntry;
|
|
9
6
|
/** A captured token + where it was captured. */
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export type IWiringTokenSite = IExtractedSite;
|
|
8
|
+
/**
|
|
9
|
+
* Per-rule outcome. `skipped` is deliberately DISTINCT from `passed`: a rule
|
|
10
|
+
* whose source side extracted nothing checked nothing, and reporting that as a
|
|
11
|
+
* pass is the single failure mode that lets a stale selector ship a real
|
|
12
|
+
* violation. See {@link IWiringSkip}.
|
|
13
|
+
*/
|
|
14
|
+
export type WiringRuleStatus = 'passed' | 'failed' | 'skipped' | 'error';
|
|
15
15
|
export interface IWiringViolation {
|
|
16
16
|
readonly ruleId: string;
|
|
17
|
-
/** The token that is missing from the other side. */
|
|
17
|
+
/** The token that is missing from (or wrongly shared with) the other side. */
|
|
18
18
|
readonly token: string;
|
|
19
19
|
/** Declaring (or registering) file (project-relative) + 1-based line. */
|
|
20
20
|
readonly file: string;
|
|
@@ -23,20 +23,53 @@ export interface IWiringViolation {
|
|
|
23
23
|
/**
|
|
24
24
|
* Which side the token is missing from. `declared-missing` (the subset case):
|
|
25
25
|
* declared but not registered. `registered-missing` (parity only): registered
|
|
26
|
-
* but never declared.
|
|
26
|
+
* but never declared. `overlap` (disjoint only): present on both sides.
|
|
27
27
|
*/
|
|
28
|
-
readonly direction?: 'declared-missing' | 'registered-missing';
|
|
28
|
+
readonly direction?: 'declared-missing' | 'registered-missing' | 'overlap';
|
|
29
|
+
/** For a `chain` rule: which hop pair failed (0-based). */
|
|
30
|
+
readonly hop?: number;
|
|
31
|
+
/** Rendered `message` template, when the rule sets one. */
|
|
32
|
+
readonly message?: string;
|
|
29
33
|
readonly hint?: string;
|
|
30
34
|
}
|
|
35
|
+
/** A rule that could not be evaluated because its source side matched nothing. */
|
|
36
|
+
export interface IWiringSkip {
|
|
37
|
+
readonly ruleId: string;
|
|
38
|
+
/** Human-readable cause (`0 files matched` / `0 ids extracted`). */
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
/** True when the rule set `failOnEmpty` — the skip counts as a failure. */
|
|
41
|
+
readonly failed: boolean;
|
|
42
|
+
readonly severity: 'error' | 'warning';
|
|
43
|
+
}
|
|
44
|
+
/** One `hop_i ⊆ hop_i+1` step of a multi-hop chain rule. */
|
|
45
|
+
export interface IWiringHopResult {
|
|
46
|
+
readonly index: number;
|
|
47
|
+
readonly fromCount: number;
|
|
48
|
+
readonly toCount: number;
|
|
49
|
+
readonly missing: number;
|
|
50
|
+
}
|
|
31
51
|
export interface IWiringRuleResult {
|
|
32
52
|
readonly ruleId: string;
|
|
33
53
|
readonly description?: string;
|
|
34
54
|
readonly severity: 'error' | 'warning';
|
|
55
|
+
readonly status: WiringRuleStatus;
|
|
35
56
|
readonly declaredCount: number;
|
|
36
57
|
readonly registeredCount: number;
|
|
58
|
+
/** Files actually scanned per side (after glob resolution) — the stale-glob signal. */
|
|
59
|
+
readonly declaredFiles: number;
|
|
60
|
+
readonly registeredFiles: number;
|
|
37
61
|
readonly violations: readonly IWiringViolation[];
|
|
38
62
|
/** Set when the rule is misconfigured (bad regex / no capture group / bad source). */
|
|
39
63
|
readonly error?: string;
|
|
64
|
+
/**
|
|
65
|
+
* True when the SINK extracted 0 ids while the source extracted some. Every
|
|
66
|
+
* declared token then "fails", which is a real failure — but almost always
|
|
67
|
+
* because the sink glob went stale, so the diff is annotated rather than
|
|
68
|
+
* silently reported as N unrelated violations.
|
|
69
|
+
*/
|
|
70
|
+
readonly emptySink?: boolean;
|
|
71
|
+
/** Per-hop breakdown for a `chain` rule. */
|
|
72
|
+
readonly hops?: readonly IWiringHopResult[];
|
|
40
73
|
}
|
|
41
74
|
export interface IWiringReport {
|
|
42
75
|
readonly schema: typeof WIRING_SCHEMA;
|
|
@@ -44,34 +77,54 @@ export interface IWiringReport {
|
|
|
44
77
|
readonly violations: readonly IWiringViolation[];
|
|
45
78
|
/** Rule-level misconfiguration messages (never throws — degrades gracefully). */
|
|
46
79
|
readonly diagnostics: readonly string[];
|
|
80
|
+
/** Rules that checked nothing, reported loudly instead of as a green pass. */
|
|
81
|
+
readonly skipped: readonly IWiringSkip[];
|
|
47
82
|
/**
|
|
48
|
-
* Count of rules that actually ran a comparison. A rule whose
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* error is not swallowed.
|
|
83
|
+
* Count of rules that actually ran a comparison. A rule whose source side
|
|
84
|
+
* matched 0 files or extracted 0 ids is NOT evaluated (see {@link skipped}).
|
|
85
|
+
* Misconfigured rules count as evaluated so their error is not swallowed.
|
|
52
86
|
*/
|
|
53
87
|
readonly evaluated: number;
|
|
54
88
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
55
89
|
}
|
|
56
90
|
/** Resolves a rule-side's globs to the concrete files (path + content) to scan. */
|
|
57
91
|
export type WiringFileResolver = (source: IWiringSource) => readonly IWiringFileEntry[];
|
|
92
|
+
/** Normalize the `registered` field (single source or union array) to an array. */
|
|
93
|
+
export declare function registeredSources(reg: IWiringRule['registered']): readonly IWiringSource[];
|
|
94
|
+
/**
|
|
95
|
+
* The rule's SOURCE side — the set whose membership is being asserted. For a
|
|
96
|
+
* chain rule that is hop 0. Used for glob collection, the loud-skip test, and
|
|
97
|
+
* `selfTest` expectations.
|
|
98
|
+
*/
|
|
99
|
+
export declare function wiringSourceSide(rule: IWiringRule): IWiringSource | undefined;
|
|
100
|
+
/** Every source a rule references, in declaration order. */
|
|
101
|
+
export declare function wiringSourcesOf(rule: IWiringRule): readonly IWiringSource[];
|
|
102
|
+
/** Every glob a rule references (both sides / every hop). */
|
|
103
|
+
export declare function wiringGlobsOf(rule: IWiringRule): string[];
|
|
58
104
|
/**
|
|
59
|
-
* Public: extract every token site from ONE wiring source
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* (never throws).
|
|
105
|
+
* Public: extract every token site from ONE wiring source over the given files.
|
|
106
|
+
* Shared by the wiring evaluator, the registry-inventory engine, and the
|
|
107
|
+
* extractor-backed baseline compute, so all three honour identical semantics. A
|
|
108
|
+
* misconfigured source returns an `error` and no sites (never throws).
|
|
64
109
|
*/
|
|
65
110
|
export declare function collectSourceSites(source: IWiringSource, files: readonly IWiringFileEntry[]): {
|
|
66
111
|
sites: readonly IWiringTokenSite[];
|
|
67
112
|
error?: string;
|
|
68
113
|
};
|
|
114
|
+
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
115
|
+
export declare function validateWiringRule(rule: IWiringRule): string | undefined;
|
|
69
116
|
/**
|
|
70
|
-
* Pure wiring evaluation.
|
|
71
|
-
*
|
|
72
|
-
* the
|
|
73
|
-
*
|
|
74
|
-
*
|
|
117
|
+
* Pure wiring evaluation.
|
|
118
|
+
*
|
|
119
|
+
* For each rule the SOURCE token set is compared against the SINK sets per
|
|
120
|
+
* `mode` (`subset` / `parity` / `disjoint`) and `registeredMode` (`union` /
|
|
121
|
+
* `intersection`). A `chain` rule runs the same comparison across each adjacent
|
|
122
|
+
* hop pair. With `groupBy`, membership is checked within the same dir/package.
|
|
123
|
+
*
|
|
124
|
+
* Three outcomes are kept distinct, because collapsing them is what makes a
|
|
125
|
+
* homegrown gate untrustworthy: a rule that PASSED, a rule that FAILED, and a
|
|
126
|
+
* rule that checked NOTHING (`skipped`, promoted to a failure by `failOnEmpty`).
|
|
127
|
+
*
|
|
75
128
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
76
129
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
77
130
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IWiringRule } from '@shrkcrft/core';
|
|
2
|
-
import { type IWiringTokenSite } from './evaluate-wiring.js';
|
|
2
|
+
import { type IWiringHopResult, type IWiringTokenSite } from './evaluate-wiring.js';
|
|
3
3
|
export declare const WIRING_EXPLAIN_SCHEMA: "sharkcraft.wiring-explain/v1";
|
|
4
4
|
/** One side (declared or registered) of a wiring rule, as extracted from the tree. */
|
|
5
5
|
export interface IWiringSideExplain {
|
|
@@ -22,7 +22,8 @@ export interface IWiringExplain {
|
|
|
22
22
|
readonly schema: typeof WIRING_EXPLAIN_SCHEMA;
|
|
23
23
|
readonly ruleId: string;
|
|
24
24
|
readonly description?: string;
|
|
25
|
-
readonly mode: 'subset' | 'parity';
|
|
25
|
+
readonly mode: 'subset' | 'parity' | 'disjoint';
|
|
26
|
+
readonly registeredMode: 'union' | 'intersection';
|
|
26
27
|
readonly groupBy?: 'dir' | 'package';
|
|
27
28
|
readonly severity: 'error' | 'warning';
|
|
28
29
|
readonly declared: IWiringSideExplain;
|
|
@@ -31,6 +32,14 @@ export interface IWiringExplain {
|
|
|
31
32
|
readonly declaredNotRegistered: readonly IWiringTokenSite[];
|
|
32
33
|
/** Registered tokens absent from the declared set (parity-only `registered-missing`). */
|
|
33
34
|
readonly registeredNotDeclared: readonly IWiringTokenSite[];
|
|
35
|
+
/** Tokens present on BOTH sides (disjoint-only `overlap`). */
|
|
36
|
+
readonly overlap: readonly IWiringTokenSite[];
|
|
37
|
+
/** Per-hop breakdown when the rule is a multi-hop `chain`. */
|
|
38
|
+
readonly hops?: readonly IWiringHopResult[];
|
|
39
|
+
/** `passed` / `failed` / `skipped` / `error` — skipped is never a pass. */
|
|
40
|
+
readonly status: 'passed' | 'failed' | 'skipped' | 'error';
|
|
41
|
+
/** Why the rule checked nothing, when it was skipped. */
|
|
42
|
+
readonly skipReason?: string;
|
|
34
43
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
35
44
|
/** Rule-level misconfiguration messages (engine degrades gracefully). */
|
|
36
45
|
readonly diagnostics: readonly string[];
|
|
@@ -43,11 +52,10 @@ export interface IExplainWiringOptions {
|
|
|
43
52
|
* Dry-run a single wiring rule against the live tree and return what each side
|
|
44
53
|
* extracted (declared set, registered set, the set-difference, the verdict) —
|
|
45
54
|
* WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
|
|
46
|
-
* <candidate>`, and `check wiring --explain <ruleId>`: the
|
|
47
|
-
*
|
|
48
|
-
* a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
49
|
-
* exactly (incl. `groupBy` membership)
|
|
50
|
-
* with the shared {@link collectSourceSites}. Never throws.
|
|
55
|
+
* <candidate>`, `gates explain <id>`, and `check wiring --explain <ruleId>`: the
|
|
56
|
+
* author can SEE the cross-file set-difference the gate computes before
|
|
57
|
+
* committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
58
|
+
* the gate exactly (incl. `groupBy` membership). Never throws.
|
|
51
59
|
*/
|
|
52
60
|
export declare function explainWiring(projectRoot: string, rule: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
|
|
53
61
|
//# sourceMappingURL=explain-wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,
|
|
1
|
+
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,cAAc,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,oFAAoF;IACpF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,yFAAyF;IACzF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC3D,yDAAyD;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IACjD,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAQD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAyEhB"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { matchesAny } from "../scan/glob.js";
|
|
2
2
|
import { readMatchingFiles } from "../util/walk-files.js";
|
|
3
|
-
import { collectSourceSites, evaluateWiring, } from "./evaluate-wiring.js";
|
|
3
|
+
import { collectSourceSites, evaluateWiring, registeredSources, wiringGlobsOf, wiringSourceSide, } from "./evaluate-wiring.js";
|
|
4
4
|
export const WIRING_EXPLAIN_SCHEMA = 'sharkcraft.wiring-explain/v1';
|
|
5
|
-
function registeredSources(reg) {
|
|
6
|
-
return Array.isArray(reg) ? reg : [reg];
|
|
7
|
-
}
|
|
8
5
|
function sortSites(sites) {
|
|
9
6
|
return [...sites].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.token.localeCompare(b.token));
|
|
10
7
|
}
|
|
@@ -12,31 +9,32 @@ function sortSites(sites) {
|
|
|
12
9
|
* Dry-run a single wiring rule against the live tree and return what each side
|
|
13
10
|
* extracted (declared set, registered set, the set-difference, the verdict) —
|
|
14
11
|
* WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
|
|
15
|
-
* <candidate>`, and `check wiring --explain <ruleId>`: the
|
|
16
|
-
*
|
|
17
|
-
* a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
18
|
-
* exactly (incl. `groupBy` membership)
|
|
19
|
-
* with the shared {@link collectSourceSites}. Never throws.
|
|
12
|
+
* <candidate>`, `gates explain <id>`, and `check wiring --explain <ruleId>`: the
|
|
13
|
+
* author can SEE the cross-file set-difference the gate computes before
|
|
14
|
+
* committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
15
|
+
* the gate exactly (incl. `groupBy` membership). Never throws.
|
|
20
16
|
*/
|
|
21
17
|
export function explainWiring(projectRoot, rule, options = {}) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const sourceSide = wiringSourceSide(rule);
|
|
19
|
+
// For a chain, the reported "registered" side is the LAST hop (the far end).
|
|
20
|
+
const sinkSources = rule.chain && rule.chain.length >= 2
|
|
21
|
+
? [rule.chain[rule.chain.length - 1]]
|
|
22
|
+
: registeredSources(rule.registered);
|
|
23
|
+
const allGlobs = [...new Set(wiringGlobsOf(rule))];
|
|
26
24
|
const cache = readMatchingFiles(projectRoot, allGlobs, new Set(options.excludeDirs ?? []));
|
|
27
25
|
const entries = [...cache.entries()].map(([path, content]) => ({
|
|
28
26
|
path,
|
|
29
27
|
content,
|
|
30
28
|
}));
|
|
31
|
-
const filesFor = (source) => entries.filter((f) => matchesAny(f.path, source.files));
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
const filesFor = (source) => entries.filter((f) => matchesAny(f.path, source.files ?? []));
|
|
30
|
+
const declaredFiles = sourceSide ? filesFor(sourceSide) : [];
|
|
31
|
+
const declaredRes = sourceSide
|
|
32
|
+
? collectSourceSites(sourceSide, declaredFiles)
|
|
33
|
+
: { sites: [], error: 'rule sets no source side' };
|
|
36
34
|
const registeredFiles = new Set();
|
|
37
35
|
const registeredSites = [];
|
|
38
36
|
let registeredError;
|
|
39
|
-
for (const source of
|
|
37
|
+
for (const source of sinkSources) {
|
|
40
38
|
const files = filesFor(source);
|
|
41
39
|
for (const f of files)
|
|
42
40
|
registeredFiles.add(f.path);
|
|
@@ -48,17 +46,16 @@ export function explainWiring(projectRoot, rule, options = {}) {
|
|
|
48
46
|
// Canonical diff + counts + verdict from the gate engine (same groupBy logic).
|
|
49
47
|
const report = evaluateWiring([rule], filesFor);
|
|
50
48
|
const ruleResult = report.rules[0];
|
|
51
|
-
const
|
|
52
|
-
.filter((v) => v.direction ===
|
|
53
|
-
.map((v) => ({ token: v.token, file: v.file, line: v.line })));
|
|
54
|
-
const registeredNotDeclared = sortSites((ruleResult?.violations ?? [])
|
|
55
|
-
.filter((v) => v.direction === 'registered-missing')
|
|
49
|
+
const byDirection = (d) => sortSites((ruleResult?.violations ?? [])
|
|
50
|
+
.filter((v) => v.direction === d)
|
|
56
51
|
.map((v) => ({ token: v.token, file: v.file, line: v.line })));
|
|
52
|
+
const skip = report.skipped[0];
|
|
57
53
|
return {
|
|
58
54
|
schema: WIRING_EXPLAIN_SCHEMA,
|
|
59
55
|
ruleId: rule.id,
|
|
60
56
|
...(rule.description ? { description: rule.description } : {}),
|
|
61
|
-
mode: rule.mode
|
|
57
|
+
mode: rule.mode ?? 'subset',
|
|
58
|
+
registeredMode: rule.registeredMode ?? 'union',
|
|
62
59
|
...(rule.groupBy ? { groupBy: rule.groupBy } : {}),
|
|
63
60
|
severity: rule.severity ?? 'error',
|
|
64
61
|
declared: {
|
|
@@ -73,8 +70,12 @@ export function explainWiring(projectRoot, rule, options = {}) {
|
|
|
73
70
|
filesScanned: registeredFiles.size,
|
|
74
71
|
...(registeredError ? { error: registeredError } : {}),
|
|
75
72
|
},
|
|
76
|
-
declaredNotRegistered,
|
|
77
|
-
registeredNotDeclared,
|
|
73
|
+
declaredNotRegistered: byDirection('declared-missing'),
|
|
74
|
+
registeredNotDeclared: byDirection('registered-missing'),
|
|
75
|
+
overlap: byDirection('overlap'),
|
|
76
|
+
...(ruleResult?.hops ? { hops: ruleResult.hops } : {}),
|
|
77
|
+
status: ruleResult?.status ?? 'error',
|
|
78
|
+
...(skip ? { skipReason: skip.reason } : {}),
|
|
78
79
|
verdict: report.verdict,
|
|
79
80
|
diagnostics: report.diagnostics,
|
|
80
81
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IRegistrationIdiom } from '@shrkcrft/core';
|
|
2
|
+
import { type IWiringFileEntry } from './evaluate-wiring.js';
|
|
2
3
|
export declare const REGISTRATION_GRAPH_SCHEMA: "sharkcraft.registration-graph/v1";
|
|
3
4
|
/** A token role-site: which idiom + file:line it was found at. */
|
|
4
5
|
export interface IRegistrationSite {
|
|
@@ -54,6 +55,14 @@ export declare function buildRegistrationGraph(projectRoot: string, idioms: read
|
|
|
54
55
|
* only (no file reads), so it stays far cheaper than a full build. Never throws.
|
|
55
56
|
*/
|
|
56
57
|
export declare function registrationGraphSignature(projectRoot: string, idioms: readonly IRegistrationIdiom[], options?: IBuildRegistrationGraphOptions): string;
|
|
58
|
+
/**
|
|
59
|
+
* The set of tokens the given file entries PROVIDE under any idiom. Used to diff
|
|
60
|
+
* a base-ref snapshot against the worktree: a REMOVED provider leaves NO site in
|
|
61
|
+
* the changed file (so post-change site-scoping can't see it), but the base
|
|
62
|
+
* content still shows the token was provided there — so a provider deletion
|
|
63
|
+
* (the most common way DI wiring silently breaks) stays change-attributable.
|
|
64
|
+
*/
|
|
65
|
+
export declare function providedTokensFromEntries(idioms: readonly IRegistrationIdiom[], entries: readonly IWiringFileEntry[]): Set<string>;
|
|
57
66
|
/** A token's full registration chain, with role presence flags. */
|
|
58
67
|
export interface IRegistrationChain extends IRegistrationNode {
|
|
59
68
|
readonly isDeclared: boolean;
|
|
@@ -71,13 +80,26 @@ export interface IUnprovidedToken {
|
|
|
71
80
|
readonly declared: readonly IRegistrationSite[];
|
|
72
81
|
readonly consumed: readonly IRegistrationSite[];
|
|
73
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Does the changed-file set touch ANY role-site of ANY token in the graph? Lets
|
|
85
|
+
* a changed-only gate tell "evaluated the registration scope, found it clean"
|
|
86
|
+
* (a real pass) apart from "the change touched no wiring at all" (nothing to
|
|
87
|
+
* verify → the caller should skip, never paint green). An empty `changedFiles`
|
|
88
|
+
* is vacuously false.
|
|
89
|
+
*/
|
|
90
|
+
export declare function registrationTouchesChanged(graph: IRegistrationGraph, changedFiles: readonly string[]): boolean;
|
|
74
91
|
/**
|
|
75
92
|
* `wiring unprovided` — tokens that are DECLARED or CONSUMED but have ZERO
|
|
76
93
|
* provided sites. This is the silent-at-runtime class: typecheck/AOT-green, but
|
|
77
94
|
* the provider is never registered (or the injected token has no provider
|
|
78
95
|
* anywhere), so it resolves to undefined at runtime. The thing imports can't see.
|
|
96
|
+
*
|
|
97
|
+
* When `changedFiles` is supplied the result is CHANGESET-SCOPED: a token is
|
|
98
|
+
* kept only if one of its declared/consumed sites is in the changed set — i.e.
|
|
99
|
+
* "did THIS change leave a token unprovided", the change-attributed question a
|
|
100
|
+
* `wiring unprovided --changed-only` gate needs.
|
|
79
101
|
*/
|
|
80
|
-
export declare function registrationUnprovided(graph: IRegistrationGraph): readonly IUnprovidedToken[];
|
|
102
|
+
export declare function registrationUnprovided(graph: IRegistrationGraph, changedFiles?: readonly string[]): readonly IUnprovidedToken[];
|
|
81
103
|
/** A token provided/registered that nothing consumes — a dead registration. */
|
|
82
104
|
export interface IOrphanRegistration {
|
|
83
105
|
readonly token: string;
|
|
@@ -87,6 +109,9 @@ export interface IOrphanRegistration {
|
|
|
87
109
|
* `wiring orphans` — tokens that ARE provided/registered but have ZERO consumed
|
|
88
110
|
* sites: a provider/registration nothing injects (a build-clean no-op, or a
|
|
89
111
|
* sign the consumer was renamed/removed).
|
|
112
|
+
*
|
|
113
|
+
* `changedFiles`, when supplied, scopes the result to orphans whose provided
|
|
114
|
+
* site is in the changed set (the registration THIS change added/touched).
|
|
90
115
|
*/
|
|
91
|
-
export declare function registrationOrphans(graph: IRegistrationGraph): readonly IOrphanRegistration[];
|
|
116
|
+
export declare function registrationOrphans(graph: IRegistrationGraph, changedFiles?: readonly string[]): readonly IOrphanRegistration[];
|
|
92
117
|
//# sourceMappingURL=registration-graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -86,6 +86,22 @@ export function registrationGraphSignature(projectRoot, idioms, options = {}) {
|
|
|
86
86
|
}
|
|
87
87
|
return createHash('sha1').update(parts.join('\n')).digest('hex').slice(0, 16);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The set of tokens the given file entries PROVIDE under any idiom. Used to diff
|
|
91
|
+
* a base-ref snapshot against the worktree: a REMOVED provider leaves NO site in
|
|
92
|
+
* the changed file (so post-change site-scoping can't see it), but the base
|
|
93
|
+
* content still shows the token was provided there — so a provider deletion
|
|
94
|
+
* (the most common way DI wiring silently breaks) stays change-attributable.
|
|
95
|
+
*/
|
|
96
|
+
export function providedTokensFromEntries(idioms, entries) {
|
|
97
|
+
const tokens = new Set();
|
|
98
|
+
for (const idiom of idioms) {
|
|
99
|
+
const files = entries.filter((f) => matchesAny(f.path, idiom.provided.files));
|
|
100
|
+
for (const s of collectSourceSites(idiom.provided, files).sites)
|
|
101
|
+
tokens.add(s.token);
|
|
102
|
+
}
|
|
103
|
+
return tokens;
|
|
104
|
+
}
|
|
89
105
|
/**
|
|
90
106
|
* `wiring chain <token>` — the declared → provided → consumed hops of one token
|
|
91
107
|
* with file:line at each, or `undefined` if the token is unknown to the graph.
|
|
@@ -101,24 +117,57 @@ export function registrationChain(graph, token) {
|
|
|
101
117
|
isConsumed: node.consumed.length > 0,
|
|
102
118
|
};
|
|
103
119
|
}
|
|
120
|
+
/** Project-relative path normalizer so site files and git changed-files compare. */
|
|
121
|
+
function normalizeRel(p) {
|
|
122
|
+
return p.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
123
|
+
}
|
|
124
|
+
/** True when ANY of `sites` falls in the changed-file scope. */
|
|
125
|
+
function anySiteInScope(sites, scope) {
|
|
126
|
+
return sites.some((s) => scope.has(normalizeRel(s.file)));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Does the changed-file set touch ANY role-site of ANY token in the graph? Lets
|
|
130
|
+
* a changed-only gate tell "evaluated the registration scope, found it clean"
|
|
131
|
+
* (a real pass) apart from "the change touched no wiring at all" (nothing to
|
|
132
|
+
* verify → the caller should skip, never paint green). An empty `changedFiles`
|
|
133
|
+
* is vacuously false.
|
|
134
|
+
*/
|
|
135
|
+
export function registrationTouchesChanged(graph, changedFiles) {
|
|
136
|
+
if (changedFiles.length === 0)
|
|
137
|
+
return false;
|
|
138
|
+
const scope = new Set(changedFiles.map(normalizeRel));
|
|
139
|
+
return graph.tokens.some((t) => anySiteInScope([...t.declared, ...t.provided, ...t.consumed], scope));
|
|
140
|
+
}
|
|
104
141
|
/**
|
|
105
142
|
* `wiring unprovided` — tokens that are DECLARED or CONSUMED but have ZERO
|
|
106
143
|
* provided sites. This is the silent-at-runtime class: typecheck/AOT-green, but
|
|
107
144
|
* the provider is never registered (or the injected token has no provider
|
|
108
145
|
* anywhere), so it resolves to undefined at runtime. The thing imports can't see.
|
|
146
|
+
*
|
|
147
|
+
* When `changedFiles` is supplied the result is CHANGESET-SCOPED: a token is
|
|
148
|
+
* kept only if one of its declared/consumed sites is in the changed set — i.e.
|
|
149
|
+
* "did THIS change leave a token unprovided", the change-attributed question a
|
|
150
|
+
* `wiring unprovided --changed-only` gate needs.
|
|
109
151
|
*/
|
|
110
|
-
export function registrationUnprovided(graph) {
|
|
152
|
+
export function registrationUnprovided(graph, changedFiles) {
|
|
153
|
+
const scope = changedFiles ? new Set(changedFiles.map(normalizeRel)) : undefined;
|
|
111
154
|
return graph.tokens
|
|
112
155
|
.filter((t) => t.provided.length === 0 && (t.declared.length > 0 || t.consumed.length > 0))
|
|
156
|
+
.filter((t) => !scope || anySiteInScope([...t.declared, ...t.consumed], scope))
|
|
113
157
|
.map((t) => ({ token: t.token, declared: t.declared, consumed: t.consumed }));
|
|
114
158
|
}
|
|
115
159
|
/**
|
|
116
160
|
* `wiring orphans` — tokens that ARE provided/registered but have ZERO consumed
|
|
117
161
|
* sites: a provider/registration nothing injects (a build-clean no-op, or a
|
|
118
162
|
* sign the consumer was renamed/removed).
|
|
163
|
+
*
|
|
164
|
+
* `changedFiles`, when supplied, scopes the result to orphans whose provided
|
|
165
|
+
* site is in the changed set (the registration THIS change added/touched).
|
|
119
166
|
*/
|
|
120
|
-
export function registrationOrphans(graph) {
|
|
167
|
+
export function registrationOrphans(graph, changedFiles) {
|
|
168
|
+
const scope = changedFiles ? new Set(changedFiles.map(normalizeRel)) : undefined;
|
|
121
169
|
return graph.tokens
|
|
122
170
|
.filter((t) => t.provided.length > 0 && t.consumed.length === 0)
|
|
171
|
+
.filter((t) => !scope || anySiteInScope(t.provided, scope))
|
|
123
172
|
.map((t) => ({ token: t.token, provided: t.provided }));
|
|
124
173
|
}
|
|
@@ -37,4 +37,13 @@ export declare function scanRegistry(projectRoot: string, decl: IRegistryDeclara
|
|
|
37
37
|
export declare function registryExists(inventory: IRegistryInventory, id: string): boolean;
|
|
38
38
|
/** The entry for `id`, or undefined if not declared. */
|
|
39
39
|
export declare function registryWhere(inventory: IRegistryInventory, id: string): IRegistryEntry | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Ids declared in more than one place.
|
|
42
|
+
*
|
|
43
|
+
* A latent bug the compiler cannot see: two roots both claiming the same
|
|
44
|
+
* registry id compile fine, and whichever registration wins at runtime is an
|
|
45
|
+
* accident of load order. Reported with EVERY site so the duplicate can be
|
|
46
|
+
* resolved, not just detected.
|
|
47
|
+
*/
|
|
48
|
+
export declare function registryDuplicates(inventory: IRegistryInventory): readonly IRegistryEntry[];
|
|
40
49
|
//# sourceMappingURL=registry-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAK3D,eAAO,MAAM,eAAe,EAAG,kCAA2C,CAAC;AAE3E,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,eAAe,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,oBAAyB,GACjC,kBAAkB,CA+CpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjF;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEnG"}
|
|
1
|
+
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAK3D,eAAO,MAAM,eAAe,EAAG,kCAA2C,CAAC;AAE3E,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,eAAe,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,oBAAyB,GACjC,kBAAkB,CA+CpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjF;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEnG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,GAAG,SAAS,cAAc,EAAE,CAE3F"}
|
|
@@ -63,3 +63,14 @@ export function registryExists(inventory, id) {
|
|
|
63
63
|
export function registryWhere(inventory, id) {
|
|
64
64
|
return inventory.entries.find((e) => e.id === id);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Ids declared in more than one place.
|
|
68
|
+
*
|
|
69
|
+
* A latent bug the compiler cannot see: two roots both claiming the same
|
|
70
|
+
* registry id compile fine, and whichever registration wins at runtime is an
|
|
71
|
+
* accident of load order. Reported with EVERY site so the duplicate can be
|
|
72
|
+
* resolved, not just detected.
|
|
73
|
+
*/
|
|
74
|
+
export function registryDuplicates(inventory) {
|
|
75
|
+
return inventory.entries.filter((e) => e.sites.length > 1);
|
|
76
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAO9B,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,iGAAiG;IACjG,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,GAAE,iBAAsB,GAC9B,aAAa,CA+Bf"}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { matchesAny } from "../scan/glob.js";
|
|
2
2
|
import { readMatchingFiles } from "../util/walk-files.js";
|
|
3
|
-
import { evaluateWiring, } from "./evaluate-wiring.js";
|
|
4
|
-
/** All file globs a rule references:
|
|
3
|
+
import { evaluateWiring, wiringGlobsOf, } from "./evaluate-wiring.js";
|
|
4
|
+
/** All file globs a rule references: every side / every hop. */
|
|
5
5
|
function ruleGlobs(rule) {
|
|
6
|
-
|
|
7
|
-
const sources = Array.isArray(reg)
|
|
8
|
-
? reg
|
|
9
|
-
: [reg];
|
|
10
|
-
return [...rule.declared.files, ...sources.flatMap((s) => [...s.files])];
|
|
6
|
+
return wiringGlobsOf(rule);
|
|
11
7
|
}
|
|
12
8
|
/**
|
|
13
9
|
* Filesystem-backed wiring run: walks the project once, reads only the files a
|
|
@@ -33,6 +29,7 @@ export function runWiring(projectRoot, rules, options = {}) {
|
|
|
33
29
|
rules: [],
|
|
34
30
|
violations: [],
|
|
35
31
|
diagnostics: [],
|
|
32
|
+
skipped: [],
|
|
36
33
|
evaluated: 0,
|
|
37
34
|
verdict: 'pass',
|
|
38
35
|
};
|
|
@@ -7,12 +7,18 @@ export declare const TRACE_DEFAULT_GLOBS: readonly string[];
|
|
|
7
7
|
* - `declare` — a canonical definition (`const X = 'lit'`, `kind: 'lit'`, an enum value).
|
|
8
8
|
* - `register` — added to a collection / mapping (`register('lit')`, an array element, `{ 'lit': … }`).
|
|
9
9
|
* - `consume` — compared / switched / handled (`=== 'lit'`, `case 'lit':`).
|
|
10
|
+
* - `render` — rendered / emitted at the boundary (JSX child `>{ 'lit' }`, template
|
|
11
|
+
* interpolation `${ 'lit' }`, a `render('lit')` / `res.send('lit')` call).
|
|
10
12
|
* - `reference`— an occurrence we can't confidently classify (still reported, with context).
|
|
13
|
+
*
|
|
14
|
+
* `render` only ever refines what would otherwise be a `reference`; it never
|
|
15
|
+
* steals a site from `declare` / `register` / `consume`.
|
|
11
16
|
*/
|
|
12
17
|
export declare enum TraceRole {
|
|
13
18
|
Declare = "declare",
|
|
14
19
|
Register = "register",
|
|
15
20
|
Consume = "consume",
|
|
21
|
+
Render = "render",
|
|
16
22
|
Reference = "reference"
|
|
17
23
|
}
|
|
18
24
|
/** One occurrence of the traced literal (or a const aliased to it). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace-literal.d.ts","sourceRoot":"","sources":["../../src/wiring/trace-literal.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAG,qBAA8B,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAShD,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"trace-literal.d.ts","sourceRoot":"","sources":["../../src/wiring/trace-literal.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAG,qBAA8B,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAShD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,oBAAY,SAAS;IACnB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,SAAS,cAAc;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,YAAY,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC;IACpE,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AA6ED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,YAAY,CAuFd"}
|