@archwall/core 0.1.0 → 0.2.1
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/CHANGELOG.md +82 -0
- package/dist/index.cjs +53 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -5
- package/dist/index.d.mts +117 -5
- package/dist/index.mjs +53 -8
- package/dist/index.mjs.map +1 -1
- package/dist/internal.cjs +2 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.mts +3 -3
- package/dist/internal.mjs +2 -2
- package/dist/{prepare-BJHgDEui.mjs → prepare-BjMxyYOm.mjs} +29 -5
- package/dist/prepare-BjMxyYOm.mjs.map +1 -0
- package/dist/{prepare-C1FfL8Qd.cjs → prepare-CEaZxLPI.cjs} +29 -5
- package/dist/prepare-CEaZxLPI.cjs.map +1 -0
- package/dist/{transform-CnUPOO0E.d.mts → transform-CzxyWbUC.d.cts} +139 -14
- package/dist/{transform-CnUPOO0E.d.cts → transform-CzxyWbUC.d.mts} +139 -14
- package/package.json +10 -4
- package/dist/prepare-BJHgDEui.mjs.map +0 -1
- package/dist/prepare-C1FfL8Qd.cjs.map +0 -1
|
@@ -54,7 +54,16 @@ type WellKnownCapability =
|
|
|
54
54
|
* that matches on specifiers must require this, or it silently matches nothing on hosts
|
|
55
55
|
* that cannot supply them and reports a clean run rather than an unavailable one.
|
|
56
56
|
*/
|
|
57
|
-
"raw-specifiers"
|
|
57
|
+
"raw-specifiers" |
|
|
58
|
+
/**
|
|
59
|
+
* Type-only edges are PRESENT and carry `attributes.typeOnly`. See {@link EdgeAttributes}.
|
|
60
|
+
*
|
|
61
|
+
* A rule that treats type-only imports differently must require this. Without it, a host
|
|
62
|
+
* that erases type imports (every bundler does) is indistinguishable from one where the
|
|
63
|
+
* code genuinely has no type imports — and "no `attributes.typeOnly` anywhere" would be
|
|
64
|
+
* read as "nothing is type-only" rather than "nobody asked".
|
|
65
|
+
*/
|
|
66
|
+
"type-only-edges";
|
|
58
67
|
/**
|
|
59
68
|
* Open union: adapters may declare capabilities core does not know about, and rules may
|
|
60
69
|
* require them, without an IR major. `WellKnownCapability` keeps autocomplete useful for
|
|
@@ -75,11 +84,44 @@ interface SourceLocation {
|
|
|
75
84
|
}
|
|
76
85
|
type WellKnownEdgeKind = "static" | "dynamic" | "reexport";
|
|
77
86
|
/**
|
|
78
|
-
* Open union so future graph facts (CSS imports, worker edges
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* Open union so future graph facts (CSS imports, worker edges) arrive additively. Consumers
|
|
88
|
+
* must treat an unrecognised kind as "some dependency exists" — never assume exhaustiveness.
|
|
89
|
+
*
|
|
90
|
+
* `kind` answers ONE question — roughly "what syntax produced this edge" — and deliberately
|
|
91
|
+
* keeps answering only that. Everything else an edge might be belongs in
|
|
92
|
+
* {@link EdgeAttributes}; see the note there for why.
|
|
81
93
|
*/
|
|
82
94
|
type EdgeKind = WellKnownEdgeKind | (string & {});
|
|
95
|
+
/**
|
|
96
|
+
* Orthogonal facts about an edge, as an OPEN bag.
|
|
97
|
+
*
|
|
98
|
+
* `kind` is one enum, but the domain is not one-dimensional. `export type * from "./x"` is
|
|
99
|
+
* *both* a re-export and type-only; a dynamic import of a barrel is both dynamic and a
|
|
100
|
+
* re-export. Modelling those as one enum forces every producer to pick a winner, and forces
|
|
101
|
+
* every consumer to guess which axis the winner came from.
|
|
102
|
+
*
|
|
103
|
+
* So the axes split: `kind` keeps the syntactic one, and everything else lands here, where it
|
|
104
|
+
* composes. A bag rather than named fields because the next axis is always unknown — import
|
|
105
|
+
* attributes (`with { type: "json" }`), worker edges, CSS edges, `require` vs `import`
|
|
106
|
+
* interop — and each one arriving as a new top-level `Edge` field would be a new IR major.
|
|
107
|
+
*
|
|
108
|
+
* **Absent means "the host did not say", never "false".** That distinction is the whole
|
|
109
|
+
* reason {@link WellKnownCapability} has `type-only-edges`: a bundler that erased type imports
|
|
110
|
+
* before ArchWall saw them reports nothing here, and a rule must be able to tell that apart
|
|
111
|
+
* from a codebase with no type imports in it.
|
|
112
|
+
*/
|
|
113
|
+
interface EdgeAttributes {
|
|
114
|
+
/**
|
|
115
|
+
* The import is erased at compile time (`import type`, `export type`, or an
|
|
116
|
+
* `import { type X }` specifier where every named binding is type-only).
|
|
117
|
+
*
|
|
118
|
+
* Requires the `type-only-edges` capability to be meaningful. `true` or absent — never
|
|
119
|
+
* `false`, so that a producer cannot accidentally assert the negative it does not know.
|
|
120
|
+
*/
|
|
121
|
+
typeOnly?: true;
|
|
122
|
+
/** Third parties and future core versions extend here without an IR major. */
|
|
123
|
+
[key: string]: string | true | undefined;
|
|
124
|
+
}
|
|
83
125
|
/**
|
|
84
126
|
* What a module *is*, relative to the project being analysed.
|
|
85
127
|
*
|
|
@@ -129,6 +171,8 @@ interface Edge {
|
|
|
129
171
|
kind: EdgeKind;
|
|
130
172
|
/** Present only if host capability allows. */
|
|
131
173
|
loc?: SourceLocation;
|
|
174
|
+
/** Orthogonal facts; see {@link EdgeAttributes}. Absent when the host reported none. */
|
|
175
|
+
attributes?: EdgeAttributes;
|
|
132
176
|
}
|
|
133
177
|
type GraphDelivery = "complete" | "progressive";
|
|
134
178
|
interface ProjectGraphInit {
|
|
@@ -139,6 +183,14 @@ interface ProjectGraphInit {
|
|
|
139
183
|
edges: readonly Edge[];
|
|
140
184
|
/** Default {@link IR_VERSION}. Adapters should leave this alone. */
|
|
141
185
|
irVersion?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Opaque identity for this graph. Defaults to a fresh process-local number.
|
|
188
|
+
*
|
|
189
|
+
* The contract is one-directional and deliberately weak: **equal revisions mean the same
|
|
190
|
+
* graph; unequal revisions mean nothing.** Set it explicitly only if you can guarantee the
|
|
191
|
+
* first half — a producer that content-hashes its inputs, for instance.
|
|
192
|
+
*/
|
|
193
|
+
revision?: number;
|
|
142
194
|
}
|
|
143
195
|
/**
|
|
144
196
|
* The module graph, as an OPAQUE handle.
|
|
@@ -155,6 +207,12 @@ declare class ProjectGraph {
|
|
|
155
207
|
readonly irVersion: string;
|
|
156
208
|
readonly host: HostInfo;
|
|
157
209
|
readonly delivery: GraphDelivery;
|
|
210
|
+
/**
|
|
211
|
+
* See {@link ProjectGraphInit.revision}. Preserved across {@link replaceStores}, because a
|
|
212
|
+
* derived graph is a deterministic function of this one and the config that derived it —
|
|
213
|
+
* so a cache keyed on `(revision, configKey)` stays sound through the prepare pipeline.
|
|
214
|
+
*/
|
|
215
|
+
readonly revision: number;
|
|
158
216
|
private constructor();
|
|
159
217
|
static create(init: ProjectGraphInit): ProjectGraph;
|
|
160
218
|
get moduleCount(): number;
|
|
@@ -261,8 +319,6 @@ type Severity = "error" | "warn" | "info";
|
|
|
261
319
|
* offending location — it has N of them, and the old model could only name one and had to
|
|
262
320
|
* serialise the rest into the message string. A finding about a package, a directory, or
|
|
263
321
|
* the configuration has no module at all.
|
|
264
|
-
*
|
|
265
|
-
* See docs/adr/0004-violation-locations.md.
|
|
266
322
|
*/
|
|
267
323
|
type ViolationLocation = {
|
|
268
324
|
type: "edge";
|
|
@@ -355,8 +411,7 @@ declare function renderMessage(template: string, data: Readonly<Record<string, s
|
|
|
355
411
|
* Fingerprint scheme version. Bump when the algorithm changes so that a stale baseline
|
|
356
412
|
* ERRORS instead of silently mismatching every entry.
|
|
357
413
|
*
|
|
358
|
-
* `aw3` is the first scheme over canonical module ids
|
|
359
|
-
* (docs/adr/0012-canonical-module-identity.md). Before it, a violation about `react` hashed the
|
|
414
|
+
* `aw3` is the first scheme over canonical module ids. Before it, a violation about `react` hashed the
|
|
360
415
|
* host's own id — a resolved `node_modules` path under the CLI, the bare specifier under esbuild
|
|
361
416
|
* — so the same finding fingerprinted differently under two bundlers.
|
|
362
417
|
*/
|
|
@@ -405,7 +460,24 @@ type WellKnownDiagnosticCode =
|
|
|
405
460
|
/** A configured rule, or one of its options, is deprecated. */
|
|
406
461
|
"rule-deprecated" |
|
|
407
462
|
/** A graph transform threw. The pipeline continued without its contribution. */
|
|
408
|
-
"transform-failed"
|
|
463
|
+
"transform-failed" |
|
|
464
|
+
/**
|
|
465
|
+
* Files are inside the project boundary that the producer cannot read, so they are absent
|
|
466
|
+
* from the graph entirely — not excluded, not unresolved, just invisible.
|
|
467
|
+
*
|
|
468
|
+
* Only a producer that ENUMERATES a directory tree can detect this, which in practice means
|
|
469
|
+
* the CLI: a bundler adapter is handed a graph whose membership its compiler already decided.
|
|
470
|
+
*/
|
|
471
|
+
"unscannable-files" |
|
|
472
|
+
/**
|
|
473
|
+
* The baseline lists violations this run did not produce — they were fixed, or the code
|
|
474
|
+
* they were about is gone.
|
|
475
|
+
*
|
|
476
|
+
* Worth saying out loud rather than tolerating: a baseline that is never pruned stops being
|
|
477
|
+
* "debt we accepted" and becomes a permanent hole, and the stale entry will silently
|
|
478
|
+
* re-suppress the finding if it ever comes back.
|
|
479
|
+
*/
|
|
480
|
+
"baseline-stale";
|
|
409
481
|
type DiagnosticCode = WellKnownDiagnosticCode | (string & {});
|
|
410
482
|
/**
|
|
411
483
|
* Everything the run wants to say that is *not* a violation of the user's architecture: a
|
|
@@ -428,6 +500,15 @@ interface RuleSkippedDetails {
|
|
|
428
500
|
missingCapabilities: readonly Capability[];
|
|
429
501
|
host: string;
|
|
430
502
|
}
|
|
503
|
+
/** Payload shape for `code: "unscannable-files"`. */
|
|
504
|
+
interface UnscannableFilesDetails {
|
|
505
|
+
/** How many in-boundary files the producer could not read. */
|
|
506
|
+
count: number;
|
|
507
|
+
/** Distinct extensions, most common first — what to act on. */
|
|
508
|
+
extensions: readonly string[];
|
|
509
|
+
/** A bounded sample of repo-relative paths, for a message a human can follow. */
|
|
510
|
+
sample: readonly string[];
|
|
511
|
+
}
|
|
431
512
|
/** Payload shape for `code: "empty-scope"`. */
|
|
432
513
|
interface EmptyScopeDetails {
|
|
433
514
|
/** The scope as configured, so the message can be acted on without reopening the config. */
|
|
@@ -459,6 +540,16 @@ interface EdgeFilter {
|
|
|
459
540
|
toTag?: Record<string, string>;
|
|
460
541
|
/** Tag key; keep edge iff BOTH endpoints have the tag and values differ. */
|
|
461
542
|
crossing?: string;
|
|
543
|
+
/**
|
|
544
|
+
* Selects on {@link EdgeAttributes}. `true` requires the attribute present; `false` requires
|
|
545
|
+
* it ABSENT; a string requires that exact value.
|
|
546
|
+
*
|
|
547
|
+
* `false` and "absent" are the same test on purpose — attributes are never stored as `false`
|
|
548
|
+
* (see {@link EdgeAttributes}), so "not type-only" and "nobody said" are indistinguishable
|
|
549
|
+
* *here* by construction. A rule that must tell them apart declares the corresponding
|
|
550
|
+
* capability and gets skipped loudly instead, which is the only honest answer.
|
|
551
|
+
*/
|
|
552
|
+
attributes?: Readonly<Record<string, string | boolean>>;
|
|
462
553
|
}
|
|
463
554
|
/**
|
|
464
555
|
* Stable key for a filter, so the engine can bucket rules that want the same slice of the
|
|
@@ -504,7 +595,41 @@ interface ModuleSelection extends Iterable<ModuleNode> {
|
|
|
504
595
|
edgesIn(filter?: EdgeFilter): readonly Edge[];
|
|
505
596
|
}
|
|
506
597
|
/**
|
|
507
|
-
* The
|
|
598
|
+
* The read surface a rule gets — and the type it should name.
|
|
599
|
+
*
|
|
600
|
+
* An INTERFACE rather than the class, because the two are different promises. What ArchWall
|
|
601
|
+
* owes a rule author is a set of questions that can be asked about a graph; what it must stay
|
|
602
|
+
* free to change is how those questions are answered. Naming the class in `RuleContext` fused
|
|
603
|
+
* the two: the concrete implementation became observable via `instanceof`, a test double became
|
|
604
|
+
* impossible to supply, and an interned or columnar store became a breaking change rather than
|
|
605
|
+
* an optimisation.
|
|
606
|
+
*
|
|
607
|
+
* {@link GraphQuery} is the only implementation core ships, and it lives in
|
|
608
|
+
* `@archwall/core/internal`. Rules never construct one — they are handed one — so nothing is
|
|
609
|
+
* taken away by that; a rule author who needs one for a TEST gets it from
|
|
610
|
+
* `@archwall/test-utils`, which is the supported way to build a graph by hand.
|
|
611
|
+
*
|
|
612
|
+
* See {@link GraphQuery} for what scope does to each of these operations.
|
|
613
|
+
*/
|
|
614
|
+
interface GraphView {
|
|
615
|
+
module(id: ModuleId): ModuleNode | undefined;
|
|
616
|
+
moduleCount(): number;
|
|
617
|
+
moduleIds(): Iterable<ModuleId>;
|
|
618
|
+
has(id: ModuleId): boolean;
|
|
619
|
+
tagOf(id: ModuleId, key: string): string | undefined;
|
|
620
|
+
modules(filter?: ModuleFilter): ModuleSelection;
|
|
621
|
+
edges(filter?: EdgeFilter): readonly Edge[];
|
|
622
|
+
edgesOutOf(id: ModuleId): readonly Edge[];
|
|
623
|
+
edgesInto(id: ModuleId): readonly Edge[];
|
|
624
|
+
reachableFrom(id: ModuleId, filter?: EdgeFilter): ReadonlySet<ModuleId>;
|
|
625
|
+
reaching(id: ModuleId, filter?: EdgeFilter): ReadonlySet<ModuleId>;
|
|
626
|
+
pathBetween(from: ModuleId, to: ModuleId, filter?: EdgeFilter): readonly ModuleId[] | null;
|
|
627
|
+
filterEdges(edges: readonly Edge[], filter?: EdgeFilter): readonly Edge[];
|
|
628
|
+
matchesEdge(e: Edge, filter: EdgeFilter): boolean;
|
|
629
|
+
matchesModule(m: ModuleNode, filter: ModuleFilter): boolean;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* The only sanctioned way to read a graph; the sole implementation of {@link GraphView}.
|
|
508
633
|
*
|
|
509
634
|
* A scoped query is a VIEW: it shares the underlying {@link GraphIndex} with the query it
|
|
510
635
|
* came from and differs only in which modules it is *about*.
|
|
@@ -528,7 +653,7 @@ interface ModuleSelection extends Iterable<ModuleNode> {
|
|
|
528
653
|
* thing it can find; hiding the target would turn `layer-dependencies` under a scope from a
|
|
529
654
|
* finding into silence.
|
|
530
655
|
*/
|
|
531
|
-
declare class GraphQuery {
|
|
656
|
+
declare class GraphQuery implements GraphView {
|
|
532
657
|
#private;
|
|
533
658
|
constructor(graph: ProjectGraph, index?: GraphIndex, scope?: ReadonlySet<ModuleId>);
|
|
534
659
|
/** A view of the same graph restricted to `scope`, sharing this query's index. */
|
|
@@ -585,7 +710,7 @@ declare class GraphQuery {
|
|
|
585
710
|
*/
|
|
586
711
|
interface GraphComputation<T> {
|
|
587
712
|
name: string;
|
|
588
|
-
compute(graph:
|
|
713
|
+
compute(graph: GraphView): T;
|
|
589
714
|
}
|
|
590
715
|
declare function defineGraphComputation<T>(computation: GraphComputation<T>): GraphComputation<T>;
|
|
591
716
|
//#endregion
|
|
@@ -634,5 +759,5 @@ interface GraphTransform {
|
|
|
634
759
|
}
|
|
635
760
|
declare function defineTransform(transform: GraphTransform): GraphTransform;
|
|
636
761
|
//#endregion
|
|
637
|
-
export {
|
|
638
|
-
//# sourceMappingURL=transform-
|
|
762
|
+
export { ProjectGraph as $, primaryEdge as A, EdgeAttributes as B, Violation as C, countBySeverity as D, compareViolations as E, ClassifierContext as F, GraphMutation as G, FIRST_PARTY_KINDS as H, TagPatch as I, MODULE_ID_SCHEMES as J, HostInfo as K, defineClassifier as L, primarySourceLocation as M, renderMessage as N, fingerprintOf as O, Classifier as P, ModuleNode as Q, Capability as R, SeverityCounts as S, ViolationLocation as T, GraphDelivery as U, EdgeKind as V, GraphDraft as W, ModuleIdScheme as X, ModuleId as Y, ModuleKind as Z, RuleSkippedDetails as _, defineGraphComputation as a, assertIrCompatible as at, FINGERPRINT_SCHEME as b, GraphQuery as c, isFirstParty as ct, ModuleSelection as d, ProjectGraphInit as et, filterKey as f, EmptyScopeDetails as g, DiagnosticSeverity as h, GraphComputation as i, WellKnownEdgeKind as it, primaryModule as j, locationsOf as k, GraphView as l, isThirdParty as lt, DiagnosticCode as m, TransformContext as n, THIRD_PARTY_KINDS as nt, EdgeFilter as o, displayModuleId as ot, Diagnostic as p, IR_VERSION as q, defineTransform as r, WellKnownCapability as rt, GraphIndex as s, irMajor as st, GraphTransform as t, SourceLocation as tt, ModuleFilter as u, parseModuleId as ut, UnscannableFilesDetails as v, ViolationInput as w, Severity as x, WellKnownDiagnosticCode as y, Edge as z };
|
|
763
|
+
//# sourceMappingURL=transform-CzxyWbUC.d.cts.map
|
|
@@ -54,7 +54,16 @@ type WellKnownCapability =
|
|
|
54
54
|
* that matches on specifiers must require this, or it silently matches nothing on hosts
|
|
55
55
|
* that cannot supply them and reports a clean run rather than an unavailable one.
|
|
56
56
|
*/
|
|
57
|
-
"raw-specifiers"
|
|
57
|
+
"raw-specifiers" |
|
|
58
|
+
/**
|
|
59
|
+
* Type-only edges are PRESENT and carry `attributes.typeOnly`. See {@link EdgeAttributes}.
|
|
60
|
+
*
|
|
61
|
+
* A rule that treats type-only imports differently must require this. Without it, a host
|
|
62
|
+
* that erases type imports (every bundler does) is indistinguishable from one where the
|
|
63
|
+
* code genuinely has no type imports — and "no `attributes.typeOnly` anywhere" would be
|
|
64
|
+
* read as "nothing is type-only" rather than "nobody asked".
|
|
65
|
+
*/
|
|
66
|
+
"type-only-edges";
|
|
58
67
|
/**
|
|
59
68
|
* Open union: adapters may declare capabilities core does not know about, and rules may
|
|
60
69
|
* require them, without an IR major. `WellKnownCapability` keeps autocomplete useful for
|
|
@@ -75,11 +84,44 @@ interface SourceLocation {
|
|
|
75
84
|
}
|
|
76
85
|
type WellKnownEdgeKind = "static" | "dynamic" | "reexport";
|
|
77
86
|
/**
|
|
78
|
-
* Open union so future graph facts (CSS imports, worker edges
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* Open union so future graph facts (CSS imports, worker edges) arrive additively. Consumers
|
|
88
|
+
* must treat an unrecognised kind as "some dependency exists" — never assume exhaustiveness.
|
|
89
|
+
*
|
|
90
|
+
* `kind` answers ONE question — roughly "what syntax produced this edge" — and deliberately
|
|
91
|
+
* keeps answering only that. Everything else an edge might be belongs in
|
|
92
|
+
* {@link EdgeAttributes}; see the note there for why.
|
|
81
93
|
*/
|
|
82
94
|
type EdgeKind = WellKnownEdgeKind | (string & {});
|
|
95
|
+
/**
|
|
96
|
+
* Orthogonal facts about an edge, as an OPEN bag.
|
|
97
|
+
*
|
|
98
|
+
* `kind` is one enum, but the domain is not one-dimensional. `export type * from "./x"` is
|
|
99
|
+
* *both* a re-export and type-only; a dynamic import of a barrel is both dynamic and a
|
|
100
|
+
* re-export. Modelling those as one enum forces every producer to pick a winner, and forces
|
|
101
|
+
* every consumer to guess which axis the winner came from.
|
|
102
|
+
*
|
|
103
|
+
* So the axes split: `kind` keeps the syntactic one, and everything else lands here, where it
|
|
104
|
+
* composes. A bag rather than named fields because the next axis is always unknown — import
|
|
105
|
+
* attributes (`with { type: "json" }`), worker edges, CSS edges, `require` vs `import`
|
|
106
|
+
* interop — and each one arriving as a new top-level `Edge` field would be a new IR major.
|
|
107
|
+
*
|
|
108
|
+
* **Absent means "the host did not say", never "false".** That distinction is the whole
|
|
109
|
+
* reason {@link WellKnownCapability} has `type-only-edges`: a bundler that erased type imports
|
|
110
|
+
* before ArchWall saw them reports nothing here, and a rule must be able to tell that apart
|
|
111
|
+
* from a codebase with no type imports in it.
|
|
112
|
+
*/
|
|
113
|
+
interface EdgeAttributes {
|
|
114
|
+
/**
|
|
115
|
+
* The import is erased at compile time (`import type`, `export type`, or an
|
|
116
|
+
* `import { type X }` specifier where every named binding is type-only).
|
|
117
|
+
*
|
|
118
|
+
* Requires the `type-only-edges` capability to be meaningful. `true` or absent — never
|
|
119
|
+
* `false`, so that a producer cannot accidentally assert the negative it does not know.
|
|
120
|
+
*/
|
|
121
|
+
typeOnly?: true;
|
|
122
|
+
/** Third parties and future core versions extend here without an IR major. */
|
|
123
|
+
[key: string]: string | true | undefined;
|
|
124
|
+
}
|
|
83
125
|
/**
|
|
84
126
|
* What a module *is*, relative to the project being analysed.
|
|
85
127
|
*
|
|
@@ -129,6 +171,8 @@ interface Edge {
|
|
|
129
171
|
kind: EdgeKind;
|
|
130
172
|
/** Present only if host capability allows. */
|
|
131
173
|
loc?: SourceLocation;
|
|
174
|
+
/** Orthogonal facts; see {@link EdgeAttributes}. Absent when the host reported none. */
|
|
175
|
+
attributes?: EdgeAttributes;
|
|
132
176
|
}
|
|
133
177
|
type GraphDelivery = "complete" | "progressive";
|
|
134
178
|
interface ProjectGraphInit {
|
|
@@ -139,6 +183,14 @@ interface ProjectGraphInit {
|
|
|
139
183
|
edges: readonly Edge[];
|
|
140
184
|
/** Default {@link IR_VERSION}. Adapters should leave this alone. */
|
|
141
185
|
irVersion?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Opaque identity for this graph. Defaults to a fresh process-local number.
|
|
188
|
+
*
|
|
189
|
+
* The contract is one-directional and deliberately weak: **equal revisions mean the same
|
|
190
|
+
* graph; unequal revisions mean nothing.** Set it explicitly only if you can guarantee the
|
|
191
|
+
* first half — a producer that content-hashes its inputs, for instance.
|
|
192
|
+
*/
|
|
193
|
+
revision?: number;
|
|
142
194
|
}
|
|
143
195
|
/**
|
|
144
196
|
* The module graph, as an OPAQUE handle.
|
|
@@ -155,6 +207,12 @@ declare class ProjectGraph {
|
|
|
155
207
|
readonly irVersion: string;
|
|
156
208
|
readonly host: HostInfo;
|
|
157
209
|
readonly delivery: GraphDelivery;
|
|
210
|
+
/**
|
|
211
|
+
* See {@link ProjectGraphInit.revision}. Preserved across {@link replaceStores}, because a
|
|
212
|
+
* derived graph is a deterministic function of this one and the config that derived it —
|
|
213
|
+
* so a cache keyed on `(revision, configKey)` stays sound through the prepare pipeline.
|
|
214
|
+
*/
|
|
215
|
+
readonly revision: number;
|
|
158
216
|
private constructor();
|
|
159
217
|
static create(init: ProjectGraphInit): ProjectGraph;
|
|
160
218
|
get moduleCount(): number;
|
|
@@ -261,8 +319,6 @@ type Severity = "error" | "warn" | "info";
|
|
|
261
319
|
* offending location — it has N of them, and the old model could only name one and had to
|
|
262
320
|
* serialise the rest into the message string. A finding about a package, a directory, or
|
|
263
321
|
* the configuration has no module at all.
|
|
264
|
-
*
|
|
265
|
-
* See docs/adr/0004-violation-locations.md.
|
|
266
322
|
*/
|
|
267
323
|
type ViolationLocation = {
|
|
268
324
|
type: "edge";
|
|
@@ -355,8 +411,7 @@ declare function renderMessage(template: string, data: Readonly<Record<string, s
|
|
|
355
411
|
* Fingerprint scheme version. Bump when the algorithm changes so that a stale baseline
|
|
356
412
|
* ERRORS instead of silently mismatching every entry.
|
|
357
413
|
*
|
|
358
|
-
* `aw3` is the first scheme over canonical module ids
|
|
359
|
-
* (docs/adr/0012-canonical-module-identity.md). Before it, a violation about `react` hashed the
|
|
414
|
+
* `aw3` is the first scheme over canonical module ids. Before it, a violation about `react` hashed the
|
|
360
415
|
* host's own id — a resolved `node_modules` path under the CLI, the bare specifier under esbuild
|
|
361
416
|
* — so the same finding fingerprinted differently under two bundlers.
|
|
362
417
|
*/
|
|
@@ -405,7 +460,24 @@ type WellKnownDiagnosticCode =
|
|
|
405
460
|
/** A configured rule, or one of its options, is deprecated. */
|
|
406
461
|
"rule-deprecated" |
|
|
407
462
|
/** A graph transform threw. The pipeline continued without its contribution. */
|
|
408
|
-
"transform-failed"
|
|
463
|
+
"transform-failed" |
|
|
464
|
+
/**
|
|
465
|
+
* Files are inside the project boundary that the producer cannot read, so they are absent
|
|
466
|
+
* from the graph entirely — not excluded, not unresolved, just invisible.
|
|
467
|
+
*
|
|
468
|
+
* Only a producer that ENUMERATES a directory tree can detect this, which in practice means
|
|
469
|
+
* the CLI: a bundler adapter is handed a graph whose membership its compiler already decided.
|
|
470
|
+
*/
|
|
471
|
+
"unscannable-files" |
|
|
472
|
+
/**
|
|
473
|
+
* The baseline lists violations this run did not produce — they were fixed, or the code
|
|
474
|
+
* they were about is gone.
|
|
475
|
+
*
|
|
476
|
+
* Worth saying out loud rather than tolerating: a baseline that is never pruned stops being
|
|
477
|
+
* "debt we accepted" and becomes a permanent hole, and the stale entry will silently
|
|
478
|
+
* re-suppress the finding if it ever comes back.
|
|
479
|
+
*/
|
|
480
|
+
"baseline-stale";
|
|
409
481
|
type DiagnosticCode = WellKnownDiagnosticCode | (string & {});
|
|
410
482
|
/**
|
|
411
483
|
* Everything the run wants to say that is *not* a violation of the user's architecture: a
|
|
@@ -428,6 +500,15 @@ interface RuleSkippedDetails {
|
|
|
428
500
|
missingCapabilities: readonly Capability[];
|
|
429
501
|
host: string;
|
|
430
502
|
}
|
|
503
|
+
/** Payload shape for `code: "unscannable-files"`. */
|
|
504
|
+
interface UnscannableFilesDetails {
|
|
505
|
+
/** How many in-boundary files the producer could not read. */
|
|
506
|
+
count: number;
|
|
507
|
+
/** Distinct extensions, most common first — what to act on. */
|
|
508
|
+
extensions: readonly string[];
|
|
509
|
+
/** A bounded sample of repo-relative paths, for a message a human can follow. */
|
|
510
|
+
sample: readonly string[];
|
|
511
|
+
}
|
|
431
512
|
/** Payload shape for `code: "empty-scope"`. */
|
|
432
513
|
interface EmptyScopeDetails {
|
|
433
514
|
/** The scope as configured, so the message can be acted on without reopening the config. */
|
|
@@ -459,6 +540,16 @@ interface EdgeFilter {
|
|
|
459
540
|
toTag?: Record<string, string>;
|
|
460
541
|
/** Tag key; keep edge iff BOTH endpoints have the tag and values differ. */
|
|
461
542
|
crossing?: string;
|
|
543
|
+
/**
|
|
544
|
+
* Selects on {@link EdgeAttributes}. `true` requires the attribute present; `false` requires
|
|
545
|
+
* it ABSENT; a string requires that exact value.
|
|
546
|
+
*
|
|
547
|
+
* `false` and "absent" are the same test on purpose — attributes are never stored as `false`
|
|
548
|
+
* (see {@link EdgeAttributes}), so "not type-only" and "nobody said" are indistinguishable
|
|
549
|
+
* *here* by construction. A rule that must tell them apart declares the corresponding
|
|
550
|
+
* capability and gets skipped loudly instead, which is the only honest answer.
|
|
551
|
+
*/
|
|
552
|
+
attributes?: Readonly<Record<string, string | boolean>>;
|
|
462
553
|
}
|
|
463
554
|
/**
|
|
464
555
|
* Stable key for a filter, so the engine can bucket rules that want the same slice of the
|
|
@@ -504,7 +595,41 @@ interface ModuleSelection extends Iterable<ModuleNode> {
|
|
|
504
595
|
edgesIn(filter?: EdgeFilter): readonly Edge[];
|
|
505
596
|
}
|
|
506
597
|
/**
|
|
507
|
-
* The
|
|
598
|
+
* The read surface a rule gets — and the type it should name.
|
|
599
|
+
*
|
|
600
|
+
* An INTERFACE rather than the class, because the two are different promises. What ArchWall
|
|
601
|
+
* owes a rule author is a set of questions that can be asked about a graph; what it must stay
|
|
602
|
+
* free to change is how those questions are answered. Naming the class in `RuleContext` fused
|
|
603
|
+
* the two: the concrete implementation became observable via `instanceof`, a test double became
|
|
604
|
+
* impossible to supply, and an interned or columnar store became a breaking change rather than
|
|
605
|
+
* an optimisation.
|
|
606
|
+
*
|
|
607
|
+
* {@link GraphQuery} is the only implementation core ships, and it lives in
|
|
608
|
+
* `@archwall/core/internal`. Rules never construct one — they are handed one — so nothing is
|
|
609
|
+
* taken away by that; a rule author who needs one for a TEST gets it from
|
|
610
|
+
* `@archwall/test-utils`, which is the supported way to build a graph by hand.
|
|
611
|
+
*
|
|
612
|
+
* See {@link GraphQuery} for what scope does to each of these operations.
|
|
613
|
+
*/
|
|
614
|
+
interface GraphView {
|
|
615
|
+
module(id: ModuleId): ModuleNode | undefined;
|
|
616
|
+
moduleCount(): number;
|
|
617
|
+
moduleIds(): Iterable<ModuleId>;
|
|
618
|
+
has(id: ModuleId): boolean;
|
|
619
|
+
tagOf(id: ModuleId, key: string): string | undefined;
|
|
620
|
+
modules(filter?: ModuleFilter): ModuleSelection;
|
|
621
|
+
edges(filter?: EdgeFilter): readonly Edge[];
|
|
622
|
+
edgesOutOf(id: ModuleId): readonly Edge[];
|
|
623
|
+
edgesInto(id: ModuleId): readonly Edge[];
|
|
624
|
+
reachableFrom(id: ModuleId, filter?: EdgeFilter): ReadonlySet<ModuleId>;
|
|
625
|
+
reaching(id: ModuleId, filter?: EdgeFilter): ReadonlySet<ModuleId>;
|
|
626
|
+
pathBetween(from: ModuleId, to: ModuleId, filter?: EdgeFilter): readonly ModuleId[] | null;
|
|
627
|
+
filterEdges(edges: readonly Edge[], filter?: EdgeFilter): readonly Edge[];
|
|
628
|
+
matchesEdge(e: Edge, filter: EdgeFilter): boolean;
|
|
629
|
+
matchesModule(m: ModuleNode, filter: ModuleFilter): boolean;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* The only sanctioned way to read a graph; the sole implementation of {@link GraphView}.
|
|
508
633
|
*
|
|
509
634
|
* A scoped query is a VIEW: it shares the underlying {@link GraphIndex} with the query it
|
|
510
635
|
* came from and differs only in which modules it is *about*.
|
|
@@ -528,7 +653,7 @@ interface ModuleSelection extends Iterable<ModuleNode> {
|
|
|
528
653
|
* thing it can find; hiding the target would turn `layer-dependencies` under a scope from a
|
|
529
654
|
* finding into silence.
|
|
530
655
|
*/
|
|
531
|
-
declare class GraphQuery {
|
|
656
|
+
declare class GraphQuery implements GraphView {
|
|
532
657
|
#private;
|
|
533
658
|
constructor(graph: ProjectGraph, index?: GraphIndex, scope?: ReadonlySet<ModuleId>);
|
|
534
659
|
/** A view of the same graph restricted to `scope`, sharing this query's index. */
|
|
@@ -585,7 +710,7 @@ declare class GraphQuery {
|
|
|
585
710
|
*/
|
|
586
711
|
interface GraphComputation<T> {
|
|
587
712
|
name: string;
|
|
588
|
-
compute(graph:
|
|
713
|
+
compute(graph: GraphView): T;
|
|
589
714
|
}
|
|
590
715
|
declare function defineGraphComputation<T>(computation: GraphComputation<T>): GraphComputation<T>;
|
|
591
716
|
//#endregion
|
|
@@ -634,5 +759,5 @@ interface GraphTransform {
|
|
|
634
759
|
}
|
|
635
760
|
declare function defineTransform(transform: GraphTransform): GraphTransform;
|
|
636
761
|
//#endregion
|
|
637
|
-
export {
|
|
638
|
-
//# sourceMappingURL=transform-
|
|
762
|
+
export { ProjectGraph as $, primaryEdge as A, EdgeAttributes as B, Violation as C, countBySeverity as D, compareViolations as E, ClassifierContext as F, GraphMutation as G, FIRST_PARTY_KINDS as H, TagPatch as I, MODULE_ID_SCHEMES as J, HostInfo as K, defineClassifier as L, primarySourceLocation as M, renderMessage as N, fingerprintOf as O, Classifier as P, ModuleNode as Q, Capability as R, SeverityCounts as S, ViolationLocation as T, GraphDelivery as U, EdgeKind as V, GraphDraft as W, ModuleIdScheme as X, ModuleId as Y, ModuleKind as Z, RuleSkippedDetails as _, defineGraphComputation as a, assertIrCompatible as at, FINGERPRINT_SCHEME as b, GraphQuery as c, isFirstParty as ct, ModuleSelection as d, ProjectGraphInit as et, filterKey as f, EmptyScopeDetails as g, DiagnosticSeverity as h, GraphComputation as i, WellKnownEdgeKind as it, primaryModule as j, locationsOf as k, GraphView as l, isThirdParty as lt, DiagnosticCode as m, TransformContext as n, THIRD_PARTY_KINDS as nt, EdgeFilter as o, displayModuleId as ot, Diagnostic as p, IR_VERSION as q, defineTransform as r, WellKnownCapability as rt, GraphIndex as s, irMajor as st, GraphTransform as t, SourceLocation as tt, ModuleFilter as u, parseModuleId as ut, UnscannableFilesDetails as v, ViolationInput as w, Severity as x, WellKnownDiagnosticCode as y, Edge as z };
|
|
763
|
+
//# sourceMappingURL=transform-CzxyWbUC.d.mts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archwall/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Graph IR, analysis engine, and rule/preset/reporter contracts for ArchWall.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,8 +27,14 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
|
-
".":
|
|
31
|
-
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./internal": {
|
|
35
|
+
"import": "./dist/internal.mjs",
|
|
36
|
+
"require": "./dist/internal.cjs"
|
|
37
|
+
},
|
|
32
38
|
"./package.json": "./package.json"
|
|
33
39
|
},
|
|
34
40
|
"publishConfig": {
|
|
@@ -51,4 +57,4 @@
|
|
|
51
57
|
"devDependencies": {
|
|
52
58
|
"@types/picomatch": "^4.0.3"
|
|
53
59
|
}
|
|
54
|
-
}
|
|
60
|
+
}
|