@descryy/adapter-csharp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +31 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +263 -0
- package/dist/adapter.js.map +1 -0
- package/dist/aspnetcore.d.ts +207 -0
- package/dist/aspnetcore.d.ts.map +1 -0
- package/dist/aspnetcore.js +410 -0
- package/dist/aspnetcore.js.map +1 -0
- package/dist/client-base.d.ts +123 -0
- package/dist/client-base.d.ts.map +1 -0
- package/dist/client-base.js +123 -0
- package/dist/client-base.js.map +1 -0
- package/dist/client.d.ts +168 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +387 -0
- package/dist/client.js.map +1 -0
- package/dist/efcore.d.ts +198 -0
- package/dist/efcore.d.ts.map +1 -0
- package/dist/efcore.js +611 -0
- package/dist/efcore.js.map +1 -0
- package/dist/extract.d.ts +83 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +1120 -0
- package/dist/extract.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/nullability.d.ts +117 -0
- package/dist/nullability.d.ts.map +1 -0
- package/dist/nullability.js +152 -0
- package/dist/nullability.js.map +1 -0
- package/dist/parse.d.ts +240 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +861 -0
- package/dist/parse.js.map +1 -0
- package/dist/projects.d.ts +87 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +226 -0
- package/dist/projects.js.map +1 -0
- package/dist/tree-sitter-c_sharp.wasm +0 -0
- package/package.json +35 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D-FIX-3's C# half: a call through a known HTTP client is disclosed, never
|
|
3
|
+
* silent — even though this adapter extracts no `USES_API` edges in C# at
|
|
4
|
+
* all yet.
|
|
5
|
+
*
|
|
6
|
+
* ## Why disclosure without extraction is the right first producer here
|
|
7
|
+
*
|
|
8
|
+
* Java's `client.ts` extracts real edges for Spring's clients and files a
|
|
9
|
+
* ledger row for the shapes it cannot read. C# has no extraction path at
|
|
10
|
+
* all — `capabilities()` already declares no `USES_API` for this adapter
|
|
11
|
+
* (PROGRESS.md), so *every* call through a known client is unclaimed today.
|
|
12
|
+
* Building the extractor itself (reading `GetAsync("/orders/{id}")`'s literal
|
|
13
|
+
* path, the way `bench/client-call-census.mjs`'s C# census shows is
|
|
14
|
+
* structurally the same shape as Spring's `RestTemplate` — the URL is the
|
|
15
|
+
* call's own first argument, not a separately built request object) is a
|
|
16
|
+
* **separate, larger row**: it needs the DEC-129 counting-first admission
|
|
17
|
+
* this project requires before an extractor ships, a golden fixture change,
|
|
18
|
+
* and a capability declaration tested against pattern 08 the way DEC-195
|
|
19
|
+
* records for Java. That is not built here.
|
|
20
|
+
*
|
|
21
|
+
* What *is* in scope, and small: recognise the receiver's declared type and
|
|
22
|
+
* turn "nothing" into a disclosed refusal, so `0 USES_API edges` stops being
|
|
23
|
+
* indistinguishable from "no HTTP calls in this codebase" — the exact
|
|
24
|
+
* distinction DEC-113's ledger exists to carry, and the reason a `cf-worker`
|
|
25
|
+
* zero and a `sherpa-frontend` zero are different facts.
|
|
26
|
+
*
|
|
27
|
+
* ## The bounded population, measured before it was picked
|
|
28
|
+
*
|
|
29
|
+
* `bench/client-call-census.mjs csharp` names `HttpClient`, `IHttpClientFactory`,
|
|
30
|
+
* `HttpRequestMessage` and `RestClient` as candidates. Measured directly against
|
|
31
|
+
* this row: `RestClient` (RestSharp's type) has **zero** occurrences across all
|
|
32
|
+
* six C# reference corpora (`jellyfin`, `eShop`, `eShopOnWeb`, `Ocelot`,
|
|
33
|
+
* `CleanArchitecture`, `Dapper`) — counted, not assumed, the same way DEC-129
|
|
34
|
+
* counted before admitting a corpus. `IHttpClientFactory` produces an
|
|
35
|
+
* `HttpClient` from `CreateClient()`; the client census's 61 relative-path call
|
|
36
|
+
* sites are all through `HttpClient` variables and fields once the factory call
|
|
37
|
+
* has already returned. So the known-type set here is **just `HttpClient`** —
|
|
38
|
+
* narrower than the census's candidate list, on a measurement rather than a
|
|
39
|
+
* guess, and it can widen the day a corpus exercises `RestClient` for real.
|
|
40
|
+
*
|
|
41
|
+
* `HttpRequestMessage` is not a receiver in any of the census's call sites — it
|
|
42
|
+
* is the *argument* to `SendAsync`, never the thing a verb method is called on
|
|
43
|
+
* — so it does not belong in a receiver-type set at all; including it would
|
|
44
|
+
* have been the coarser-than-node-key mistake DEC-165/187 already paid for once.
|
|
45
|
+
*
|
|
46
|
+
* ## Origin check, added after DEC-260
|
|
47
|
+
*
|
|
48
|
+
* The first pass of this file filed every candidate as "no extractor," with no
|
|
49
|
+
* distinction between a call whose literal path stays inside this repository
|
|
50
|
+
* and one that names a different origin outright — a divergence from Java's
|
|
51
|
+
* `client.ts`, which has always told the two apart. DEC-260 §3 names the
|
|
52
|
+
* consequence directly: two adapters disagreeing on the same refusal shape
|
|
53
|
+
* corrupts `refusalClass` (DEC-242) the moment a consumer trusts it, because
|
|
54
|
+
* C# would systematically over-report `capability-gap` against Java's
|
|
55
|
+
* classifier on the same underlying population.
|
|
56
|
+
*
|
|
57
|
+
* **Not Java's exact predicate, on purpose — measured, not assumed.** A first
|
|
58
|
+
* attempt reused Java's "does not start with `/`" rule verbatim and ran it
|
|
59
|
+
* against the real admitted corpora before calling the row done, per this
|
|
60
|
+
* project's own discipline. It misfired: `jellyfin` and `eShopOnWeb` both pass
|
|
61
|
+
* a **bare relative path with no leading slash** (`"Users/New"`) to `HttpClient`
|
|
62
|
+
* calls resolved against `HttpClient.BaseAddress` at run time — a shape Java's
|
|
63
|
+
* own population never contains, so Java's rule was never tested against it.
|
|
64
|
+
* Every such call site found is an integration test through
|
|
65
|
+
* `_factory.CreateClient()`, ASP.NET's in-process test client pointed at the
|
|
66
|
+
* application under test — a same-repository call this reader was about to
|
|
67
|
+
* mislabel as "another origin." See {@link namesAnotherOrigin}'s own comment
|
|
68
|
+
* for the corrected, narrower rule. This origin check was later carried back
|
|
69
|
+
* into `adapter-java`'s own `relativePath` rule, which carried the identical
|
|
70
|
+
* latent defect (`adapter-java` `1bfd2ae`) — the two adapters now agree on
|
|
71
|
+
* this shape by construction, not by two independently-tuned rules.
|
|
72
|
+
*
|
|
73
|
+
* ## Extraction, built on the shape the origin check already separated out
|
|
74
|
+
*
|
|
75
|
+
* `DEC-241` §4 named this row: `HttpClient`'s verb methods take the path as
|
|
76
|
+
* their **own first argument** — `GetAsync("/orders/{id}")`, never a
|
|
77
|
+
* separately built request object the way `OkHttpClient`/`java.net.http.HttpClient`
|
|
78
|
+
* require — the same structurally-straightforward shape Java's `RestTemplate`
|
|
79
|
+
* already extracts from. `clientCalls` below mints a real `USES_API` edge for
|
|
80
|
+
* every call whose receiver is a known client type, whose method names a
|
|
81
|
+
* literal verb, and whose path is a repository-relative literal
|
|
82
|
+
* (`relativePath`, unchanged from the origin check's own reasoning) — and
|
|
83
|
+
* files a ledger row, never silently drops the call, for every other shape:
|
|
84
|
+
* an unrecognised verb (`SendAsync`, which takes a pre-built
|
|
85
|
+
* `HttpRequestMessage` — verb and path both live inside an object this reader
|
|
86
|
+
* does not open), a non-literal path, an origin outside this repository, or a
|
|
87
|
+
* bare relative path this reader genuinely cannot place (the same
|
|
88
|
+
* `BaseAddress`-resolved shape the origin check exists to disclose rather
|
|
89
|
+
* than guess).
|
|
90
|
+
*/
|
|
91
|
+
import { endpointQsp, nodeId } from "@descryy/ir";
|
|
92
|
+
const CAPABILITY_GAP = { blockedBy: null, refusalClass: "capability-gap" };
|
|
93
|
+
const VARIES_PER_CALL = { blockedBy: null, refusalClass: "varies-per-call" };
|
|
94
|
+
/** `Environment.GetEnvironmentVariable("X")` and an `IConfiguration` indexer named `configuration`/`Configuration`. */
|
|
95
|
+
const ENV_READ_PATTERNS = [
|
|
96
|
+
/^Environment\.GetEnvironmentVariable\(\s*"[^"]*"\s*\)$/,
|
|
97
|
+
/^\w*[Cc]onfiguration\w*\[\s*"[^"]*"\s*\]$/,
|
|
98
|
+
];
|
|
99
|
+
/** Same heuristic as every other adapter's — see `adapter-rust/src/client.ts`'s doc. */
|
|
100
|
+
function argumentKindOf(text) {
|
|
101
|
+
if (text.endsWith(")") && text.includes("("))
|
|
102
|
+
return "call";
|
|
103
|
+
if (text.includes("+"))
|
|
104
|
+
return "concatenation";
|
|
105
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(text))
|
|
106
|
+
return "field";
|
|
107
|
+
return "other";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Why the first argument of a client call could not be read as a literal
|
|
111
|
+
* path — mirrors `firstArgumentTextOf`'s own reach: a bare identifier, a
|
|
112
|
+
* known environment/configuration-read expression, or something this reader
|
|
113
|
+
* does not trace.
|
|
114
|
+
*/
|
|
115
|
+
function diagnoseArgument(text, parameterNames) {
|
|
116
|
+
if (text === undefined)
|
|
117
|
+
return CAPABILITY_GAP;
|
|
118
|
+
const trimmed = text.trim();
|
|
119
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed) && parameterNames.has(trimmed))
|
|
120
|
+
return VARIES_PER_CALL;
|
|
121
|
+
if (ENV_READ_PATTERNS.some((pattern) => pattern.test(trimmed))) {
|
|
122
|
+
return { blockedBy: trimmed, refusalClass: "value-unknown" };
|
|
123
|
+
}
|
|
124
|
+
return { ...CAPABILITY_GAP, argumentKind: argumentKindOf(trimmed) };
|
|
125
|
+
}
|
|
126
|
+
/** Verb methods on `HttpClient`. Taken from `bench/client-call-census.mjs`'s `METHODS.csharp`. */
|
|
127
|
+
const CLIENT_METHODS = new Set([
|
|
128
|
+
"GetAsync",
|
|
129
|
+
"PostAsync",
|
|
130
|
+
"PutAsync",
|
|
131
|
+
"DeleteAsync",
|
|
132
|
+
"PatchAsync",
|
|
133
|
+
"SendAsync",
|
|
134
|
+
"GetStringAsync",
|
|
135
|
+
"GetByteArrayAsync",
|
|
136
|
+
"GetStreamAsync",
|
|
137
|
+
"GetFromJsonAsync",
|
|
138
|
+
"PostAsJsonAsync",
|
|
139
|
+
"PutAsJsonAsync",
|
|
140
|
+
"DeleteFromJsonAsync",
|
|
141
|
+
]);
|
|
142
|
+
const KNOWN_CLIENT_TYPES = new Set(["HttpClient"]);
|
|
143
|
+
/**
|
|
144
|
+
* The HTTP verb a method name implies.
|
|
145
|
+
*
|
|
146
|
+
* `SendAsync` takes a pre-built `HttpRequestMessage` — the verb (and the
|
|
147
|
+
* path) live inside that object, not in this call's own arguments — so it is
|
|
148
|
+
* absent here and refused rather than defaulted, the identical reasoning
|
|
149
|
+
* Java's `exchange`/`execute`/`uri` carry: a wrong verb mints an endpoint
|
|
150
|
+
* that joins the wrong route, or none, and root-cause traversal would follow
|
|
151
|
+
* it.
|
|
152
|
+
*/
|
|
153
|
+
const VERB = {
|
|
154
|
+
GetAsync: "GET",
|
|
155
|
+
GetStringAsync: "GET",
|
|
156
|
+
GetByteArrayAsync: "GET",
|
|
157
|
+
GetStreamAsync: "GET",
|
|
158
|
+
GetFromJsonAsync: "GET",
|
|
159
|
+
PostAsync: "POST",
|
|
160
|
+
PostAsJsonAsync: "POST",
|
|
161
|
+
PutAsync: "PUT",
|
|
162
|
+
PutAsJsonAsync: "PUT",
|
|
163
|
+
DeleteAsync: "DELETE",
|
|
164
|
+
DeleteFromJsonAsync: "DELETE",
|
|
165
|
+
PatchAsync: "PATCH",
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* A path this reader will claim: repository-relative, no scheme, no assembly.
|
|
169
|
+
* Unchanged from the origin check's own reasoning — a leading `/` and nothing
|
|
170
|
+
* ambiguous about it, query string dropped because `SERVES_API` never mints
|
|
171
|
+
* one and keeping it would guarantee the join fails.
|
|
172
|
+
*/
|
|
173
|
+
function relativePath(value) {
|
|
174
|
+
if (!value.startsWith("/"))
|
|
175
|
+
return undefined;
|
|
176
|
+
if (value.startsWith("//"))
|
|
177
|
+
return undefined;
|
|
178
|
+
const cut = value.indexOf("?");
|
|
179
|
+
return cut === -1 ? value : value.slice(0, cut);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A literal argument this reader can say, with no ambiguity, names a
|
|
183
|
+
* *different* origin — an explicit scheme (`http://...`) or a protocol-relative
|
|
184
|
+
* URL (`//host/...`). Not Java's `relativePath` predicate, and deliberately
|
|
185
|
+
* not the same rule even though the two must agree on `refusalClass` (DEC-242)
|
|
186
|
+
* for the shapes they both actually see.
|
|
187
|
+
*
|
|
188
|
+
* Java's population (measured, DEC-260 §1) is `RestTemplate`/`TestRestTemplate`
|
|
189
|
+
* calls, which are either a leading-`/` repository-relative path or a fully
|
|
190
|
+
* qualified URL — nothing in between, so "does not start with `/`" was a safe
|
|
191
|
+
* proxy for "names another origin" *there*. C#'s real corpora are not the same
|
|
192
|
+
* population: `HttpClient` calls routinely pass a **bare relative path with no
|
|
193
|
+
* leading slash** (`"Users/New"`, `"SyncPlay/List"`) resolved against
|
|
194
|
+
* `HttpClient.BaseAddress` at run time — measured directly against jellyfin and
|
|
195
|
+
* eShopOnWeb, where every bare-relative call site found is an integration test
|
|
196
|
+
* driving `_factory.CreateClient()`, ASP.NET's own in-process test client
|
|
197
|
+
* whose `BaseAddress` is the application under test. Porting Java's predicate
|
|
198
|
+
* verbatim filed those as "another origin," which is not a fact this reader
|
|
199
|
+
* has — `BaseAddress` is a runtime value this reader does not see, so a bare
|
|
200
|
+
* relative path is genuinely undetermined, not a confirmed scope boundary.
|
|
201
|
+
* Rule 2 (precision over recall): when in doubt, the row stays in the
|
|
202
|
+
* capability-gap bucket rather than asserting a boundary this reader cannot
|
|
203
|
+
* back up. Only an unambiguous absolute form earns `out-of-scope`.
|
|
204
|
+
*/
|
|
205
|
+
function namesAnotherOrigin(value) {
|
|
206
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value) || value.startsWith("//");
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Every reference {@link clientCalls} will act on — an unbounded receiver
|
|
210
|
+
* type still makes it a true silence past this point, exactly as in Java. See
|
|
211
|
+
* `client.test.ts` for the reconciliation this makes checkable: every
|
|
212
|
+
* candidate whose type resolves lands in either `calls` or `refusals`, with
|
|
213
|
+
* nothing else.
|
|
214
|
+
*/
|
|
215
|
+
export function candidateClientRefs(refs) {
|
|
216
|
+
return refs.filter((ref) => ref.kind === "call" &&
|
|
217
|
+
CLIENT_METHODS.has(ref.name) &&
|
|
218
|
+
ref.receiver !== undefined &&
|
|
219
|
+
ref.receiver !== "this" &&
|
|
220
|
+
ref.receiver !== "base");
|
|
221
|
+
}
|
|
222
|
+
/** The literal path's own query string dropped — `SERVES_API` never mints one either. */
|
|
223
|
+
function stripQuery(value) {
|
|
224
|
+
const cut = value.indexOf("?");
|
|
225
|
+
return cut === -1 ? value : value.slice(0, cut);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* DEC-097's composition, with the separator neither half is guaranteed to
|
|
229
|
+
* supply. `path` (a resolved base's own prefix) never ends in `/`
|
|
230
|
+
* (`pathPrefixOf` in `client-base.ts` strips it) and a call's own literal
|
|
231
|
+
* may or may not start with one (`"orders/{id}"` vs `"/order/my-orders"`),
|
|
232
|
+
* so joining with a bare `+` silently drops the boundary exactly once: `"/api/v1"
|
|
233
|
+
* + "orders/{id}"` is `"/api/v1orders/{id}"`, one wrong path, not a missing
|
|
234
|
+
* slash `normaliseEndpointPath` can recover — it repairs a *doubled* slash,
|
|
235
|
+
* never a *missing* one. Always inserting `/` and letting `normaliseEndpointPath`'s
|
|
236
|
+
* own `/+` collapse handle the doubled case when the suffix already had one
|
|
237
|
+
* is the one join that is correct for both shapes.
|
|
238
|
+
*/
|
|
239
|
+
function joinBase(path, suffix) {
|
|
240
|
+
return `${path}/${suffix}`;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Read one method's calls for outbound HTTP.
|
|
244
|
+
*
|
|
245
|
+
* `typeOfReceiver` is supplied by `extract.ts`, which already has the scope
|
|
246
|
+
* tables (`fn.scope.locals`, the enclosing type's members) a second copy of
|
|
247
|
+
* here would drift from — the same division of labour as Java's `client.ts`.
|
|
248
|
+
*
|
|
249
|
+
* `clientBaseOfReceiver` is DEC-164's contract (`client-base.ts`'s own
|
|
250
|
+
* header): a per-call determination of what base, if any, this reader traced
|
|
251
|
+
* for the receiver. Called only for refs that already passed the known-client-
|
|
252
|
+
* type gate below, matching `typeOfReceiver`'s own division of labour.
|
|
253
|
+
*/
|
|
254
|
+
export function clientCalls(refs, fromId, typeOfReceiver, isTest, parameterNames, clientBaseOfReceiver) {
|
|
255
|
+
const calls = [];
|
|
256
|
+
const refusals = [];
|
|
257
|
+
for (const ref of candidateClientRefs(refs)) {
|
|
258
|
+
const receiverType = typeOfReceiver(ref);
|
|
259
|
+
if (receiverType === undefined || !KNOWN_CLIENT_TYPES.has(receiverType))
|
|
260
|
+
continue;
|
|
261
|
+
const refuse = (reason, diagnosis) => {
|
|
262
|
+
refusals.push({ fromId, raw: ref.raw, line: ref.line, reason, ...diagnosis });
|
|
263
|
+
};
|
|
264
|
+
const verb = VERB[ref.name];
|
|
265
|
+
if (verb === undefined) {
|
|
266
|
+
refuse(`an HTTP call through ${receiverType}, but \`${ref.name}\` takes a pre-built ` +
|
|
267
|
+
`HttpRequestMessage — the verb and the path both live inside that object, not in this ` +
|
|
268
|
+
`call's own arguments. Refused rather than defaulted to GET: a wrong verb mints an ` +
|
|
269
|
+
`endpoint that joins the wrong route, or none, and root-cause traversal would follow it.`, CAPABILITY_GAP);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (ref.firstStringArgument === undefined) {
|
|
273
|
+
refuse(`an HTTP call through ${receiverType}, but its path is not a bare string literal — a ` +
|
|
274
|
+
`variable, a concatenation, or an interpolated string. A path assembled at run time is ` +
|
|
275
|
+
`not one this adapter can read, and an endpoint template invented here would join this ` +
|
|
276
|
+
`caller to a route it never calls.`, diagnoseArgument(ref.firstArgumentText, parameterNames));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
// A literal argument unambiguously naming a different origin is a
|
|
280
|
+
// resolved fact, not a gap — DEC-113's out-of-scope population, the same
|
|
281
|
+
// one Java's client.ts already separates out for the shapes the two
|
|
282
|
+
// adapters' real corpora actually share (an absolute URL to another
|
|
283
|
+
// host). Checked before `clientBase` — an absolute request URI overrides
|
|
284
|
+
// `HttpClient.BaseAddress` regardless of what this reader traced for it.
|
|
285
|
+
if (namesAnotherOrigin(ref.firstStringArgument)) {
|
|
286
|
+
refuse(`an HTTP call through ${receiverType} to \`${ref.firstStringArgument}\`, which names ` +
|
|
287
|
+
`another origin rather than a path in this repository. A scope boundary, not a gap.`, { blockedBy: null, refusalClass: "out-of-scope" });
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const base = clientBaseOfReceiver(ref);
|
|
291
|
+
if (base.state === "unresolved") {
|
|
292
|
+
// DEC-164: never guess. A base-setting mechanism was found and its
|
|
293
|
+
// value could not be read — zero witnesses for this branch in the six
|
|
294
|
+
// corpora this row measured (see `client-base.ts`'s own header), kept
|
|
295
|
+
// rather than deleted for the day one exists.
|
|
296
|
+
refusals.push({
|
|
297
|
+
fromId,
|
|
298
|
+
raw: ref.raw,
|
|
299
|
+
line: ref.line,
|
|
300
|
+
reason: `an HTTP call through ${receiverType}, whose base this reader traced to a DI ` +
|
|
301
|
+
`registration but could not read as a literal: ${base.why}. DEC-164 refuses rather ` +
|
|
302
|
+
`than guesses what an unreadable base composes to.`,
|
|
303
|
+
blockedBy: base.why,
|
|
304
|
+
refusalClass: "capability-gap",
|
|
305
|
+
clientBase: base,
|
|
306
|
+
});
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (base.state === "resolved") {
|
|
310
|
+
// DEC-097: only the origin was ever stripped from a resolved base: the
|
|
311
|
+
// call's own literal path — leading slash or not, the base is real and
|
|
312
|
+
// traced — composes directly against `path`.
|
|
313
|
+
calls.push({
|
|
314
|
+
fromId,
|
|
315
|
+
method: ref.name,
|
|
316
|
+
template: joinBase(base.path, stripQuery(ref.firstStringArgument)),
|
|
317
|
+
verb,
|
|
318
|
+
line: ref.line,
|
|
319
|
+
callerKind: isTest ? "test" : "production",
|
|
320
|
+
clientBase: base,
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
// `base.state === "none"` — no traced mechanism. Unchanged from every C#
|
|
325
|
+
// `USES_API` edge minted before this row: a leading-slash literal is
|
|
326
|
+
// trusted as complete on its own; a bare relative path is not, because
|
|
327
|
+
// this reader cannot tell whether an untraced `BaseAddress` elsewhere
|
|
328
|
+
// would give it a different meaning.
|
|
329
|
+
const template = relativePath(ref.firstStringArgument);
|
|
330
|
+
if (template === undefined) {
|
|
331
|
+
// A bare relative path with no leading slash and no traced base —
|
|
332
|
+
// structurally identical to Java's own `restTemplate.getForObject("orders/5",
|
|
333
|
+
// ...)` shape once `adapter-java` `1bfd2ae` closed the same latent
|
|
334
|
+
// defect there. It may resolve inside this repository, against a
|
|
335
|
+
// `BaseAddress` configured elsewhere this reader does not trace, or it
|
|
336
|
+
// may not; neither an edge nor an out-of-scope claim is honest here —
|
|
337
|
+
// rule 2, applied to a classification rather than an edge. Not a
|
|
338
|
+
// per-call value and not a named blocking expression either, so
|
|
339
|
+
// `capability-gap` rather than `varies-per-call`.
|
|
340
|
+
refusals.push({
|
|
341
|
+
fromId,
|
|
342
|
+
raw: ref.raw,
|
|
343
|
+
line: ref.line,
|
|
344
|
+
reason: `an HTTP call through ${receiverType} to \`${ref.firstStringArgument}\`, a relative path ` +
|
|
345
|
+
`with no leading slash and no base this reader traced (DEC-164: \`clientBase.state === ` +
|
|
346
|
+
`"none"\`). This reader cannot tell whether it resolves inside this repository — against ` +
|
|
347
|
+
`a configured BaseAddress this call site does not state — or elsewhere, so neither an ` +
|
|
348
|
+
`endpoint nor a scope boundary is claimed.`,
|
|
349
|
+
...CAPABILITY_GAP,
|
|
350
|
+
clientBase: base,
|
|
351
|
+
});
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
calls.push({
|
|
355
|
+
fromId,
|
|
356
|
+
method: ref.name,
|
|
357
|
+
template,
|
|
358
|
+
verb,
|
|
359
|
+
line: ref.line,
|
|
360
|
+
callerKind: isTest ? "test" : "production",
|
|
361
|
+
clientBase: base,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return { calls, refusals };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The `API_ENDPOINT` a call joins to — minted identically to the route side.
|
|
368
|
+
*
|
|
369
|
+
* Shares `endpointQsp` with `extract.ts`'s `SERVES_API` block rather than
|
|
370
|
+
* recomputing the id, because the two must agree exactly and the failure when
|
|
371
|
+
* they do not is silent.
|
|
372
|
+
*/
|
|
373
|
+
export function endpointFor(scope, call, producedBy, normalise) {
|
|
374
|
+
const template = normalise(call.template);
|
|
375
|
+
return {
|
|
376
|
+
id: nodeId(scope, "API_ENDPOINT", endpointQsp(call.verb, call.template), null),
|
|
377
|
+
type: "API_ENDPOINT",
|
|
378
|
+
name: `${call.verb} ${template}`,
|
|
379
|
+
file: null,
|
|
380
|
+
range: null,
|
|
381
|
+
language: null,
|
|
382
|
+
producedBy,
|
|
383
|
+
resolution: 2,
|
|
384
|
+
attrs: { method: call.verb, pathTemplate: template },
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAmC,MAAM,aAAa,CAAC;AAkEnF,MAAM,cAAc,GAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAC1F,MAAM,eAAe,GAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAE5F,uHAAuH;AACvH,MAAM,iBAAiB,GAAG;IACxB,wDAAwD;IACxD,2CAA2C;CAC5C,CAAC;AAEF,wFAAwF;AACxF,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5D,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,eAAe,CAAC;IAC/C,IAAI,qDAAqD,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IACrF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,IAAwB,EAAE,cAAmC;IACrF,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,eAAe,CAAC;IACpG,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,CAAC;IAC/D,CAAC;IACD,OAAO,EAAE,GAAG,cAAc,EAAE,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,kGAAkG;AAClG,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,UAAU;IACV,WAAW;IACX,UAAU;IACV,aAAa;IACb,YAAY;IACZ,WAAW;IACX,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;CACtB,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,IAAI,GAAqC;IAC7C,QAAQ,EAAE,KAAK;IACf,cAAc,EAAE,KAAK;IACrB,iBAAiB,EAAE,KAAK;IACxB,cAAc,EAAE,KAAK;IACrB,gBAAgB,EAAE,KAAK;IACvB,SAAS,EAAE,MAAM;IACjB,eAAe,EAAE,MAAM;IACvB,QAAQ,EAAE,KAAK;IACf,cAAc,EAAE,KAAK;IACrB,WAAW,EAAE,QAAQ;IACrB,mBAAmB,EAAE,QAAQ;IAC7B,UAAU,EAAE,OAAO;CACpB,CAAC;AAEF;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7C,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,+BAA+B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAsB;IACxD,OAAO,IAAI,CAAC,MAAM,CAChB,CAAC,GAAG,EAAgD,EAAE,CACpD,GAAG,CAAC,IAAI,KAAK,MAAM;QACnB,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5B,GAAG,CAAC,QAAQ,KAAK,SAAS;QAC1B,GAAG,CAAC,QAAQ,KAAK,MAAM;QACvB,GAAG,CAAC,QAAQ,KAAK,MAAM,CAC1B,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CACzB,IAAsB,EACtB,MAAc,EACd,cAAkF,EAClF,MAAe,EACf,cAAmC,EACnC,oBAAsF;IAEtF,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,YAAY,CAAC;YAAE,SAAS;QAElF,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,SAA8F,EAAQ,EAAE;YACtI,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC;QAChF,CAAC,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CACJ,wBAAwB,YAAY,WAAW,GAAG,CAAC,IAAI,uBAAuB;gBAC5E,uFAAuF;gBACvF,oFAAoF;gBACpF,yFAAyF,EAC3F,cAAc,CACf,CAAC;YACF,SAAS;QACX,CAAC;QAED,IAAI,GAAG,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;YAC1C,MAAM,CACJ,wBAAwB,YAAY,kDAAkD;gBACpF,wFAAwF;gBACxF,wFAAwF;gBACxF,mCAAmC,EACrC,gBAAgB,CAAC,GAAG,CAAC,iBAAiB,EAAE,cAAc,CAAC,CACxD,CAAC;YACF,SAAS;QACX,CAAC;QAED,kEAAkE;QAClE,yEAAyE;QACzE,oEAAoE;QACpE,oEAAoE;QACpE,yEAAyE;QACzE,yEAAyE;QACzE,IAAI,kBAAkB,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAChD,MAAM,CACJ,wBAAwB,YAAY,SAAS,GAAG,CAAC,mBAAmB,kBAAkB;gBACpF,oFAAoF,EACtF,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,cAAc,EAAE,CAClD,CAAC;YACF,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAEvC,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;YAChC,mEAAmE;YACnE,sEAAsE;YACtE,sEAAsE;YACtE,8CAA8C;YAC9C,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM;gBACN,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EACJ,wBAAwB,YAAY,0CAA0C;oBAC9E,iDAAiD,IAAI,CAAC,GAAG,2BAA2B;oBACpF,mDAAmD;gBACrD,SAAS,EAAE,IAAI,CAAC,GAAG;gBACnB,YAAY,EAAE,gBAAgB;gBAC9B,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC9B,uEAAuE;YACvE,uEAAuE;YACvE,6CAA6C;YAC7C,KAAK,CAAC,IAAI,CAAC;gBACT,MAAM;gBACN,MAAM,EAAE,GAAG,CAAC,IAAI;gBAChB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;gBAClE,IAAI;gBACJ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY;gBAC1C,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,yEAAyE;QACzE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,qCAAqC;QACrC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,kEAAkE;YAClE,8EAA8E;YAC9E,mEAAmE;YACnE,iEAAiE;YACjE,uEAAuE;YACvE,sEAAsE;YACtE,iEAAiE;YACjE,gEAAgE;YAChE,kDAAkD;YAClD,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM;gBACN,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EACJ,wBAAwB,YAAY,SAAS,GAAG,CAAC,mBAAmB,sBAAsB;oBAC1F,wFAAwF;oBACxF,0FAA0F;oBAC1F,uFAAuF;oBACvF,2CAA2C;gBAC7C,GAAG,cAAc;gBACjB,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,KAAK,CAAC,IAAI,CAAC;YACT,MAAM;YACN,MAAM,EAAE,GAAG,CAAC,IAAI;YAChB,QAAQ;YACR,IAAI;YACJ,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY;YAC1C,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,KAAoB,EACpB,IAAgB,EAChB,UAAkB,EAClB,SAAuC;IAEvC,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;QAC9E,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE;QAChC,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,IAAI;QACd,UAAU;QACV,UAAU,EAAE,CAAC;QACb,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE;KACrD,CAAC;AACJ,CAAC"}
|
package/dist/efcore.d.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ORM framework extractor for C# — Entity Framework Core.
|
|
3
|
+
*
|
|
4
|
+
* **A `MODEL` is a framework claim, and this file is where it is earned.**
|
|
5
|
+
* DEC-128 fixed the rules before extraction and **DEC-180 amended them after
|
|
6
|
+
* counting the corpus**, which is the part worth reading first: four of
|
|
7
|
+
* DEC-128's ORM rules were wrong, and every one of them was wrong in the same
|
|
8
|
+
* way — written from how EF Core is documented rather than from how the corpus
|
|
9
|
+
* is spelled. That is how a rule acquires zero witnesses.
|
|
10
|
+
*
|
|
11
|
+
* ## Admission — what the corpus actually contains
|
|
12
|
+
*
|
|
13
|
+
* | evidence | jellyfin | Ocelot | eShopOnWeb | CleanArchitecture |
|
|
14
|
+
* | --- | --- | --- | --- | --- |
|
|
15
|
+
* | `DbSet<` | 71 | 0 | 7 | 4 |
|
|
16
|
+
* | `IEntityTypeConfiguration<` | 26 | 0 | 7 | 2 |
|
|
17
|
+
* | `modelBuilder.Entity<` | **0** | **0** | **0** | **0** |
|
|
18
|
+
* | `[Table(` | **0** | **0** | **0** | **0** |
|
|
19
|
+
*
|
|
20
|
+
* So the admission grounds are:
|
|
21
|
+
*
|
|
22
|
+
* 1. **`DbSet<T>` on a `DbContext`.** Primary, and the only rule with witnesses
|
|
23
|
+
* in three of four corpora.
|
|
24
|
+
* 2. **`IEntityTypeConfiguration<T>`.** This *replaces* DEC-128's
|
|
25
|
+
* `modelBuilder.Entity<T>()` rule, which is the same fact written in a form
|
|
26
|
+
* the corpus does not contain.
|
|
27
|
+
* 3. `[Table]` and `modelBuilder.Entity<T>()` are still **read** where they
|
|
28
|
+
* appear, and are reported as **unwitnessed** — `0 of 0`, undefined, never as
|
|
29
|
+
* passing. A rule with zero witnesses cannot be shown to work or to fail, and
|
|
30
|
+
* its green is the green of a branch never taken.
|
|
31
|
+
*
|
|
32
|
+
* **Refused:** a type in a `Models` or `Entities` namespace with no mapping
|
|
33
|
+
* evidence — the `IFoo` guess this project forbids — and an owned type reached
|
|
34
|
+
* through `OwnsOne`, which shares its owner's table and has none of its own.
|
|
35
|
+
*
|
|
36
|
+
* ## Generated migrations are excluded, and the exclusion is counted
|
|
37
|
+
*
|
|
38
|
+
* DEC-128 read the table name from `ToTable(…)`. Measured: **1,338 of
|
|
39
|
+
* `jellyfin`'s 1,339 `ToTable` calls are inside `Migrations/`**, under a
|
|
40
|
+
* `// <auto-generated />` header, and 50 of `eShopOnWeb`'s 51. Those files are
|
|
41
|
+
* EF Core's serialised model from whenever someone last scaffolded a migration —
|
|
42
|
+
* an artefact of an operation, read as evidence about the world.
|
|
43
|
+
*
|
|
44
|
+
* An extractor reading them without excluding them is, to three significant
|
|
45
|
+
* figures, reading **only** generated code. So they are excluded — and the count
|
|
46
|
+
* is **reported**, because a silent exclusion of 1,338 of 1,339 is a correct
|
|
47
|
+
* decision that looks identical to a reader that never found them.
|
|
48
|
+
*
|
|
49
|
+
* ## No corpus can exercise the provenance rule
|
|
50
|
+
*
|
|
51
|
+
* `Dapper`, `NHibernate` and `linq2db` occur **zero** times across all four
|
|
52
|
+
* corpora. There is no C# repository here in which a rival ORM's types are
|
|
53
|
+
* present for these rules to correctly refuse — the identical hole the Java
|
|
54
|
+
* control set has. Stated before extraction rather than discovered after a green
|
|
55
|
+
* run, and every report carries the EF negative control as `0 of 0 — undefined`.
|
|
56
|
+
*/
|
|
57
|
+
import type { CsFile } from "./parse.ts";
|
|
58
|
+
import { type NullableContext } from "./nullability.ts";
|
|
59
|
+
/** Mirrors `JpaField` and `OrmField` so one shape crosses three languages. */
|
|
60
|
+
export interface EfColumn {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
/** `undefined` when nothing this reader can evaluate decides it. */
|
|
63
|
+
readonly nullable: boolean | undefined;
|
|
64
|
+
readonly nullableFrom: "fluent" | "annotation" | "value-type" | "nullable-value-type" | "reference-type" | "unresolved";
|
|
65
|
+
}
|
|
66
|
+
export interface EfModel {
|
|
67
|
+
readonly typeName: string;
|
|
68
|
+
/**
|
|
69
|
+
* Namespace-qualified, because the simple name is not what identifies a type.
|
|
70
|
+
*
|
|
71
|
+
* The consumer decides `MODEL` versus `CLASS` from this map, and it used to
|
|
72
|
+
* decide on `typeName`. While every simple-name collision was refused here
|
|
73
|
+
* that was invisible; the moment collisions began resolving, **every**
|
|
74
|
+
* declaration sharing a resolved name became a `MODEL` — `eShopOnWeb` emitted
|
|
75
|
+
* a second `Basket` for its Razor `ViewComponent` and a second `CatalogItem`
|
|
76
|
+
* for its Blazor DTO, each with the entity's columns attached to the wrong
|
|
77
|
+
* type.
|
|
78
|
+
*
|
|
79
|
+
* So a conservative refusal in this file was holding a defect in another one
|
|
80
|
+
* shut. Widening the refusal was correct and it was also the thing that fired
|
|
81
|
+
* the second bug — which is the argument for re-running precision after a
|
|
82
|
+
* widening rather than after a release.
|
|
83
|
+
*/
|
|
84
|
+
readonly fqn: string;
|
|
85
|
+
readonly path: readonly string[];
|
|
86
|
+
/** `null` when only a convention this reader does not compute would give it. */
|
|
87
|
+
readonly table: string | null;
|
|
88
|
+
readonly admittedBy: "dbset" | "configuration" | "table-attribute" | "model-builder";
|
|
89
|
+
readonly columns: readonly EfColumn[];
|
|
90
|
+
readonly line: number;
|
|
91
|
+
readonly file: string;
|
|
92
|
+
}
|
|
93
|
+
/** What was read and what was refused, so a silent exclusion cannot pass for a finding. */
|
|
94
|
+
export interface EfCensus {
|
|
95
|
+
readonly models: readonly EfModel[];
|
|
96
|
+
/** Files skipped as generated. See the file comment — reported, never silent. */
|
|
97
|
+
readonly generatedFilesSkipped: number;
|
|
98
|
+
/** `ToTable` calls inside those files, which is the 1,338-of-1,339 number. */
|
|
99
|
+
readonly generatedToTableSkipped: number;
|
|
100
|
+
/**
|
|
101
|
+
* Entities named by a mapping whose simple name is declared more than once.
|
|
102
|
+
*
|
|
103
|
+
* **Refused rather than guessed, and this is not hypothetical.** `eShopOnWeb`
|
|
104
|
+
* declares `Basket`, `CatalogBrand`, `CatalogItem` and `CatalogType` **twice**
|
|
105
|
+
* — once in `ApplicationCore/Entities` as the entity and once in
|
|
106
|
+
* `BlazorShared/Models` as the view DTO. A simple-name index with first-wins
|
|
107
|
+
* publishes the DTO's properties as the entity's columns: a wrong answer, and
|
|
108
|
+
* one that looks entirely healthy because a model *is* emitted with plausible
|
|
109
|
+
* columns.
|
|
110
|
+
*
|
|
111
|
+
* This is the mistake `adapter-java` records for `airbyte`, arriving in C#: a
|
|
112
|
+
* `Map<string, Declared>` keeps the last (or first) of several and resolves
|
|
113
|
+
* every reference to it. Two declarations is genuinely ambiguous and the
|
|
114
|
+
* `DbSet<T>` argument is rarely qualified, so there is nothing to break the tie
|
|
115
|
+
* with. Refused, and counted so the refusal is visible.
|
|
116
|
+
*/
|
|
117
|
+
readonly ambiguousEntityNames: readonly string[];
|
|
118
|
+
/**
|
|
119
|
+
* Simple-name collisions, split by whether the namespace rule settled them.
|
|
120
|
+
*
|
|
121
|
+
* Reported because "eight refusals" and "eight refusals, all resolvable" are
|
|
122
|
+
* different disclosures and only the second is actionable.
|
|
123
|
+
*/
|
|
124
|
+
readonly namespaceResolved: {
|
|
125
|
+
readonly resolved: number;
|
|
126
|
+
readonly unresolved: number;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Properties refused because their declared type is a class declared in this
|
|
130
|
+
* repository — a navigation, or an owned type. Not scalar columns.
|
|
131
|
+
*
|
|
132
|
+
* **The owned-type rule DEC-128 §2 states has no readable witness.**
|
|
133
|
+
* `eShopOnWeb`'s only `OwnsOne` calls are inside `Migrations/*.Designer.cs`,
|
|
134
|
+
* which are generated and correctly excluded, so `Address` and
|
|
135
|
+
* `CatalogItemOrdered` are never marked owned. Reading them anyway would have
|
|
136
|
+
* published `ShipToAddress` as one non-nullable column, when EF spreads an
|
|
137
|
+
* owned type across several columns of the owner's table under names this
|
|
138
|
+
* reader cannot compute.
|
|
139
|
+
*
|
|
140
|
+
* So the rule applied is broader and rests on the declaration rather than on
|
|
141
|
+
* the configuration: a property whose type is a repo-declared `class`,
|
|
142
|
+
* `record` or `interface` is not a scalar column. Enums and structs are, and
|
|
143
|
+
* stay.
|
|
144
|
+
*/
|
|
145
|
+
readonly navigationsRefused: number;
|
|
146
|
+
/** Which rule *won* precedence for each admitted model. */
|
|
147
|
+
readonly admittedBy: Readonly<Record<EfModel["admittedBy"], number>>;
|
|
148
|
+
/**
|
|
149
|
+
* How many entities **each rule saw**, independent of which one won.
|
|
150
|
+
*
|
|
151
|
+
* Reported beside `admittedBy` because the two answer different questions and
|
|
152
|
+
* the first alone is misleading. `DbSet<T>` outranks `IEntityTypeConfiguration<T>`
|
|
153
|
+
* and names the same entities, so `admittedBy.configuration` is **0 on every
|
|
154
|
+
* corpus** — which reads as *the rule never fires* when it means *nothing
|
|
155
|
+
* needed it to*. A rule that fires and never wins is not an unwitnessed rule,
|
|
156
|
+
* and the distinction is exactly the one DEC-180 was written to protect.
|
|
157
|
+
*/
|
|
158
|
+
readonly evidenceFor: Readonly<Record<EfModel["admittedBy"], number>>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Does this file import EF Core at all? Provenance, not spelling.
|
|
162
|
+
*
|
|
163
|
+
* **A `global using` counts, and it is in force across the whole assembly.**
|
|
164
|
+
* That is not an edge case: `eShop`'s `OrderingContext` declares nine
|
|
165
|
+
* `DbSet<T>` and its only file-local `using` is `eShop.IntegrationEventLogEF` —
|
|
166
|
+
* `Microsoft.EntityFrameworkCore` arrives from `GlobalUsings.cs` three files
|
|
167
|
+
* away. Checking file-local usings alone read **zero** models from a corpus with
|
|
168
|
+
* nine, silently.
|
|
169
|
+
*
|
|
170
|
+
* `parse.ts` already says a `global using` is *"in force for every file in the
|
|
171
|
+
* project, not just this one"* and models the flag. This is the first consumer
|
|
172
|
+
* to act on it. Modern C# leans on it heavily — .NET 6 onward emits an implicit
|
|
173
|
+
* global-usings file per project — so a reader that ignores it fails on new
|
|
174
|
+
* codebases specifically.
|
|
175
|
+
*
|
|
176
|
+
* Scoped by **assembly**, because that is what a project is. A global using in
|
|
177
|
+
* one project does not reach another.
|
|
178
|
+
*/
|
|
179
|
+
export declare function importsEfCore(file: CsFile, globalsByAssembly?: ReadonlyMap<string, boolean>): boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Is this file generated?
|
|
182
|
+
*
|
|
183
|
+
* Two signals, and both are needed. The `// <auto-generated />` header is the
|
|
184
|
+
* language's own convention and is what EF Core's scaffolder emits. The
|
|
185
|
+
* `Migrations/` path catches the migration classes beside the snapshot, which
|
|
186
|
+
* carry no header of their own in every version.
|
|
187
|
+
*/
|
|
188
|
+
export declare function isGenerated(path: string, source: string): boolean;
|
|
189
|
+
export interface EfInput {
|
|
190
|
+
readonly files: readonly {
|
|
191
|
+
readonly parsed: CsFile;
|
|
192
|
+
readonly source: string;
|
|
193
|
+
}[];
|
|
194
|
+
/** NRT context governing each file, resolved by the adapter. */
|
|
195
|
+
readonly contextOf: (file: string) => NullableContext;
|
|
196
|
+
}
|
|
197
|
+
export declare function efModels(input: EfInput): EfCensus;
|
|
198
|
+
//# sourceMappingURL=efcore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"efcore.d.ts","sourceRoot":"","sources":["../src/efcore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE/E,8EAA8E;AAC9E,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,YAAY,EACjB,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,qBAAqB,GACrB,gBAAgB,GAChB,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,eAAe,GAAG,iBAAiB,GAAG,eAAe,CAAC;IACrF,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,2FAA2F;AAC3F,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IACpC,iFAAiF;IACjF,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE;;;;;;;;;OASG;IACH,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACvE;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGrG;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAKjE;AAoND,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChF,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;CACvD;AAuDD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAqPjD"}
|