@descryy/adapter-csharp 0.5.0 → 0.5.2

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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The dependency boundary, C# side — and the narrowest of the ten by a wide
3
+ * margin, which is the finding rather than a shortfall against it.
4
+ *
5
+ * ## The measurement this module does not contest
6
+ *
7
+ * `lane-extmint6` filed
8
+ * `DEC-NEXT-csharp-cannot-attribute-a-dependency-boundary-from-source` after
9
+ * measuring two facts that are structural and remain true:
10
+ *
11
+ * **1. A `using` binds a namespace, not a name.** `using System.IO;` does not
12
+ * say `Path` is `System.IO.Path`; it says `System.IO` is one of eleven places a
13
+ * bare name may be found. That is `adapter-java`'s wildcard import and
14
+ * `adapter-python`'s `from x import *`, and in C# it is not a minority idiom —
15
+ * it is the only idiom. Counted: `jellyfin` 13,435 plain usings, `eShopOnWeb`
16
+ * 884. Every one of them is refused here, and that refusal is the module's
17
+ * largest single behaviour.
18
+ *
19
+ * **2. A dotted C# name cannot be split into namespace and symbol by
20
+ * convention.** Go splits on the import path, Java and Kotlin at the first
21
+ * capitalised segment, PHP at the last `\`. C# namespaces and type names are
22
+ * both PascalCase, so `System.Text.Json.JsonSerializer` offers nothing that says
23
+ * where the namespace stops.
24
+ *
25
+ * ## What is attributable anyway, and the one thing the prior measurement did
26
+ * not use
27
+ *
28
+ * Two written forms name a symbol rather than a namespace, and both are read:
29
+ *
30
+ * - **`using Alias = N.T;`** binds one name outright. The developer wrote the
31
+ * name, the target, and (by using the alias in type or static-call position)
32
+ * that the target is a type.
33
+ * - **a name written out in full at the reference** — `System.Text.Json.
34
+ * JsonSerializer.Serialize(x)`. Every segment is written by the developer at
35
+ * the site.
36
+ *
37
+ * Fact 2 blocks both, and the way past it is not a convention but **another
38
+ * written binding**: a plain `using N;` is the language's own guarantee that `N`
39
+ * is a namespace. So the repository's plain usings — the very population this
40
+ * module refuses to resolve *names* with — are read instead as a **dictionary of
41
+ * strings the source states are namespaces**, and a dotted target is split at
42
+ * the longest prefix that appears in it. `System.Text.Json` is a namespace
43
+ * because some file in this repository wrote `using System.Text.Json;`, not
44
+ * because three segments looked like enough. A target whose prefix no file
45
+ * writes down is refused, not guessed.
46
+ *
47
+ * This reads no `.csproj` `<PackageReference>`, no `packages.lock.json`, no
48
+ * `~/.nuget`, no assembly. Two reasons, Go's two: most checkouts have nothing
49
+ * restored, and an identity taken from a restored graph would move the first
50
+ * time somebody ran `dotnet restore`.
51
+ *
52
+ * ## `using static` is read as a namespace fact and never as a name binding
53
+ *
54
+ * `using static N.T;` is the other form the task named, and it is **refused for
55
+ * edges on purpose.** Unlike Java's `import static a.b.C.m`, C#'s form names no
56
+ * member: it imports *every* static member of `T`, so an unqualified `m()` under
57
+ * it is a member of the enclosing type, or of an unread base type, or one of
58
+ * `T`'s — and the file writes down nothing that chooses. That is fact 1 again in
59
+ * a smaller population. What the directive does write down is that `N` is a
60
+ * namespace-or-type and `T` a type, so its prefix joins the namespace dictionary
61
+ * and nothing else.
62
+ *
63
+ * ## What this is worth, measured
64
+ *
65
+ * Counted over the C# reference subjects, and the honest headline is that the
66
+ * attributable population is tiny **and mostly in-repo**:
67
+ *
68
+ * | subject | plain `using` | `using static` | alias `using` | of those, target OUTSIDE the repo |
69
+ * | --- | --- | --- | --- | --- |
70
+ * | `jellyfin` | 13,435 | 9 | 125 | **11** |
71
+ * | `eShopOnWeb` | 884 | 0 | 0 | **0** |
72
+ *
73
+ * `jellyfin`'s 125 aliases are overwhelmingly `Episode = MediaBrowser.
74
+ * Controller.Entities.TV.Episode` — a shortening of its own name, not a
75
+ * dependency. Eleven name something outside, over six distinct targets. So this
76
+ * module's yield on `jellyfin` is a two-digit number of edges against a graded
77
+ * population of 74,383 calls, and on `eShopOnWeb` it is zero. **That is the
78
+ * result, reported rather than improved by lowering the bar**, and it is what
79
+ * the prior lane predicted.
80
+ */
81
+ /** What written form bound the name. */
82
+ export type CsBinding = "alias-using" | "written-fully-qualified";
83
+ export interface ExternalContext {
84
+ /**
85
+ * Alias -> target, for one file: its own `using X = N.T;` plus the assembly's
86
+ * `global using X = N.T;`. The one C# directive that binds a name.
87
+ */
88
+ readonly aliases: ReadonlyMap<string, string>;
89
+ /**
90
+ * Every string this repository's own source states is a namespace: the target
91
+ * of every plain `using N;`, and every prefix of one, plus the prefixes of
92
+ * `using static` targets. Repository-wide on purpose — a namespace is a
93
+ * property of the string, not of the file that wrote it down.
94
+ */
95
+ readonly writtenNamespaces: ReadonlySet<string>;
96
+ /** Namespaces this repository declares. A type under one is OURS whether or
97
+ * not this run read it — calling unanalysed code of ours a dependency is the
98
+ * error that never surfaces. */
99
+ readonly ownNamespaces: ReadonlySet<string>;
100
+ /** Fully-qualified names this run actually read. One of ours is not a
101
+ * boundary. */
102
+ readonly analysed: (fqn: string) => boolean;
103
+ }
104
+ export interface ExternalAttribution {
105
+ /** The namespace **as the repository's own source writes it down** — this is
106
+ * the identity. Never a NuGet package id, never an assembly, never a version:
107
+ * a package-to-namespace mapping lives inside the assembly, and
108
+ * `<PackageReference>` binds an id to no namespace at all. */
109
+ readonly moduleOrNamespace: string;
110
+ /** The type chain, then any member named on it. */
111
+ readonly symbolPath: readonly string[];
112
+ readonly binding: CsBinding;
113
+ /** Outside the .NET base class library, decided from the namespace alone
114
+ * against `PLATFORM_BASIS`. Disclosure only, and outside the hash: a
115
+ * namespace that moved between the two would otherwise change identity with
116
+ * no line of source changing. */
117
+ readonly thirdParty: boolean;
118
+ }
119
+ /**
120
+ * Provenance of the platform-namespace list, carried on every attributed node.
121
+ * A property of the framework, frozen into this source — never read from
122
+ * whatever SDK a developer happens to have installed, which would make the
123
+ * answer depend on the machine.
124
+ *
125
+ * `Microsoft` is **not** taken whole, and that is the whole reason this constant
126
+ * is a list rather than two prefixes: `Microsoft.Extensions.*` and
127
+ * `Microsoft.AspNetCore.*` ship as NuGet packages, `Microsoft.Win32` and
128
+ * `Microsoft.CSharp` ship with the runtime. Taking the prefix either way would
129
+ * be a wrong disclosure on every node under it.
130
+ */
131
+ export declare const PLATFORM_BASIS = "dotnet base class library: System.* roots, plus the runtime's own Microsoft.* namespaces";
132
+ /** Is `name` the namespace `prefix`, or inside it? Whole segments only — a raw
133
+ * `startsWith` would put `Acme.ServiceBus` inside `Acme.Service`, which is the
134
+ * vendor-namespace hazard rule 2 prices above a missing edge. */
135
+ export declare function isUnder(name: string, prefix: string): boolean;
136
+ /** Generic arguments, nullable marks, array brackets and `global::` are not part
137
+ * of a name's identity, and the type arguments inside them are separate
138
+ * references the extractor already files on their own. */
139
+ export declare function bareTypeName(written: string): string;
140
+ /**
141
+ * A dotted name split into the namespace the repository states it is in and the
142
+ * symbol chain inside it, or `null` when no plain `using` in this repository
143
+ * writes a namespace that prefixes it.
144
+ *
145
+ * The **longest** written prefix wins, and it must be a proper prefix: a name
146
+ * that is itself a written namespace names no symbol and is refused.
147
+ */
148
+ export declare function splitAtWrittenNamespace(fqn: string, writtenNamespaces: ReadonlySet<string>): {
149
+ namespace: string;
150
+ symbols: string[];
151
+ } | null;
152
+ /**
153
+ * The namespace and type behind a **type reference** that left the repository,
154
+ * or `null` when no binding the developer wrote settles it.
155
+ */
156
+ export declare function attributeExternalType(written: string, context: ExternalContext): ExternalAttribution | null;
157
+ /**
158
+ * The namespace, type and member behind a **call** that left the repository, or
159
+ * `null` when no binding the developer wrote settles it.
160
+ *
161
+ * `receiverWritten` is the receiver exactly as the source wrote it — an alias, or
162
+ * a dotted path written out in full. A local, a field, a property or a `var` is
163
+ * not a written type and `extract.ts` never reaches here with one, because it
164
+ * consults this module only where its own resolution already declined.
165
+ */
166
+ export declare function attributeExternalCall(receiverWritten: string | undefined, method: string, context: ExternalContext): ExternalAttribution | null;
167
+ //# sourceMappingURL=external.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../src/external.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AAEH,wCAAwC;AACxC,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,yBAAyB,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD;;qCAEiC;IACjC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C;oBACgB;IAChB,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC;;;mEAG+D;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;;sCAGkC;IAClC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,6FAA6F,CAAC;AA0BzH;;kEAEkE;AAClE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE7D;AAYD;;2DAE2D;AAC3D,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,GACrC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAUjD;AA+ED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,GACvB,mBAAmB,GAAG,IAAI,CAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,eAAe,GACvB,mBAAmB,GAAG,IAAI,CAK5B"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * The dependency boundary, C# side — and the narrowest of the ten by a wide
3
+ * margin, which is the finding rather than a shortfall against it.
4
+ *
5
+ * ## The measurement this module does not contest
6
+ *
7
+ * `lane-extmint6` filed
8
+ * `DEC-NEXT-csharp-cannot-attribute-a-dependency-boundary-from-source` after
9
+ * measuring two facts that are structural and remain true:
10
+ *
11
+ * **1. A `using` binds a namespace, not a name.** `using System.IO;` does not
12
+ * say `Path` is `System.IO.Path`; it says `System.IO` is one of eleven places a
13
+ * bare name may be found. That is `adapter-java`'s wildcard import and
14
+ * `adapter-python`'s `from x import *`, and in C# it is not a minority idiom —
15
+ * it is the only idiom. Counted: `jellyfin` 13,435 plain usings, `eShopOnWeb`
16
+ * 884. Every one of them is refused here, and that refusal is the module's
17
+ * largest single behaviour.
18
+ *
19
+ * **2. A dotted C# name cannot be split into namespace and symbol by
20
+ * convention.** Go splits on the import path, Java and Kotlin at the first
21
+ * capitalised segment, PHP at the last `\`. C# namespaces and type names are
22
+ * both PascalCase, so `System.Text.Json.JsonSerializer` offers nothing that says
23
+ * where the namespace stops.
24
+ *
25
+ * ## What is attributable anyway, and the one thing the prior measurement did
26
+ * not use
27
+ *
28
+ * Two written forms name a symbol rather than a namespace, and both are read:
29
+ *
30
+ * - **`using Alias = N.T;`** binds one name outright. The developer wrote the
31
+ * name, the target, and (by using the alias in type or static-call position)
32
+ * that the target is a type.
33
+ * - **a name written out in full at the reference** — `System.Text.Json.
34
+ * JsonSerializer.Serialize(x)`. Every segment is written by the developer at
35
+ * the site.
36
+ *
37
+ * Fact 2 blocks both, and the way past it is not a convention but **another
38
+ * written binding**: a plain `using N;` is the language's own guarantee that `N`
39
+ * is a namespace. So the repository's plain usings — the very population this
40
+ * module refuses to resolve *names* with — are read instead as a **dictionary of
41
+ * strings the source states are namespaces**, and a dotted target is split at
42
+ * the longest prefix that appears in it. `System.Text.Json` is a namespace
43
+ * because some file in this repository wrote `using System.Text.Json;`, not
44
+ * because three segments looked like enough. A target whose prefix no file
45
+ * writes down is refused, not guessed.
46
+ *
47
+ * This reads no `.csproj` `<PackageReference>`, no `packages.lock.json`, no
48
+ * `~/.nuget`, no assembly. Two reasons, Go's two: most checkouts have nothing
49
+ * restored, and an identity taken from a restored graph would move the first
50
+ * time somebody ran `dotnet restore`.
51
+ *
52
+ * ## `using static` is read as a namespace fact and never as a name binding
53
+ *
54
+ * `using static N.T;` is the other form the task named, and it is **refused for
55
+ * edges on purpose.** Unlike Java's `import static a.b.C.m`, C#'s form names no
56
+ * member: it imports *every* static member of `T`, so an unqualified `m()` under
57
+ * it is a member of the enclosing type, or of an unread base type, or one of
58
+ * `T`'s — and the file writes down nothing that chooses. That is fact 1 again in
59
+ * a smaller population. What the directive does write down is that `N` is a
60
+ * namespace-or-type and `T` a type, so its prefix joins the namespace dictionary
61
+ * and nothing else.
62
+ *
63
+ * ## What this is worth, measured
64
+ *
65
+ * Counted over the C# reference subjects, and the honest headline is that the
66
+ * attributable population is tiny **and mostly in-repo**:
67
+ *
68
+ * | subject | plain `using` | `using static` | alias `using` | of those, target OUTSIDE the repo |
69
+ * | --- | --- | --- | --- | --- |
70
+ * | `jellyfin` | 13,435 | 9 | 125 | **11** |
71
+ * | `eShopOnWeb` | 884 | 0 | 0 | **0** |
72
+ *
73
+ * `jellyfin`'s 125 aliases are overwhelmingly `Episode = MediaBrowser.
74
+ * Controller.Entities.TV.Episode` — a shortening of its own name, not a
75
+ * dependency. Eleven name something outside, over six distinct targets. So this
76
+ * module's yield on `jellyfin` is a two-digit number of edges against a graded
77
+ * population of 74,383 calls, and on `eShopOnWeb` it is zero. **That is the
78
+ * result, reported rather than improved by lowering the bar**, and it is what
79
+ * the prior lane predicted.
80
+ */
81
+ /**
82
+ * Provenance of the platform-namespace list, carried on every attributed node.
83
+ * A property of the framework, frozen into this source — never read from
84
+ * whatever SDK a developer happens to have installed, which would make the
85
+ * answer depend on the machine.
86
+ *
87
+ * `Microsoft` is **not** taken whole, and that is the whole reason this constant
88
+ * is a list rather than two prefixes: `Microsoft.Extensions.*` and
89
+ * `Microsoft.AspNetCore.*` ship as NuGet packages, `Microsoft.Win32` and
90
+ * `Microsoft.CSharp` ship with the runtime. Taking the prefix either way would
91
+ * be a wrong disclosure on every node under it.
92
+ */
93
+ export const PLATFORM_BASIS = "dotnet base class library: System.* roots, plus the runtime's own Microsoft.* namespaces";
94
+ /** Namespace roots the runtime owns outright. */
95
+ const PLATFORM_ROOTS = ["System"];
96
+ /** The `Microsoft` subnamespaces the runtime ships. Everything else under
97
+ * `Microsoft` is a package. */
98
+ const PLATFORM_MICROSOFT = new Set([
99
+ "Microsoft.Win32", "Microsoft.CSharp", "Microsoft.VisualBasic", "Microsoft.SafeHandles",
100
+ ]);
101
+ /**
102
+ * Namespaces whose whole purpose is to make a call target dynamic.
103
+ *
104
+ * `System.Reflection`'s `MethodInfo.Invoke` and `System.Linq.Expressions`'
105
+ * compiled delegates are where a C# call graph deliberately stops being
106
+ * readable, and golden pattern 12 — the corpus's most important fixture —
107
+ * forbids a `CALLS` edge out of a method that dispatches through them *to any
108
+ * target at any resolution*. The edge would be true and it is still the one
109
+ * edge that pattern exists to forbid: it makes a traversal look as though it
110
+ * continued through a site where the graph cannot see the next hop. Java's
111
+ * `external.ts` declines `java.lang.reflect` and `java.lang.invoke` for exactly
112
+ * this reason (`DEC-NEXT-external-nodes-through-the-reflection-packages`).
113
+ */
114
+ const DYNAMIC_DISPATCH_NAMESPACES = ["System.Reflection", "System.Linq.Expressions"];
115
+ /** Is `name` the namespace `prefix`, or inside it? Whole segments only — a raw
116
+ * `startsWith` would put `Acme.ServiceBus` inside `Acme.Service`, which is the
117
+ * vendor-namespace hazard rule 2 prices above a missing edge. */
118
+ export function isUnder(name, prefix) {
119
+ return name === prefix || name.startsWith(`${prefix}.`);
120
+ }
121
+ function isPlatform(namespace) {
122
+ for (const root of PLATFORM_ROOTS) {
123
+ if (isUnder(namespace, root))
124
+ return true;
125
+ }
126
+ for (const known of PLATFORM_MICROSOFT) {
127
+ if (isUnder(namespace, known))
128
+ return true;
129
+ }
130
+ return false;
131
+ }
132
+ /** Generic arguments, nullable marks, array brackets and `global::` are not part
133
+ * of a name's identity, and the type arguments inside them are separate
134
+ * references the extractor already files on their own. */
135
+ export function bareTypeName(written) {
136
+ const stripped = written.trim().replace(/^global::/, "");
137
+ const cut = stripped.indexOf("<");
138
+ const head = (cut === -1 ? stripped : stripped.slice(0, cut)).trim();
139
+ return head.replace(/(\[[\s,]*\])+$/, "").replace(/\?+$/, "").trim();
140
+ }
141
+ /**
142
+ * A dotted name split into the namespace the repository states it is in and the
143
+ * symbol chain inside it, or `null` when no plain `using` in this repository
144
+ * writes a namespace that prefixes it.
145
+ *
146
+ * The **longest** written prefix wins, and it must be a proper prefix: a name
147
+ * that is itself a written namespace names no symbol and is refused.
148
+ */
149
+ export function splitAtWrittenNamespace(fqn, writtenNamespaces) {
150
+ const parts = fqn.split(".");
151
+ if (parts.length < 2 || parts.some((part) => part === ""))
152
+ return null;
153
+ for (let cut = parts.length - 1; cut >= 1; cut -= 1) {
154
+ const prefix = parts.slice(0, cut).join(".");
155
+ if (writtenNamespaces.has(prefix)) {
156
+ return { namespace: prefix, symbols: parts.slice(cut) };
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ /** Ours, whether or not this run read it. */
162
+ function isOurs(fqn, context) {
163
+ if (context.analysed(fqn))
164
+ return true;
165
+ for (const own of context.ownNamespaces) {
166
+ if (own !== "" && isUnder(fqn, own))
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+ function attribute(fqn, binding, extra, context) {
172
+ if (isOurs(fqn, context))
173
+ return null;
174
+ // The whole written name is itself a namespace some file states is one. A
175
+ // namespace declares no members, so there is nothing here to point at.
176
+ if (context.writtenNamespaces.has(fqn))
177
+ return null;
178
+ const split = splitAtWrittenNamespace(fqn, context.writtenNamespaces);
179
+ if (split === null)
180
+ return null;
181
+ if (isOurs(split.namespace, context))
182
+ return null;
183
+ // The written namespace must be the type's IMMEDIATE parent, and this is an
184
+ // identity rule rather than a taste one. `Microsoft.AspNetCore.WebUtilities.
185
+ // QueryHelpers.AddQueryString` splits at `Microsoft.AspNetCore` when no file
186
+ // happens to write `using Microsoft.AspNetCore.WebUtilities;` and at the
187
+ // deeper namespace when one does — so the same symbol would carry two
188
+ // different node ids depending on a `using` in an unrelated file, and adding
189
+ // that line would silently move the node. Capping the split at one segment
190
+ // turns that churn into presence or absence, which is what rule 2 asks for: a
191
+ // missing edge is a disclosed gap, a moving identity destroys every link that
192
+ // ever pointed at it. The cost is real and disclosed — a nested type
193
+ // (`System.Text.Encoding.UTF8`, `Map.Entry`'s C# analogue) is refused.
194
+ if (split.symbols.length !== 1)
195
+ return null;
196
+ for (const dynamic of DYNAMIC_DISPATCH_NAMESPACES) {
197
+ if (isUnder(split.namespace, dynamic))
198
+ return null;
199
+ }
200
+ const symbolPath = [...split.symbols, ...extra];
201
+ if (symbolPath.some((part) => part === ""))
202
+ return null;
203
+ return {
204
+ moduleOrNamespace: split.namespace,
205
+ symbolPath,
206
+ binding,
207
+ thirdParty: !isPlatform(split.namespace),
208
+ };
209
+ }
210
+ /** The fully-qualified name a written type resolves to through the bindings this
211
+ * file wrote, and which binding did it. `null` when none did. */
212
+ function qualify(written, context) {
213
+ const name = bareTypeName(written);
214
+ if (name === "")
215
+ return null;
216
+ const separator = name.indexOf(".");
217
+ const head = separator === -1 ? name : name.slice(0, separator);
218
+ const aliased = context.aliases.get(head);
219
+ if (aliased !== undefined && aliased !== "") {
220
+ const rest = separator === -1 ? "" : name.slice(separator + 1);
221
+ const target = bareTypeName(aliased);
222
+ return { fqn: rest === "" ? target : `${target}.${rest}`, binding: "alias-using" };
223
+ }
224
+ // Written out in full at the reference. Every segment is the developer's, so
225
+ // it is as much a written binding as an alias is — and the namespace boundary
226
+ // inside it is decided by `splitAtWrittenNamespace`, from plain usings, never
227
+ // by how the segments are capitalised.
228
+ if (separator !== -1)
229
+ return { fqn: name, binding: "written-fully-qualified" };
230
+ // A bare name under a plain `using`. The largest population and the refusal
231
+ // this module exists to make: eleven candidate namespaces and nothing written
232
+ // that chooses between them.
233
+ return null;
234
+ }
235
+ /**
236
+ * The namespace and type behind a **type reference** that left the repository,
237
+ * or `null` when no binding the developer wrote settles it.
238
+ */
239
+ export function attributeExternalType(written, context) {
240
+ const qualified = qualify(written, context);
241
+ if (qualified === null)
242
+ return null;
243
+ return attribute(qualified.fqn, qualified.binding, [], context);
244
+ }
245
+ /**
246
+ * The namespace, type and member behind a **call** that left the repository, or
247
+ * `null` when no binding the developer wrote settles it.
248
+ *
249
+ * `receiverWritten` is the receiver exactly as the source wrote it — an alias, or
250
+ * a dotted path written out in full. A local, a field, a property or a `var` is
251
+ * not a written type and `extract.ts` never reaches here with one, because it
252
+ * consults this module only where its own resolution already declined.
253
+ */
254
+ export function attributeExternalCall(receiverWritten, method, context) {
255
+ if (receiverWritten === undefined || method === "")
256
+ return null;
257
+ const qualified = qualify(receiverWritten, context);
258
+ if (qualified === null)
259
+ return null;
260
+ return attribute(qualified.fqn, qualified.binding, [method], context);
261
+ }
262
+ //# sourceMappingURL=external.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.js","sourceRoot":"","sources":["../src/external.ts"],"names":[],"mappings":"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"}
package/dist/extract.d.ts CHANGED
@@ -64,5 +64,32 @@ export interface Extraction {
64
64
  readonly edges: readonly IREdge[];
65
65
  readonly unresolved: readonly UnresolvedRef[];
66
66
  }
67
+ /**
68
+ * The fallback graph when `extract()` throws mid-run, per
69
+ * `DEC-NEXT-degraded-graph-shape-for-adapters-without-one.md`.
70
+ *
71
+ * `FILE`, not `MODULE`: a C# module's identity here is
72
+ * `assembly::namespace::stem`, and both the assembly (`.csproj` resolution,
73
+ * `projects.ts`) and the namespace (`namespace Foo.Bar;`, a declaration
74
+ * *inside* the file) are exactly the machinery a mid-extraction throw means
75
+ * just failed or cannot be trusted to have finished. Unlike Go's import path
76
+ * or Rust's crate/module path — both resolved from the file's own location
77
+ * plus a manifest, never from the file's parsed content — nothing about a C#
78
+ * file's identity is recoverable without a successful parse. `FILE` (via the
79
+ * shared `@descryy/adapter-common#fileNode` helper) is the fact that
80
+ * survives: the file exists, on disk, at this path, and that is all this
81
+ * function claims.
82
+ *
83
+ * One disclosure row, not one per file — the fact is about the run. Anchored
84
+ * to the first file's `FILE` node because the ledger requires a `fromNodeId`
85
+ * and `FILE` is the only node type this path emits.
86
+ */
87
+ export declare function degradedGraph(input: {
88
+ readonly repo: string;
89
+ readonly workspace: string | undefined;
90
+ readonly producedBy: string;
91
+ readonly files: readonly string[];
92
+ readonly cause: string;
93
+ }): Extraction;
67
94
  export declare function extract(input: ExtractInput): Extraction;
68
95
  //# sourceMappingURL=extract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,MAAM,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAyB,KAAK,QAAQ,EAAgB,MAAM,aAAa,CAAC;AAIjF,OAAO,EAAuC,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAI7F,eAAO,MAAM,QAAQ,WAAW,CAAC;AAkDjC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C;AAiCD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,CAupCvD"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,MAAM,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAyB,KAAK,QAAQ,EAAgB,MAAM,aAAa,CAAC;AAKjF,OAAO,EAAuC,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAW7F,eAAO,MAAM,QAAQ,WAAW,CAAC;AA+DjC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GAAG,UAAU,CA0Bb;AAiCD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,CA22CvD"}