@descryy/adapter-kotlin 0.2.0 → 0.3.1
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/LICENSE +8 -0
- package/dist/adapter.d.ts +4 -8
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +56 -58
- package/dist/adapter.js.map +1 -1
- package/dist/client.d.ts +23 -41
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +42 -67
- package/dist/client.js.map +1 -1
- package/dist/extract.d.ts +18 -32
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +107 -173
- package/dist/extract.js.map +1 -1
- package/dist/parse.d.ts +20 -37
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +70 -118
- package/dist/parse.js.map +1 -1
- package/dist/retrofit.d.ts +23 -49
- package/dist/retrofit.d.ts.map +1 -1
- package/dist/retrofit.js +20 -46
- package/dist/retrofit.js.map +1 -1
- package/dist/routes.d.ts +13 -20
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +24 -45
- package/dist/routes.js.map +1 -1
- package/dist/spring.d.ts +16 -24
- package/dist/spring.d.ts.map +1 -1
- package/dist/spring.js +21 -33
- package/dist/spring.js.map +1 -1
- package/package.json +16 -7
package/dist/extract.d.ts
CHANGED
|
@@ -3,35 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## The identity rule, and why it refuses
|
|
5
5
|
*
|
|
6
|
-
* Kotlin shares `@descryy/adapter-jvm`'s anchor with Java:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* derive, ktor 2,146 of 2,324.
|
|
6
|
+
* Kotlin shares `@descryy/adapter-jvm`'s anchor with Java: identity is `module/sourceSet/package`,
|
|
7
|
+
* derived by subtracting the package path from the file's directory (measured: okhttp 570/576
|
|
8
|
+
* files derive, ktor 2,146/2,324).
|
|
10
9
|
*
|
|
11
|
-
* **When the subtraction fails, the node is refused
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* **When the subtraction fails, the node is refused** rather than falling back to the bare
|
|
11
|
+
* package name as Java does — Kotlin Multiplatform declares one qualified name across several
|
|
12
|
+
* source sets *by design* (`expect`/`actual`), and a bare-package fallback would collapse those
|
|
13
|
+
* onto one id (measured on ktor: 4 FQNs, including `io.ktor.utils.io.charsets.Charsets`, across
|
|
14
|
+
* seven platform files). `attrs` was rejected as the disclosure channel instead: nothing above
|
|
15
|
+
* the IR reads it, so a refusal beats a merged node with an unseen note (DEC-173).
|
|
17
16
|
*
|
|
18
|
-
*
|
|
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).
|
|
17
|
+
* ## No cross-language edges — a measurement, not an omission
|
|
23
18
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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.
|
|
19
|
+
* The build plan calls for Java-shaped target ids for references into `.java` files. Not
|
|
20
|
+
* implemented: the corpus can't witness it. okhttp's 68 `.java` files are 56 samples with `kt/`
|
|
21
|
+
* twins, 11 `module-info.java` (no types), 3 `*JavaTest.java` exercising the Kotlin API — traffic
|
|
22
|
+
* runs Java→Kotlin; ktor is 2,324 `.kt` against 1 `.java`. A rule with zero witnesses is declared
|
|
23
|
+
* absent rather than written blind.
|
|
35
24
|
*/
|
|
36
25
|
import { type IRBatch, type ResolutionLevel } from "@descryy/ir";
|
|
37
26
|
import type { KotlinFile } from "./parse.ts";
|
|
@@ -43,12 +32,9 @@ export interface ExtractInput {
|
|
|
43
32
|
readonly workspace?: string | undefined;
|
|
44
33
|
};
|
|
45
34
|
/**
|
|
46
|
-
* The resolution level `prepare()` actually reached, gating `push()` below
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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.
|
|
35
|
+
* The resolution level `prepare()` actually reached, gating `push()` below —
|
|
36
|
+
* without it a batch claiming R0 could still emit R1 edges, which the
|
|
37
|
+
* conformance harness flags as `RESOLUTION_EXCEEDS_BATCH`.
|
|
52
38
|
*/
|
|
53
39
|
readonly reached: ResolutionLevel;
|
|
54
40
|
}
|
package/dist/extract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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;;;;OAIG;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,CAipB5F"}
|
package/dist/extract.js
CHANGED
|
@@ -3,35 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## The identity rule, and why it refuses
|
|
5
5
|
*
|
|
6
|
-
* Kotlin shares `@descryy/adapter-jvm`'s anchor with Java:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* derive, ktor 2,146 of 2,324.
|
|
6
|
+
* Kotlin shares `@descryy/adapter-jvm`'s anchor with Java: identity is `module/sourceSet/package`,
|
|
7
|
+
* derived by subtracting the package path from the file's directory (measured: okhttp 570/576
|
|
8
|
+
* files derive, ktor 2,146/2,324).
|
|
10
9
|
*
|
|
11
|
-
* **When the subtraction fails, the node is refused
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* **When the subtraction fails, the node is refused** rather than falling back to the bare
|
|
11
|
+
* package name as Java does — Kotlin Multiplatform declares one qualified name across several
|
|
12
|
+
* source sets *by design* (`expect`/`actual`), and a bare-package fallback would collapse those
|
|
13
|
+
* onto one id (measured on ktor: 4 FQNs, including `io.ktor.utils.io.charsets.Charsets`, across
|
|
14
|
+
* seven platform files). `attrs` was rejected as the disclosure channel instead: nothing above
|
|
15
|
+
* the IR reads it, so a refusal beats a merged node with an unseen note (DEC-173).
|
|
17
16
|
*
|
|
18
|
-
*
|
|
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).
|
|
17
|
+
* ## No cross-language edges — a measurement, not an omission
|
|
23
18
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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.
|
|
19
|
+
* The build plan calls for Java-shaped target ids for references into `.java` files. Not
|
|
20
|
+
* implemented: the corpus can't witness it. okhttp's 68 `.java` files are 56 samples with `kt/`
|
|
21
|
+
* twins, 11 `module-info.java` (no types), 3 `*JavaTest.java` exercising the Kotlin API — traffic
|
|
22
|
+
* runs Java→Kotlin; ktor is 2,324 `.kt` against 1 `.java`. A rule with zero witnesses is declared
|
|
23
|
+
* absent rather than written blind.
|
|
35
24
|
*/
|
|
36
25
|
import { endpointQsp, nodeId, normaliseEndpointPath, symbolQsp, } from "@descryy/ir";
|
|
37
26
|
const LANGUAGE = "kotlin";
|
|
@@ -48,9 +37,8 @@ export function extract(input) {
|
|
|
48
37
|
const funcsByKey = new Map();
|
|
49
38
|
const moduleIdOf = new Map();
|
|
50
39
|
const seenEdges = new Set();
|
|
51
|
-
// `IREdge` carries no `language
|
|
52
|
-
//
|
|
53
|
-
// be wrong.
|
|
40
|
+
// `IREdge` carries no `language`: an edge's language is its nodes' language,
|
|
41
|
+
// and duplicating it here is a second place for it to be wrong.
|
|
54
42
|
const push = (edge, confidence, resolution) => {
|
|
55
43
|
if (resolution > input.reached)
|
|
56
44
|
return false;
|
|
@@ -73,27 +61,15 @@ export function extract(input) {
|
|
|
73
61
|
...(attrs === undefined ? {} : { attrs }),
|
|
74
62
|
});
|
|
75
63
|
};
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// - Overloads: two methods on the same class sharing a name over different
|
|
86
|
-
// parameters, which this adapter cannot distinguish without a type
|
|
87
|
-
// checker. Measured on ktor: `AuthenticationConfig.provider(name)` and
|
|
88
|
-
// `.provider(name, configure, body)` collide; the second's own body is
|
|
89
|
-
// the one that calls `requireProviderNotRegistered`, and it was silently
|
|
90
|
-
// unreachable through the merged node.
|
|
91
|
-
//
|
|
92
|
-
// Pass 1 used to keep the first declaration under a key and silently drop
|
|
93
|
-
// the rest with no disclosure — a rule-7 violation — which cross-attributes
|
|
94
|
-
// every edge that belonged to a dropped declaration onto whichever one
|
|
95
|
-
// happened to parse first, rather than losing them cleanly.
|
|
96
|
-
// -------------------------------------------------------------------------
|
|
64
|
+
// Pass 0 — count declarations sharing an identity key (same identityPackage, owner chain, name):
|
|
65
|
+
// this adapter's identity has no parameter-type or file component to break a tie, by design
|
|
66
|
+
// (DEC-004 — a file component churns on rename). Two shapes collide: (1) two files in one package
|
|
67
|
+
// each declaring top-level `fun main()` — the JVM synthesises a distinct `FileNameKt` class per
|
|
68
|
+
// file, nothing here does (okhttp's `samples/` has 20+); (2) overloads, indistinguishable without
|
|
69
|
+
// a type checker (ktor's `AuthenticationConfig.provider(name)` vs `.provider(name, configure,
|
|
70
|
+
// body)` — the second's body calls `requireProviderNotRegistered`, silently unreachable when
|
|
71
|
+
// merged). Previously the first declaration under a key won silently, cross-attributing dropped
|
|
72
|
+
// declarations' edges onto it (rule-7 violation) — this pass refuses the collision instead.
|
|
97
73
|
const declNameCount = new Map();
|
|
98
74
|
for (const unit of input.files) {
|
|
99
75
|
if (unit.identityPackage === undefined)
|
|
@@ -107,10 +83,8 @@ export function extract(input) {
|
|
|
107
83
|
declNameCount.set(k, (declNameCount.get(k) ?? 0) + 1);
|
|
108
84
|
}
|
|
109
85
|
}
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// nothing at all, and says so once for the file rather than once per symbol.
|
|
113
|
-
// -------------------------------------------------------------------------
|
|
86
|
+
// Pass 1 — declarations. A file with no derivable identity mints nothing,
|
|
87
|
+
// disclosed once per file rather than once per symbol.
|
|
114
88
|
for (const unit of input.files) {
|
|
115
89
|
if (unit.identityPackage === undefined) {
|
|
116
90
|
refuse("", "IMPORTS", unit.file, unit.file, 1, "This file does not sit where its package declaration says it should, so its module and " +
|
|
@@ -136,9 +110,8 @@ export function extract(input) {
|
|
|
136
110
|
...(unit.hasError ? { parsedWithErrors: true } : {}),
|
|
137
111
|
},
|
|
138
112
|
});
|
|
139
|
-
// Counted in parse.ts, disclosed here
|
|
140
|
-
//
|
|
141
|
-
// exactly the thing a reader would otherwise assume absent from the source.
|
|
113
|
+
// Counted in parse.ts, disclosed here — a declined declaration is exactly
|
|
114
|
+
// what a reader would otherwise assume simply absent (rule 7).
|
|
142
115
|
if (unit.unparseableDeclarations > 0) {
|
|
143
116
|
refuse(moduleId, "IMPORTS", fileStem(unit.file), unit.file, 1, `${unit.unparseableDeclarations} declaration(s) in this file could not be placed. The pinned ` +
|
|
144
117
|
"grammar does not parse Kotlin's `fun interface`, so the body arrives as an orphaned lambda " +
|
|
@@ -231,34 +204,31 @@ export function extract(input) {
|
|
|
231
204
|
...node,
|
|
232
205
|
attrs: {
|
|
233
206
|
...node.attrs,
|
|
234
|
-
// `type` is the element type
|
|
235
|
-
//
|
|
236
|
-
//
|
|
207
|
+
// `type` is the element type, `list` a boolean — the contract Lane E
|
|
208
|
+
// stated and every adapter follows. Kotlin infers, so an absent
|
|
209
|
+
// type means "not written," never "no type."
|
|
237
210
|
fields: fields.map((f) => ({ name: f.name, type: f.type ?? null, list: false })),
|
|
238
211
|
},
|
|
239
212
|
};
|
|
240
213
|
}
|
|
241
214
|
}
|
|
242
|
-
// -------------------------------------------------------------------------
|
|
243
215
|
// Pass 2 — edges.
|
|
244
|
-
// -------------------------------------------------------------------------
|
|
245
216
|
/** Types a file can name: its own package, plus everything it imports. */
|
|
246
217
|
function resolveType(unit, written) {
|
|
247
218
|
if (unit.identityPackage === undefined)
|
|
248
219
|
return undefined;
|
|
249
|
-
// A written type may be a dotted path
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// refused against `DiskLruCache`, where the member genuinely is not.
|
|
220
|
+
// A written type may be a dotted nested path (`DiskLruCache.Snapshot`);
|
|
221
|
+
// the whole path is tried first — resolving only the outermost segment is
|
|
222
|
+
// how `snapshot.getSource()` was wrongly refused against `DiskLruCache`.
|
|
253
223
|
const segments = written.split(".");
|
|
254
224
|
const name = segments[segments.length - 1];
|
|
255
225
|
if (segments.length > 1) {
|
|
256
226
|
const nested = typesByKey.get(typeKey(unit.identityPackage, segments));
|
|
257
227
|
if (nested !== undefined)
|
|
258
228
|
return nested;
|
|
259
|
-
// The enclosing type may be imported from
|
|
260
|
-
//
|
|
261
|
-
// paths
|
|
229
|
+
// The enclosing type may be imported from elsewhere while the path is
|
|
230
|
+
// written relative to it, so the tail is matched against declared
|
|
231
|
+
// paths, not assumed package-local.
|
|
262
232
|
const byPath = [...typesByKey.values()].filter((candidate) => candidate.path.join(".").endsWith(segments.join(".")));
|
|
263
233
|
if (byPath.length === 1)
|
|
264
234
|
return byPath[0];
|
|
@@ -275,10 +245,9 @@ export function extract(input) {
|
|
|
275
245
|
if (candidate.path[candidate.path.length - 1] === name)
|
|
276
246
|
return candidate;
|
|
277
247
|
}
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
// refused, not invented.
|
|
248
|
+
// Explicit import names the symbol; wildcard names its package. Both
|
|
249
|
+
// matched against the DECLARATION table — an import of something never
|
|
250
|
+
// read resolves to nothing and is refused, not invented.
|
|
282
251
|
const matches = [];
|
|
283
252
|
for (const imported of unit.imports) {
|
|
284
253
|
const wanted = imported.isWildcard ? name : imported.path.split(".").pop();
|
|
@@ -295,15 +264,12 @@ export function extract(input) {
|
|
|
295
264
|
return matches[0];
|
|
296
265
|
if (matches.length > 1)
|
|
297
266
|
return undefined;
|
|
298
|
-
// Same declared
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// `RedisDataFactory`, declared under both `src/test/java` and
|
|
305
|
-
// `src/test-integration/java`) — so ambiguity here still refuses rather than
|
|
306
|
-
// guesses, the same rule Java's own `pick` applies.
|
|
267
|
+
// Same declared package, different source set (`src/test/kotlin` naming a class in
|
|
268
|
+
// `src/main/kotlin` with no import — legal, since a test source set's classpath includes main).
|
|
269
|
+
// Tried last, only with exactly one candidate: cross-source-set/module name collisions are
|
|
270
|
+
// exactly what `adapter-jvm`'s identity design exists to catch (airbyte's `RedisDataFactory`
|
|
271
|
+
// under both `src/test/java` and `src/test-integration/java`), so ambiguity still refuses,
|
|
272
|
+
// same as Java's own `pick`.
|
|
307
273
|
if (unit.packageName === "")
|
|
308
274
|
return undefined;
|
|
309
275
|
const samePackageName = [...typesByKey.values()].filter((candidate) => packageOf(candidate.identityPackage) === unit.packageName &&
|
|
@@ -314,15 +280,12 @@ export function extract(input) {
|
|
|
314
280
|
return funcsByKey.get(typeKey(owner.identityPackage, [...owner.path, name]));
|
|
315
281
|
}
|
|
316
282
|
/**
|
|
317
|
-
* A top-level function a bare unqualified call may name
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* this lookup only ever checked the caller's OWN package. Kotlin needs no
|
|
324
|
-
* import for a same-package call, which is what made the gap invisible until
|
|
325
|
-
* a construct genuinely needing a cross-package import existed to hit it.
|
|
283
|
+
* A top-level function a bare unqualified call may name — own package, or
|
|
284
|
+
* reached through an import. Mirrors `resolveType`'s gap for functions: a
|
|
285
|
+
* call to an imported top-level function was wrongly refused because this
|
|
286
|
+
* lookup only checked the caller's own package (invisible until a
|
|
287
|
+
* cross-package call actually needed it, since same-package calls need no
|
|
288
|
+
* import).
|
|
326
289
|
*/
|
|
327
290
|
function resolveFreeFunction(unit, name) {
|
|
328
291
|
if (unit.identityPackage === undefined)
|
|
@@ -354,9 +317,8 @@ export function extract(input) {
|
|
|
354
317
|
return matches[0];
|
|
355
318
|
if (matches.length > 1)
|
|
356
319
|
return undefined;
|
|
357
|
-
// Same declared package, different source set —
|
|
358
|
-
// identical fallback
|
|
359
|
-
// top-level function needs it exactly as a bare type reference does.
|
|
320
|
+
// Same declared package, different source set — same reasoning as
|
|
321
|
+
// `resolveType`'s identical fallback.
|
|
360
322
|
if (unit.packageName === "")
|
|
361
323
|
return undefined;
|
|
362
324
|
const samePackageName = [...funcsByKey.values()].filter((fn) => fn.owners.length === 0 && fn.name === name && packageOf(fn.identityPackage) === unit.packageName);
|
|
@@ -369,17 +331,12 @@ export function extract(input) {
|
|
|
369
331
|
// IMPORTS — file-granular, to the module of the imported symbol.
|
|
370
332
|
if (moduleId !== undefined) {
|
|
371
333
|
for (const imported of unit.imports) {
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
// That made IMPORTS 89,080 of 98,551 edges at roughly 79% wrong — 90%
|
|
380
|
-
// of the graph, and the single reason both precision gates failed.
|
|
381
|
-
// Rule 2 decides it: a wrong edge corrupts every layer above, a missing
|
|
382
|
-
// one is a disclosed gap.
|
|
334
|
+
// Star imports are refused: `import io.ktor.util.*` names a package, but the IR has no
|
|
335
|
+
// PACKAGE node, so the only edge available is MODULE->MODULE once per file in that package.
|
|
336
|
+
// Measured on ktor: 144 of 183 sampled such edges point at a file the importer never
|
|
337
|
+
// actually names (9,222 star imports vs 4,877 explicit) — this made IMPORTS 89,080/98,551
|
|
338
|
+
// edges (~79% wrong, ~90% of the graph) and was the reason both precision gates failed.
|
|
339
|
+
// Rule 2: a wrong edge corrupts every layer above; a missing one is a disclosed gap.
|
|
383
340
|
if (imported.isWildcard) {
|
|
384
341
|
refuse(moduleId, "IMPORTS", `${imported.path}.*`, unit.file, imported.line, "A star import names a package, and the IR has no PACKAGE node. The only edge " +
|
|
385
342
|
"available is one per file in that package, and most of those files declare nothing " +
|
|
@@ -387,11 +344,9 @@ export function extract(input) {
|
|
|
387
344
|
"rather than emitted; the dependency is real but not expressible at this resolution.");
|
|
388
345
|
continue;
|
|
389
346
|
}
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
// ShadowDnsResolver, and made mockwebserver and mockwebserver-deprecated
|
|
394
|
-
// resolve into each other in both directions.
|
|
347
|
+
// Matched on the FULL PACKAGE PATH, not the last segment — matching by simple name gave
|
|
348
|
+
// `import okhttp3.Request` an edge to an unrelated nested `Request` class, and made
|
|
349
|
+
// mockwebserver/mockwebserver-deprecated resolve into each other both ways.
|
|
395
350
|
const wanted = imported.path;
|
|
396
351
|
const targets = new Set();
|
|
397
352
|
for (const candidate of typesByKey.values()) {
|
|
@@ -402,12 +357,10 @@ export function extract(input) {
|
|
|
402
357
|
if (target !== undefined && target !== moduleId)
|
|
403
358
|
targets.add(target);
|
|
404
359
|
}
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
// the outside because the declaring file is named for its subject
|
|
410
|
-
// (`ApplicationReceiveFunctions.kt`) rather than for the symbol.
|
|
360
|
+
// An import may name a top-level function, not only a type (`io.ktor.server.request.receive`
|
|
361
|
+
// is an extension function). Matching only types caused 21 of 22 adjudicated wrong refusals
|
|
362
|
+
// on ktor — it looked like a filename problem, since the declaring file is named for its
|
|
363
|
+
// subject (`ApplicationReceiveFunctions.kt`), not the symbol.
|
|
411
364
|
if (targets.size === 0) {
|
|
412
365
|
for (const fn of funcsByKey.values()) {
|
|
413
366
|
if (fn.owners.length > 0)
|
|
@@ -466,10 +419,10 @@ export function extract(input) {
|
|
|
466
419
|
const free = resolveFreeFunction(unit, ref.name);
|
|
467
420
|
const target = member ?? free;
|
|
468
421
|
if (target === undefined) {
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
// rather than a function is a class
|
|
472
|
-
// (DEC-068), not a missing declaration.
|
|
422
|
+
// Kotlin has no `new` keyword, so a constructor call is
|
|
423
|
+
// syntactically identical to an ordinary one — a name resolving
|
|
424
|
+
// to a TYPE rather than a function is a class/enum used as a
|
|
425
|
+
// VALUE (DEC-068), not a missing declaration.
|
|
473
426
|
const asType = resolveType(unit, ref.name);
|
|
474
427
|
if (asType !== undefined) {
|
|
475
428
|
push({ from: from.id, to: asType.id, type: "USES_TYPE" }, 0.85, 1);
|
|
@@ -482,12 +435,10 @@ export function extract(input) {
|
|
|
482
435
|
continue;
|
|
483
436
|
}
|
|
484
437
|
if (ref.receiverType === undefined) {
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
// `
|
|
488
|
-
//
|
|
489
|
-
// IS the type. Tried before refusing, not instead of the ordinary
|
|
490
|
-
// path — a local variable always wins when one exists.
|
|
438
|
+
// No written local type — but the receiver name may itself resolve to a declared type
|
|
439
|
+
// (`object`/companion, static-style access): `Routes.getOrder(id)` has no local var
|
|
440
|
+
// `Routes`, because `Routes` IS the type. Tried before refusing; a local variable still
|
|
441
|
+
// wins when one exists.
|
|
491
442
|
const asStatic = ref.receiver === undefined ? undefined : resolveType(unit, ref.receiver);
|
|
492
443
|
const staticMember = asStatic === undefined ? undefined : memberOf(asStatic, ref.name);
|
|
493
444
|
if (staticMember !== undefined) {
|
|
@@ -510,13 +461,10 @@ export function extract(input) {
|
|
|
510
461
|
"be inherited, an extension function, or defined in a dependency.");
|
|
511
462
|
continue;
|
|
512
463
|
}
|
|
513
|
-
// R1,
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
// it would be claiming a capability the adapter does not have. Raised by
|
|
518
|
-
// the lane grading this adapter, who could not read this file and asked
|
|
519
|
-
// what the 2 was backed by; the honest answer was nothing.
|
|
464
|
+
// R1, not R2: the ladder defines R2 as "+ LSP references/definitions," and there's no LSP
|
|
465
|
+
// here — this rests on a written type annotation resolved against the declaration table
|
|
466
|
+
// (module/import resolution + source text). Claiming R2 would claim a capability this
|
|
467
|
+
// adapter doesn't have.
|
|
520
468
|
push({ from: from.id, to: target.id, type: "CALLS" }, 0.9, 1);
|
|
521
469
|
}
|
|
522
470
|
}
|
|
@@ -530,10 +478,8 @@ export function extract(input) {
|
|
|
530
478
|
continue;
|
|
531
479
|
push({ from: edge.from, to: edge.to, type: "TESTS" }, edge.confidence, edge.resolution);
|
|
532
480
|
}
|
|
533
|
-
// ---------------------------------------------------------------------------
|
|
534
481
|
// Routes — Ktor's routing DSL. Last, because every ledger row is attributed
|
|
535
482
|
// to a MODULE the pass above has already minted.
|
|
536
|
-
// ---------------------------------------------------------------------------
|
|
537
483
|
const routeNodes = new Map();
|
|
538
484
|
const endpointNodes = new Map();
|
|
539
485
|
for (const unit of input.files) {
|
|
@@ -591,16 +537,13 @@ export function extract(input) {
|
|
|
591
537
|
push({ from: routeId, to: endpointId, type: "SERVES_API" }, 0.9, 2);
|
|
592
538
|
}
|
|
593
539
|
}
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
// that never meet.
|
|
598
|
-
// ---------------------------------------------------------------------------
|
|
540
|
+
// Client calls — the caller half, minted through the same `endpointQsp` so a
|
|
541
|
+
// call and the route serving it land on one endpoint node, not two that
|
|
542
|
+
// never meet.
|
|
599
543
|
/**
|
|
600
|
-
* DEC-242's `attrs.blockedBy`/`attrs.refusalClass`, plus
|
|
601
|
-
* `argumentKind
|
|
602
|
-
* `refuse` omits `attrs` entirely rather than
|
|
603
|
-
* `undefined` field (unset stays legal and means unclassified).
|
|
544
|
+
* DEC-242's `attrs.blockedBy`/`attrs.refusalClass`, plus diagnostic-only
|
|
545
|
+
* `argumentKind`. `undefined` when `refusalClass` was never set, so
|
|
546
|
+
* `refuse` omits `attrs` entirely rather than an undefined field.
|
|
604
547
|
*/
|
|
605
548
|
const clientAttrsOf = (refusal) => refusal.refusalClass === undefined
|
|
606
549
|
? undefined
|
|
@@ -619,17 +562,16 @@ export function extract(input) {
|
|
|
619
562
|
if (input.reached < 2)
|
|
620
563
|
continue;
|
|
621
564
|
for (const call of unit.clientCalls) {
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
565
|
+
// Caller = innermost function containing the call (the thing a reader
|
|
566
|
+
// would blame). Outside any function (a file-scope property
|
|
567
|
+
// initialiser) attributes to the MODULE, the file-scoped carrier
|
|
568
|
+
// (DEC-124).
|
|
626
569
|
const enclosing = unit.funcs
|
|
627
570
|
.filter((fn) => fn.startLine <= call.line && call.line <= fn.endLine)
|
|
628
571
|
.sort((a, b) => b.startLine - a.startLine)[0];
|
|
629
|
-
// A file
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
// so the edge degrades to the file rather than being dropped.
|
|
572
|
+
// A file with no derivable identity package has no function ids to
|
|
573
|
+
// point at (DEC-173); the MODULE still does (DEC-124), so the edge
|
|
574
|
+
// degrades to the file rather than dropping.
|
|
633
575
|
const from = enclosing === undefined || unit.identityPackage === undefined
|
|
634
576
|
? moduleId
|
|
635
577
|
: funcsByKey.get(typeKey(unit.identityPackage, [...enclosing.owners, enclosing.name]))?.id ?? moduleId;
|
|
@@ -651,12 +593,9 @@ export function extract(input) {
|
|
|
651
593
|
push({ from, to: endpointId, type: "USES_API", attrs: { client: call.client, rawPath: call.rawPath } }, 0.9, 2);
|
|
652
594
|
}
|
|
653
595
|
}
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
// the caller, and ordinary CALLS resolution (unchanged) is what connects a
|
|
658
|
-
// real caller to it, one hop further out. See `retrofit.ts`'s own header.
|
|
659
|
-
// ---------------------------------------------------------------------------
|
|
596
|
+
// Retrofit — a declarative client interface. No call site to attribute to:
|
|
597
|
+
// the annotated function's own id IS the caller; ordinary CALLS resolution
|
|
598
|
+
// connects a real caller one hop further out. See `retrofit.ts`.
|
|
660
599
|
for (const unit of input.files) {
|
|
661
600
|
const moduleId = moduleIdOf.get(unit.file);
|
|
662
601
|
if (moduleId === undefined)
|
|
@@ -671,11 +610,9 @@ export function extract(input) {
|
|
|
671
610
|
? undefined
|
|
672
611
|
: funcsByKey.get(typeKey(unit.identityPackage, [...endpoint.owners, endpoint.funcName]))?.id;
|
|
673
612
|
if (from === undefined) {
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
// edge to the MODULE instead would be a second, misleading claim
|
|
678
|
-
// about a function this adapter declined to name.
|
|
613
|
+
// No derivable identity, or the declaring function collided and was
|
|
614
|
+
// refused in Pass 1 — already disclosed there. Falling back to the
|
|
615
|
+
// MODULE would be a second, misleading claim about a declined function.
|
|
679
616
|
continue;
|
|
680
617
|
}
|
|
681
618
|
const template = normaliseEndpointPath(endpoint.path);
|
|
@@ -700,22 +637,19 @@ export function extract(input) {
|
|
|
700
637
|
return { nodes, edges, unresolved };
|
|
701
638
|
}
|
|
702
639
|
/**
|
|
703
|
-
* `INHERITS` or `IMPLEMENTS`, decided against the declaration table
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* unambiguously implemented whatever the syntax. Anything else falls to
|
|
709
|
-
* `IMPLEMENTS`, which is the weaker claim of the two.
|
|
640
|
+
* `INHERITS` or `IMPLEMENTS`, decided against the declaration table — the
|
|
641
|
+
* parenthesised invocation form is checked only as confirmation. An invoked
|
|
642
|
+
* `class` target is unambiguously the superclass; an `interface` target is
|
|
643
|
+
* unambiguously implemented regardless of syntax. Anything else falls to the
|
|
644
|
+
* weaker `IMPLEMENTS`.
|
|
710
645
|
*/
|
|
711
646
|
function classifySupertype(target, written) {
|
|
712
647
|
if (target.kind === "interface")
|
|
713
648
|
return "IMPLEMENTS";
|
|
714
649
|
if (written.invoked)
|
|
715
650
|
return "INHERITS";
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
// IMPLEMENTS understates rather than inventing an inheritance chain.
|
|
651
|
+
// `interface A : B` where B resolves to a class is malformed Kotlin —
|
|
652
|
+
// likelier the table is incomplete. IMPLEMENTS understates rather than invents.
|
|
719
653
|
return target.kind === "class" ? "INHERITS" : "IMPLEMENTS";
|
|
720
654
|
}
|
|
721
655
|
const fileStem = (file) => file.slice(file.lastIndexOf("/") + 1).replace(/\.kt$/, "");
|