@descryy/adapter-java 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/client.js ADDED
@@ -0,0 +1,471 @@
1
+ /**
2
+ * The caller's half of the HTTP boundary: `USES_API`, golden pattern 08.
3
+ *
4
+ * `SERVES_API` is minted in `extract.ts` from a route's annotations. This reads
5
+ * the other end — an outbound call with a statically knowable path — and mints
6
+ * the **same** `API_ENDPOINT` node from the same `endpointQsp`, so the two join
7
+ * without either side knowing the other ran. DEC-115's one hard constraint: two
8
+ * producers that mint different ids do not conflict, they silently fail to join.
9
+ *
10
+ * FUNCTION ──USES_API──▶ API_ENDPOINT ◀──SERVES_API── API_ROUTE
11
+ *
12
+ * ## What these edges actually mean, and why it must be said in the edge
13
+ *
14
+ * Measured before this was written (`bench/client-call-census.mjs`, DEC-189):
15
+ * across `shopizer`, `mall`, `morphia`, `okhttp` and `spring-petclinic` there
16
+ * are **16** client call sites carrying a repository-relative literal path.
17
+ * **Twelve are in test files, four are on a mock transport, and none are
18
+ * production code.**
19
+ *
20
+ * So in Java these edges are almost entirely `TEST_CASE`-shaped: an integration
21
+ * test driving `TestRestTemplate` at the application's own routes. That is real
22
+ * and it is useful — it answers *which routes have integration coverage*, which
23
+ * is the **Verification status** axis (AI layer §20, §42), a separate axis from
24
+ * the confidence label. It is **not** service-to-service coupling, and a
25
+ * consumer reading it as "this service calls that service" would be wrong.
26
+ *
27
+ * The distinction therefore travels **in the edge**, as `attrs.callerKind`, not
28
+ * in a comment here. A count in a decision record is invisible to a consumer;
29
+ * `attrs` is what the edge carries. (`attrs` is not read above the IR — which is
30
+ * exactly why the same fact is also stated in the disclosure below, where a
31
+ * reader will see it.)
32
+ *
33
+ * ## Why the receiver's type is required
34
+ *
35
+ * `execute`, `put`, `delete` and `uri` are ordinary method names. Admitting a
36
+ * call on its name alone would emit `USES_API` for `list.put("/tmp/x")`, and a
37
+ * wrong edge here joins a caller to a route it never calls — which root-cause
38
+ * traversal then follows. So a call is admitted only when its receiver's
39
+ * **declared type** is a known client. That is R2 evidence: a reference resolved
40
+ * to a definition through a written annotation, exactly as the rest of this
41
+ * adapter reads receivers.
42
+ *
43
+ * The cost is disclosed rather than hidden: a chained builder
44
+ * (`new Request.Builder().url("/")`) has no receiver with a written type and is
45
+ * refused. All four such sites in the corpora are `mockWebServer`, so nothing
46
+ * real is lost today — but the refusal is a rule, not a measurement, and it will
47
+ * cost real call sites in a codebase that chains.
48
+ *
49
+ * ## Ledger row D-FIX-3: a known client this reader does not extract
50
+ *
51
+ * Everything above requires the receiver's type to already be in
52
+ * {@link CLIENT_TYPES} before any refusal fires — so a call through a client
53
+ * this adapter has never heard of took neither branch. It matched no name in
54
+ * {@link URL_FIRST}'s companion set below, `typeOfReceiver` was never asked,
55
+ * and the reference fell out of the loop with `continue` before reaching a
56
+ * single `refuse(...)` call. **Zero edges and zero ledger rows is the same
57
+ * output a repository with no HTTP calls at all produces.** Sherpa's
58
+ * TypeScript diagnosis found the identical shape — 26 wrapped-`axios` call
59
+ * sites across 14 files, none reaching the refusal ledger — and it is the
60
+ * reason this row exists rather than being filed as ordinary recall.
61
+ *
62
+ * {@link BUILDER_CLIENT_TYPES} closes it for the two client families measured
63
+ * on the reference corpora (`bench/client-call-census.mjs`'s `CLIENTS.java`
64
+ * list, `okhttp3.OkHttpClient` and `java.net.http.HttpClient`) — **not by
65
+ * reading their calls**, but by recognising the receiver type and filing a
66
+ * refusal for every call through it. Both clients pass the outbound request as
67
+ * a **separately constructed object** (`Request`, `HttpRequest`) rather than a
68
+ * literal argument on the call itself, so building real extraction for them is
69
+ * a different, larger row — a second `firstStringArgument`-shaped read through
70
+ * a builder chain — and is out of scope here. What is in scope is closing the
71
+ * silence: **every** call through a receiver whose declared type names one of
72
+ * these clients is now accounted for, either as an edge (impossible for these
73
+ * two, by construction) or as a ledger row.
74
+ *
75
+ * ### Why the trigger is receiver-type resolution, not a file-level import scan
76
+ *
77
+ * The population D-FIX-3 was scoped against is "a file that imports a known
78
+ * HTTP client and makes calls the extractor did not claim." Read literally,
79
+ * that would fire on **every** method call in a file that merely imports
80
+ * `okhttp3.OkHttpClient` anywhere — including calls with no relation to it —
81
+ * which is exactly the "fires on everything" failure the row warns against.
82
+ * This adapter already has a narrower, previously-measured discriminator for
83
+ * the identical question: `typeOfReceiver`, R2 evidence, a written declaration
84
+ * this reader can point at. Reusing it means the new population is *provably*
85
+ * bounded to calls whose receiver is declared as one of these types — no
86
+ * broader — and it costs nothing new: `writtenReceiverType` in `extract.ts`
87
+ * already resolves an arbitrary type name syntactically, whether or not this
88
+ * adapter goes on to model it, so `OkHttpClient` and `HttpClient` resolve
89
+ * exactly as `RestTemplate` does today.
90
+ *
91
+ * ### The reconciliation this row makes an assertion rather than a discipline
92
+ *
93
+ * `candidateClientRefs` (below) recomputes, independently of the loop in
94
+ * {@link clientCalls}, which references in a method are candidates at all —
95
+ * same predicate, factored out so a future edit to the loop cannot silently
96
+ * narrow what counts as a candidate without the test that calls both noticing.
97
+ * `test/client.test.ts` asserts `calls.length + refusals.length ===
98
+ * candidateClientRefs(method).length` on every fixture, which is the shape
99
+ * DEC-098 found missing the hard way: a guard that dropped 113 of 140 call
100
+ * sites with no edge, no row, and no failing test.
101
+ */
102
+ import { endpointQsp, nodeId } from "@descryy/ir";
103
+ /**
104
+ * Client types whose methods name an outbound HTTP call.
105
+ *
106
+ * `TestRestTemplate` is here deliberately. It is Spring's own integration-test
107
+ * client and it drives **real HTTP at the application's real routes** — the
108
+ * transport is what decides whether a call site is a route reference, not the
109
+ * directory the file sits in. That distinction cost this project a gate: a
110
+ * census that split on the file path reported two `mockWebServer` calls as
111
+ * production code.
112
+ */
113
+ const CLIENT_TYPES = new Set([
114
+ "RestTemplate",
115
+ "TestRestTemplate",
116
+ "WebClient",
117
+ "RestClient",
118
+ "AsyncRestTemplate",
119
+ ]);
120
+ /**
121
+ * Methods that take a URL or path as their first argument.
122
+ *
123
+ * `exchange` and `execute` are `RestTemplate`'s general forms; `uri` is
124
+ * `WebClient`'s fluent builder step. Every one is admitted only in combination
125
+ * with a receiver of a {@link CLIENT_TYPES} type.
126
+ */
127
+ const URL_FIRST = new Set([
128
+ "getForObject",
129
+ "getForEntity",
130
+ "postForObject",
131
+ "postForEntity",
132
+ "postForLocation",
133
+ "patchForObject",
134
+ "put",
135
+ "delete",
136
+ "exchange",
137
+ "execute",
138
+ "uri",
139
+ ]);
140
+ /**
141
+ * Known HTTP clients this reader recognises by declared type but does not
142
+ * extract — see "Ledger row D-FIX-3" above. Keyed by type name, valued by the
143
+ * method names on that type which initiate a request; every such call is
144
+ * refused, unconditionally, because the URL and verb live inside a request
145
+ * object built in a separate expression rather than in this call's own
146
+ * arguments — there is nothing for `firstStringArgument` to read.
147
+ *
148
+ * `java.net.http.HttpClient`'s simple name is `HttpClient`, distinct from
149
+ * Spring's `WebClient` — no collision with {@link CLIENT_TYPES}.
150
+ */
151
+ const BUILDER_CLIENT_TYPES = {
152
+ OkHttpClient: new Set(["newCall"]),
153
+ HttpClient: new Set(["send", "sendAsync"]),
154
+ };
155
+ /**
156
+ * `WebClient`/`RestClient`'s own intermediate builder-step interfaces —
157
+ * `WebClient.RequestHeadersUriSpec`, `RestClient.RequestBodyUriSpec`, and
158
+ * their siblings, always referred to by simple name since nobody writes the
159
+ * outer-class-qualified form. Reached when the chain is split across a local
160
+ * variable (`var spec = client.get(); spec.uri("/x");`) rather than written
161
+ * inline — the other half of ledger row D-FIX-3, alongside the inline chain
162
+ * {@link BUILDER_VERB} closes. This adapter has no initializer-tracing (the
163
+ * verb-bearing `.get()`/`.post()` call is a separate statement, gone by the
164
+ * time `spec`'s declared type is the only thing left to read), so every call
165
+ * through one of these is refused rather than silently dropped — the same
166
+ * "account for it as an edge or a ledger row, never nothing" standard
167
+ * {@link BUILDER_CLIENT_TYPES} already holds OkHttp/`HttpClient` to.
168
+ */
169
+ const WEBCLIENT_SPLIT_BUILDER_TYPES = new Set([
170
+ "RequestHeadersUriSpec",
171
+ "RequestBodyUriSpec",
172
+ "RequestHeadersSpec",
173
+ "RequestBodySpec",
174
+ ]);
175
+ /**
176
+ * `WebClient`/`RestClient`'s fluent verb-starters — the call that opens the
177
+ * chain a real call site always uses (`client.get().uri("/x")...`), not the
178
+ * client's own declared type's method. This is why {@link URL_FIRST}'s `uri`
179
+ * entry alone was never enough: nothing in `VERB` maps `uri` to a verb,
180
+ * because `uri` never carries one — the verb sits one call earlier in the
181
+ * chain, on the call `uri`'s receiver expression *is*. See ledger row
182
+ * D-FIX-3 in the file header.
183
+ *
184
+ * `head`/`options` are real `WebClientRequestSpec`/`RestClient` entry points
185
+ * with no `VERB`-string precedent elsewhere in this file, so they are spelled
186
+ * out here rather than derived.
187
+ */
188
+ const BUILDER_VERB = {
189
+ get: "GET",
190
+ post: "POST",
191
+ put: "PUT",
192
+ patch: "PATCH",
193
+ delete: "DELETE",
194
+ head: "HEAD",
195
+ options: "OPTIONS",
196
+ };
197
+ /**
198
+ * Matches `parse.ts`'s `receiverName` chain encoding for a `method_invocation`
199
+ * object: `<base>.<name>()`. The literal `()` is a label, not a copy of the
200
+ * call's real arguments — see that function's doc.
201
+ */
202
+ const CHAIN_PATTERN = /^(.+)\.([A-Za-z_$][A-Za-z0-9_$]*)\(\)$/;
203
+ /** Every method name either extraction path or the builder-client path acts on. */
204
+ const TRACKED_METHODS = new Set([...URL_FIRST, ...Object.values(BUILDER_CLIENT_TYPES).flatMap((s) => [...s])]);
205
+ /**
206
+ * The HTTP verb a method name implies.
207
+ *
208
+ * `exchange`, `execute` and `uri` take the verb as a *separate argument* — an
209
+ * enum reference, not a literal — so they cannot be read here and are refused
210
+ * rather than defaulted to `GET`. A wrong verb produces an `endpointQsp` that
211
+ * joins nothing, which is a silent miss, or worse joins the wrong route.
212
+ */
213
+ const VERB = {
214
+ getForObject: "GET",
215
+ getForEntity: "GET",
216
+ postForObject: "POST",
217
+ postForEntity: "POST",
218
+ postForLocation: "POST",
219
+ patchForObject: "PATCH",
220
+ put: "PUT",
221
+ delete: "DELETE",
222
+ };
223
+ const CAPABILITY_GAP = { blockedBy: null, refusalClass: "capability-gap" };
224
+ const VARIES_PER_CALL = { blockedBy: null, refusalClass: "varies-per-call" };
225
+ /** `System.getenv("X")`, `System.getenv().get("X")`, and Spring's `env`/`environment`-named `Environment.getProperty("X")`. */
226
+ const ENV_READ_PATTERNS = [
227
+ /^System\.getenv\(\s*"[^"]*"\s*\)$/,
228
+ /^System\.getenv\(\s*\)\.get\(\s*"[^"]*"\s*\)$/,
229
+ /^\w*(?:[Ee]nv|[Ee]nvironment)\w*\.getProperty\(\s*"[^"]*"\s*\)$/,
230
+ ];
231
+ /** Same heuristic as every other adapter's — see `adapter-rust/src/client.ts`'s doc. */
232
+ function argumentKindOf(text) {
233
+ if (text.endsWith(")") && text.includes("("))
234
+ return "call";
235
+ if (text.includes("+"))
236
+ return "concatenation";
237
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(text))
238
+ return "field";
239
+ return "other";
240
+ }
241
+ /**
242
+ * Why the first argument of a client call could not be read as a literal
243
+ * path — mirrors `firstArgumentTextOf`'s own reach: a bare identifier, a
244
+ * known environment-read call, or something this reader does not trace.
245
+ */
246
+ function diagnoseArgument(text, parameterNames) {
247
+ if (text === undefined)
248
+ return CAPABILITY_GAP;
249
+ const trimmed = text.trim();
250
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed) && parameterNames.has(trimmed))
251
+ return VARIES_PER_CALL;
252
+ if (ENV_READ_PATTERNS.some((pattern) => pattern.test(trimmed))) {
253
+ return { blockedBy: trimmed, refusalClass: "value-unknown" };
254
+ }
255
+ return { ...CAPABILITY_GAP, argumentKind: argumentKindOf(trimmed) };
256
+ }
257
+ /** A path this reader will claim: repository-relative, no scheme, no assembly. */
258
+ function relativePath(value) {
259
+ if (!value.startsWith("/"))
260
+ return undefined;
261
+ // `//host/path` is protocol-relative and names another origin.
262
+ if (value.startsWith("//"))
263
+ return undefined;
264
+ // A query string is not part of the route template. `?` and everything after
265
+ // it is dropped, because `SERVES_API` never mints one and keeping it would
266
+ // guarantee the join fails.
267
+ const cut = value.indexOf("?");
268
+ return cut === -1 ? value : value.slice(0, cut);
269
+ }
270
+ /**
271
+ * A literal this reader can say, with no ambiguity, names a *different*
272
+ * origin — an explicit scheme (`http://...`) or a protocol-relative URL
273
+ * (`//host/...`). Anything else that fails {@link relativePath} (a bare
274
+ * relative path with no leading slash, e.g. `"orders/5"`) is NOT covered by
275
+ * this check and must not be reported through it.
276
+ *
277
+ * Split out after DEC-260's C# origin-check row found the shared bug one
278
+ * language over: `adapter-csharp` originally treated "does not start with
279
+ * `/`" as sufficient evidence of another origin, and that misfired on real
280
+ * `jellyfin`/`eShopOnWeb` call sites — bare relative paths resolved against
281
+ * `HttpClient.BaseAddress` at runtime, same-repository calls incorrectly
282
+ * filed as a scope boundary. Spring's `RestTemplate`/`WebClient` support the
283
+ * identical pattern (`RootUriTemplateHandler`, `WebClient.baseUrl(...)`), so
284
+ * the same rule, applied here unchanged since before that row, carries the
285
+ * identical latent defect — confirmed synthetically
286
+ * (`restTemplate.getForObject("orders/5", String.class)` was filed as
287
+ * "another origin" before this fix). **Not observed in either admitted
288
+ * corpus** (`spring-petclinic`, `shopizer` — all 3 real `USES_API` refusals
289
+ * naming another origin are unambiguous `http://` URLs, unaffected by this
290
+ * change), so this closes a rule that was correct only because nothing had
291
+ * tested it yet, not a live regression.
292
+ */
293
+ function namesAnotherOrigin(value) {
294
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value) || value.startsWith("//");
295
+ }
296
+ /**
297
+ * Every reference in a method that {@link clientCalls} will act on — an edge,
298
+ * a refusal, or (only past this point, where the receiver's type is unknown)
299
+ * a true silence. Factored out of the loop below so the reconciliation
300
+ * assertion in `test/client.test.ts` recomputes candidacy independently of
301
+ * whatever the loop does with each candidate: a future edit that narrows the
302
+ * loop's own filter without touching this function fails the test instead of
303
+ * silently dropping call sites the way DEC-098's `containerOf` guard did.
304
+ *
305
+ * Receiver-type resolution is deliberately **not** done here — it needs
306
+ * `extract.ts`'s scope tables, which this module does not have — so a
307
+ * candidate here may still turn out to be a true silence in
308
+ * {@link clientCalls} if `typeOfReceiver` returns `undefined`. That is the
309
+ * one gap this reconciliation cannot close, and it is the same gap every
310
+ * other refusal ledger in this project discloses rather than guesses past:
311
+ * an unresolved receiver is not evidence the call is or is not a client.
312
+ */
313
+ export function candidateClientRefs(method) {
314
+ return method.refs.filter((ref) => ref.kind === "call" &&
315
+ TRACKED_METHODS.has(ref.name) &&
316
+ ref.receiver !== undefined &&
317
+ ref.receiver !== "this");
318
+ }
319
+ /**
320
+ * Read one method's calls for outbound HTTP.
321
+ *
322
+ * `typeOfReceiver` resolves a receiver name to its declared type's simple name
323
+ * and is supplied by the caller, because the scope rules for that live in
324
+ * `extract.ts` and a second copy of them would drift — the failure that
325
+ * `scopesFor`'s own header records.
326
+ */
327
+ export function clientCalls(method, fromId, typeOfReceiver, inTestSourceSet) {
328
+ const calls = [];
329
+ const refusals = [];
330
+ /**
331
+ * Read `ref`'s first argument as a path and either push a `ClientCall` or a
332
+ * refusal — the shared tail every admitted verb reaches, whether the verb
333
+ * came from `VERB[ref.name]` directly or from a builder-chain's opening
334
+ * call via {@link BUILDER_VERB}.
335
+ */
336
+ const readPathAndEmit = (receiverType, ref, verb, refuse) => {
337
+ if (ref.firstStringArgument === undefined) {
338
+ refuse(`an HTTP call through ${receiverType}, but its path is not a bare string literal — a ` +
339
+ `variable, a concatenation, or a builder. A path assembled at run time is not one ` +
340
+ `this adapter can read, and an endpoint template invented here would join this ` +
341
+ `caller to a route it never calls.`, diagnoseArgument(ref.firstArgumentText, method.parameterNames));
342
+ return;
343
+ }
344
+ const template = relativePath(ref.firstStringArgument);
345
+ if (template === undefined) {
346
+ if (namesAnotherOrigin(ref.firstStringArgument)) {
347
+ refuse(`an HTTP call through ${receiverType} to \`${ref.firstStringArgument}\`, which names ` +
348
+ `another origin rather than a path in this repository. A scope boundary, not a gap.`, { blockedBy: null, refusalClass: "out-of-scope" });
349
+ return;
350
+ }
351
+ // A bare relative path with no leading slash and no scheme —
352
+ // structurally identical to C#'s `HttpClient.BaseAddress` case. It may
353
+ // resolve inside this repository, against a configured root URI
354
+ // (`RootUriTemplateHandler`, `WebClient.baseUrl(...)`), or it may not;
355
+ // this reader does not see that configuration, so neither an edge nor
356
+ // an out-of-scope claim is honest here — rule 2, applied to a
357
+ // classification rather than an edge. Not a per-call value and not a
358
+ // named blocking expression either, so `capability-gap` rather than
359
+ // `varies-per-call`: nothing here is caller-supplied, this reader just
360
+ // cannot see the base configuration.
361
+ refuse(`an HTTP call through ${receiverType} to \`${ref.firstStringArgument}\`, a relative path ` +
362
+ `with no leading slash. This reader cannot tell whether it resolves inside this repository ` +
363
+ `— against a configured base URI this call site does not state — or elsewhere, so neither ` +
364
+ `an endpoint nor a scope boundary is claimed.`, CAPABILITY_GAP);
365
+ return;
366
+ }
367
+ calls.push({
368
+ fromId,
369
+ method: ref.name,
370
+ template,
371
+ verb,
372
+ line: ref.line,
373
+ // **The source set, not just the annotation.** `method.isTest` reads
374
+ // `@Test`, and shopizer's `ServicesTestSupport.getHeader` drives
375
+ // `TestRestTemplate` from an unannotated helper — so annotation alone
376
+ // labelled two of nine edges `production` when both sit in
377
+ // `src/test/java`. The build system states which source set is test code;
378
+ // inferring it from the annotation asks a narrower question than the one
379
+ // being answered. If the framework will tell you, never infer it.
380
+ callerKind: method.isTest || inTestSourceSet ? "test" : "production",
381
+ });
382
+ };
383
+ for (const ref of candidateClientRefs(method)) {
384
+ // WebClient/RestClient's fluent chain: `client.get().uri("/x")`. The
385
+ // receiver `parse.ts` hands back for the `uri` call is the *opening* call
386
+ // encoded as text (`client.get()`), not a name `typeOfReceiver` can look
387
+ // up directly — so the base is resolved instead, and the verb comes from
388
+ // the chain, not from `uri` itself. See ledger row D-FIX-3 and
389
+ // {@link BUILDER_VERB}'s doc.
390
+ const chain = ref.name === "uri" ? CHAIN_PATTERN.exec(ref.receiver) : null;
391
+ if (chain !== null) {
392
+ // Both groups are guaranteed by `CHAIN_PATTERN`'s two capturing groups
393
+ // once `exec` succeeds — `noUncheckedIndexedAccess` cannot see that from
394
+ // the pattern alone.
395
+ const base = chain[1];
396
+ const opener = chain[2];
397
+ const receiverType = typeOfReceiver(base);
398
+ if (receiverType === undefined || !CLIENT_TYPES.has(receiverType))
399
+ continue;
400
+ const refuse = (reason, diagnosis) => {
401
+ refusals.push({ fromId, raw: ref.raw, line: ref.line, reason, ...diagnosis });
402
+ };
403
+ const verb = BUILDER_VERB[opener];
404
+ if (verb === undefined) {
405
+ refuse(`an HTTP call through ${receiverType}, but \`${opener}\` takes its method as a ` +
406
+ `separate argument — an enum reference this adapter does not read. Refused rather ` +
407
+ `than defaulted to GET: a wrong verb mints an endpoint that joins the wrong route, ` +
408
+ `or none, and root-cause traversal would follow it.`, CAPABILITY_GAP);
409
+ continue;
410
+ }
411
+ readPathAndEmit(receiverType, ref, verb, refuse);
412
+ continue;
413
+ }
414
+ const receiverType = typeOfReceiver(ref.receiver);
415
+ if (receiverType === undefined)
416
+ continue;
417
+ const refuse = (reason, diagnosis) => {
418
+ refusals.push({ fromId, raw: ref.raw, line: ref.line, reason, ...diagnosis });
419
+ };
420
+ const builderMethods = BUILDER_CLIENT_TYPES[receiverType];
421
+ if (builderMethods?.has(ref.name) === true) {
422
+ refuse(`an HTTP call through ${receiverType}, via a request object built in a separate expression ` +
423
+ `— this adapter reads only a path passed directly as this call's own argument, not one ` +
424
+ `assembled through a separate constructor or builder chain and passed in.`, CAPABILITY_GAP);
425
+ continue;
426
+ }
427
+ if (WEBCLIENT_SPLIT_BUILDER_TYPES.has(receiverType) && ref.name === "uri") {
428
+ refuse(`a WebClient/RestClient \`.uri(...)\` call through a ${receiverType} held in a variable — ` +
429
+ `the opening \`.get()\`/\`.post()\`/... call that carries the verb is a separate statement ` +
430
+ `this adapter does not trace back to, so the verb is unrecoverable here even though the ` +
431
+ `path is a literal. See ledger row D-FIX-3's split-variable case.`, CAPABILITY_GAP);
432
+ continue;
433
+ }
434
+ if (!CLIENT_TYPES.has(receiverType))
435
+ continue;
436
+ if (!URL_FIRST.has(ref.name))
437
+ continue;
438
+ const verb = VERB[ref.name];
439
+ if (verb === undefined) {
440
+ refuse(`an HTTP call through ${receiverType}, but \`${ref.name}\` takes its method as a ` +
441
+ `separate argument — an enum reference this adapter does not read. Refused rather ` +
442
+ `than defaulted to GET: a wrong verb mints an endpoint that joins the wrong route, ` +
443
+ `or none, and root-cause traversal would follow it.`, CAPABILITY_GAP);
444
+ continue;
445
+ }
446
+ readPathAndEmit(receiverType, ref, verb, refuse);
447
+ }
448
+ return { calls, refusals };
449
+ }
450
+ /**
451
+ * The `API_ENDPOINT` a call joins to — minted identically to the route side.
452
+ *
453
+ * Shares `endpointQsp` with `extract.ts`'s `SERVES_API` block rather than
454
+ * recomputing the id, because the two must agree exactly and the failure when
455
+ * they do not is silent.
456
+ */
457
+ export function endpointFor(scope, call, producedBy, normalise) {
458
+ const template = normalise(call.template);
459
+ return {
460
+ id: nodeId(scope, "API_ENDPOINT", endpointQsp(call.verb, call.template), null),
461
+ type: "API_ENDPOINT",
462
+ name: `${call.verb} ${template}`,
463
+ file: null,
464
+ range: null,
465
+ language: null,
466
+ producedBy,
467
+ resolution: 2,
468
+ attrs: { method: call.verb, pathTemplate: template },
469
+ };
470
+ }
471
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Java units to Canonical IR.
3
+ *
4
+ * ## Why a breadth adapter reaches further in Java than in most languages
5
+ *
6
+ * Every import is explicit and fully qualified, every field and parameter
7
+ * carries a written type, and a public type's simple name is its file's name.
8
+ * None of that needs a type checker to read, so `repo.findById(id)` resolves
9
+ * from the declaration `private final OwnerRepository repo` alone — a call
10
+ * through a receiver, which in TypeScript needed R3.
11
+ *
12
+ * That is read as **R2 evidence, and never R3**. R2 is a reference resolved to a
13
+ * specific definition, which is exactly what happens here — the level names the
14
+ * evidence, not the machinery, and Java hands over the same evidence an LSP
15
+ * would without one being run. R3 is a *checked shape*, and a written annotation
16
+ * is not one: it can be shadowed, it can be generic, and nothing here verifies
17
+ * which overload the compiler would select. Claiming R3 for a name-level fact is
18
+ * the confusion DEC-058 was written about, where 83% of an adapter's edges
19
+ * claimed a level they had not earned.
20
+ *
21
+ * `IMPORTS` stays at R1. An import is module resolution and nothing more.
22
+ *
23
+ * ## What is deliberately not resolved
24
+ *
25
+ * `var` declarations, chained calls whose receiver is another call, generic type
26
+ * variables, and any name reachable only through a wildcard import that matches
27
+ * more than one analysed type. Each goes to the ledger with its reason. A
28
+ * missing edge is a disclosed gap; a guessed one corrupts every layer above.
29
+ */
30
+ import { type IdentityScope, type IREdge, type IRNode, type ResolutionLevel, type UnresolvedRef } from "@descryy/ir";
31
+ import type { JavaUnit } from "./parse.ts";
32
+ export declare const LANGUAGE = "java";
33
+ /** A compilation unit with no `package` declaration. Not a legal package name,
34
+ * so it cannot collide with one. */
35
+ export declare const DEFAULT_PACKAGE = "(default)";
36
+ export interface ExtractInput {
37
+ readonly repo: string;
38
+ readonly scope: IdentityScope;
39
+ readonly producedBy: string;
40
+ readonly reached: ResolutionLevel;
41
+ readonly units: readonly JavaUnit[];
42
+ }
43
+ export interface Extraction {
44
+ readonly nodes: readonly IRNode[];
45
+ readonly edges: readonly IREdge[];
46
+ readonly unresolved: readonly UnresolvedRef[];
47
+ }
48
+ export declare function extract(input: ExtractInput): Extraction;
49
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAiC,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK1E,eAAO,MAAM,QAAQ,SAAS,CAAC;AAK/B;qCACqC;AACrC,eAAO,MAAM,eAAe,cAAc,CAAC;AAwC3C,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,QAAQ,EAAE,CAAC;CACrC;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;AAwJD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,CA+vBvD"}