@suss/adapter-ruby 0.3.2-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/index.cjs ADDED
@@ -0,0 +1,927 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ancestryOf: () => ancestryOf,
34
+ createFileCache: () => createFileCache,
35
+ discoverUnits: () => discoverUnits,
36
+ emitEntryFact: () => emitEntryFact,
37
+ extractRubyProject: () => extractRubyProject,
38
+ findRubyFiles: () => findRubyFiles,
39
+ graphqlTypeNameFromQualified: () => graphqlTypeNameFromQualified,
40
+ inheritedStatements: () => inheritedStatements,
41
+ methodInAncestry: () => methodInAncestry,
42
+ parseRuby: () => parseRuby,
43
+ qualifyConstantRef: () => qualifyConstantRef,
44
+ reachDefinition: () => reachDefinition,
45
+ resolveConstantFile: () => resolveConstantFile,
46
+ typeShapeFromNode: () => typeShapeFromNode,
47
+ underscoreConstantPath: () => underscoreConstantPath,
48
+ unitKey: () => unitKey,
49
+ walkClasses: () => walkClasses,
50
+ walkDefinitions: () => walkDefinitions
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // ../../../node_modules/tsup/assets/cjs_shims.js
55
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
56
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
57
+
58
+ // src/ast.ts
59
+ function rangeOf(node) {
60
+ return {
61
+ start: node.startPosition.row + 1,
62
+ end: node.endPosition.row + 1
63
+ };
64
+ }
65
+ function field(node, name) {
66
+ return node.childForFieldName(name);
67
+ }
68
+ function bodyStatements(body) {
69
+ return body.namedChildren.filter((child) => child !== null);
70
+ }
71
+ function symbolValue(node) {
72
+ return node.type === "simple_symbol" ? node.text.slice(1) : null;
73
+ }
74
+ function instanceMethodsByName(body) {
75
+ const methods = /* @__PURE__ */ new Map();
76
+ for (const stmt of bodyStatements(body)) {
77
+ if (stmt.type !== "method") {
78
+ continue;
79
+ }
80
+ const name = field(stmt, "name")?.text;
81
+ if (name !== void 0) {
82
+ methods.set(name, stmt);
83
+ }
84
+ }
85
+ return methods;
86
+ }
87
+ function bareCallArgumentGroups(body, name) {
88
+ const groups = [];
89
+ for (const stmt of bodyStatements(body)) {
90
+ if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
91
+ continue;
92
+ }
93
+ if (field(stmt, "method")?.text !== name) {
94
+ continue;
95
+ }
96
+ const args = field(stmt, "arguments");
97
+ groups.push(args === null ? [] : bodyStatements(args));
98
+ }
99
+ return groups;
100
+ }
101
+ function methodHasStatements(method) {
102
+ const body = field(method, "body");
103
+ if (body === null) {
104
+ return false;
105
+ }
106
+ if (body.type !== "body_statement") {
107
+ return true;
108
+ }
109
+ return bodyStatements(body).length > 0;
110
+ }
111
+ function hashKeySymbolName(node) {
112
+ return node.type === "hash_key_symbol" ? node.text : null;
113
+ }
114
+ function booleanLiteralValue(node) {
115
+ if (node.type === "true") {
116
+ return true;
117
+ }
118
+ if (node.type === "false") {
119
+ return false;
120
+ }
121
+ return null;
122
+ }
123
+ function readCallArgs(argumentList) {
124
+ const positional = [];
125
+ const keyword = {};
126
+ if (argumentList === null) {
127
+ return { positional, keyword };
128
+ }
129
+ for (const child of bodyStatements(argumentList)) {
130
+ if (child.type === "pair") {
131
+ const keyNode = field(child, "key");
132
+ const valueNode = field(child, "value");
133
+ const name = keyNode !== null ? hashKeySymbolName(keyNode) : null;
134
+ if (name !== null && valueNode !== null) {
135
+ keyword[name] = valueNode;
136
+ }
137
+ continue;
138
+ }
139
+ positional.push(child);
140
+ }
141
+ return { positional, keyword };
142
+ }
143
+
144
+ // src/constantPath.ts
145
+ var import_node_fs = __toESM(require("fs"), 1);
146
+ var import_node_path = __toESM(require("path"), 1);
147
+ function underscoreConstantPath(qualifiedName) {
148
+ return qualifiedName.replaceAll("::", "/").replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
149
+ }
150
+ var PATH_CONVENTIONS = {
151
+ railsUnderscore: underscoreConstantPath
152
+ };
153
+ function resolveConstantFile(root, qualifiedName, convention) {
154
+ const relative = PATH_CONVENTIONS[convention](qualifiedName);
155
+ const file = import_node_path.default.join(root, `${relative}.rb`);
156
+ return import_node_fs.default.existsSync(file) ? file : null;
157
+ }
158
+
159
+ // src/scope.ts
160
+ function qualifyConstantRef(node, nesting) {
161
+ if (node.type === "scope_resolution") {
162
+ return node.text;
163
+ }
164
+ if (node.type === "constant") {
165
+ const innermost = nesting[0] ?? null;
166
+ return innermost === null ? node.text : `${innermost}::${node.text}`;
167
+ }
168
+ return null;
169
+ }
170
+ function shadowingClassFor(node, nesting, knownClasses) {
171
+ if (node.type !== "constant") {
172
+ return null;
173
+ }
174
+ for (const level of nesting) {
175
+ const candidate = `${level}::${node.text}`;
176
+ if (knownClasses.has(candidate)) {
177
+ return candidate;
178
+ }
179
+ }
180
+ return knownClasses.has(node.text) ? node.text : null;
181
+ }
182
+ function walkClasses(root, visit) {
183
+ walkDefinitions(root, (info) => {
184
+ if (info.kind === "class") {
185
+ visit(info);
186
+ }
187
+ });
188
+ }
189
+ function walkDefinitions(root, visit) {
190
+ walkBody(bodyStatements(root), [], visit);
191
+ }
192
+ function walkBody(statements, nesting, visit) {
193
+ for (const stmt of statements) {
194
+ if (stmt.type === "class") {
195
+ visitClass(stmt, nesting, visit);
196
+ continue;
197
+ }
198
+ if (stmt.type === "module") {
199
+ visitModule(stmt, nesting, visit);
200
+ }
201
+ }
202
+ }
203
+ function ownIdentity(nameNode, outerNesting) {
204
+ const qualifiedName = qualifyConstantRef(nameNode, outerNesting);
205
+ if (qualifiedName === null) {
206
+ return null;
207
+ }
208
+ return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
209
+ }
210
+ function visitClass(node, nesting, visit) {
211
+ const nameNode = field(node, "name");
212
+ const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
213
+ if (identity === null) {
214
+ return;
215
+ }
216
+ const superclassWrapper = field(node, "superclass");
217
+ const superclassExpr = superclassWrapper?.namedChild(0) ?? null;
218
+ const superclassQualifiedName = superclassExpr !== null ? qualifyConstantRef(superclassExpr, nesting) : null;
219
+ const bodyNode = field(node, "body");
220
+ visit({
221
+ node,
222
+ kind: "class",
223
+ qualifiedName: identity.qualifiedName,
224
+ superclassQualifiedName,
225
+ bodyNode,
226
+ bodyNesting: identity.bodyNesting
227
+ });
228
+ if (bodyNode !== null) {
229
+ walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
230
+ }
231
+ }
232
+ function visitModule(node, nesting, visit) {
233
+ const nameNode = field(node, "name");
234
+ const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
235
+ if (identity === null) {
236
+ return;
237
+ }
238
+ const bodyNode = field(node, "body");
239
+ visit({
240
+ node,
241
+ kind: "module",
242
+ qualifiedName: identity.qualifiedName,
243
+ superclassQualifiedName: null,
244
+ bodyNode,
245
+ bodyNesting: identity.bodyNesting
246
+ });
247
+ if (bodyNode !== null) {
248
+ walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
249
+ }
250
+ }
251
+ function stripTypeSuffixName(qualifiedName) {
252
+ const shortName = qualifiedName.split("::").at(-1) ?? qualifiedName;
253
+ return shortName.endsWith("Type") ? shortName.slice(0, -4) : shortName;
254
+ }
255
+ var TYPE_NAME_CONVENTIONS = {
256
+ stripTypeSuffix: stripTypeSuffixName
257
+ };
258
+ function graphqlTypeNameFromQualified(qualifiedName, convention) {
259
+ return TYPE_NAME_CONVENTIONS[convention](qualifiedName);
260
+ }
261
+
262
+ // src/ancestry.ts
263
+ var INCLUDE_CALL = "include";
264
+ var PREPEND_CALL = "prepend";
265
+ var DEFINE_METHOD_CALL = "define_method";
266
+ async function reachDefinition(qualifiedName, lookup) {
267
+ const filePath = resolveConstantFile(
268
+ lookup.root,
269
+ qualifiedName,
270
+ lookup.pathConvention
271
+ );
272
+ const fileRoot = filePath === null ? null : await lookup.parsedFile(filePath);
273
+ if (fileRoot === null) {
274
+ return null;
275
+ }
276
+ const all = [];
277
+ walkDefinitions(fileRoot, (info) => all.push(info));
278
+ const matches = all.filter((info) => info.qualifiedName === qualifiedName);
279
+ if (matches.length === 0) {
280
+ return null;
281
+ }
282
+ const knownClasses = new Set(all.map((info) => info.qualifiedName));
283
+ return matches.map((info) => ({ info, knownClasses }));
284
+ }
285
+ function ancestryOf(name, blocks, lookup) {
286
+ return chainOf(
287
+ { type: "bodies", name, blocks: [...blocks] },
288
+ lookup,
289
+ /* @__PURE__ */ new Set([name])
290
+ );
291
+ }
292
+ async function chainOf(self, lookup, active) {
293
+ const superChain = await superclassChain(self, lookup, active);
294
+ const present = new Set(superChain.map((ancestor) => ancestor.name));
295
+ present.add(self.name);
296
+ const mixins = await mixinChain(self, INCLUDE_CALL, lookup, active, present);
297
+ const prepends = await mixinChain(
298
+ self,
299
+ PREPEND_CALL,
300
+ lookup,
301
+ active,
302
+ present
303
+ );
304
+ return [...prepends, self, ...mixins, ...superChain];
305
+ }
306
+ async function superclassChain(self, lookup, active) {
307
+ const superclass = superclassOf(self.blocks);
308
+ if (superclass === null || lookup.ancestryRootClassNames.includes(superclass) || active.has(superclass)) {
309
+ return [];
310
+ }
311
+ return chainFor(superclass, lookup, active);
312
+ }
313
+ async function mixinChain(self, callName, lookup, active, present) {
314
+ let chain = [];
315
+ for (const ref of moduleRefs(self.blocks, callName)) {
316
+ if (present.has(ref.name)) {
317
+ continue;
318
+ }
319
+ if (!ref.readable) {
320
+ present.add(ref.name);
321
+ chain = [{ type: "unfollowed", name: ref.name }, ...chain];
322
+ continue;
323
+ }
324
+ const inserted = (await chainFor(ref.name, lookup, active)).filter(
325
+ (ancestor) => !present.has(ancestor.name)
326
+ );
327
+ for (const ancestor of inserted) {
328
+ present.add(ancestor.name);
329
+ }
330
+ chain = [...inserted, ...chain];
331
+ }
332
+ return chain;
333
+ }
334
+ async function chainFor(qualifiedName, lookup, active) {
335
+ const blocks = await reachDefinition(qualifiedName, lookup);
336
+ if (blocks === null) {
337
+ return [{ type: "unfollowed", name: qualifiedName }];
338
+ }
339
+ return chainOf(
340
+ { type: "bodies", name: qualifiedName, blocks },
341
+ lookup,
342
+ /* @__PURE__ */ new Set([...active, qualifiedName])
343
+ );
344
+ }
345
+ function moduleRefs(blocks, callName) {
346
+ const refs = [];
347
+ for (const block of blocks) {
348
+ if (block.info.bodyNode === null) {
349
+ continue;
350
+ }
351
+ for (const group of bareCallArgumentGroups(block.info.bodyNode, callName)) {
352
+ for (const arg of [...group].reverse()) {
353
+ const qualified = qualifyConstantRef(arg, block.info.bodyNesting);
354
+ refs.push(
355
+ qualified !== null ? { name: qualified, readable: true } : { name: arg.text, readable: false }
356
+ );
357
+ }
358
+ }
359
+ }
360
+ return refs;
361
+ }
362
+ function superclassOf(blocks) {
363
+ for (const block of blocks) {
364
+ if (block.info.superclassQualifiedName !== null) {
365
+ return block.info.superclassQualifiedName;
366
+ }
367
+ }
368
+ return null;
369
+ }
370
+ function methodInAncestry(ancestry, name) {
371
+ for (const entry of ancestry) {
372
+ if (entry.type === "unfollowed") {
373
+ return {
374
+ type: "unsettled",
375
+ reason: `inherited from ${entry.name}, which this run did not read`
376
+ };
377
+ }
378
+ const found = definitionIn(entry.blocks, name);
379
+ if (found.method !== null) {
380
+ return { type: "found", method: found.method };
381
+ }
382
+ if (found.dynamic) {
383
+ return {
384
+ type: "unsettled",
385
+ reason: "defined with define_method, which this reader does not follow"
386
+ };
387
+ }
388
+ }
389
+ return { type: "none" };
390
+ }
391
+ function definitionIn(blocks, name) {
392
+ let method = null;
393
+ let dynamic = false;
394
+ for (const block of blocks) {
395
+ const body = block.info.bodyNode;
396
+ if (body === null) {
397
+ continue;
398
+ }
399
+ method = instanceMethodsByName(body).get(name) ?? method;
400
+ dynamic ||= bareCallArgumentGroups(body, DEFINE_METHOD_CALL).length > 0;
401
+ }
402
+ return { method, dynamic };
403
+ }
404
+ function inheritedStatements(ancestry) {
405
+ const statements = [];
406
+ for (const entry of [...ancestry].reverse()) {
407
+ if (entry.type !== "bodies") {
408
+ continue;
409
+ }
410
+ for (const block of entry.blocks) {
411
+ const body = block.info.bodyNode;
412
+ if (body === null) {
413
+ continue;
414
+ }
415
+ for (const statement of bodyStatements(body)) {
416
+ statements.push({ block, statement });
417
+ }
418
+ }
419
+ }
420
+ return statements;
421
+ }
422
+
423
+ // src/discovery.ts
424
+ var import_behavioral_ir = require("@suss/behavioral-ir");
425
+ var import_extractor = require("@suss/extractor");
426
+
427
+ // src/typeShape.ts
428
+ function scalarLookupName(qualifiedName, prefixes) {
429
+ for (const prefix of prefixes) {
430
+ if (qualifiedName.startsWith(prefix)) {
431
+ return qualifiedName.slice(prefix.length);
432
+ }
433
+ }
434
+ return qualifiedName;
435
+ }
436
+ function typeShapeFromNode(node, ctx) {
437
+ if (node.type === "array") {
438
+ const inner = node.namedChild(0);
439
+ if (inner === null) {
440
+ return null;
441
+ }
442
+ const items = typeShapeFromNode(inner, ctx);
443
+ return items === null ? null : { type: "array", items };
444
+ }
445
+ if (node.type === "constant") {
446
+ const shadow = shadowingClassFor(node, ctx.nesting, ctx.knownClasses);
447
+ if (shadow !== null) {
448
+ return {
449
+ type: "ref",
450
+ name: graphqlTypeNameFromQualified(shadow, ctx.typeNameConvention)
451
+ };
452
+ }
453
+ const bareScalar = ctx.scalars[node.text];
454
+ if (bareScalar !== void 0) {
455
+ return bareScalar;
456
+ }
457
+ const qualified = qualifyConstantRef(node, ctx.nesting);
458
+ return qualified === null ? null : {
459
+ type: "ref",
460
+ name: graphqlTypeNameFromQualified(qualified, ctx.typeNameConvention)
461
+ };
462
+ }
463
+ if (node.type === "scope_resolution") {
464
+ const qualified = node.text;
465
+ const scalar = ctx.scalars[scalarLookupName(qualified, ctx.scalarNamePrefixes)];
466
+ if (scalar !== void 0) {
467
+ return scalar;
468
+ }
469
+ return {
470
+ type: "ref",
471
+ name: graphqlTypeNameFromQualified(qualified, ctx.typeNameConvention)
472
+ };
473
+ }
474
+ return null;
475
+ }
476
+
477
+ // src/discovery.ts
478
+ function createFileCache(parse, readFile) {
479
+ const trees = /* @__PURE__ */ new Map();
480
+ return {
481
+ async get(absPath) {
482
+ const cached = trees.get(absPath);
483
+ if (cached !== void 0) {
484
+ return cached;
485
+ }
486
+ const source = readFile(absPath);
487
+ const tree = source !== null ? await parse(source) : null;
488
+ trees.set(absPath, tree);
489
+ return tree;
490
+ }
491
+ };
492
+ }
493
+ function fieldReadContext(pattern, cache) {
494
+ return {
495
+ pattern,
496
+ cache,
497
+ lookup: {
498
+ root: pattern.root,
499
+ pathConvention: pattern.pathConvention,
500
+ ancestryRootClassNames: pattern.ancestryRootClassNames,
501
+ parsedFile: (absPath) => cache.get(absPath)
502
+ }
503
+ };
504
+ }
505
+ function typeContext(scope, pattern) {
506
+ return {
507
+ nesting: scope.nesting,
508
+ knownClasses: scope.knownClasses,
509
+ scalars: pattern.scalars,
510
+ scalarNamePrefixes: pattern.scalarNamePrefixes,
511
+ typeNameConvention: pattern.typeNameConvention
512
+ };
513
+ }
514
+ async function discoverUnits(root, options) {
515
+ const classes = [];
516
+ walkClasses(root, (info) => classes.push(info));
517
+ const knownClasses = new Set(classes.map((info) => info.qualifiedName));
518
+ const units = [];
519
+ for (const info of classes) {
520
+ const ownBlocks = classes.filter((other) => other.qualifiedName === info.qualifiedName).map((other) => ({ info: other, knownClasses }));
521
+ for (const pack of options.packs) {
522
+ for (const pattern of pack.discovery) {
523
+ units.push(
524
+ ...await unitsFor(pattern, pack, info, ownBlocks, options)
525
+ );
526
+ }
527
+ }
528
+ }
529
+ return units;
530
+ }
531
+ function unitsFor(pattern, pack, info, ownBlocks, options) {
532
+ const table = {
533
+ graphqlObjectFields: (p) => graphqlObjectFieldUnits(p, pack, info, ownBlocks, options)
534
+ };
535
+ return (0, import_behavioral_ir.dispatchByType)(table, pattern);
536
+ }
537
+ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, options) {
538
+ if (info.superclassQualifiedName === null || !pattern.baseClassNames.includes(info.superclassQualifiedName) || info.bodyNode === null) {
539
+ return [];
540
+ }
541
+ const typeName = graphqlTypeNameFromQualified(
542
+ info.qualifiedName,
543
+ pattern.typeNameConvention
544
+ );
545
+ const ctx = fieldReadContext(pattern, options.cache);
546
+ const knownClasses = ownBlocks[0]?.knownClasses ?? /* @__PURE__ */ new Set();
547
+ const scope = { nesting: info.bodyNesting, knownClasses };
548
+ const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, ctx.lookup);
549
+ const declsByName = /* @__PURE__ */ new Map();
550
+ for (const stmt of bodyStatements(info.bodyNode)) {
551
+ const decl = await readFieldCall(stmt, scope, ctx, ancestry);
552
+ if (decl !== null) {
553
+ declsByName.set(decl.fieldName, decl);
554
+ }
555
+ }
556
+ return [...declsByName.values()].map(
557
+ (decl) => buildFieldUnit(pack, typeName, decl, options.filePath)
558
+ );
559
+ }
560
+ function bodyOfMethod(method) {
561
+ return {
562
+ bodyContent: methodHasStatements(method) ? "statements" : "empty",
563
+ readings: []
564
+ };
565
+ }
566
+ var NO_METHOD_BEHIND_IT = {
567
+ bodyContent: "absent",
568
+ readings: []
569
+ };
570
+ function methodNotSettled(reason, range) {
571
+ return { readings: [(0, import_extractor.unreadableReading)(reason, range)] };
572
+ }
573
+ function bodyFromLookup(found, range, subject, nothingThere) {
574
+ const table = {
575
+ found: (lookup) => bodyOfMethod(lookup.method),
576
+ unsettled: (lookup) => methodNotSettled(
577
+ `${subject} could be answered by a method ${lookup.reason}, so whether one exists was not settled here`,
578
+ range
579
+ ),
580
+ none: () => nothingThere
581
+ };
582
+ return (0, import_behavioral_ir.dispatchByType)(table, found);
583
+ }
584
+ async function readFieldCall(stmt, scope, ctx, ancestry) {
585
+ if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
586
+ return null;
587
+ }
588
+ if (field(stmt, "method")?.text !== ctx.pattern.fieldCallName) {
589
+ return null;
590
+ }
591
+ const callArgs = readCallArgs(field(stmt, "arguments"));
592
+ const nameArg = callArgs.positional[0];
593
+ const symbol = nameArg !== void 0 ? symbolValue(nameArg) : null;
594
+ if (symbol === null) {
595
+ return null;
596
+ }
597
+ const read = await readFieldShape(
598
+ symbol,
599
+ callArgs,
600
+ scope,
601
+ ctx,
602
+ ancestry,
603
+ rangeOf(stmt)
604
+ );
605
+ return {
606
+ fieldName: resolvedName(symbol, callArgs, ctx.pattern),
607
+ node: stmt,
608
+ contract: read.contract,
609
+ body: read.body
610
+ };
611
+ }
612
+ function wiringReference(callArgs, wiringKeywords) {
613
+ for (const keyword of wiringKeywords) {
614
+ const ref = callArgs.keyword[keyword];
615
+ if (ref !== void 0) {
616
+ return ref;
617
+ }
618
+ }
619
+ return null;
620
+ }
621
+ async function readFieldShape(symbol, callArgs, scope, ctx, ancestry, range) {
622
+ const oneHopRef = wiringReference(callArgs, ctx.pattern.wiringKeywords);
623
+ if (oneHopRef !== null) {
624
+ return readWiredClass(oneHopRef, scope, ctx);
625
+ }
626
+ return {
627
+ contract: literalContract(callArgs, scope, ctx),
628
+ body: bodyFromLookup(
629
+ methodInAncestry(ancestry, symbol),
630
+ range,
631
+ "This field",
632
+ NO_METHOD_BEHIND_IT
633
+ )
634
+ };
635
+ }
636
+ function literalContract(callArgs, scope, ctx) {
637
+ const typeArg = callArgs.positional[1];
638
+ if (typeArg === void 0) {
639
+ return null;
640
+ }
641
+ const returnType = typeShapeFromNode(
642
+ typeArg,
643
+ typeContext(scope, ctx.pattern)
644
+ );
645
+ return returnType === null ? null : { returnType, args: [] };
646
+ }
647
+ async function readWiredClass(ref, scope, ctx) {
648
+ const range = rangeOf(ref);
649
+ const targetQualifiedName = qualifyConstantRef(ref, scope.nesting);
650
+ if (targetQualifiedName === null) {
651
+ return {
652
+ contract: null,
653
+ body: methodNotSettled(
654
+ `This field is wired to ${ref.text}, which is not a constant path this reader follows, so nothing about what it does was read here`,
655
+ range
656
+ )
657
+ };
658
+ }
659
+ const reached = await reachDefinition(targetQualifiedName, ctx.lookup);
660
+ if (reached === null) {
661
+ return {
662
+ contract: null,
663
+ body: methodNotSettled(
664
+ `This field is wired to ${targetQualifiedName}, which this run did not read, so nothing about what it does was read here`,
665
+ range
666
+ )
667
+ };
668
+ }
669
+ const ancestry = await ancestryOf(targetQualifiedName, reached, ctx.lookup);
670
+ return {
671
+ contract: readClassContract(ancestry, ctx.pattern),
672
+ body: bodyFromLookup(
673
+ methodInAncestry(ancestry, ctx.pattern.resolverMethodName),
674
+ range,
675
+ `This field's ${targetQualifiedName}`,
676
+ methodNotSettled(
677
+ `This field is wired to ${targetQualifiedName}, which defines no ${ctx.pattern.resolverMethodName} method anywhere in its ancestry, so nothing about what it does was read here`,
678
+ range
679
+ )
680
+ )
681
+ };
682
+ }
683
+ function readTypeCall(callArgs, scope, pattern, out) {
684
+ const typeArg = callArgs.positional[0];
685
+ if (typeArg !== void 0) {
686
+ out.typeCallShape = typeShapeFromNode(typeArg, typeContext(scope, pattern));
687
+ }
688
+ }
689
+ function readPayloadFieldCall(callArgs, scope, pattern, out) {
690
+ const nameArg = callArgs.positional[0];
691
+ const symbol = nameArg !== void 0 ? symbolValue(nameArg) : null;
692
+ const typeArg = callArgs.positional[1];
693
+ if (symbol === null || typeArg === void 0) {
694
+ return;
695
+ }
696
+ const name = resolvedName(symbol, callArgs, pattern);
697
+ out.fieldProperties[name] = typeShapeFromNode(
698
+ typeArg,
699
+ typeContext(scope, pattern)
700
+ ) ?? { type: "unknown" };
701
+ }
702
+ function readArgumentCall(callArgs, scope, pattern, out) {
703
+ const nameArg = callArgs.positional[0];
704
+ const symbol = nameArg !== void 0 ? symbolValue(nameArg) : null;
705
+ if (symbol === null) {
706
+ return;
707
+ }
708
+ const typeArg = callArgs.positional[1];
709
+ const shape = typeArg !== void 0 ? typeShapeFromNode(typeArg, typeContext(scope, pattern)) : null;
710
+ const requiredNode = callArgs.keyword[pattern.requiredKeyword];
711
+ const required = requiredNode !== void 0 ? booleanLiteralValue(requiredNode) ?? pattern.requiredDefault : pattern.requiredDefault;
712
+ const name = resolvedName(symbol, callArgs, pattern);
713
+ out.args.set(name, {
714
+ name,
715
+ type: shape ?? { type: "unknown" },
716
+ required,
717
+ typeText: typeArg?.text ?? null
718
+ });
719
+ }
720
+ function classCallHandlers(pattern) {
721
+ return {
722
+ [pattern.typeCallName]: readTypeCall,
723
+ [pattern.fieldCallName]: readPayloadFieldCall,
724
+ [pattern.argumentCallName]: readArgumentCall
725
+ };
726
+ }
727
+ function readClassContract(ancestry, pattern) {
728
+ const out = {
729
+ typeCallShape: null,
730
+ fieldProperties: {},
731
+ args: /* @__PURE__ */ new Map()
732
+ };
733
+ const handlers = classCallHandlers(pattern);
734
+ for (const { block, statement } of inheritedStatements(ancestry)) {
735
+ if (statement.type !== "call" || field(statement, "receiver") !== null) {
736
+ continue;
737
+ }
738
+ const method = field(statement, "method")?.text;
739
+ const handler = method !== void 0 ? handlers[method] : void 0;
740
+ if (handler === void 0) {
741
+ continue;
742
+ }
743
+ const scope = {
744
+ nesting: block.info.bodyNesting,
745
+ knownClasses: block.knownClasses
746
+ };
747
+ handler(readCallArgs(field(statement, "arguments")), scope, pattern, out);
748
+ }
749
+ const returnType = out.typeCallShape ?? (Object.keys(out.fieldProperties).length > 0 ? { type: "record", properties: out.fieldProperties } : null);
750
+ return returnType === null ? null : { returnType, args: [...out.args.values()] };
751
+ }
752
+ function resolvedName(symbol, callArgs, pattern) {
753
+ const override = callArgs.keyword[pattern.camelizeKeyword];
754
+ const camelizeThis = override !== void 0 ? booleanLiteralValue(override) ?? pattern.camelizeDefault : pattern.camelizeDefault;
755
+ return camelizeThis ? toCamelCase(symbol) : symbol;
756
+ }
757
+ function toCamelCase(name) {
758
+ const [first, ...rest] = name.split("_");
759
+ return [
760
+ first,
761
+ ...rest.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
762
+ ].join("");
763
+ }
764
+ function buildFieldUnit(pack, typeName, decl, filePath) {
765
+ const parameters = (decl.contract?.args ?? []).map(
766
+ (arg, position) => ({
767
+ name: arg.name,
768
+ position,
769
+ role: "args",
770
+ typeText: arg.typeText
771
+ })
772
+ );
773
+ const graphqlDeclaredContract = decl.contract !== null ? {
774
+ returnType: decl.contract.returnType,
775
+ args: decl.contract.args.map((a) => ({
776
+ name: a.name,
777
+ type: a.type,
778
+ required: a.required
779
+ })),
780
+ provenance: "derived",
781
+ framework: pack.name
782
+ } : void 0;
783
+ return {
784
+ identity: {
785
+ name: `${typeName}.${decl.fieldName}`,
786
+ nameKind: "label",
787
+ kind: "resolver",
788
+ file: filePath,
789
+ range: rangeOf(decl.node),
790
+ exportName: null,
791
+ exportPath: null
792
+ },
793
+ boundaryBinding: (0, import_behavioral_ir.graphqlResolverBinding)({
794
+ transport: pack.protocol,
795
+ recognition: pack.name,
796
+ typeName,
797
+ fieldName: decl.fieldName
798
+ }),
799
+ parameters,
800
+ branches: [],
801
+ ...decl.body.bodyContent !== void 0 ? { bodyContent: decl.body.bodyContent } : {},
802
+ dependencyCalls: [],
803
+ declaredContract: null,
804
+ ...decl.body.readings.length > 0 ? { readings: decl.body.readings } : {},
805
+ ...graphqlDeclaredContract !== void 0 ? { graphqlDeclaredContract } : {}
806
+ };
807
+ }
808
+
809
+ // src/facts.ts
810
+ function unitKey(filePath, range, name) {
811
+ return `${filePath}:${range.start}-${range.end}#${name}`;
812
+ }
813
+ function emitEntryFact(db, filePath, range, name) {
814
+ db.add("entry", [unitKey(filePath, range, name)]);
815
+ }
816
+
817
+ // src/parser.ts
818
+ var import_node_fs2 = __toESM(require("fs"), 1);
819
+ var import_node_path2 = __toESM(require("path"), 1);
820
+ var import_node_url = require("url");
821
+ var import_web_tree_sitter = require("web-tree-sitter");
822
+ var GRAMMAR_PATH = import_node_path2.default.join(
823
+ import_node_path2.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl)),
824
+ "..",
825
+ "grammar",
826
+ "tree-sitter-ruby.wasm"
827
+ );
828
+ var languagePromise = null;
829
+ function loadLanguage() {
830
+ languagePromise ??= (async () => {
831
+ if (!import_node_fs2.default.existsSync(GRAMMAR_PATH)) {
832
+ throw new Error(
833
+ `Ruby grammar not found at ${GRAMMAR_PATH}. See grammar/README.md.`
834
+ );
835
+ }
836
+ await import_web_tree_sitter.Parser.init();
837
+ return import_web_tree_sitter.Language.load(GRAMMAR_PATH);
838
+ })();
839
+ return languagePromise;
840
+ }
841
+ async function parseRuby(source) {
842
+ const language = await loadLanguage();
843
+ const parser = new import_web_tree_sitter.Parser();
844
+ parser.setLanguage(language);
845
+ try {
846
+ const tree = parser.parse(source);
847
+ if (tree === null) {
848
+ throw new Error("tree-sitter produced no tree for this source");
849
+ }
850
+ return tree;
851
+ } finally {
852
+ parser.delete();
853
+ }
854
+ }
855
+
856
+ // src/project.ts
857
+ var import_node_fs3 = __toESM(require("fs"), 1);
858
+ var import_node_path3 = __toESM(require("path"), 1);
859
+ var import_datalog = require("@suss/datalog");
860
+ var import_extractor2 = require("@suss/extractor");
861
+ async function extractRubyProject(options) {
862
+ const db = new import_datalog.Database();
863
+ const summaries = [];
864
+ const cache = createFileCache(
865
+ (source) => parseRuby(source).then((tree) => tree.rootNode),
866
+ (absPath) => import_node_fs3.default.existsSync(absPath) ? import_node_fs3.default.readFileSync(absPath, "utf8") : null
867
+ );
868
+ for (const file of options.files) {
869
+ const root = await cache.get(file);
870
+ if (root === null) {
871
+ continue;
872
+ }
873
+ const displayPath = options.workspaceRoot !== void 0 ? import_node_path3.default.relative(options.workspaceRoot, file) : file;
874
+ const rawUnits = await discoverUnits(root, {
875
+ packs: options.packs,
876
+ filePath: displayPath,
877
+ cache
878
+ });
879
+ for (const raw of rawUnits) {
880
+ const summary = (0, import_extractor2.assembleSummary)(raw, { gapHandling: "permissive" });
881
+ summary.confidence = { source: "inferred_static", level: "low" };
882
+ summaries.push(summary);
883
+ emitEntryFact(db, file, raw.identity.range, raw.identity.name);
884
+ }
885
+ }
886
+ return { summaries, facts: db };
887
+ }
888
+ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["vendor", "node_modules", "tmp", ".git"]);
889
+ function findRubyFiles(root) {
890
+ const found = [];
891
+ const walk = (dir) => {
892
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
893
+ if (entry.isDirectory()) {
894
+ if (!SKIPPED_DIRECTORIES.has(entry.name)) {
895
+ walk(import_node_path3.default.join(dir, entry.name));
896
+ }
897
+ continue;
898
+ }
899
+ if (entry.isFile() && entry.name.endsWith(".rb")) {
900
+ found.push(import_node_path3.default.join(dir, entry.name));
901
+ }
902
+ }
903
+ };
904
+ walk(root);
905
+ return found.sort();
906
+ }
907
+ // Annotate the CommonJS export names for ESM import in node:
908
+ 0 && (module.exports = {
909
+ ancestryOf,
910
+ createFileCache,
911
+ discoverUnits,
912
+ emitEntryFact,
913
+ extractRubyProject,
914
+ findRubyFiles,
915
+ graphqlTypeNameFromQualified,
916
+ inheritedStatements,
917
+ methodInAncestry,
918
+ parseRuby,
919
+ qualifyConstantRef,
920
+ reachDefinition,
921
+ resolveConstantFile,
922
+ typeShapeFromNode,
923
+ underscoreConstantPath,
924
+ unitKey,
925
+ walkClasses,
926
+ walkDefinitions
927
+ });