@descryy/adapter-kotlin 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,344 @@
1
+ /**
2
+ * HTTP calls made *from* Kotlin — the caller half of the contract join.
3
+ *
4
+ * ```
5
+ * API_ROUTE ──SERVES_API──▶ API_ENDPOINT ◀──USES_API── caller
6
+ * ```
7
+ *
8
+ * `routes.ts` produces the provider half. This produces the consumer half, and
9
+ * until it existed `adapter-kotlin` declared no `USES_API` at all, so golden
10
+ * pattern 08 was skipped rather than passed — declared incapacity, per DEC-043.
11
+ *
12
+ * ## Provenance, never the name — and here that is not optional
13
+ *
14
+ * `client.get("/orders/1")` is the single most dangerous shape available to a
15
+ * reader of Kotlin. `adapter-python` measured the equivalent: matching on the
16
+ * verb name alone would have minted **2,280 edges** to endpoints nothing
17
+ * serves, because every Django and Flask test suite calls `client.get(...)` on
18
+ * a *test client* that makes no HTTP request. Ktor's own `testApplication { }`
19
+ * hands the test a `client` with exactly that shape.
20
+ *
21
+ * Two independent conditions, both required:
22
+ *
23
+ * | condition | evidence |
24
+ * | --- | --- |
25
+ * | the file imports the verb | `io.ktor.client.request.*` (wildcard) or `io.ktor.client.request.get` (explicit) |
26
+ * | the receiver is a real client | a local or property **constructed** `HttpClient(...)`, or a parameter/property whose **written type** is `HttpClient` |
27
+ *
28
+ * The wildcard case is not a convenience — it is the common case. Counted
29
+ * across the reference set: **385** files write `import io.ktor.client.request.*`
30
+ * against **3** that import a verb explicitly. A reader that required the
31
+ * explicit form would have been built for a shape the corpus does not contain,
32
+ * which is the failure `adapter-python`'s own header records paying for.
33
+ *
34
+ * ## Interpolation means the opposite of what it means on the route side
35
+ *
36
+ * `routes.ts` **refuses** an interpolated path: a route template is written
37
+ * literally (`"/orders/{id}"`), so interpolation there means the route is
38
+ * assembled elsewhere and half of it is an endpoint that does not exist.
39
+ *
40
+ * Here it is inverted. `client.get("/orders/$id")` is how a caller writes a
41
+ * path *parameter*, and the interpolation is precisely the part that must
42
+ * become `{param}` for the call to join the route that serves it. Refusing it
43
+ * would refuse the only shape that ever joins.
44
+ *
45
+ * Both of Kotlin's interpolation spellings are read — `$id`
46
+ * (`interpolated_identifier`) and `${id}` (`interpolated_expression`). Naming
47
+ * only one of them is the defect this adapter's route reader shipped with.
48
+ *
49
+ * ## What is refused
50
+ *
51
+ * A path that is not a string literal at all · a path that is not
52
+ * repository-relative, which is an outbound call to somebody else's service and
53
+ * has no route on this side of the join · an absolute URL, for the same reason
54
+ * plus [DEC-014](../../../descry-core/DECISIONS.md): an endpoint is host-free,
55
+ * so `/v1/charges` on Stripe and `/v1/charges` here would be one node ·
56
+ * a receiver whose name is bound to `HttpClient` in one place and to something
57
+ * else in another, which this reader cannot separate without a type checker.
58
+ */
59
+ /** The package whose verb extension functions are HTTP calls. */
60
+ const REQUEST_PACKAGE = "io.ktor.client.request";
61
+ /** The client type whose construction, or written type, is provenance. */
62
+ const CLIENT_TYPE = "HttpClient";
63
+ const CLIENT_PACKAGE = "io.ktor.client";
64
+ const VERBS = new Map([
65
+ ["get", "GET"],
66
+ ["post", "POST"],
67
+ ["put", "PUT"],
68
+ ["patch", "PATCH"],
69
+ ["delete", "DELETE"],
70
+ ["head", "HEAD"],
71
+ ["options", "OPTIONS"],
72
+ ]);
73
+ const CAPABILITY_GAP = { blockedBy: null, refusalClass: "capability-gap" };
74
+ const VARIES_PER_CALL = { blockedBy: null, refusalClass: "varies-per-call" };
75
+ const OUT_OF_SCOPE = { blockedBy: null, refusalClass: "out-of-scope" };
76
+ /** `System.getenv("X")`, `System.getenv()["X"]`, `System.getenv().get("X")`. */
77
+ const ENV_READ_PATTERNS = [
78
+ /^System\.getenv\(\s*"[^"]*"\s*\)$/,
79
+ /^System\.getenv\(\s*\)\[\s*"[^"]*"\s*\]$/,
80
+ /^System\.getenv\(\s*\)\.get\(\s*"[^"]*"\s*\)$/,
81
+ ];
82
+ /** Same heuristic as every other adapter's — see `adapter-rust/src/client.ts`'s doc. */
83
+ function argumentKindOf(text) {
84
+ if (text.endsWith(")") && text.includes("("))
85
+ return "call";
86
+ if (text.includes("+"))
87
+ return "concatenation";
88
+ if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(text))
89
+ return "field";
90
+ return "other";
91
+ }
92
+ /**
93
+ * Why an argument node could not be read as a literal path — a bare
94
+ * identifier that is the innermost enclosing function's own parameter, a
95
+ * known `System.getenv` read, or something this reader does not trace.
96
+ */
97
+ function diagnoseArgument(text, parameterNames) {
98
+ if (text === undefined)
99
+ return CAPABILITY_GAP;
100
+ const trimmed = text.trim();
101
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed) && parameterNames.has(trimmed))
102
+ return VARIES_PER_CALL;
103
+ if (ENV_READ_PATTERNS.some((pattern) => pattern.test(trimmed))) {
104
+ return { blockedBy: trimmed, refusalClass: "value-unknown" };
105
+ }
106
+ return { ...CAPABILITY_GAP, argumentKind: argumentKindOf(trimmed) };
107
+ }
108
+ const namedChildren = (node) => {
109
+ const out = [];
110
+ for (let i = 0; i < node.namedChildCount; i += 1) {
111
+ const child = node.namedChild(i);
112
+ if (child !== null)
113
+ out.push(child);
114
+ }
115
+ return out;
116
+ };
117
+ const firstOfType = (node, type) => namedChildren(node).find((child) => child.type === type);
118
+ /**
119
+ * A string literal as a path template: plain content kept, every interpolation
120
+ * — both spellings — collapsed to `{param}`. `undefined` when the argument is
121
+ * not a string literal at all.
122
+ */
123
+ function templateOf(argument) {
124
+ if (argument === undefined)
125
+ return undefined;
126
+ const literal = argument.type === "string_literal" ? argument : firstOfType(argument, "string_literal");
127
+ if (literal === undefined)
128
+ return undefined;
129
+ let out = "";
130
+ for (const child of namedChildren(literal)) {
131
+ switch (child.type) {
132
+ case "string_content":
133
+ out += child.text;
134
+ break;
135
+ case "interpolated_identifier":
136
+ case "interpolated_expression":
137
+ out += "{param}";
138
+ break;
139
+ default:
140
+ // An escape sequence or a node this reader does not model. A path it
141
+ // cannot render exactly is not a path it should guess at.
142
+ return undefined;
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+ /**
148
+ * Unwrap a call to `receiver.verb(args)`, including the trailing-lambda form.
149
+ *
150
+ * A call carrying both arguments and a trailing lambda parses as two nested
151
+ * `call_expression`s — the same grammar fact `routes.ts` unwraps. Reading only
152
+ * the outer one loses every `client.get("/x") { header(...) }` in the corpus.
153
+ */
154
+ function verbCallOf(node) {
155
+ if (node.type !== "call_expression")
156
+ return undefined;
157
+ const children = namedChildren(node);
158
+ const head = children[0];
159
+ if (head === undefined)
160
+ return undefined;
161
+ if (head.type === "call_expression")
162
+ return verbCallOf(head);
163
+ if (head.type !== "navigation_expression")
164
+ return undefined;
165
+ const receiver = head.namedChild(0);
166
+ const suffix = head.namedChild(1);
167
+ const verbNode = suffix === null ? null : firstOfType(suffix, "simple_identifier");
168
+ if (receiver === null || receiver.type !== "simple_identifier" || verbNode === undefined || verbNode === null) {
169
+ return undefined;
170
+ }
171
+ const callSuffix = children[1];
172
+ const args = callSuffix?.type === "call_suffix" ? firstOfType(callSuffix, "value_arguments") : undefined;
173
+ const firstArg = args === undefined ? undefined : firstOfType(args, "value_argument");
174
+ // The verb token's own line, never the call's: a receiver spanning lines
175
+ // would otherwise report every call at the chain's first line.
176
+ return { receiver: receiver.text, verb: verbNode.text, firstArg, line: verbNode.startPosition.row + 1 };
177
+ }
178
+ /** Does the file import the verb extension functions at all? */
179
+ function importsRequestPackage(imports) {
180
+ return imports.some((i) => i.isWildcard ? i.path === REQUEST_PACKAGE : i.path.startsWith(`${REQUEST_PACKAGE}.`));
181
+ }
182
+ /** Is `HttpClient` a name this file can legitimately be constructing? */
183
+ function importsClientType(imports) {
184
+ return imports.some((i) => i.isWildcard ? i.path === CLIENT_PACKAGE : i.path === `${CLIENT_PACKAGE}.${CLIENT_TYPE}`);
185
+ }
186
+ const typeNameOf = (node) => firstOfType(node, "user_type") === undefined
187
+ ? undefined
188
+ : firstOfType(firstOfType(node, "user_type"), "type_identifier")?.text;
189
+ /**
190
+ * The name being *constructed* by a call expression, unwrapping the nested form.
191
+ *
192
+ * `HttpClient()` is one `call_expression`; `HttpClient(MockEngine) { … }` — args
193
+ * plus a trailing lambda — is **two nested** ones, the same grammar fact
194
+ * `verbCallOf` unwraps. Reading only the outer node finds a `call_expression`
195
+ * where it wants an identifier and concludes nothing was constructed. That is
196
+ * not a small miss: `HttpClient(engine) { … }` is how essentially every real
197
+ * Ktor client is built, so the reader saw zero clients on a repository holding
198
+ * hundreds.
199
+ */
200
+ function constructedName(node) {
201
+ if (node.type !== "call_expression")
202
+ return undefined;
203
+ const head = node.namedChild(0);
204
+ if (head === null)
205
+ return undefined;
206
+ if (head.type === "call_expression")
207
+ return constructedName(head);
208
+ return head.type === "simple_identifier" ? head.text : undefined;
209
+ }
210
+ /**
211
+ * Names bound to an `HttpClient`, and names bound to anything else.
212
+ *
213
+ * Collected file-wide and then **intersected**: a name that is an `HttpClient`
214
+ * in one scope and something else in another cannot be separated at R2 without
215
+ * a type checker, so it is disclosed rather than admitted. Kotlin infers
216
+ * aggressively, which makes that collision likelier here than in a language
217
+ * that writes its types down.
218
+ */
219
+ function clientNames(root, imports) {
220
+ const verified = new Set();
221
+ const other = new Set();
222
+ const constructible = importsClientType(imports);
223
+ const bindingsOf = (node) => {
224
+ // `val client: HttpClient` / `client: HttpClient` — a written type, on a
225
+ // property, a constructor parameter or a function parameter alike.
226
+ if (node.type === "property_declaration" || node.type === "class_parameter" || node.type === "parameter") {
227
+ const name = node.type === "property_declaration"
228
+ ? firstOfType(node, "variable_declaration") === undefined
229
+ ? undefined
230
+ : firstOfType(firstOfType(node, "variable_declaration"), "simple_identifier")?.text
231
+ : firstOfType(node, "simple_identifier")?.text;
232
+ const written = typeNameOf(node);
233
+ const initialiser = firstOfType(node, "call_expression");
234
+ const constructed = constructible && initialiser !== undefined && constructedName(initialiser) === CLIENT_TYPE;
235
+ if (name !== undefined) {
236
+ if (written === CLIENT_TYPE || constructed)
237
+ verified.add(name);
238
+ else if (written !== undefined || initialiser !== undefined)
239
+ other.add(name);
240
+ }
241
+ }
242
+ for (const child of namedChildren(node))
243
+ bindingsOf(child);
244
+ };
245
+ bindingsOf(root);
246
+ const ambiguous = new Set([...verified].filter((name) => other.has(name)));
247
+ for (const name of ambiguous)
248
+ verified.delete(name);
249
+ return { verified, ambiguous };
250
+ }
251
+ /** The bindings a `function_declaration`'s own value-parameter list introduces. */
252
+ function parameterNamesOf(fn) {
253
+ const names = new Set();
254
+ const params = firstOfType(fn, "function_value_parameters");
255
+ if (params === undefined)
256
+ return names;
257
+ for (const parameter of namedChildren(params)) {
258
+ if (parameter.type !== "parameter")
259
+ continue;
260
+ const name = firstOfType(parameter, "simple_identifier")?.text;
261
+ if (name !== undefined)
262
+ names.add(name);
263
+ }
264
+ return names;
265
+ }
266
+ /** Every HTTP call written in one file, and every one refused. */
267
+ export function readKtorClientCalls(root, imports) {
268
+ if (!importsRequestPackage(imports))
269
+ return { calls: [], refusals: [] };
270
+ const calls = [];
271
+ const refusals = [];
272
+ const { verified, ambiguous } = clientNames(root, imports);
273
+ // Replaced, not merged, on entering a nested `function_declaration` — the
274
+ // innermost function's own parameters are what "varies-per-call" means for
275
+ // a call inside it, the same scoping every other language's diagnoseArgument
276
+ // uses. A name closed over from an outer function is an ordinary ambiguous
277
+ // capture here, not a parameter.
278
+ const visit = (node, parameterNames) => {
279
+ const scoped = node.type === "function_declaration" ? parameterNamesOf(node) : parameterNames;
280
+ const call = verbCallOf(node);
281
+ if (call !== undefined) {
282
+ const method = VERBS.get(call.verb);
283
+ if (method !== undefined) {
284
+ if (ambiguous.has(call.receiver)) {
285
+ refusals.push({
286
+ rawTarget: `${call.receiver}.${call.verb}`,
287
+ reason: "an HTTP call whose receiver name is bound to an HttpClient in one place and to something " +
288
+ "else in another. Refused rather than guessed: separating them needs a type checker, and " +
289
+ "attributing the call to the wrong one joins a caller to an endpoint it never reaches.",
290
+ line: call.line,
291
+ ...CAPABILITY_GAP,
292
+ });
293
+ }
294
+ else if (verified.has(call.receiver)) {
295
+ readVerified(call, method, scoped);
296
+ }
297
+ // A receiver that is neither verified nor ambiguous is not a client
298
+ // this reader can see. No ledger row: `x.get(...)` is overwhelmingly
299
+ // a map lookup or a test client, and filing every one would bury the
300
+ // real refusals under the 2,280-row population.
301
+ }
302
+ }
303
+ for (const child of namedChildren(node))
304
+ visit(child, scoped);
305
+ };
306
+ function readVerified(call, method, parameterNames) {
307
+ const raw = call.firstArg?.text ?? "";
308
+ const path = templateOf(call.firstArg);
309
+ if (path === undefined) {
310
+ refusals.push({
311
+ rawTarget: raw.slice(0, 80) || `${call.receiver}.${call.verb}`,
312
+ reason: "an HTTP call whose path is not a string literal. Refused rather than guessed: the path is " +
313
+ "assembled elsewhere, and half a path is an endpoint that does not exist.",
314
+ line: call.line,
315
+ ...diagnoseArgument(call.firstArg?.text, parameterNames),
316
+ });
317
+ return;
318
+ }
319
+ if (/^[a-zA-Z][\w+.-]*:\/\//.test(path)) {
320
+ refusals.push({
321
+ rawTarget: path.slice(0, 80),
322
+ reason: "an HTTP call to an absolute URL, which names a host this repository does not serve. Refused " +
323
+ "rather than joined: an endpoint is host-free, so claiming it would attach this caller to a " +
324
+ "local route it never reaches.",
325
+ line: call.line,
326
+ ...OUT_OF_SCOPE,
327
+ });
328
+ return;
329
+ }
330
+ if (!path.startsWith("/")) {
331
+ refusals.push({
332
+ rawTarget: path.slice(0, 80),
333
+ reason: "an HTTP call whose path is not repository-relative, so no route on this side of the join serves it.",
334
+ line: call.line,
335
+ ...CAPABILITY_GAP,
336
+ });
337
+ return;
338
+ }
339
+ calls.push({ method, path, rawPath: raw, client: `${call.receiver}.${call.verb}`, line: call.line });
340
+ }
341
+ visit(root, new Set());
342
+ return { calls, refusals };
343
+ }
344
+ //# 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,56 @@
1
+ /**
2
+ * Kotlin structures into IR nodes and edges.
3
+ *
4
+ * ## The identity rule, and why it refuses
5
+ *
6
+ * Kotlin shares `@descryy/adapter-jvm`'s anchor with Java: a symbol's identity is
7
+ * `module/sourceSet/package`, derived by subtracting the package path from the
8
+ * file's directory. Measured before this was written — okhttp 570 of 576 files
9
+ * derive, ktor 2,146 of 2,324.
10
+ *
11
+ * **When the subtraction fails, the node is refused.** Java falls back to the
12
+ * bare package name; Kotlin must not, because Kotlin Multiplatform declares one
13
+ * qualified name in several source sets **by design** — `expect`/`actual`. A
14
+ * bare-package fallback collapses those onto one id. Measured on ktor: 4 FQNs,
15
+ * `io.ktor.utils.io.charsets.Charsets` among them, declared across seven
16
+ * platform files.
17
+ *
18
+ * Disclosing the collapse on `attrs` instead was considered and rejected:
19
+ * nothing above the IR reads `attrs`, so scoring, governance and blast radius
20
+ * would act on a merged node while the note sat somewhere they cannot see.
21
+ * A refusal is visible to the layers that act on it; that is the whole
22
+ * difference (DEC-173).
23
+ *
24
+ * ## No cross-language edges, and that is a measurement
25
+ *
26
+ * The build plan has this adapter mint Java-shaped target ids for references
27
+ * into `.java` files. **It is not implemented, because the corpus chosen to
28
+ * measure it cannot witness it.** okhttp's 68 `.java` files are 56 samples with
29
+ * `kt/` twins, 11 `module-info.java` declaring no types, and 3 `*JavaTest.java`
30
+ * exercising the Kotlin API. The traffic runs Java→Kotlin. ktor is 2,324 `.kt`
31
+ * against 1 `.java`.
32
+ *
33
+ * A rule with zero witnesses is an untaken branch, not a weak rule — so it is
34
+ * declared absent rather than written blind.
35
+ */
36
+ import { type IRBatch, type ResolutionLevel } from "@descryy/ir";
37
+ import type { KotlinFile } from "./parse.ts";
38
+ export interface ExtractInput {
39
+ readonly files: readonly KotlinFile[];
40
+ readonly producedBy: string;
41
+ readonly scope: {
42
+ readonly repo: string;
43
+ readonly workspace?: string | undefined;
44
+ };
45
+ /**
46
+ * The resolution level `prepare()` actually reached, gating `push()` below.
47
+ * Without it every edge always carries its full resolution regardless of
48
+ * `maxResolution`, and a batch claiming R0 would still emit R1 edges — which
49
+ * the conformance harness's boundary check reports as
50
+ * `RESOLUTION_EXCEEDS_BATCH`: an edge whose own resolution exceeds what the
51
+ * batch that carries it claims.
52
+ */
53
+ readonly reached: ResolutionLevel;
54
+ }
55
+ export declare function extract(input: ExtractInput): Pick<IRBatch, "nodes" | "edges" | "unresolved">;
56
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAKL,KAAK,OAAO,EAGZ,KAAK,eAAe,EAErB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AAqB9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACnF;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AASD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC,CAqsB5F"}