@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
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP-route framework extractor for C# — ASP.NET Core.
|
|
3
|
+
*
|
|
4
|
+
* **An `API_ROUTE` is a framework claim, and this file is where it is earned.**
|
|
5
|
+
* DEC-128 §1 fixed the rules before extraction; **DEC-181 amended them** after
|
|
6
|
+
* re-counting in the unit the rules decide, and that amendment is most of what
|
|
7
|
+
* is load-bearing here.
|
|
8
|
+
*
|
|
9
|
+
* ## The evidence is on an ancestor and the subject is the descendant
|
|
10
|
+
*
|
|
11
|
+
* DEC-128 §1a admits a method carrying `[HttpGet]` on a controller, where a
|
|
12
|
+
* controller is `[ApiController]` or a `ControllerBase` base. Counted in files:
|
|
13
|
+
*
|
|
14
|
+
* | corpus | files with a verb attribute | directly admissible | **need the base chain** |
|
|
15
|
+
* | --- | --- | --- | --- |
|
|
16
|
+
* | `jellyfin` | 62 | **1** | **61** |
|
|
17
|
+
* | `Ocelot` | 9 | 9 | 0 |
|
|
18
|
+
* | `eShopOnWeb` | 4 | 3 | 1 |
|
|
19
|
+
*
|
|
20
|
+
* Every `jellyfin` controller is `class XController : BaseJellyfinApiController`,
|
|
21
|
+
* and the base carries `[ApiController]` and `[Route("[controller]")]`. **A reader
|
|
22
|
+
* checking the class itself admits 1 of 62 and emits nothing for ~400 verb
|
|
23
|
+
* attributes** — a silent recall hole, which the precision apparatus is
|
|
24
|
+
* structurally blind to.
|
|
25
|
+
*
|
|
26
|
+
* This is the **third instance of one construct class**: the evidence sits on an
|
|
27
|
+
* ancestor and the subject is the descendant. JPA's `@MappedSuperclass` cost a
|
|
28
|
+
* third of an entity population; this is 98% of a corpus. It is checked for by
|
|
29
|
+
* name now rather than met by surprise.
|
|
30
|
+
*
|
|
31
|
+
* ## `[controller]` expands against the SUBJECT class, not the declaring one
|
|
32
|
+
*
|
|
33
|
+
* The `[Route("[controller]")]` governing 61 `jellyfin` controllers is declared
|
|
34
|
+
* **once, on the base**. A reader expanding the token where it finds the
|
|
35
|
+
* attribute produces `BaseJellyfinApi` sixty-one times — **one wrong template,
|
|
36
|
+
* sixty-one routes**. That is a wrong answer where the recall hole above is only
|
|
37
|
+
* a silence, and rule 2 does not treat them the same.
|
|
38
|
+
*
|
|
39
|
+
* The template is inherited; the *name substituted into it* never is.
|
|
40
|
+
*
|
|
41
|
+
* ## A derived `[Route]` REPLACES an inherited one — it does not join
|
|
42
|
+
*
|
|
43
|
+
* `ActivityLogController` carries `[Route("System/ActivityLog")]` while
|
|
44
|
+
* inheriting `[Route("[controller]")]`. ASP.NET takes the most-derived; they do
|
|
45
|
+
* not concatenate.
|
|
46
|
+
*
|
|
47
|
+
* §1a *does* join a class template to a method template, and the two joins look
|
|
48
|
+
* alike — which is the trap. **Before reusing a composition rule on a new pair,
|
|
49
|
+
* establish that the new pair composes at all.** Class↔method concatenates;
|
|
50
|
+
* class↔base overrides.
|
|
51
|
+
*
|
|
52
|
+
* ## Both mechanisms, counted apart
|
|
53
|
+
*
|
|
54
|
+
* | corpus | verb attributes | `Map*` | `MapGroup` |
|
|
55
|
+
* | --- | --- | --- | --- |
|
|
56
|
+
* | `jellyfin` | 400 | 0 | 0 |
|
|
57
|
+
* | `Ocelot` | 19 | 3 | 0 |
|
|
58
|
+
* | `eShopOnWeb` | 26 | 7 | 0 |
|
|
59
|
+
* | `CleanArchitecture` | **0** | **15** | **1** |
|
|
60
|
+
*
|
|
61
|
+
* `CleanArchitecture` is attribute-free and `jellyfin` is minimal-API-free, so an
|
|
62
|
+
* extractor supporting one mechanism scores zero on a whole corpus rather than a
|
|
63
|
+
* degraded number. Counted separately so a total miss cannot average into a
|
|
64
|
+
* healthy-looking figure. **`MapGroup` has one witness in the entire control set**
|
|
65
|
+
* — stated rather than discovered later, because a rule with one witness is not
|
|
66
|
+
* measured.
|
|
67
|
+
*/
|
|
68
|
+
const MVC_NAMESPACES = ["Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Builder", "Microsoft.AspNetCore.Routing"];
|
|
69
|
+
const VERB_ATTRIBUTES = new Map([
|
|
70
|
+
["HttpGet", "GET"],
|
|
71
|
+
["HttpPost", "POST"],
|
|
72
|
+
["HttpPut", "PUT"],
|
|
73
|
+
["HttpPatch", "PATCH"],
|
|
74
|
+
["HttpDelete", "DELETE"],
|
|
75
|
+
["HttpHead", "HEAD"],
|
|
76
|
+
["HttpOptions", "OPTIONS"],
|
|
77
|
+
]);
|
|
78
|
+
const MINIMAL_VERBS = new Map([
|
|
79
|
+
["MapGet", "GET"],
|
|
80
|
+
["MapPost", "POST"],
|
|
81
|
+
["MapPut", "PUT"],
|
|
82
|
+
["MapPatch", "PATCH"],
|
|
83
|
+
["MapDelete", "DELETE"],
|
|
84
|
+
]);
|
|
85
|
+
const importsIn = (file, onlyGlobal = false) => file.usings.some((using) => (!onlyGlobal || using.isGlobal) &&
|
|
86
|
+
MVC_NAMESPACES.some((ns) => using.target === ns || using.target.startsWith(`${ns}.`)));
|
|
87
|
+
/**
|
|
88
|
+
* Does this file import ASP.NET's routing surface? Provenance, not spelling.
|
|
89
|
+
*
|
|
90
|
+
* **A `global using` counts and is in force across the assembly**, which is how
|
|
91
|
+
* modern C# is written — .NET 6 onward emits an implicit global-usings file per
|
|
92
|
+
* project. `efcore.ts` records the measured cost of ignoring it: nine `DbSet<T>`
|
|
93
|
+
* read as zero because the `using` sat three files away.
|
|
94
|
+
*
|
|
95
|
+
* Scoped by assembly, because a global using in one project does not reach
|
|
96
|
+
* another.
|
|
97
|
+
*/
|
|
98
|
+
export function importsAspNet(file, globalsByAssembly) {
|
|
99
|
+
if (importsIn(file))
|
|
100
|
+
return true;
|
|
101
|
+
return globalsByAssembly?.get(file.assembly) === true;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Normalise a route template to its joinable form.
|
|
105
|
+
*
|
|
106
|
+
* `{id}`, `{id:int}`, `{id:guid}`, `{id?}` and `{*rest}` all serve the same URL
|
|
107
|
+
* shape and must mint the same endpoint id, or a caller in another language
|
|
108
|
+
* joins to nothing. Golden 04's note names `{id:guid}` explicitly.
|
|
109
|
+
*
|
|
110
|
+
* The *written* form is kept beside this one. Identity normalises and fidelity
|
|
111
|
+
* preserves; collapsing to one deletes a fact that surfaces as a clean zero
|
|
112
|
+
* somewhere else.
|
|
113
|
+
*/
|
|
114
|
+
export function normaliseTemplate(written) {
|
|
115
|
+
return written.replace(/\{[^}]*\}/g, "{param}");
|
|
116
|
+
}
|
|
117
|
+
/** Join a class prefix to a method suffix. Slashes are forgiving on both sides. */
|
|
118
|
+
function join(prefix, suffix) {
|
|
119
|
+
const left = prefix.replace(/^\/+|\/+$/g, "");
|
|
120
|
+
const right = suffix.replace(/^\/+|\/+$/g, "");
|
|
121
|
+
if (left === "" && right === "")
|
|
122
|
+
return "/";
|
|
123
|
+
if (left === "")
|
|
124
|
+
return `/${right}`;
|
|
125
|
+
if (right === "")
|
|
126
|
+
return `/${left}`;
|
|
127
|
+
return `/${left}/${right}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Expand `[controller]`, `[action]` and `[area]` against the **subject**.
|
|
131
|
+
*
|
|
132
|
+
* `subject` is the most-derived class the route is being computed for, never the
|
|
133
|
+
* class that declared the attribute — see the file comment. `[controller]` drops
|
|
134
|
+
* the `Controller` suffix, which is the framework's own rule and the one place a
|
|
135
|
+
* class name legitimately enters a path.
|
|
136
|
+
*
|
|
137
|
+
* A token this reader does not know yields `undefined`: the route is refused and
|
|
138
|
+
* logged rather than emitted with a guessed path.
|
|
139
|
+
*/
|
|
140
|
+
export function expandTokens(template, subject, action) {
|
|
141
|
+
let tokens = 0;
|
|
142
|
+
let unknown = false;
|
|
143
|
+
const expanded = template.replace(/\[([a-zA-Z]+)\]/g, (whole, token) => {
|
|
144
|
+
const lower = token.toLowerCase();
|
|
145
|
+
if (lower === "controller") {
|
|
146
|
+
tokens += 1;
|
|
147
|
+
return subject.replace(/Controller$/, "");
|
|
148
|
+
}
|
|
149
|
+
if (lower === "action") {
|
|
150
|
+
tokens += 1;
|
|
151
|
+
return action;
|
|
152
|
+
}
|
|
153
|
+
// `[area]` needs the area attribute, which this does not read. Refused
|
|
154
|
+
// rather than dropped: a path missing a segment is a wrong path.
|
|
155
|
+
unknown = true;
|
|
156
|
+
return whole;
|
|
157
|
+
});
|
|
158
|
+
return unknown ? undefined : { expanded, tokens };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Every route the batch declares, with the admission accounting reported.
|
|
162
|
+
*
|
|
163
|
+
* Cross-file by necessity: the evidence making a class a controller is usually on
|
|
164
|
+
* a base declared elsewhere. Same shape as JPA's mapped-superclass walk, for the
|
|
165
|
+
* same construct.
|
|
166
|
+
*/
|
|
167
|
+
export function aspNetRoutes(input) {
|
|
168
|
+
/** Simple name -> every analysed declaration. A list, never one — DEC-181 and
|
|
169
|
+
* the `eShopOnWeb` DTO collision that made a list necessary in `efcore.ts`. */
|
|
170
|
+
const byName = new Map();
|
|
171
|
+
for (const { parsed } of input.files) {
|
|
172
|
+
for (const type of parsed.types) {
|
|
173
|
+
const bucket = byName.get(type.name);
|
|
174
|
+
if (bucket === undefined)
|
|
175
|
+
byName.set(type.name, [{ type, file: parsed }]);
|
|
176
|
+
else
|
|
177
|
+
bucket.push({ type, file: parsed });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const ambiguousBaseNames = new Set();
|
|
181
|
+
let baseChainsLeftAnalysedSet = 0;
|
|
182
|
+
const unresolvedBases = new Map();
|
|
183
|
+
/**
|
|
184
|
+
* Walk a type's base chain, nearest first, collecting what the chain states.
|
|
185
|
+
*
|
|
186
|
+
* Returns whether the chain reaches controller evidence, and the **nearest**
|
|
187
|
+
* `[Route]` template found — nearest, because a derived route replaces an
|
|
188
|
+
* inherited one rather than joining it.
|
|
189
|
+
*/
|
|
190
|
+
const chainOf = (start) => {
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
let current = start;
|
|
193
|
+
let isController = false;
|
|
194
|
+
let direct = false;
|
|
195
|
+
let route;
|
|
196
|
+
let unresolvedBase;
|
|
197
|
+
let depth = 0;
|
|
198
|
+
while (current !== undefined) {
|
|
199
|
+
const key = `${current.file.file}::${current.type.path.join(".")}`;
|
|
200
|
+
if (seen.has(key))
|
|
201
|
+
break;
|
|
202
|
+
seen.add(key);
|
|
203
|
+
const attributes = new Set(current.type.attributes);
|
|
204
|
+
if (attributes.has("ApiController")) {
|
|
205
|
+
isController = true;
|
|
206
|
+
if (depth === 0)
|
|
207
|
+
direct = true;
|
|
208
|
+
}
|
|
209
|
+
// `[Route("…")]` — the nearest declaration wins and the walk keeps going
|
|
210
|
+
// only to look for controller evidence, never to join another template.
|
|
211
|
+
if (route === undefined) {
|
|
212
|
+
const declared = current.type.attributeArguments["Route"];
|
|
213
|
+
if (declared !== undefined)
|
|
214
|
+
route = declared;
|
|
215
|
+
}
|
|
216
|
+
const base = current.type.baseList[0];
|
|
217
|
+
if (base === undefined)
|
|
218
|
+
break;
|
|
219
|
+
if (base === "Controller" || base === "ControllerBase") {
|
|
220
|
+
isController = true;
|
|
221
|
+
if (depth === 0)
|
|
222
|
+
direct = true;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
const candidates = byName.get(base);
|
|
226
|
+
if (candidates === undefined || candidates.length === 0) {
|
|
227
|
+
baseChainsLeftAnalysedSet += 1;
|
|
228
|
+
// A base written across lines (`EndpointBaseAsync\n .WithRequest<T>`)
|
|
229
|
+
// is one name; collapsing the whitespace keeps the report readable.
|
|
230
|
+
const flat = base.replace(/\s+/g, "");
|
|
231
|
+
unresolvedBases.set(flat, (unresolvedBases.get(flat) ?? 0) + 1);
|
|
232
|
+
// Attributed to `start` regardless of how many hops away this base
|
|
233
|
+
// sits — `chainOf` answers "is `start` transitively a controller,"
|
|
234
|
+
// and an ancestor's unresolved base is exactly why `start`'s own
|
|
235
|
+
// answer could not be determined.
|
|
236
|
+
unresolvedBase = flat;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
if (candidates.length > 1) {
|
|
240
|
+
// Two declarations of one base name. Refused rather than guessed: the
|
|
241
|
+
// wrong base gives the wrong `[Route]` and therefore the wrong path.
|
|
242
|
+
ambiguousBaseNames.add(base);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
current = candidates[0];
|
|
246
|
+
depth += 1;
|
|
247
|
+
}
|
|
248
|
+
return { isController, direct, route, unresolvedBase };
|
|
249
|
+
};
|
|
250
|
+
const routes = [];
|
|
251
|
+
const unresolvedBaseSites = [];
|
|
252
|
+
let admittedDirectly = 0;
|
|
253
|
+
let admittedViaBaseChain = 0;
|
|
254
|
+
let refusedNonLiteral = 0;
|
|
255
|
+
let tokensExpanded = 0;
|
|
256
|
+
let tokensUnknown = 0;
|
|
257
|
+
let mapGroupsSeen = 0;
|
|
258
|
+
let minimalRefusedUnknownGroup = 0;
|
|
259
|
+
// Global usings, gathered once per assembly. See `importsAspNet`.
|
|
260
|
+
const mvcGlobals = new Map();
|
|
261
|
+
for (const { parsed } of input.files) {
|
|
262
|
+
if (importsIn(parsed, true))
|
|
263
|
+
mvcGlobals.set(parsed.assembly, true);
|
|
264
|
+
}
|
|
265
|
+
for (const { parsed, source } of input.files) {
|
|
266
|
+
if (!importsAspNet(parsed, mvcGlobals))
|
|
267
|
+
continue;
|
|
268
|
+
for (const type of parsed.types) {
|
|
269
|
+
const chain = chainOf({ type, file: parsed });
|
|
270
|
+
if (!chain.isController) {
|
|
271
|
+
// Not silently dropped (D-FIX-3's own finding, this row) — `start`
|
|
272
|
+
// never reached controller evidence *because* an ancestor's base
|
|
273
|
+
// left the analysed set, so this class's own controller status is
|
|
274
|
+
// genuinely undetermined, not "no". `extract.ts` turns this into a
|
|
275
|
+
// `SERVES_API` ledger row; an ordinary non-controller class (no
|
|
276
|
+
// `unresolvedBase`) is correctly not reported at all.
|
|
277
|
+
if (chain.unresolvedBase !== undefined) {
|
|
278
|
+
unresolvedBaseSites.push({
|
|
279
|
+
path: type.path,
|
|
280
|
+
file: parsed.file,
|
|
281
|
+
line: type.startLine,
|
|
282
|
+
base: chain.unresolvedBase,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (chain.direct)
|
|
288
|
+
admittedDirectly += 1;
|
|
289
|
+
else
|
|
290
|
+
admittedViaBaseChain += 1;
|
|
291
|
+
for (const func of parsed.funcs) {
|
|
292
|
+
if (func.ownerPath.join(".") !== type.path.join("."))
|
|
293
|
+
continue;
|
|
294
|
+
for (const attribute of func.attributes) {
|
|
295
|
+
const verb = VERB_ATTRIBUTES.get(attribute);
|
|
296
|
+
if (verb === undefined)
|
|
297
|
+
continue;
|
|
298
|
+
// RT-208 (descry-runtime): a bare, argument-less verb attribute
|
|
299
|
+
// (`[HttpPost]`) can still carry its own route suffix on a
|
|
300
|
+
// *sibling* `[Route("...")]` attribute on the same method --
|
|
301
|
+
// ASP.NET Core's own documented composition, confirmed on a real,
|
|
302
|
+
// unmodified repo (`dotnet-architecture/eShopOnWeb`'s
|
|
303
|
+
// `UserController.Logout()`: `[Route("Logout")] [HttpPost]`
|
|
304
|
+
// serves `POST /User/Logout`, not the bare `POST /User` this
|
|
305
|
+
// reader used to emit by only ever looking at the verb
|
|
306
|
+
// attribute's own argument). The verb attribute's own argument
|
|
307
|
+
// still wins when both are present -- that shape is unchanged.
|
|
308
|
+
const suffix = func.attributeArguments[attribute] ?? func.attributeArguments["Route"];
|
|
309
|
+
// A verb attribute with no argument and no sibling `[Route]` is
|
|
310
|
+
// legal and means "the class template". It is only refused when
|
|
311
|
+
// there was something to read and it could not be read as a
|
|
312
|
+
// literal.
|
|
313
|
+
const classPart = chain.route ?? "";
|
|
314
|
+
const expanded = expandTokens(join(classPart, suffix ?? ""), type.name, func.name);
|
|
315
|
+
if (expanded === undefined) {
|
|
316
|
+
tokensUnknown += 1;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (expanded.expanded.includes("[")) {
|
|
320
|
+
refusedNonLiteral += 1;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
tokensExpanded += expanded.tokens;
|
|
324
|
+
routes.push({
|
|
325
|
+
method: verb,
|
|
326
|
+
template: normaliseTemplate(expanded.expanded),
|
|
327
|
+
written: expanded.expanded,
|
|
328
|
+
line: func.startLine,
|
|
329
|
+
file: parsed.file,
|
|
330
|
+
handlerPath: [...type.path, func.name],
|
|
331
|
+
mechanism: "attribute",
|
|
332
|
+
framework: "aspnetcore",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// `MapGroup` prefixes, bound to the variable that holds the builder.
|
|
338
|
+
//
|
|
339
|
+
// **DEC-128 §1b named this and the first version of this file did not do
|
|
340
|
+
// it**, emitting `/items` where the served path is `/api/catalog/items` — a
|
|
341
|
+
// wrong template silently, which §1b explicitly forbids. It was found by
|
|
342
|
+
// adding `eShop`, the corpus the gap register said was missing, which is the
|
|
343
|
+
// whole argument for filling a gap rather than carrying it.
|
|
344
|
+
//
|
|
345
|
+
// `var api = vApi.MapGroup("api/catalog")` binds `api`. A group whose prefix
|
|
346
|
+
// is not a literal binds nothing, so a `Map*` on it is refused below rather
|
|
347
|
+
// than emitted bare.
|
|
348
|
+
const groups = new Map();
|
|
349
|
+
const groupDecl = /\b(?:var|[A-Za-z_][A-Za-z0-9_<>,. ]*?)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*[^;]*?\.\s*MapGroup\s*\(\s*"([^"]*)"/g;
|
|
350
|
+
for (let match = groupDecl.exec(source); match !== null; match = groupDecl.exec(source)) {
|
|
351
|
+
groups.set(match[1], match[2]);
|
|
352
|
+
mapGroupsSeen += 1;
|
|
353
|
+
}
|
|
354
|
+
/** Groups whose prefix could not be read. A `Map*` on one of these is refused. */
|
|
355
|
+
const opaqueGroups = new Set();
|
|
356
|
+
const opaqueDecl = /\b(?:var|[A-Za-z_][A-Za-z0-9_<>,. ]*?)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*[^;]*?\.\s*MapGroup\s*\(\s*(?!")/g;
|
|
357
|
+
for (let match = opaqueDecl.exec(source); match !== null; match = opaqueDecl.exec(source)) {
|
|
358
|
+
if (!groups.has(match[1]))
|
|
359
|
+
opaqueGroups.add(match[1]);
|
|
360
|
+
}
|
|
361
|
+
// Minimal APIs. `app.MapGet("/orders/{id}", handler)` — the verb is the
|
|
362
|
+
// method name and the path is the first argument, **only when literal**.
|
|
363
|
+
const minimal = /([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*(Map(?:Get|Post|Put|Patch|Delete))\s*\(\s*([^,)]*)/g;
|
|
364
|
+
for (let match = minimal.exec(source); match !== null; match = minimal.exec(source)) {
|
|
365
|
+
const receiver = match[1];
|
|
366
|
+
const verb = MINIMAL_VERBS.get(match[2]);
|
|
367
|
+
const literal = /^\s*"([^"]*)"/.exec(match[3] ?? "");
|
|
368
|
+
if (literal === null) {
|
|
369
|
+
// A path from a constant or a concatenation. DEC-128 refuses it: a path
|
|
370
|
+
// this cannot read is not one it may invent.
|
|
371
|
+
refusedNonLiteral += 1;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (opaqueGroups.has(receiver)) {
|
|
375
|
+
// The group exists and its prefix is unreadable. Emitting the suffix
|
|
376
|
+
// alone would publish a path no route serves.
|
|
377
|
+
minimalRefusedUnknownGroup += 1;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const written = join(groups.get(receiver) ?? "", literal[1]);
|
|
381
|
+
routes.push({
|
|
382
|
+
method: verb,
|
|
383
|
+
template: normaliseTemplate(written),
|
|
384
|
+
written,
|
|
385
|
+
line: source.slice(0, match.index).split("\n").length,
|
|
386
|
+
file: parsed.file,
|
|
387
|
+
handlerPath: [],
|
|
388
|
+
mechanism: "minimal",
|
|
389
|
+
framework: "aspnetcore",
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
routes,
|
|
395
|
+
admittedDirectly,
|
|
396
|
+
admittedViaBaseChain,
|
|
397
|
+
ambiguousBaseNames: [...ambiguousBaseNames].sort(),
|
|
398
|
+
baseChainsLeftAnalysedSet,
|
|
399
|
+
unresolvedBases: [...unresolvedBases]
|
|
400
|
+
.map(([name, count]) => ({ name, count }))
|
|
401
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
|
|
402
|
+
unresolvedBaseSites,
|
|
403
|
+
refusedNonLiteral,
|
|
404
|
+
tokensExpanded,
|
|
405
|
+
tokensUnknown,
|
|
406
|
+
mapGroupsSeen,
|
|
407
|
+
minimalRefusedUnknownGroup,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=aspnetcore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspnetcore.js","sourceRoot":"","sources":["../src/aspnetcore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D-FIX-3's client-base extractor — DEC-164's contract, and the first adapter
|
|
3
|
+
* in this project to build it. `client.ts` already reads a call's own literal
|
|
4
|
+
* path; this is the separate, larger row that row's own header named as
|
|
5
|
+
* unaddressed: what the receiver's *base* is, so a bare-relative call
|
|
6
|
+
* (`client.GetAsync("Users/Me")`, no leading slash) composes against
|
|
7
|
+
* something real instead of being refused for want of one.
|
|
8
|
+
*
|
|
9
|
+
* ## Two shapes built, and why not more
|
|
10
|
+
*
|
|
11
|
+
* §7's original audit named `services.AddHttpClient<T>(c => c.BaseAddress =
|
|
12
|
+
* new Uri(...))` as *the* pattern to build, on the strength of "jellyfin and
|
|
13
|
+
* eShopOnWeb both confirmed to use exactly this shape." Read against the real
|
|
14
|
+
* source, that premise does not hold for either named repository: jellyfin's
|
|
15
|
+
* own `AddHttpClient(NamedClient.Default, ...)` registrations
|
|
16
|
+
* (`Jellyfin.Server/Startup.cs`) never set `BaseAddress` at all, and
|
|
17
|
+
* eShopOnWeb has no `AddHttpClient<T>(...)` call anywhere.
|
|
18
|
+
*
|
|
19
|
+
* **What every one of both corpora's real `USES_API` edges (and several
|
|
20
|
+
* previously-silent call sites) actually goes through** is
|
|
21
|
+
* `WebApplicationFactory<TEntryPoint>.CreateClient(...)` — ASP.NET's own
|
|
22
|
+
* integration-test client, whose documented default `BaseAddress` is the
|
|
23
|
+
* application under test itself. `derivesFromWebApplicationFactory` (in
|
|
24
|
+
* `extract.ts`, where the type graph lives) is the whole mechanism: does the
|
|
25
|
+
* receiver's own class (directly, or through one in-repo indirection —
|
|
26
|
+
* `CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>`
|
|
27
|
+
* is `CleanArchitecture`'s own shape) carry `WebApplicationFactory<` in its
|
|
28
|
+
* written base list. Measured: **100% of jellyfin's and eShopOnWeb's existing
|
|
29
|
+
* 27 `USES_API` edges, plus every previously-silent or previously-refused
|
|
30
|
+
* bare-relative call this row newly reaches, go through exactly this
|
|
31
|
+
* receiver.**
|
|
32
|
+
*
|
|
33
|
+
* The named `AddHttpClient<T>(...)` shape is real, just not in the two
|
|
34
|
+
* repositories the audit cited — `dotnet/eShop` has it
|
|
35
|
+
* (`WebApp/Extensions.cs`, `WebhookClient/Extensions.cs`,
|
|
36
|
+
* `HybridApp/MauiProgram.cs`, 4 sites), and every one of those typed clients'
|
|
37
|
+
* own call sites builds its path by string interpolation
|
|
38
|
+
* (`$"{remoteServiceBaseUrl}items/{id}"`), already and separately refused by
|
|
39
|
+
* `client.ts`'s "not a bare string literal" rule — so resolving this base
|
|
40
|
+
* moves **zero** measured edges in any corpus this row has on disk.
|
|
41
|
+
* {@link typedClientBases} below reads it anyway, narrowly (a literal string
|
|
42
|
+
* argument to `new Uri(...)`/`new(...)` only — a field, a config read, or any
|
|
43
|
+
* other expression stays `unresolved`, never guessed): the mechanism is real
|
|
44
|
+
* corpus evidence, not invented for a fixture, and golden pattern 08a needs a
|
|
45
|
+
* *non-empty* composed path to prove `DEC-097`'s composition is real rather
|
|
46
|
+
* than coincidentally correct when the prefix is always empty. No other DI
|
|
47
|
+
* mechanism — `IHttpClientFactory.CreateClient(name)`'s named-client form
|
|
48
|
+
* included — is read here; every such call was invisible before this row and
|
|
49
|
+
* stays exactly as invisible after it, a disclosed, unaffected limit rather
|
|
50
|
+
* than a claim this file makes and cannot back.
|
|
51
|
+
*
|
|
52
|
+
* ## The state built but never produced by real corpus evidence
|
|
53
|
+
*
|
|
54
|
+
* DEC-164 names four states; three are real here. `unresolved` is reachable
|
|
55
|
+
* in code (a `AddHttpClient<T>` registration whose `BaseAddress` is a field
|
|
56
|
+
* or a config read rather than a literal) but has zero witnesses in any of
|
|
57
|
+
* the six corpora this row measured — kept rather than deleted, the same way
|
|
58
|
+
* `efcore.ts`'s rival-ORM exclusion rule stays written down at zero
|
|
59
|
+
* witnesses: a real rule with no corpus evidence yet, not a guess.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* DEC-164's four-state contract, minus the fourth — the field's own absence
|
|
63
|
+
* from `attrs`, which is a fact about the *adapter*, not a value this type
|
|
64
|
+
* can hold.
|
|
65
|
+
*/
|
|
66
|
+
export type ClientBaseResult = {
|
|
67
|
+
/**
|
|
68
|
+
* A real base was traced to its receiver's own construction. DEC-097:
|
|
69
|
+
* only the origin is stripped, so `path` is empty for every
|
|
70
|
+
* witness this row found — `WebApplicationFactory<T>.CreateClient()`'s
|
|
71
|
+
* default `BaseAddress` is `http://localhost/`, origin only, no path —
|
|
72
|
+
* but the field exists for the DI-registration case
|
|
73
|
+
* (`AddHttpClient<T>(o => o.BaseAddress = new("http://host/api/v1"))`)
|
|
74
|
+
* that would carry one, the day a corpus exercises it.
|
|
75
|
+
*/
|
|
76
|
+
readonly state: "resolved";
|
|
77
|
+
readonly path: string;
|
|
78
|
+
} | {
|
|
79
|
+
/**
|
|
80
|
+
* No base-setting mechanism this reader knows was found for this
|
|
81
|
+
* receiver. Not a claim that no base exists — a constructor parameter
|
|
82
|
+
* of type `HttpClient` with no traced origin is exactly this case, and
|
|
83
|
+
* a real base may be configured somewhere this reader does not reach.
|
|
84
|
+
* The call's own literal path is trusted as complete, unchanged from
|
|
85
|
+
* every C# `USES_API` edge minted before this row existed.
|
|
86
|
+
*/
|
|
87
|
+
readonly state: "none";
|
|
88
|
+
} | {
|
|
89
|
+
/** A base-setting mechanism was found; its value is not a literal this reader can evaluate. */
|
|
90
|
+
readonly state: "unresolved";
|
|
91
|
+
readonly why: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* `services.AddHttpClient<TClient>(o => o.BaseAddress = <expr>)` —
|
|
95
|
+
* `dotnet/eShop`'s own shape (`WebApp/Extensions.cs`,
|
|
96
|
+
* `WebhookClient/Extensions.cs`, `HybridApp/MauiProgram.cs`), read textually
|
|
97
|
+
* rather than through the AST (the same division of labour `aspnetcore.ts`
|
|
98
|
+
* and `efcore.ts` already keep for a fluent registration chain — see
|
|
99
|
+
* `extract.ts`'s own header for why). Keyed by `TClient`'s own written name
|
|
100
|
+
* (short, unqualified — matched the same way against the enclosing type's own
|
|
101
|
+
* simple name, a disclosed limit rather than a full type resolution this
|
|
102
|
+
* textual pass cannot do): DI hands the configured client straight to
|
|
103
|
+
* `TClient`'s own constructor, so a call inside `TClient` whose receiver
|
|
104
|
+
* resolves to a bare, otherwise-untraced `HttpClient` is exactly this base.
|
|
105
|
+
*
|
|
106
|
+
* The RHS is matched as `new(...)`/`new Uri(...)`'s own single argument —
|
|
107
|
+
* not "everything up to the next `;`", which real `eShop` source would get
|
|
108
|
+
* wrong: `AddHttpClient<CatalogService>(o => o.BaseAddress = new(...))` is
|
|
109
|
+
* followed by a fluent chain (`.AddApiVersion(2.0).AddAuthToken();`) whose
|
|
110
|
+
* terminating `;` is lines away and whose own parentheses would otherwise be
|
|
111
|
+
* swallowed into the "expression." Real corpus shapes have no nested parens
|
|
112
|
+
* inside the argument itself (a string literal, or a bare field reference),
|
|
113
|
+
* so matching up to the first `)` is exact for both, not an approximation.
|
|
114
|
+
* A bare string literal (`new Uri("https+http://catalog-api")`,
|
|
115
|
+
* `new("http://webhooks-api")`) resolves; anything else (`new(MobileBffHost)`
|
|
116
|
+
* — a static field, `eShop`'s own `HybridApp/MauiProgram.cs`) is `unresolved`
|
|
117
|
+
* with the argument's own text as `why`. Both are measured, not invented for
|
|
118
|
+
* this file.
|
|
119
|
+
*/
|
|
120
|
+
export declare function typedClientBases(files: readonly {
|
|
121
|
+
readonly source: string;
|
|
122
|
+
}[]): Map<string, ClientBaseResult>;
|
|
123
|
+
//# sourceMappingURL=client-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-base.d.ts","sourceRoot":"","sources":["../src/client-base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IACE;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACD;IACE;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GACD;IACE,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAeN;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAqB7G"}
|