kmp-api-lookup-mcp 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/README.md +219 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +33 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/index.d.ts +12 -0
- package/dist/indexer/index.js +245 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/search-utils.d.ts +10 -0
- package/dist/search-utils.js +81 -0
- package/dist/search-utils.js.map +1 -0
- package/dist/server/createServer.d.ts +11 -0
- package/dist/server/createServer.js +30 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lookupDetail.d.ts +3 -0
- package/dist/server/lookupDetail.js +295 -0
- package/dist/server/lookupDetail.js.map +1 -0
- package/dist/server/lookupService.d.ts +34 -0
- package/dist/server/lookupService.js +822 -0
- package/dist/server/lookupService.js.map +1 -0
- package/dist/server/metadataInspector.d.ts +14 -0
- package/dist/server/metadataInspector.js +288 -0
- package/dist/server/metadataInspector.js.map +1 -0
- package/dist/storage/index.d.ts +58 -0
- package/dist/storage/index.js +554 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +488 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +287 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { discoverKotlinNativeInstallations, dumpFrameworkMetadata, dumpFrameworkSignatures, listFrameworkSources, } from '../indexer/index.js';
|
|
3
|
+
import { compactSelector, normalizeSelector, normalizeText, pickLatestVersion, toIsoNow, uniqueSorted, } from '../search-utils.js';
|
|
4
|
+
import { KlibIndexStorage } from '../storage/index.js';
|
|
5
|
+
import { applyLookupDetailToClassCard, applyLookupDetailToMemberCard } from './lookupDetail.js';
|
|
6
|
+
import { buildClassCardFromMetadata, buildTopLevelMemberCardFromMetadata } from './metadataInspector.js';
|
|
7
|
+
export class KlibLookupService {
|
|
8
|
+
config;
|
|
9
|
+
storage;
|
|
10
|
+
metadataCache = new Map();
|
|
11
|
+
constructor(config, storage = new KlibIndexStorage(config)) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
}
|
|
15
|
+
close() {
|
|
16
|
+
this.storage.close();
|
|
17
|
+
}
|
|
18
|
+
async lookup(request) {
|
|
19
|
+
const query = request.query.trim();
|
|
20
|
+
const detailLevel = request.detail ?? 'compact';
|
|
21
|
+
const queryKind = request.queryKind ?? 'auto';
|
|
22
|
+
if (!query) {
|
|
23
|
+
throw new Error('lookup_symbol requires a non-empty query');
|
|
24
|
+
}
|
|
25
|
+
const context = this.resolveSearchContext(request);
|
|
26
|
+
const requestedLimit = request.limit ?? 5;
|
|
27
|
+
const initialSearchLimit = Math.max(requestedLimit, 20);
|
|
28
|
+
let results = this.searchLookupCandidates(query, context, initialSearchLimit);
|
|
29
|
+
const effectiveTarget = pickPreferredTarget(context.effectiveTargets);
|
|
30
|
+
if (results.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
query,
|
|
33
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
34
|
+
effectiveTarget,
|
|
35
|
+
detailLevel,
|
|
36
|
+
resultKind: 'not_found',
|
|
37
|
+
classCard: null,
|
|
38
|
+
memberCard: null,
|
|
39
|
+
alternatives: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
let resolution = this.resolveLookupResolution(query, queryKind, results, requestedLimit);
|
|
43
|
+
if (shouldExpandLookupCandidateWindow(query, queryKind, resolution)) {
|
|
44
|
+
const expandedSearchLimit = Math.max(initialSearchLimit, 300);
|
|
45
|
+
if (expandedSearchLimit > initialSearchLimit) {
|
|
46
|
+
const expandedResults = this.searchLookupCandidates(query, context, expandedSearchLimit);
|
|
47
|
+
if (expandedResults.length > results.length) {
|
|
48
|
+
results = expandedResults;
|
|
49
|
+
resolution = this.resolveLookupResolution(query, queryKind, results, requestedLimit);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (resolution.kind === 'not_found') {
|
|
54
|
+
return {
|
|
55
|
+
query,
|
|
56
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
57
|
+
effectiveTarget,
|
|
58
|
+
detailLevel,
|
|
59
|
+
resultKind: 'not_found',
|
|
60
|
+
classCard: null,
|
|
61
|
+
memberCard: null,
|
|
62
|
+
alternatives: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (resolution.kind === 'ambiguous') {
|
|
66
|
+
return {
|
|
67
|
+
query,
|
|
68
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
69
|
+
effectiveTarget,
|
|
70
|
+
detailLevel,
|
|
71
|
+
resultKind: 'ambiguous',
|
|
72
|
+
classCard: null,
|
|
73
|
+
memberCard: null,
|
|
74
|
+
alternatives: resolution.alternatives,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (resolution.kind === 'top_level') {
|
|
78
|
+
const memberCard = await this.loadTopLevelMemberCard(resolution.symbol);
|
|
79
|
+
if (!memberCard) {
|
|
80
|
+
throw new Error(`Failed to load Kotlin metadata for top-level symbol ${resolution.symbol.packageName}.${resolution.symbol.symbolName}`);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
query,
|
|
84
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
85
|
+
effectiveTarget: resolution.symbol.target,
|
|
86
|
+
detailLevel,
|
|
87
|
+
resultKind: 'member',
|
|
88
|
+
classCard: null,
|
|
89
|
+
memberCard: applyLookupDetailToMemberCard(memberCard, detailLevel),
|
|
90
|
+
alternatives: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const classCard = await this.loadClassCard(resolution.owner);
|
|
94
|
+
if (!classCard) {
|
|
95
|
+
const topLevelFallback = await this.loadTopLevelMemberCard({
|
|
96
|
+
framework: resolution.owner.framework,
|
|
97
|
+
packageName: resolution.owner.packageName,
|
|
98
|
+
symbolName: resolution.owner.className,
|
|
99
|
+
target: resolution.owner.target,
|
|
100
|
+
konanHome: resolution.owner.konanHome,
|
|
101
|
+
rawSignatures: resolution.owner.declarationRawSignatures,
|
|
102
|
+
rank: resolution.owner.rank,
|
|
103
|
+
});
|
|
104
|
+
if (topLevelFallback) {
|
|
105
|
+
return {
|
|
106
|
+
query,
|
|
107
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
108
|
+
effectiveTarget: resolution.owner.target,
|
|
109
|
+
detailLevel,
|
|
110
|
+
resultKind: 'member',
|
|
111
|
+
classCard: null,
|
|
112
|
+
memberCard: applyLookupDetailToMemberCard(topLevelFallback, detailLevel),
|
|
113
|
+
alternatives: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`Failed to load Kotlin metadata for ${resolution.owner.packageName}.${resolution.owner.className}`);
|
|
117
|
+
}
|
|
118
|
+
if (resolution.kind === 'class') {
|
|
119
|
+
return {
|
|
120
|
+
query,
|
|
121
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
122
|
+
effectiveTarget: resolution.owner.target,
|
|
123
|
+
detailLevel,
|
|
124
|
+
resultKind: 'class',
|
|
125
|
+
classCard: applyLookupDetailToClassCard(classCard, detailLevel),
|
|
126
|
+
memberCard: null,
|
|
127
|
+
alternatives: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const memberCard = this.buildMemberCard(classCard, resolution.memberName);
|
|
131
|
+
if (!memberCard) {
|
|
132
|
+
throw new Error(`Failed to locate ${resolution.memberName} inside ${classCard.qualifiedName} metadata.`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
query,
|
|
136
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
137
|
+
effectiveTarget: resolution.owner.target,
|
|
138
|
+
detailLevel,
|
|
139
|
+
resultKind: 'member',
|
|
140
|
+
classCard: null,
|
|
141
|
+
memberCard: applyLookupDetailToMemberCard(memberCard, detailLevel),
|
|
142
|
+
alternatives: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async discoverInstallations(explicitKonanHome) {
|
|
146
|
+
const installations = await discoverKotlinNativeInstallations(this.config, explicitKonanHome, explicitKonanHome ? { onlyExplicit: true } : undefined);
|
|
147
|
+
if (explicitKonanHome && installations.length === 0) {
|
|
148
|
+
throw new Error(`No valid Kotlin/Native installation found at ${explicitKonanHome}`);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
explicitKonanHome: explicitKonanHome ?? null,
|
|
152
|
+
installations,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async getStatus() {
|
|
156
|
+
const discoveredInstallations = await discoverKotlinNativeInstallations(this.config);
|
|
157
|
+
const indexedDatasets = this.storage.listIndexedDatasets();
|
|
158
|
+
const recordCounts = this.storage.getRecordCounts();
|
|
159
|
+
const lastRebuild = await this.readStateFile();
|
|
160
|
+
return {
|
|
161
|
+
ready: recordCounts.symbols > 0,
|
|
162
|
+
discoveredInstallations: discoveredInstallations.map((installation) => ({
|
|
163
|
+
kotlinVersion: installation.kotlinVersion,
|
|
164
|
+
availableTargets: installation.availableTargets,
|
|
165
|
+
sources: installation.sources,
|
|
166
|
+
})),
|
|
167
|
+
indexedDatasets: indexedDatasets.map((dataset) => ({
|
|
168
|
+
kotlinVersion: dataset.kotlinVersion,
|
|
169
|
+
target: dataset.target,
|
|
170
|
+
indexedAt: dataset.indexedAt,
|
|
171
|
+
recordCount: dataset.recordCount,
|
|
172
|
+
frameworkCount: dataset.frameworkCount,
|
|
173
|
+
frameworks: dataset.frameworks,
|
|
174
|
+
})),
|
|
175
|
+
recordCounts,
|
|
176
|
+
lastRebuildAt: lastRebuild?.lastRebuildAt ?? null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async search(request) {
|
|
180
|
+
const query = request.query.trim();
|
|
181
|
+
if (!query) {
|
|
182
|
+
throw new Error('search_klib_api requires a non-empty query');
|
|
183
|
+
}
|
|
184
|
+
const context = this.resolveSearchContext(request);
|
|
185
|
+
const matchMode = request.matchMode ?? this.config.defaultMatchMode;
|
|
186
|
+
const limit = request.limit ?? this.config.defaultSearchLimit;
|
|
187
|
+
const includeMetaClasses = request.includeMetaClasses ?? this.config.defaultIncludeMetaClasses;
|
|
188
|
+
const includeRawSignature = request.includeRawSignature ?? this.config.defaultIncludeRawSignature;
|
|
189
|
+
const results = this.storage.searchSymbols({
|
|
190
|
+
query,
|
|
191
|
+
kotlinVersion: context.effectiveVersion,
|
|
192
|
+
targets: context.effectiveTargets,
|
|
193
|
+
frameworks: context.selectedFrameworks,
|
|
194
|
+
matchMode,
|
|
195
|
+
limit,
|
|
196
|
+
includeMetaClasses,
|
|
197
|
+
includeRawSignature,
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
query,
|
|
201
|
+
effectiveKotlinVersion: context.effectiveVersion,
|
|
202
|
+
effectiveTargets: context.effectiveTargets,
|
|
203
|
+
availableTargets: context.availableTargets,
|
|
204
|
+
selectedFrameworks: context.selectedFrameworks,
|
|
205
|
+
matchMode,
|
|
206
|
+
limit,
|
|
207
|
+
includeMetaClasses,
|
|
208
|
+
includeRawSignature,
|
|
209
|
+
totalResults: results.length,
|
|
210
|
+
noResults: results.length === 0,
|
|
211
|
+
results,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async explain(request) {
|
|
215
|
+
const searchResult = await this.search({
|
|
216
|
+
...request,
|
|
217
|
+
limit: 1,
|
|
218
|
+
includeRawSignature: request.includeRawSignature ?? this.config.defaultIncludeRawSignature,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
query: searchResult.query,
|
|
222
|
+
effectiveKotlinVersion: searchResult.effectiveKotlinVersion,
|
|
223
|
+
effectiveTargets: searchResult.effectiveTargets,
|
|
224
|
+
matchMode: searchResult.matchMode,
|
|
225
|
+
includeMetaClasses: searchResult.includeMetaClasses,
|
|
226
|
+
noResults: searchResult.noResults,
|
|
227
|
+
symbol: searchResult.results[0] ?? null,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
searchLookupCandidates(query, context, limit) {
|
|
231
|
+
return this.storage.searchSymbols({
|
|
232
|
+
query,
|
|
233
|
+
kotlinVersion: context.effectiveVersion,
|
|
234
|
+
targets: context.effectiveTargets,
|
|
235
|
+
frameworks: context.selectedFrameworks,
|
|
236
|
+
matchMode: 'auto',
|
|
237
|
+
limit,
|
|
238
|
+
includeMetaClasses: true,
|
|
239
|
+
includeRawSignature: true,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async rebuild(request) {
|
|
243
|
+
const cleanBefore = request.cleanBefore ?? true;
|
|
244
|
+
const dryRun = request.dryRun ?? false;
|
|
245
|
+
const force = request.force ?? false;
|
|
246
|
+
if (request.kotlinVersion && request.konanHome) {
|
|
247
|
+
throw new Error('rebuild_klib_index accepts at most one of kotlinVersion or konanHome');
|
|
248
|
+
}
|
|
249
|
+
const installation = await this.resolveInstallation(request.kotlinVersion, request.konanHome);
|
|
250
|
+
const target = request.target?.trim() || pickPreferredTarget(installation.availableTargets);
|
|
251
|
+
if (!target) {
|
|
252
|
+
throw new Error('rebuild_klib_index could not resolve a default target for the selected installation');
|
|
253
|
+
}
|
|
254
|
+
const frameworkSources = await listFrameworkSources(installation, target, request.frameworks ? uniqueSorted(request.frameworks) : undefined);
|
|
255
|
+
if (frameworkSources.length === 0) {
|
|
256
|
+
throw new Error(`No frameworks were found for target ${target}`);
|
|
257
|
+
}
|
|
258
|
+
const existingSnapshots = this.storage.getFrameworkSnapshots(installation.konanHome, target, frameworkSources.map((framework) => framework.name));
|
|
259
|
+
const reports = frameworkSources.map((framework) => {
|
|
260
|
+
const existing = existingSnapshots.get(framework.name);
|
|
261
|
+
const isFresh = !force
|
|
262
|
+
&& existing !== undefined
|
|
263
|
+
&& existing.sourceMtimeMs === framework.sourceMtimeMs
|
|
264
|
+
&& existing.symbolCount > 0;
|
|
265
|
+
return {
|
|
266
|
+
framework: framework.name,
|
|
267
|
+
action: isFresh ? 'skip-fresh' : 'rebuild',
|
|
268
|
+
lineCount: 0,
|
|
269
|
+
symbolCount: existing?.symbolCount ?? 0,
|
|
270
|
+
reason: isFresh ? 'Framework metadata mtime matches the indexed snapshot' : undefined,
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
const frameworksToBuild = frameworkSources.filter((framework) => {
|
|
274
|
+
const report = reports.find((item) => item.framework === framework.name);
|
|
275
|
+
return report?.action === 'rebuild';
|
|
276
|
+
});
|
|
277
|
+
if (dryRun || frameworksToBuild.length === 0) {
|
|
278
|
+
const indexedAt = dryRun ? null : toIsoNow();
|
|
279
|
+
const result = {
|
|
280
|
+
kotlinVersion: installation.kotlinVersion,
|
|
281
|
+
target,
|
|
282
|
+
selectedFrameworks: frameworkSources.map((framework) => framework.name),
|
|
283
|
+
rebuiltFrameworks: frameworksToBuild.map((framework) => framework.name),
|
|
284
|
+
skippedFrameworks: reports
|
|
285
|
+
.filter((report) => report.action === 'skip-fresh')
|
|
286
|
+
.map((report) => report.framework),
|
|
287
|
+
dryRun,
|
|
288
|
+
indexedAt,
|
|
289
|
+
totalRecordsWritten: dryRun ? 0 : 0,
|
|
290
|
+
reports,
|
|
291
|
+
};
|
|
292
|
+
if (!dryRun && frameworksToBuild.length === 0) {
|
|
293
|
+
await this.writeStateFile({
|
|
294
|
+
lastRebuildAt: indexedAt,
|
|
295
|
+
lastRebuild: result,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
const indexedAt = toIsoNow();
|
|
301
|
+
const batchItems = [];
|
|
302
|
+
for (const framework of frameworksToBuild) {
|
|
303
|
+
const dumpedFramework = await dumpFrameworkSignatures(installation, framework);
|
|
304
|
+
const report = reports.find((item) => item.framework === framework.name);
|
|
305
|
+
if (report) {
|
|
306
|
+
report.lineCount = dumpedFramework.lineCount;
|
|
307
|
+
report.symbolCount = dumpedFramework.records.length;
|
|
308
|
+
}
|
|
309
|
+
batchItems.push({
|
|
310
|
+
source: framework,
|
|
311
|
+
lineCount: dumpedFramework.lineCount,
|
|
312
|
+
records: dumpedFramework.records,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const totalRecordsWritten = this.storage.writeFrameworkBatch({
|
|
316
|
+
installation,
|
|
317
|
+
target,
|
|
318
|
+
indexedAt,
|
|
319
|
+
cleanBefore,
|
|
320
|
+
items: batchItems,
|
|
321
|
+
});
|
|
322
|
+
const result = {
|
|
323
|
+
kotlinVersion: installation.kotlinVersion,
|
|
324
|
+
target,
|
|
325
|
+
selectedFrameworks: frameworkSources.map((framework) => framework.name),
|
|
326
|
+
rebuiltFrameworks: frameworksToBuild.map((framework) => framework.name),
|
|
327
|
+
skippedFrameworks: reports
|
|
328
|
+
.filter((report) => report.action === 'skip-fresh')
|
|
329
|
+
.map((report) => report.framework),
|
|
330
|
+
dryRun,
|
|
331
|
+
indexedAt,
|
|
332
|
+
totalRecordsWritten,
|
|
333
|
+
reports,
|
|
334
|
+
};
|
|
335
|
+
await this.writeStateFile({
|
|
336
|
+
lastRebuildAt: indexedAt,
|
|
337
|
+
lastRebuild: result,
|
|
338
|
+
});
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
resolveEffectiveVersion(requestedVersion, indexedDatasets) {
|
|
342
|
+
if (requestedVersion) {
|
|
343
|
+
return requestedVersion.trim();
|
|
344
|
+
}
|
|
345
|
+
const latestVersion = pickLatestVersion(indexedDatasets.map((dataset) => dataset.kotlinVersion));
|
|
346
|
+
if (!latestVersion) {
|
|
347
|
+
throw new Error('No indexed Kotlin/Native version is available. Run rebuild_klib_index first.');
|
|
348
|
+
}
|
|
349
|
+
return latestVersion;
|
|
350
|
+
}
|
|
351
|
+
resolveSearchContext(request) {
|
|
352
|
+
const indexedDatasets = this.storage.listIndexedDatasets();
|
|
353
|
+
if (indexedDatasets.length === 0) {
|
|
354
|
+
throw new Error('No index is available yet. Run rebuild_klib_index first to build at least one Kotlin/Native target.');
|
|
355
|
+
}
|
|
356
|
+
const effectiveVersion = this.resolveEffectiveVersion(request.kotlinVersion, indexedDatasets);
|
|
357
|
+
const datasetsForVersion = indexedDatasets.filter((dataset) => dataset.kotlinVersion === effectiveVersion);
|
|
358
|
+
if (datasetsForVersion.length === 0) {
|
|
359
|
+
throw new Error(`No indexed dataset found for Kotlin/Native ${effectiveVersion}. Run rebuild_klib_index for that version first.`);
|
|
360
|
+
}
|
|
361
|
+
const availableTargets = uniqueSorted(datasetsForVersion.map((dataset) => dataset.target));
|
|
362
|
+
if (request.target && !availableTargets.includes(request.target)) {
|
|
363
|
+
throw new Error(`No indexed target ${request.target} found for Kotlin/Native ${effectiveVersion}. Run rebuild_klib_index for that target first.`);
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
effectiveVersion,
|
|
367
|
+
availableTargets,
|
|
368
|
+
effectiveTargets: request.target ? [request.target] : availableTargets,
|
|
369
|
+
selectedFrameworks: request.frameworks ? uniqueSorted(request.frameworks) : [],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
async resolveInstallation(kotlinVersion, konanHome) {
|
|
373
|
+
if (konanHome) {
|
|
374
|
+
const explicit = await discoverKotlinNativeInstallations(this.config, konanHome, {
|
|
375
|
+
onlyExplicit: true,
|
|
376
|
+
});
|
|
377
|
+
if (explicit.length === 0) {
|
|
378
|
+
throw new Error(`No valid Kotlin/Native installation found at ${konanHome}`);
|
|
379
|
+
}
|
|
380
|
+
return explicit[0];
|
|
381
|
+
}
|
|
382
|
+
const discovered = await discoverKotlinNativeInstallations(this.config);
|
|
383
|
+
if (discovered.length === 0) {
|
|
384
|
+
throw new Error('No Kotlin/Native installations were discovered locally.');
|
|
385
|
+
}
|
|
386
|
+
if (!kotlinVersion) {
|
|
387
|
+
return discovered[0];
|
|
388
|
+
}
|
|
389
|
+
const matches = discovered.filter((installation) => installation.kotlinVersion === kotlinVersion);
|
|
390
|
+
if (matches.length === 0) {
|
|
391
|
+
throw new Error(`No discovered Kotlin/Native installation found for version ${kotlinVersion}`);
|
|
392
|
+
}
|
|
393
|
+
if (matches.length > 1) {
|
|
394
|
+
throw new Error(`Multiple Kotlin/Native installations were found for version ${kotlinVersion}. Use konanHome to disambiguate.`);
|
|
395
|
+
}
|
|
396
|
+
return matches[0];
|
|
397
|
+
}
|
|
398
|
+
resolveLookupResolution(query, queryKind, results, limit) {
|
|
399
|
+
const classCandidates = this.collectClassCandidates(results);
|
|
400
|
+
const memberCandidates = this.collectMemberCandidates(results);
|
|
401
|
+
const topLevelCandidates = this.collectTopLevelCandidates(results);
|
|
402
|
+
const exactClassMatches = classCandidates.filter((candidate) => matchesClassQuery(query, candidate));
|
|
403
|
+
const exactMemberMatches = memberCandidates.filter((candidate) => matchesMemberQuery(query, candidate));
|
|
404
|
+
const exactTopLevelMatches = topLevelCandidates.filter((candidate) => matchesTopLevelQuery(query, candidate));
|
|
405
|
+
if (queryKind === 'class') {
|
|
406
|
+
if (exactClassMatches.length > 0) {
|
|
407
|
+
return this.resolveClassCandidates(exactClassMatches, limit);
|
|
408
|
+
}
|
|
409
|
+
if (exactTopLevelMatches.length > 0) {
|
|
410
|
+
return this.resolveTopLevelCandidates(exactTopLevelMatches, limit);
|
|
411
|
+
}
|
|
412
|
+
return this.resolveClassCandidates(classCandidates, limit);
|
|
413
|
+
}
|
|
414
|
+
if (queryKind === 'member') {
|
|
415
|
+
if (exactMemberMatches.length > 0) {
|
|
416
|
+
return this.resolveMemberCandidates(exactMemberMatches, limit);
|
|
417
|
+
}
|
|
418
|
+
if (exactTopLevelMatches.length > 0) {
|
|
419
|
+
return this.resolveTopLevelCandidates(exactTopLevelMatches, limit);
|
|
420
|
+
}
|
|
421
|
+
return this.resolveMemberCandidates(memberCandidates, limit);
|
|
422
|
+
}
|
|
423
|
+
if (query.includes('.') && exactMemberMatches.length > 0) {
|
|
424
|
+
return this.resolveMemberCandidates(exactMemberMatches, limit);
|
|
425
|
+
}
|
|
426
|
+
if (exactClassMatches.length > 0) {
|
|
427
|
+
return this.resolveClassCandidates(exactClassMatches, limit);
|
|
428
|
+
}
|
|
429
|
+
if (exactMemberMatches.length > 0) {
|
|
430
|
+
return this.resolveMemberCandidates(exactMemberMatches, limit);
|
|
431
|
+
}
|
|
432
|
+
if (exactTopLevelMatches.length > 0) {
|
|
433
|
+
return this.resolveTopLevelCandidates(exactTopLevelMatches, limit);
|
|
434
|
+
}
|
|
435
|
+
if (classCandidates.length === 1) {
|
|
436
|
+
return {
|
|
437
|
+
kind: 'class',
|
|
438
|
+
owner: classCandidates[0],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (memberCandidates.length === 1) {
|
|
442
|
+
return {
|
|
443
|
+
kind: 'member',
|
|
444
|
+
owner: memberCandidates[0],
|
|
445
|
+
memberName: memberCandidates[0].memberName,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (topLevelCandidates.length === 1 && classCandidates.length === 0 && memberCandidates.length === 0) {
|
|
449
|
+
return {
|
|
450
|
+
kind: 'top_level',
|
|
451
|
+
symbol: topLevelCandidates[0],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (classCandidates.length > 1 && looksLikeClassQuery(query)) {
|
|
455
|
+
return this.resolveClassCandidates(classCandidates, limit);
|
|
456
|
+
}
|
|
457
|
+
if (memberCandidates.length > 0) {
|
|
458
|
+
return this.resolveMemberCandidates(memberCandidates, limit);
|
|
459
|
+
}
|
|
460
|
+
if (classCandidates.length > 0) {
|
|
461
|
+
return this.resolveClassCandidates(classCandidates, limit);
|
|
462
|
+
}
|
|
463
|
+
if (topLevelCandidates.length > 0) {
|
|
464
|
+
return this.resolveTopLevelCandidates(topLevelCandidates, limit);
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
kind: 'not_found',
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
resolveClassCandidates(candidates, limit) {
|
|
471
|
+
if (candidates.length === 0) {
|
|
472
|
+
return {
|
|
473
|
+
kind: 'not_found',
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (candidates.length === 1) {
|
|
477
|
+
return {
|
|
478
|
+
kind: 'class',
|
|
479
|
+
owner: candidates[0],
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
kind: 'ambiguous',
|
|
484
|
+
alternatives: candidates.slice(0, limit).map((candidate) => ({
|
|
485
|
+
resultKind: 'class',
|
|
486
|
+
framework: candidate.framework,
|
|
487
|
+
packageName: candidate.packageName,
|
|
488
|
+
ownerName: candidate.className,
|
|
489
|
+
symbolName: candidate.className,
|
|
490
|
+
})),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
resolveMemberCandidates(candidates, limit) {
|
|
494
|
+
if (candidates.length === 0) {
|
|
495
|
+
return {
|
|
496
|
+
kind: 'not_found',
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (candidates.length === 1) {
|
|
500
|
+
return {
|
|
501
|
+
kind: 'member',
|
|
502
|
+
owner: candidates[0],
|
|
503
|
+
memberName: candidates[0].memberName,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
kind: 'ambiguous',
|
|
508
|
+
alternatives: candidates.slice(0, limit).map((candidate) => ({
|
|
509
|
+
resultKind: 'member',
|
|
510
|
+
framework: candidate.framework,
|
|
511
|
+
packageName: candidate.packageName,
|
|
512
|
+
ownerName: candidate.className,
|
|
513
|
+
symbolName: candidate.memberName,
|
|
514
|
+
})),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
resolveTopLevelCandidates(candidates, limit) {
|
|
518
|
+
if (candidates.length === 0) {
|
|
519
|
+
return {
|
|
520
|
+
kind: 'not_found',
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (candidates.length === 1) {
|
|
524
|
+
return {
|
|
525
|
+
kind: 'top_level',
|
|
526
|
+
symbol: candidates[0],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
kind: 'ambiguous',
|
|
531
|
+
alternatives: candidates.slice(0, limit).map((candidate) => ({
|
|
532
|
+
resultKind: 'member',
|
|
533
|
+
framework: candidate.framework,
|
|
534
|
+
packageName: candidate.packageName,
|
|
535
|
+
ownerName: null,
|
|
536
|
+
symbolName: candidate.symbolName,
|
|
537
|
+
})),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
collectClassCandidates(results) {
|
|
541
|
+
const candidates = new Map();
|
|
542
|
+
results.forEach((result, index) => {
|
|
543
|
+
const ownerClassName = normalizeOwnerClassName(result);
|
|
544
|
+
if (!ownerClassName) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (result.declarationForm === 'class' && result.isMetaClass) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const key = `${result.framework}|${result.packageName}|${ownerClassName}`;
|
|
551
|
+
const classDeclarationRawSignature = result.declarationForm === 'class' && result.rawSignature ? result.rawSignature : null;
|
|
552
|
+
if (candidates.has(key)) {
|
|
553
|
+
const existing = candidates.get(key);
|
|
554
|
+
if (!existing) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (classDeclarationRawSignature && !existing.declarationRawSignatures.includes(classDeclarationRawSignature)) {
|
|
558
|
+
candidates.set(key, {
|
|
559
|
+
...existing,
|
|
560
|
+
declarationRawSignatures: [...existing.declarationRawSignatures, classDeclarationRawSignature],
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
candidates.set(key, {
|
|
566
|
+
framework: result.framework,
|
|
567
|
+
packageName: result.packageName,
|
|
568
|
+
className: ownerClassName,
|
|
569
|
+
target: result.target,
|
|
570
|
+
konanHome: result.konanHome,
|
|
571
|
+
declarationRawSignatures: classDeclarationRawSignature ? [classDeclarationRawSignature] : [],
|
|
572
|
+
rank: index,
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
return [...candidates.values()].sort((left, right) => left.rank - right.rank);
|
|
576
|
+
}
|
|
577
|
+
collectMemberCandidates(results) {
|
|
578
|
+
const candidates = new Map();
|
|
579
|
+
results.forEach((result, index) => {
|
|
580
|
+
if (result.declarationForm === 'class') {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const ownerClassName = normalizeOwnerClassName(result);
|
|
584
|
+
if (!ownerClassName) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const key = `${result.framework}|${result.packageName}|${ownerClassName}|${result.memberName}`;
|
|
588
|
+
const existing = candidates.get(key);
|
|
589
|
+
if (existing) {
|
|
590
|
+
if (result.objcSelector) {
|
|
591
|
+
existing.selectorsSet.add(result.objcSelector);
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
candidates.set(key, {
|
|
596
|
+
framework: result.framework,
|
|
597
|
+
packageName: result.packageName,
|
|
598
|
+
className: ownerClassName,
|
|
599
|
+
target: result.target,
|
|
600
|
+
konanHome: result.konanHome,
|
|
601
|
+
declarationRawSignatures: [],
|
|
602
|
+
memberName: result.memberName,
|
|
603
|
+
selectors: [],
|
|
604
|
+
selectorsSet: new Set(result.objcSelector ? [result.objcSelector] : []),
|
|
605
|
+
rank: index,
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
return [...candidates.values()]
|
|
609
|
+
.map((candidate) => ({
|
|
610
|
+
framework: candidate.framework,
|
|
611
|
+
packageName: candidate.packageName,
|
|
612
|
+
className: candidate.className,
|
|
613
|
+
target: candidate.target,
|
|
614
|
+
konanHome: candidate.konanHome,
|
|
615
|
+
declarationRawSignatures: candidate.declarationRawSignatures,
|
|
616
|
+
memberName: candidate.memberName,
|
|
617
|
+
selectors: [...candidate.selectorsSet],
|
|
618
|
+
rank: candidate.rank,
|
|
619
|
+
}))
|
|
620
|
+
.sort((left, right) => left.rank - right.rank);
|
|
621
|
+
}
|
|
622
|
+
collectTopLevelCandidates(results) {
|
|
623
|
+
const candidates = new Map();
|
|
624
|
+
results.forEach((result, index) => {
|
|
625
|
+
if (!isTopLevelLookupResult(result) || !result.rawSignature) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const key = `${result.framework}|${result.packageName}|${result.memberName}`;
|
|
629
|
+
const existing = candidates.get(key);
|
|
630
|
+
if (existing) {
|
|
631
|
+
existing.rawSignatureSet.add(result.rawSignature);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
candidates.set(key, {
|
|
635
|
+
framework: result.framework,
|
|
636
|
+
packageName: result.packageName,
|
|
637
|
+
symbolName: result.memberName,
|
|
638
|
+
target: result.target,
|
|
639
|
+
konanHome: result.konanHome,
|
|
640
|
+
rawSignatures: [],
|
|
641
|
+
rawSignatureSet: new Set([result.rawSignature]),
|
|
642
|
+
rank: index,
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
return [...candidates.values()]
|
|
646
|
+
.map((candidate) => ({
|
|
647
|
+
framework: candidate.framework,
|
|
648
|
+
packageName: candidate.packageName,
|
|
649
|
+
symbolName: candidate.symbolName,
|
|
650
|
+
target: candidate.target,
|
|
651
|
+
konanHome: candidate.konanHome,
|
|
652
|
+
rawSignatures: [...candidate.rawSignatureSet].sort((left, right) => left.localeCompare(right)),
|
|
653
|
+
rank: candidate.rank,
|
|
654
|
+
}))
|
|
655
|
+
.sort((left, right) => left.rank - right.rank);
|
|
656
|
+
}
|
|
657
|
+
async loadClassCard(owner) {
|
|
658
|
+
const metadataLines = await this.getMetadataLines(owner);
|
|
659
|
+
return buildClassCardFromMetadata({
|
|
660
|
+
metadataLines,
|
|
661
|
+
framework: owner.framework,
|
|
662
|
+
packageName: owner.packageName,
|
|
663
|
+
className: owner.className,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
async loadTopLevelMemberCard(symbol) {
|
|
667
|
+
if (symbol.rawSignatures.length === 0) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
const metadataLines = await this.getMetadataLines(symbol);
|
|
671
|
+
return buildTopLevelMemberCardFromMetadata({
|
|
672
|
+
metadataLines,
|
|
673
|
+
framework: symbol.framework,
|
|
674
|
+
packageName: symbol.packageName,
|
|
675
|
+
symbolName: symbol.symbolName,
|
|
676
|
+
rawSignatures: symbol.rawSignatures,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
buildMemberCard(classCard, memberName) {
|
|
680
|
+
const entries = [
|
|
681
|
+
...classCard.constructors,
|
|
682
|
+
...classCard.members,
|
|
683
|
+
...classCard.classMembers,
|
|
684
|
+
].filter((entry) => entry.name === memberName);
|
|
685
|
+
if (entries.length === 0) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
framework: classCard.framework,
|
|
690
|
+
packageName: classCard.packageName,
|
|
691
|
+
ownerName: classCard.name,
|
|
692
|
+
ownerQualifiedName: classCard.qualifiedName,
|
|
693
|
+
detailLevel: 'full',
|
|
694
|
+
ownerKind: classCard.kind,
|
|
695
|
+
ownerKotlinSignature: classCard.kotlinSignature,
|
|
696
|
+
extendsType: classCard.extendsType,
|
|
697
|
+
implementsTypes: classCard.implementsTypes,
|
|
698
|
+
name: memberName,
|
|
699
|
+
requiredImports: uniqueSorted(entries.flatMap((entry) => entry.requiredImports)),
|
|
700
|
+
totalEntries: entries.length,
|
|
701
|
+
omittedEntries: 0,
|
|
702
|
+
entries,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async getMetadataLines(context) {
|
|
706
|
+
const cacheKey = `${context.konanHome}|${context.target}|${context.framework}`;
|
|
707
|
+
const cached = this.metadataCache.get(cacheKey);
|
|
708
|
+
if (cached) {
|
|
709
|
+
return cached;
|
|
710
|
+
}
|
|
711
|
+
const pending = this.loadMetadataLines(context);
|
|
712
|
+
this.metadataCache.set(cacheKey, pending);
|
|
713
|
+
return pending;
|
|
714
|
+
}
|
|
715
|
+
async loadMetadataLines(context) {
|
|
716
|
+
const installation = await this.resolveInstallation(undefined, context.konanHome);
|
|
717
|
+
const frameworks = await listFrameworkSources(installation, context.target, [context.framework]);
|
|
718
|
+
const framework = frameworks[0];
|
|
719
|
+
if (!framework) {
|
|
720
|
+
throw new Error(`Framework ${context.framework} is not available for ${context.target} in ${context.konanHome}`);
|
|
721
|
+
}
|
|
722
|
+
return dumpFrameworkMetadata(installation, framework);
|
|
723
|
+
}
|
|
724
|
+
async readStateFile() {
|
|
725
|
+
try {
|
|
726
|
+
const raw = await readFile(this.config.metadataPath, 'utf8');
|
|
727
|
+
return JSON.parse(raw);
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async writeStateFile(state) {
|
|
734
|
+
await mkdir(this.config.cacheDir, { recursive: true });
|
|
735
|
+
await writeFile(this.config.metadataPath, JSON.stringify(state, null, 2), 'utf8');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function normalizeOwnerClassName(result) {
|
|
739
|
+
if (!result.className) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
let owner = result.className;
|
|
743
|
+
if (owner.endsWith('.Companion')) {
|
|
744
|
+
owner = owner.slice(0, -'.Companion'.length);
|
|
745
|
+
}
|
|
746
|
+
if (result.isMetaClass && owner.endsWith('Meta')) {
|
|
747
|
+
owner = owner.slice(0, -'Meta'.length);
|
|
748
|
+
}
|
|
749
|
+
return owner;
|
|
750
|
+
}
|
|
751
|
+
function matchesClassQuery(query, candidate) {
|
|
752
|
+
const normalizedQuery = normalizeText(query);
|
|
753
|
+
return buildOwnerQueryForms(candidate).includes(normalizedQuery);
|
|
754
|
+
}
|
|
755
|
+
function matchesMemberQuery(query, candidate) {
|
|
756
|
+
const normalizedQuery = normalizeText(query);
|
|
757
|
+
const normalizedSelectorQuery = normalizeSelector(query) ?? normalizedQuery;
|
|
758
|
+
const compactQuery = compactSelector(query) ?? normalizedQuery;
|
|
759
|
+
const selectorForms = new Set();
|
|
760
|
+
for (const selector of candidate.selectors) {
|
|
761
|
+
selectorForms.add(normalizeSelector(selector) ?? normalizeText(selector));
|
|
762
|
+
selectorForms.add(compactSelector(selector) ?? normalizeText(selector));
|
|
763
|
+
}
|
|
764
|
+
if (query.includes('.')) {
|
|
765
|
+
const dotIndex = query.lastIndexOf('.');
|
|
766
|
+
const ownerQuery = normalizeText(query.slice(0, dotIndex));
|
|
767
|
+
const memberQuery = normalizeText(query.slice(dotIndex + 1));
|
|
768
|
+
return buildOwnerQueryForms(candidate).includes(ownerQuery)
|
|
769
|
+
&& (memberQuery === normalizeText(candidate.memberName)
|
|
770
|
+
|| selectorForms.has(normalizeSelector(memberQuery) ?? memberQuery)
|
|
771
|
+
|| selectorForms.has(compactSelector(memberQuery) ?? memberQuery));
|
|
772
|
+
}
|
|
773
|
+
return (normalizedQuery === normalizeText(candidate.memberName)
|
|
774
|
+
|| selectorForms.has(normalizedSelectorQuery)
|
|
775
|
+
|| selectorForms.has(compactQuery));
|
|
776
|
+
}
|
|
777
|
+
function buildOwnerQueryForms(candidate) {
|
|
778
|
+
const simpleName = candidate.className.split('.').at(-1) ?? candidate.className;
|
|
779
|
+
return uniqueSorted([
|
|
780
|
+
candidate.className,
|
|
781
|
+
simpleName,
|
|
782
|
+
`${candidate.packageName}.${candidate.className}`,
|
|
783
|
+
].map((value) => normalizeText(value)));
|
|
784
|
+
}
|
|
785
|
+
function matchesTopLevelQuery(query, candidate) {
|
|
786
|
+
const normalizedQuery = normalizeText(query);
|
|
787
|
+
return buildTopLevelQueryForms(candidate).includes(normalizedQuery);
|
|
788
|
+
}
|
|
789
|
+
function buildTopLevelQueryForms(candidate) {
|
|
790
|
+
return uniqueSorted([
|
|
791
|
+
candidate.symbolName,
|
|
792
|
+
`${candidate.packageName}.${candidate.symbolName}`,
|
|
793
|
+
`${candidate.framework}.${candidate.symbolName}`,
|
|
794
|
+
].map((value) => normalizeText(value)));
|
|
795
|
+
}
|
|
796
|
+
function isTopLevelLookupResult(result) {
|
|
797
|
+
return result.className === null && result.declarationForm === 'direct_member';
|
|
798
|
+
}
|
|
799
|
+
function shouldExpandLookupCandidateWindow(query, queryKind, resolution) {
|
|
800
|
+
if (queryKind === 'class' || !query.includes('.')) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
const memberQuery = query.slice(query.lastIndexOf('.') + 1).trim();
|
|
804
|
+
if (!memberQuery || memberQuery.includes(':')) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
return resolution.kind === 'ambiguous' || resolution.kind === 'not_found';
|
|
808
|
+
}
|
|
809
|
+
function looksLikeClassQuery(query) {
|
|
810
|
+
const trimmed = query.trim();
|
|
811
|
+
return trimmed.length > 0 && /^[A-Z]/.test(trimmed);
|
|
812
|
+
}
|
|
813
|
+
function pickPreferredTarget(targets) {
|
|
814
|
+
const preferredTargets = ['ios_simulator_arm64', 'ios_arm64', 'ios_x64'];
|
|
815
|
+
for (const preferredTarget of preferredTargets) {
|
|
816
|
+
if (targets.includes(preferredTarget)) {
|
|
817
|
+
return preferredTarget;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return [...targets].sort((left, right) => left.localeCompare(right))[0] ?? '';
|
|
821
|
+
}
|
|
822
|
+
//# sourceMappingURL=lookupService.js.map
|