@suss/adapter-ruby 0.20.0 → 0.21.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 CHANGED
@@ -72,12 +72,37 @@ function field(node, name) {
72
72
  function bodyStatements(body) {
73
73
  return body.namedChildren.filter((child) => child !== null);
74
74
  }
75
+ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
76
+ "method",
77
+ "singleton_method",
78
+ "lambda",
79
+ "class",
80
+ "module",
81
+ "singleton_class"
82
+ ]);
83
+ var ARGUMENT_LIST_TYPE = "argument_list";
84
+ function runStatements(body, blockConfigures = () => false) {
85
+ const found = [];
86
+ const readsItsChildren = (node) => {
87
+ if (OWN_BODY_TYPES.has(node.type) || node.type === ARGUMENT_LIST_TYPE) {
88
+ return false;
89
+ }
90
+ return !(node.type === "call" && blockConfigures(node));
91
+ };
92
+ (0, import_extractor.walkDescendants)(body, null, {
93
+ at: (node) => {
94
+ found.push(node);
95
+ },
96
+ into: (node) => readsItsChildren(node) ? null : import_extractor.SKIP_CHILDREN
97
+ });
98
+ return found;
99
+ }
75
100
  function symbolValue(node) {
76
101
  return node.type === "simple_symbol" ? node.text.slice(1) : null;
77
102
  }
78
103
  function instanceMethodsByName(body) {
79
104
  const methods = /* @__PURE__ */ new Map();
80
- for (const stmt of bodyStatements(body)) {
105
+ for (const stmt of runStatements(body)) {
81
106
  if (stmt.type !== "method") {
82
107
  continue;
83
108
  }
@@ -90,7 +115,7 @@ function instanceMethodsByName(body) {
90
115
  }
91
116
  function bareCallArgumentGroups(body, name) {
92
117
  const groups = [];
93
- for (const stmt of bodyStatements(body)) {
118
+ for (const stmt of runStatements(body)) {
94
119
  if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
95
120
  continue;
96
121
  }
@@ -193,7 +218,7 @@ function walkClasses(root, visit) {
193
218
  });
194
219
  }
195
220
  function walkDefinitions(root, visit) {
196
- walkBody(bodyStatements(root), [], visit);
221
+ walkBody(runStatements(root), [], visit);
197
222
  }
198
223
  function walkBody(statements, nesting, visit) {
199
224
  for (const stmt of statements) {
@@ -232,7 +257,7 @@ function visitClass(node, nesting, visit) {
232
257
  bodyNesting: identity.bodyNesting
233
258
  });
234
259
  if (bodyNode !== null) {
235
- walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
260
+ walkBody(runStatements(bodyNode), identity.bodyNesting, visit);
236
261
  }
237
262
  }
238
263
  function visitModule(node, nesting, visit) {
@@ -251,7 +276,7 @@ function visitModule(node, nesting, visit) {
251
276
  bodyNesting: identity.bodyNesting
252
277
  });
253
278
  if (bodyNode !== null) {
254
- walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
279
+ walkBody(runStatements(bodyNode), identity.bodyNesting, visit);
255
280
  }
256
281
  }
257
282
  function stripTypeSuffixName(qualifiedName) {
@@ -407,7 +432,7 @@ function definitionIn(blocks, name) {
407
432
  }
408
433
  return { method, dynamic };
409
434
  }
410
- function inheritedStatements(ancestry) {
435
+ function inheritedStatements(ancestry, blockConfigures) {
411
436
  const statements = [];
412
437
  for (const entry of [...ancestry].reverse()) {
413
438
  if (entry.type !== "bodies") {
@@ -418,7 +443,7 @@ function inheritedStatements(ancestry) {
418
443
  if (body === null) {
419
444
  continue;
420
445
  }
421
- for (const statement of bodyStatements(body)) {
446
+ for (const statement of runStatements(body, blockConfigures)) {
422
447
  statements.push({ block, statement });
423
448
  }
424
449
  }
@@ -644,14 +669,6 @@ function lowerRubyBody(body, terminals) {
644
669
  }
645
670
 
646
671
  // src/paths/effects.ts
647
- var DECLARATION_TYPES = /* @__PURE__ */ new Set([
648
- "method",
649
- "singleton_method",
650
- "lambda",
651
- "class",
652
- "module",
653
- "singleton_class"
654
- ]);
655
672
  var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
656
673
  var BLOCK_TYPES = /* @__PURE__ */ new Set([
657
674
  "body_statement",
@@ -699,7 +716,7 @@ function isRaise2(node) {
699
716
  }
700
717
  function bodyCalls(node, found = []) {
701
718
  for (const child of children(node)) {
702
- if (DECLARATION_TYPES.has(child.type)) {
719
+ if (OWN_BODY_TYPES.has(child.type)) {
703
720
  continue;
704
721
  }
705
722
  if (child.type === "call" && !isPropertyRead(child) && !isRaise2(child)) {
@@ -15302,7 +15319,7 @@ function date4(params) {
15302
15319
  // ../../../node_modules/zod/v4/classic/external.js
15303
15320
  config(en_default());
15304
15321
 
15305
- // ../../ir-core/dist/chunk-52CH76PE.js
15322
+ // ../../ir-core/dist/chunk-QHFMZPEG.js
15306
15323
  function gqlIdentityKey(typeName, fieldName) {
15307
15324
  return `gql:${typeName}.${fieldName}`;
15308
15325
  }
@@ -15312,6 +15329,9 @@ function fnIdentityKey(packageName, exportPath) {
15312
15329
  function busIdentityKey(messageBus, subject) {
15313
15330
  return `bus:${messageBus} ${subject}`;
15314
15331
  }
15332
+ function unitIdentityKey(deploymentTarget, instanceName) {
15333
+ return `unit:${deploymentTarget} ${instanceName}`;
15334
+ }
15315
15335
  function metricIdentityKey(metricSystem, metricType) {
15316
15336
  return `metric:${metricSystem} ${metricType}`;
15317
15337
  }
@@ -15724,6 +15744,14 @@ var restSemantics = defineBoundarySemantics({
15724
15744
  }
15725
15745
  return routePathAdmits(semantics.path, path4) ? "match" : "nomatch";
15726
15746
  },
15747
+ /**
15748
+ * A route the registration pattern admits is inside it. A route
15749
+ * whose path this source never gave is left outside, because
15750
+ * nothing here can show it is one the pattern covers.
15751
+ */
15752
+ withinScope(semantics, scope) {
15753
+ return semantics.path !== null && routePathAdmits(scope, semantics.path);
15754
+ },
15727
15755
  /**
15728
15756
  * A call whose base URL the deployment fills in, resolved to the
15729
15757
  * path it reaches.
@@ -15845,7 +15873,62 @@ var storageSemantics = defineBoundarySemantics({
15845
15873
  /** A query returns rows or items, not a status and a body. */
15846
15874
  exchangesHttpResponses: false,
15847
15875
  reportsUnpairedItself: false,
15848
- identityKey: () => null
15876
+ identityKey: () => null,
15877
+ displayLabel: storageLabel
15878
+ }
15879
+ });
15880
+ function storageLabel(semantics) {
15881
+ return `${semantics.storageSystem}:${storageContainerLabel(semantics)}`;
15882
+ }
15883
+ function storageContainerLabel(semantics) {
15884
+ const container = semantics.container ?? "<unnamed container>";
15885
+ const addressed = semantics.accessPath === null ? container : `${container}#${semantics.accessPath}`;
15886
+ return semantics.scope === "default" ? addressed : `${semantics.scope}/${addressed}`;
15887
+ }
15888
+ var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
15889
+ name: external_exports.literal("unit-invocation"),
15890
+ instanceName: DeployableUnitSchema.shape.instanceName.nullable()
15891
+ });
15892
+ var unitInvocationSemantics = defineBoundarySemantics({
15893
+ name: "unit-invocation",
15894
+ schema: UnitInvocationSemanticsSchema,
15895
+ // faas.name is the deployed function's own name and instanceName is
15896
+ // the deployment template's logical id, which is a different string.
15897
+ // Runtime config leaves the pair off for the same reason.
15898
+ semconv: {},
15899
+ behavior: {
15900
+ /**
15901
+ * A synchronous invoke gives back whatever the handler returned,
15902
+ * which is a value rather than a status and a body. A handler's
15903
+ * return only becomes an HTTP response under a proxy integration,
15904
+ * and that route gets a REST binding instead of this one.
15905
+ */
15906
+ exchangesHttpResponses: false,
15907
+ /**
15908
+ * A unit nothing in the run invokes is the ordinary case, since
15909
+ * most services are reached from a stack suss never read. So an
15910
+ * uninvoked unit stays in the generic unmatched list rather than
15911
+ * being reported as a problem here.
15912
+ */
15913
+ reportsUnpairedItself: false,
15914
+ identityKey(semantics) {
15915
+ if (semantics.instanceName === null) {
15916
+ return null;
15917
+ }
15918
+ return unitIdentityKey(
15919
+ semantics.deploymentTarget,
15920
+ semantics.instanceName
15921
+ );
15922
+ },
15923
+ displayLabel(semantics) {
15924
+ if (semantics.instanceName === null) {
15925
+ return `unit:${semantics.deploymentTarget} (named at runtime)`;
15926
+ }
15927
+ return unitIdentityKey(
15928
+ semantics.deploymentTarget,
15929
+ semantics.instanceName
15930
+ );
15931
+ }
15849
15932
  }
15850
15933
  });
15851
15934
  var SemanticsSchema = external_exports.discriminatedUnion("name", [
@@ -15856,7 +15939,8 @@ var SemanticsSchema = external_exports.discriminatedUnion("name", [
15856
15939
  runtimeConfigSemantics.schema,
15857
15940
  storageSemantics.schema,
15858
15941
  messageBusSemantics.schema,
15859
- metricSemantics.schema
15942
+ metricSemantics.schema,
15943
+ unitInvocationSemantics.schema
15860
15944
  ]);
15861
15945
  var DEFINITIONS = [
15862
15946
  restSemantics,
@@ -15866,7 +15950,8 @@ var DEFINITIONS = [
15866
15950
  runtimeConfigSemantics,
15867
15951
  storageSemantics,
15868
15952
  messageBusSemantics,
15869
- metricSemantics
15953
+ metricSemantics,
15954
+ unitInvocationSemantics
15870
15955
  ];
15871
15956
  var BY_NAME = new Map(
15872
15957
  DEFINITIONS.map((d) => [d.name, d])
@@ -15956,6 +16041,13 @@ var TypeShapeSchema = external_exports.lazy(
15956
16041
  );
15957
16042
 
15958
16043
  // ../../ir-core/dist/index.js
16044
+ var RelationSchema = external_exports.enum([
16045
+ "provides",
16046
+ "reads",
16047
+ "writes",
16048
+ "invokes"
16049
+ ]);
16050
+ var EffectRelationSchema = RelationSchema.exclude(["provides"]);
15959
16051
  var SuppressionSideSchema = external_exports.object({
15960
16052
  summary: external_exports.string().optional(),
15961
16053
  transitionId: external_exports.string().optional()
@@ -16675,6 +16767,23 @@ var RESOLUTION_QUESTIONS = [
16675
16767
  (0, import_datalog2.lit)("callsInto", (0, import_datalog2.variable)("g"), (0, import_datalog2.variable)("m"), (0, import_datalog2.variable)("n"))
16676
16768
  ]
16677
16769
  ),
16770
+ // Subject identity, its own demand class; the README's subject
16771
+ // section says why the origin pair rides on the written-value walk.
16772
+ (0, import_datalog2.rule)(
16773
+ "wantedSubjectWritten",
16774
+ [(0, import_datalog2.variable)("x"), (0, import_datalog2.variable)("r")],
16775
+ [(0, import_datalog2.lit)("wantedSubject", (0, import_datalog2.variable)("x")), (0, import_datalog2.lit)("isWrittenAs", (0, import_datalog2.variable)("x"), (0, import_datalog2.variable)("r"))]
16776
+ ),
16777
+ (0, import_datalog2.rule)(
16778
+ "wantedSubjectConstruction",
16779
+ [(0, import_datalog2.variable)("x"), (0, import_datalog2.variable)("r"), (0, import_datalog2.variable)("m"), (0, import_datalog2.variable)("n")],
16780
+ [
16781
+ (0, import_datalog2.lit)("wantedSubject", (0, import_datalog2.variable)("x")),
16782
+ (0, import_datalog2.lit)("isWrittenAs", (0, import_datalog2.variable)("x"), (0, import_datalog2.variable)("r")),
16783
+ (0, import_datalog2.lit)("call", (0, import_datalog2.variable)("r"), (0, import_datalog2.variable)("c")),
16784
+ (0, import_datalog2.lit)("comesFrom", (0, import_datalog2.variable)("c"), (0, import_datalog2.variable)("m"), (0, import_datalog2.variable)("n"))
16785
+ ]
16786
+ ),
16678
16787
  // The export table of one module, keyed by the module asked about.
16679
16788
  // The moduleExport rules flatten re-export chains of any length, and
16680
16789
  // the demand cone stays on export facts alone.
@@ -17022,7 +17131,10 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
17022
17131
  const knownClasses = ownBlocks[0]?.knownClasses ?? /* @__PURE__ */ new Set();
17023
17132
  const scope = { nesting: info.bodyNesting, knownClasses };
17024
17133
  const declsByName = /* @__PURE__ */ new Map();
17025
- for (const stmt of bodyStatements(info.bodyNode)) {
17134
+ for (const stmt of runStatements(
17135
+ info.bodyNode,
17136
+ blockConfiguresCall(pattern)
17137
+ )) {
17026
17138
  const decl = await readFieldCall(stmt, scope, ctx, ancestry);
17027
17139
  if (decl !== null) {
17028
17140
  declsByName.set(decl.fieldName, decl);
@@ -17032,6 +17144,17 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
17032
17144
  (decl) => buildFieldUnit(pack, typeName, decl, options.filePath)
17033
17145
  );
17034
17146
  }
17147
+ function blockConfiguresCall(pattern) {
17148
+ const names = /* @__PURE__ */ new Set([
17149
+ pattern.fieldCallName,
17150
+ pattern.typeCallName,
17151
+ pattern.argumentCallName
17152
+ ]);
17153
+ return (call) => {
17154
+ const method = field(call, "method")?.text;
17155
+ return field(call, "receiver") === null && method !== void 0 && names.has(method);
17156
+ };
17157
+ }
17035
17158
  function branchesFor(body, range) {
17036
17159
  if (body.effects === void 0 && body.extraEffects === void 0) {
17037
17160
  return [];
@@ -17107,10 +17230,13 @@ async function readFieldCall(stmt, scope, ctx, ancestry) {
17107
17230
  }
17108
17231
  const callArgs = readCallArgs(field(stmt, "arguments"));
17109
17232
  const nameArg = callArgs.positional[0];
17110
- const symbol2 = nameArg !== void 0 ? symbolValue(nameArg) : null;
17111
- if (symbol2 === null) {
17233
+ if (nameArg === void 0) {
17112
17234
  return null;
17113
17235
  }
17236
+ const symbol2 = symbolValue(nameArg);
17237
+ if (symbol2 === null) {
17238
+ return computedNameDeclaration(stmt, nameArg);
17239
+ }
17114
17240
  const read = await readFieldShape(
17115
17241
  symbol2,
17116
17242
  callArgs,
@@ -17121,11 +17247,29 @@ async function readFieldCall(stmt, scope, ctx, ancestry) {
17121
17247
  );
17122
17248
  return {
17123
17249
  fieldName: resolvedName(symbol2, callArgs, ctx.pattern),
17250
+ namedOnTheWire: true,
17124
17251
  node: stmt,
17125
17252
  contract: read.contract,
17126
17253
  body: read.body
17127
17254
  };
17128
17255
  }
17256
+ function computedNameDeclaration(stmt, nameArg) {
17257
+ const range = rangeOf(stmt);
17258
+ return {
17259
+ fieldName: nameArg.text,
17260
+ namedOnTheWire: false,
17261
+ node: stmt,
17262
+ contract: null,
17263
+ body: {
17264
+ readings: [
17265
+ (0, import_extractor3.unreadableReading)(
17266
+ `This field is named by ${nameArg.text}, which is worked out when the class body runs, so the name the schema exposes it under was not read here`,
17267
+ range
17268
+ )
17269
+ ]
17270
+ }
17271
+ };
17272
+ }
17129
17273
  function wiringReference(callArgs, wiringKeywords) {
17130
17274
  for (const keyword of wiringKeywords) {
17131
17275
  const ref = callArgs.keyword[keyword];
@@ -17250,7 +17394,10 @@ function readClassContract(ancestry, pattern) {
17250
17394
  args: /* @__PURE__ */ new Map()
17251
17395
  };
17252
17396
  const handlers = classCallHandlers(pattern);
17253
- for (const { block, statement } of inheritedStatements(ancestry)) {
17397
+ for (const { block, statement } of inheritedStatements(
17398
+ ancestry,
17399
+ blockConfiguresCall(pattern)
17400
+ )) {
17254
17401
  if (statement.type !== "call" || field(statement, "receiver") !== null) {
17255
17402
  continue;
17256
17403
  }
@@ -17341,12 +17488,12 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
17341
17488
  exportName: null,
17342
17489
  exportPath: null
17343
17490
  },
17344
- boundaryBinding: (0, import_behavioral_ir.graphqlResolverBinding)({
17491
+ boundaryBinding: decl.namedOnTheWire ? (0, import_behavioral_ir.graphqlResolverBinding)({
17345
17492
  transport: pack.protocol,
17346
17493
  recognition: pack.name,
17347
17494
  typeName,
17348
17495
  fieldName: decl.fieldName
17349
- }),
17496
+ }) : null,
17350
17497
  parameters,
17351
17498
  branches: branchesFor(decl.body, rangeOf(decl.node)),
17352
17499
  ...decl.body.bodyContent !== void 0 ? { bodyContent: decl.body.bodyContent } : {},
@@ -17417,14 +17564,6 @@ function nodeId(filePath, node) {
17417
17564
  function nameId(filePath, name) {
17418
17565
  return `${filePath}#${name}`;
17419
17566
  }
17420
- var DECLARATION_TYPES2 = /* @__PURE__ */ new Set([
17421
- "method",
17422
- "singleton_method",
17423
- "lambda",
17424
- "class",
17425
- "module",
17426
- "singleton_class"
17427
- ]);
17428
17567
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
17429
17568
  "string",
17430
17569
  "integer",
@@ -17562,7 +17701,7 @@ function emitHash(emitter, hash2) {
17562
17701
  }
17563
17702
  function walkExpressions(node, visit) {
17564
17703
  for (const child of children3(node)) {
17565
- if (DECLARATION_TYPES2.has(child.type)) {
17704
+ if (OWN_BODY_TYPES.has(child.type)) {
17566
17705
  continue;
17567
17706
  }
17568
17707
  visit(child);
@@ -17620,7 +17759,7 @@ function emitMethodFacts(emitter, method) {
17620
17759
  }
17621
17760
  const recordNested = (node) => {
17622
17761
  for (const child of children3(node)) {
17623
- if (DECLARATION_TYPES2.has(child.type)) {
17762
+ if (OWN_BODY_TYPES.has(child.type)) {
17624
17763
  add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
17625
17764
  continue;
17626
17765
  }
package/dist/index.d.cts CHANGED
@@ -4,6 +4,16 @@ import { Database } from '@suss/datalog';
4
4
  import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
5
5
  import { RawCodeStructure } from '@suss/extractor';
6
6
 
7
+ /**
8
+ * A new `Parser` for every call, so that parses running at the same time do
9
+ * not fight over one parser's mutable `language` field. The compiled `Language`
10
+ * is loaded once per process and shared.
11
+ */
12
+ declare function parseRuby(source: string): Promise<Tree>;
13
+
14
+ /** A call whose block is the thing being configured rather than a place statements run. */
15
+ type BlockConfigures = (call: Node) => boolean;
16
+
7
17
  /**
8
18
  * Finds the file behind a constant.
9
19
  *
@@ -19,13 +29,6 @@ declare function underscoreConstantPath(qualifiedName: string): string;
19
29
  /** Null when there is no file at that path. We do not fall back to another root or another spelling. */
20
30
  declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention): string | null;
21
31
 
22
- /**
23
- * A new `Parser` for every call, so that parses running at the same time do
24
- * not fight over one parser's mutable `language` field. The compiled `Language`
25
- * is loaded once per process and shared.
26
- */
27
- declare function parseRuby(source: string): Promise<Tree>;
28
-
29
32
  /**
30
33
  * Resolves Ruby constant paths against lexical nesting.
31
34
  *
@@ -65,7 +68,7 @@ interface ClassInfo {
65
68
  /** `Module.nesting` in effect inside this class's own body, innermost first. */
66
69
  bodyNesting: readonly string[];
67
70
  }
68
- /** Every `class` declaration reachable from `root` through direct module/class nesting, in source order. */
71
+ /** Every `class` declaration reachable from `root` through module/class nesting, in source order. */
69
72
  declare function walkClasses(root: Node, visit: (info: ClassInfo) => void): void;
70
73
  /** The same walk including `module` declarations, for a lookup that has to reach a module's own methods. */
71
74
  declare function walkDefinitions(root: Node, visit: (info: ClassInfo) => void): void;
@@ -134,7 +137,7 @@ type MethodLookup = {
134
137
  */
135
138
  declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
136
139
  /** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
137
- declare function inheritedStatements(ancestry: Ancestry): Array<{
140
+ declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
138
141
  block: ReachedBody;
139
142
  statement: Node;
140
143
  }>;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,16 @@ import { Database } from '@suss/datalog';
4
4
  import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
5
5
  import { RawCodeStructure } from '@suss/extractor';
6
6
 
7
+ /**
8
+ * A new `Parser` for every call, so that parses running at the same time do
9
+ * not fight over one parser's mutable `language` field. The compiled `Language`
10
+ * is loaded once per process and shared.
11
+ */
12
+ declare function parseRuby(source: string): Promise<Tree>;
13
+
14
+ /** A call whose block is the thing being configured rather than a place statements run. */
15
+ type BlockConfigures = (call: Node) => boolean;
16
+
7
17
  /**
8
18
  * Finds the file behind a constant.
9
19
  *
@@ -19,13 +29,6 @@ declare function underscoreConstantPath(qualifiedName: string): string;
19
29
  /** Null when there is no file at that path. We do not fall back to another root or another spelling. */
20
30
  declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention): string | null;
21
31
 
22
- /**
23
- * A new `Parser` for every call, so that parses running at the same time do
24
- * not fight over one parser's mutable `language` field. The compiled `Language`
25
- * is loaded once per process and shared.
26
- */
27
- declare function parseRuby(source: string): Promise<Tree>;
28
-
29
32
  /**
30
33
  * Resolves Ruby constant paths against lexical nesting.
31
34
  *
@@ -65,7 +68,7 @@ interface ClassInfo {
65
68
  /** `Module.nesting` in effect inside this class's own body, innermost first. */
66
69
  bodyNesting: readonly string[];
67
70
  }
68
- /** Every `class` declaration reachable from `root` through direct module/class nesting, in source order. */
71
+ /** Every `class` declaration reachable from `root` through module/class nesting, in source order. */
69
72
  declare function walkClasses(root: Node, visit: (info: ClassInfo) => void): void;
70
73
  /** The same walk including `module` declarations, for a lookup that has to reach a module's own methods. */
71
74
  declare function walkDefinitions(root: Node, visit: (info: ClassInfo) => void): void;
@@ -134,7 +137,7 @@ type MethodLookup = {
134
137
  */
135
138
  declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
136
139
  /** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
137
- declare function inheritedStatements(ancestry: Ancestry): Array<{
140
+ declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
138
141
  block: ReachedBody;
139
142
  statement: Node;
140
143
  }>;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/ast.ts
8
- import { IdMap, IdSet } from "@suss/extractor";
8
+ import { IdMap, IdSet, SKIP_CHILDREN, walkDescendants } from "@suss/extractor";
9
9
  function rangeOf(node) {
10
10
  return {
11
11
  start: node.startPosition.row + 1,
@@ -21,12 +21,37 @@ function field(node, name) {
21
21
  function bodyStatements(body) {
22
22
  return body.namedChildren.filter((child) => child !== null);
23
23
  }
24
+ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
25
+ "method",
26
+ "singleton_method",
27
+ "lambda",
28
+ "class",
29
+ "module",
30
+ "singleton_class"
31
+ ]);
32
+ var ARGUMENT_LIST_TYPE = "argument_list";
33
+ function runStatements(body, blockConfigures = () => false) {
34
+ const found = [];
35
+ const readsItsChildren = (node) => {
36
+ if (OWN_BODY_TYPES.has(node.type) || node.type === ARGUMENT_LIST_TYPE) {
37
+ return false;
38
+ }
39
+ return !(node.type === "call" && blockConfigures(node));
40
+ };
41
+ walkDescendants(body, null, {
42
+ at: (node) => {
43
+ found.push(node);
44
+ },
45
+ into: (node) => readsItsChildren(node) ? null : SKIP_CHILDREN
46
+ });
47
+ return found;
48
+ }
24
49
  function symbolValue(node) {
25
50
  return node.type === "simple_symbol" ? node.text.slice(1) : null;
26
51
  }
27
52
  function instanceMethodsByName(body) {
28
53
  const methods = /* @__PURE__ */ new Map();
29
- for (const stmt of bodyStatements(body)) {
54
+ for (const stmt of runStatements(body)) {
30
55
  if (stmt.type !== "method") {
31
56
  continue;
32
57
  }
@@ -39,7 +64,7 @@ function instanceMethodsByName(body) {
39
64
  }
40
65
  function bareCallArgumentGroups(body, name) {
41
66
  const groups = [];
42
- for (const stmt of bodyStatements(body)) {
67
+ for (const stmt of runStatements(body)) {
43
68
  if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
44
69
  continue;
45
70
  }
@@ -142,7 +167,7 @@ function walkClasses(root, visit) {
142
167
  });
143
168
  }
144
169
  function walkDefinitions(root, visit) {
145
- walkBody(bodyStatements(root), [], visit);
170
+ walkBody(runStatements(root), [], visit);
146
171
  }
147
172
  function walkBody(statements, nesting, visit) {
148
173
  for (const stmt of statements) {
@@ -181,7 +206,7 @@ function visitClass(node, nesting, visit) {
181
206
  bodyNesting: identity.bodyNesting
182
207
  });
183
208
  if (bodyNode !== null) {
184
- walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
209
+ walkBody(runStatements(bodyNode), identity.bodyNesting, visit);
185
210
  }
186
211
  }
187
212
  function visitModule(node, nesting, visit) {
@@ -200,7 +225,7 @@ function visitModule(node, nesting, visit) {
200
225
  bodyNesting: identity.bodyNesting
201
226
  });
202
227
  if (bodyNode !== null) {
203
- walkBody(bodyStatements(bodyNode), identity.bodyNesting, visit);
228
+ walkBody(runStatements(bodyNode), identity.bodyNesting, visit);
204
229
  }
205
230
  }
206
231
  function stripTypeSuffixName(qualifiedName) {
@@ -356,7 +381,7 @@ function definitionIn(blocks, name) {
356
381
  }
357
382
  return { method, dynamic };
358
383
  }
359
- function inheritedStatements(ancestry) {
384
+ function inheritedStatements(ancestry, blockConfigures) {
360
385
  const statements = [];
361
386
  for (const entry of [...ancestry].reverse()) {
362
387
  if (entry.type !== "bodies") {
@@ -367,7 +392,7 @@ function inheritedStatements(ancestry) {
367
392
  if (body === null) {
368
393
  continue;
369
394
  }
370
- for (const statement of bodyStatements(body)) {
395
+ for (const statement of runStatements(body, blockConfigures)) {
371
396
  statements.push({ block, statement });
372
397
  }
373
398
  }
@@ -593,14 +618,6 @@ function lowerRubyBody(body, terminals) {
593
618
  }
594
619
 
595
620
  // src/paths/effects.ts
596
- var DECLARATION_TYPES = /* @__PURE__ */ new Set([
597
- "method",
598
- "singleton_method",
599
- "lambda",
600
- "class",
601
- "module",
602
- "singleton_class"
603
- ]);
604
621
  var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
605
622
  var BLOCK_TYPES = /* @__PURE__ */ new Set([
606
623
  "body_statement",
@@ -648,7 +665,7 @@ function isRaise2(node) {
648
665
  }
649
666
  function bodyCalls(node, found = []) {
650
667
  for (const child of children(node)) {
651
- if (DECLARATION_TYPES.has(child.type)) {
668
+ if (OWN_BODY_TYPES.has(child.type)) {
652
669
  continue;
653
670
  }
654
671
  if (child.type === "call" && !isPropertyRead(child) && !isRaise2(child)) {
@@ -15251,7 +15268,7 @@ function date4(params) {
15251
15268
  // ../../../node_modules/zod/v4/classic/external.js
15252
15269
  config(en_default());
15253
15270
 
15254
- // ../../ir-core/dist/chunk-52CH76PE.js
15271
+ // ../../ir-core/dist/chunk-QHFMZPEG.js
15255
15272
  function gqlIdentityKey(typeName, fieldName) {
15256
15273
  return `gql:${typeName}.${fieldName}`;
15257
15274
  }
@@ -15261,6 +15278,9 @@ function fnIdentityKey(packageName, exportPath) {
15261
15278
  function busIdentityKey(messageBus, subject) {
15262
15279
  return `bus:${messageBus} ${subject}`;
15263
15280
  }
15281
+ function unitIdentityKey(deploymentTarget, instanceName) {
15282
+ return `unit:${deploymentTarget} ${instanceName}`;
15283
+ }
15264
15284
  function metricIdentityKey(metricSystem, metricType) {
15265
15285
  return `metric:${metricSystem} ${metricType}`;
15266
15286
  }
@@ -15673,6 +15693,14 @@ var restSemantics = defineBoundarySemantics({
15673
15693
  }
15674
15694
  return routePathAdmits(semantics.path, path4) ? "match" : "nomatch";
15675
15695
  },
15696
+ /**
15697
+ * A route the registration pattern admits is inside it. A route
15698
+ * whose path this source never gave is left outside, because
15699
+ * nothing here can show it is one the pattern covers.
15700
+ */
15701
+ withinScope(semantics, scope) {
15702
+ return semantics.path !== null && routePathAdmits(scope, semantics.path);
15703
+ },
15676
15704
  /**
15677
15705
  * A call whose base URL the deployment fills in, resolved to the
15678
15706
  * path it reaches.
@@ -15794,7 +15822,62 @@ var storageSemantics = defineBoundarySemantics({
15794
15822
  /** A query returns rows or items, not a status and a body. */
15795
15823
  exchangesHttpResponses: false,
15796
15824
  reportsUnpairedItself: false,
15797
- identityKey: () => null
15825
+ identityKey: () => null,
15826
+ displayLabel: storageLabel
15827
+ }
15828
+ });
15829
+ function storageLabel(semantics) {
15830
+ return `${semantics.storageSystem}:${storageContainerLabel(semantics)}`;
15831
+ }
15832
+ function storageContainerLabel(semantics) {
15833
+ const container = semantics.container ?? "<unnamed container>";
15834
+ const addressed = semantics.accessPath === null ? container : `${container}#${semantics.accessPath}`;
15835
+ return semantics.scope === "default" ? addressed : `${semantics.scope}/${addressed}`;
15836
+ }
15837
+ var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
15838
+ name: external_exports.literal("unit-invocation"),
15839
+ instanceName: DeployableUnitSchema.shape.instanceName.nullable()
15840
+ });
15841
+ var unitInvocationSemantics = defineBoundarySemantics({
15842
+ name: "unit-invocation",
15843
+ schema: UnitInvocationSemanticsSchema,
15844
+ // faas.name is the deployed function's own name and instanceName is
15845
+ // the deployment template's logical id, which is a different string.
15846
+ // Runtime config leaves the pair off for the same reason.
15847
+ semconv: {},
15848
+ behavior: {
15849
+ /**
15850
+ * A synchronous invoke gives back whatever the handler returned,
15851
+ * which is a value rather than a status and a body. A handler's
15852
+ * return only becomes an HTTP response under a proxy integration,
15853
+ * and that route gets a REST binding instead of this one.
15854
+ */
15855
+ exchangesHttpResponses: false,
15856
+ /**
15857
+ * A unit nothing in the run invokes is the ordinary case, since
15858
+ * most services are reached from a stack suss never read. So an
15859
+ * uninvoked unit stays in the generic unmatched list rather than
15860
+ * being reported as a problem here.
15861
+ */
15862
+ reportsUnpairedItself: false,
15863
+ identityKey(semantics) {
15864
+ if (semantics.instanceName === null) {
15865
+ return null;
15866
+ }
15867
+ return unitIdentityKey(
15868
+ semantics.deploymentTarget,
15869
+ semantics.instanceName
15870
+ );
15871
+ },
15872
+ displayLabel(semantics) {
15873
+ if (semantics.instanceName === null) {
15874
+ return `unit:${semantics.deploymentTarget} (named at runtime)`;
15875
+ }
15876
+ return unitIdentityKey(
15877
+ semantics.deploymentTarget,
15878
+ semantics.instanceName
15879
+ );
15880
+ }
15798
15881
  }
15799
15882
  });
15800
15883
  var SemanticsSchema = external_exports.discriminatedUnion("name", [
@@ -15805,7 +15888,8 @@ var SemanticsSchema = external_exports.discriminatedUnion("name", [
15805
15888
  runtimeConfigSemantics.schema,
15806
15889
  storageSemantics.schema,
15807
15890
  messageBusSemantics.schema,
15808
- metricSemantics.schema
15891
+ metricSemantics.schema,
15892
+ unitInvocationSemantics.schema
15809
15893
  ]);
15810
15894
  var DEFINITIONS = [
15811
15895
  restSemantics,
@@ -15815,7 +15899,8 @@ var DEFINITIONS = [
15815
15899
  runtimeConfigSemantics,
15816
15900
  storageSemantics,
15817
15901
  messageBusSemantics,
15818
- metricSemantics
15902
+ metricSemantics,
15903
+ unitInvocationSemantics
15819
15904
  ];
15820
15905
  var BY_NAME = new Map(
15821
15906
  DEFINITIONS.map((d) => [d.name, d])
@@ -15905,6 +15990,13 @@ var TypeShapeSchema = external_exports.lazy(
15905
15990
  );
15906
15991
 
15907
15992
  // ../../ir-core/dist/index.js
15993
+ var RelationSchema = external_exports.enum([
15994
+ "provides",
15995
+ "reads",
15996
+ "writes",
15997
+ "invokes"
15998
+ ]);
15999
+ var EffectRelationSchema = RelationSchema.exclude(["provides"]);
15908
16000
  var SuppressionSideSchema = external_exports.object({
15909
16001
  summary: external_exports.string().optional(),
15910
16002
  transitionId: external_exports.string().optional()
@@ -16624,6 +16716,23 @@ var RESOLUTION_QUESTIONS = [
16624
16716
  lit("callsInto", v("g"), v("m"), v("n"))
16625
16717
  ]
16626
16718
  ),
16719
+ // Subject identity, its own demand class; the README's subject
16720
+ // section says why the origin pair rides on the written-value walk.
16721
+ rule(
16722
+ "wantedSubjectWritten",
16723
+ [v("x"), v("r")],
16724
+ [lit("wantedSubject", v("x")), lit("isWrittenAs", v("x"), v("r"))]
16725
+ ),
16726
+ rule(
16727
+ "wantedSubjectConstruction",
16728
+ [v("x"), v("r"), v("m"), v("n")],
16729
+ [
16730
+ lit("wantedSubject", v("x")),
16731
+ lit("isWrittenAs", v("x"), v("r")),
16732
+ lit("call", v("r"), v("c")),
16733
+ lit("comesFrom", v("c"), v("m"), v("n"))
16734
+ ]
16735
+ ),
16627
16736
  // The export table of one module, keyed by the module asked about.
16628
16737
  // The moduleExport rules flatten re-export chains of any length, and
16629
16738
  // the demand cone stays on export facts alone.
@@ -16971,7 +17080,10 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
16971
17080
  const knownClasses = ownBlocks[0]?.knownClasses ?? /* @__PURE__ */ new Set();
16972
17081
  const scope = { nesting: info.bodyNesting, knownClasses };
16973
17082
  const declsByName = /* @__PURE__ */ new Map();
16974
- for (const stmt of bodyStatements(info.bodyNode)) {
17083
+ for (const stmt of runStatements(
17084
+ info.bodyNode,
17085
+ blockConfiguresCall(pattern)
17086
+ )) {
16975
17087
  const decl = await readFieldCall(stmt, scope, ctx, ancestry);
16976
17088
  if (decl !== null) {
16977
17089
  declsByName.set(decl.fieldName, decl);
@@ -16981,6 +17093,17 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
16981
17093
  (decl) => buildFieldUnit(pack, typeName, decl, options.filePath)
16982
17094
  );
16983
17095
  }
17096
+ function blockConfiguresCall(pattern) {
17097
+ const names = /* @__PURE__ */ new Set([
17098
+ pattern.fieldCallName,
17099
+ pattern.typeCallName,
17100
+ pattern.argumentCallName
17101
+ ]);
17102
+ return (call) => {
17103
+ const method = field(call, "method")?.text;
17104
+ return field(call, "receiver") === null && method !== void 0 && names.has(method);
17105
+ };
17106
+ }
16984
17107
  function branchesFor(body, range) {
16985
17108
  if (body.effects === void 0 && body.extraEffects === void 0) {
16986
17109
  return [];
@@ -17056,10 +17179,13 @@ async function readFieldCall(stmt, scope, ctx, ancestry) {
17056
17179
  }
17057
17180
  const callArgs = readCallArgs(field(stmt, "arguments"));
17058
17181
  const nameArg = callArgs.positional[0];
17059
- const symbol2 = nameArg !== void 0 ? symbolValue(nameArg) : null;
17060
- if (symbol2 === null) {
17182
+ if (nameArg === void 0) {
17061
17183
  return null;
17062
17184
  }
17185
+ const symbol2 = symbolValue(nameArg);
17186
+ if (symbol2 === null) {
17187
+ return computedNameDeclaration(stmt, nameArg);
17188
+ }
17063
17189
  const read = await readFieldShape(
17064
17190
  symbol2,
17065
17191
  callArgs,
@@ -17070,11 +17196,29 @@ async function readFieldCall(stmt, scope, ctx, ancestry) {
17070
17196
  );
17071
17197
  return {
17072
17198
  fieldName: resolvedName(symbol2, callArgs, ctx.pattern),
17199
+ namedOnTheWire: true,
17073
17200
  node: stmt,
17074
17201
  contract: read.contract,
17075
17202
  body: read.body
17076
17203
  };
17077
17204
  }
17205
+ function computedNameDeclaration(stmt, nameArg) {
17206
+ const range = rangeOf(stmt);
17207
+ return {
17208
+ fieldName: nameArg.text,
17209
+ namedOnTheWire: false,
17210
+ node: stmt,
17211
+ contract: null,
17212
+ body: {
17213
+ readings: [
17214
+ unreadableReading(
17215
+ `This field is named by ${nameArg.text}, which is worked out when the class body runs, so the name the schema exposes it under was not read here`,
17216
+ range
17217
+ )
17218
+ ]
17219
+ }
17220
+ };
17221
+ }
17078
17222
  function wiringReference(callArgs, wiringKeywords) {
17079
17223
  for (const keyword of wiringKeywords) {
17080
17224
  const ref = callArgs.keyword[keyword];
@@ -17199,7 +17343,10 @@ function readClassContract(ancestry, pattern) {
17199
17343
  args: /* @__PURE__ */ new Map()
17200
17344
  };
17201
17345
  const handlers = classCallHandlers(pattern);
17202
- for (const { block, statement } of inheritedStatements(ancestry)) {
17346
+ for (const { block, statement } of inheritedStatements(
17347
+ ancestry,
17348
+ blockConfiguresCall(pattern)
17349
+ )) {
17203
17350
  if (statement.type !== "call" || field(statement, "receiver") !== null) {
17204
17351
  continue;
17205
17352
  }
@@ -17290,12 +17437,12 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
17290
17437
  exportName: null,
17291
17438
  exportPath: null
17292
17439
  },
17293
- boundaryBinding: graphqlResolverBinding({
17440
+ boundaryBinding: decl.namedOnTheWire ? graphqlResolverBinding({
17294
17441
  transport: pack.protocol,
17295
17442
  recognition: pack.name,
17296
17443
  typeName,
17297
17444
  fieldName: decl.fieldName
17298
- }),
17445
+ }) : null,
17299
17446
  parameters,
17300
17447
  branches: branchesFor(decl.body, rangeOf(decl.node)),
17301
17448
  ...decl.body.bodyContent !== void 0 ? { bodyContent: decl.body.bodyContent } : {},
@@ -17366,14 +17513,6 @@ function nodeId(filePath, node) {
17366
17513
  function nameId(filePath, name) {
17367
17514
  return `${filePath}#${name}`;
17368
17515
  }
17369
- var DECLARATION_TYPES2 = /* @__PURE__ */ new Set([
17370
- "method",
17371
- "singleton_method",
17372
- "lambda",
17373
- "class",
17374
- "module",
17375
- "singleton_class"
17376
- ]);
17377
17516
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
17378
17517
  "string",
17379
17518
  "integer",
@@ -17511,7 +17650,7 @@ function emitHash(emitter, hash2) {
17511
17650
  }
17512
17651
  function walkExpressions(node, visit) {
17513
17652
  for (const child of children3(node)) {
17514
- if (DECLARATION_TYPES2.has(child.type)) {
17653
+ if (OWN_BODY_TYPES.has(child.type)) {
17515
17654
  continue;
17516
17655
  }
17517
17656
  visit(child);
@@ -17569,7 +17708,7 @@ function emitMethodFacts(emitter, method) {
17569
17708
  }
17570
17709
  const recordNested = (node) => {
17571
17710
  for (const child of children3(node)) {
17572
- if (DECLARATION_TYPES2.has(child.type)) {
17711
+ if (OWN_BODY_TYPES.has(child.type)) {
17573
17712
  add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
17574
17713
  continue;
17575
17714
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-ruby",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "description": "Ruby language adapter for suss, parsing source with tree-sitter (WASM), resolving constant nesting with a lexical binder, and emitting raw extractions for the suss pipeline.",
6
6
  "keywords": [
@@ -47,9 +47,9 @@
47
47
  "typecheck": "tsc --noEmit"
48
48
  },
49
49
  "dependencies": {
50
- "@suss/behavioral-ir": "0.20.0",
51
- "@suss/datalog": "0.20.0",
52
- "@suss/extractor": "0.20.0",
50
+ "@suss/behavioral-ir": "0.21.0",
51
+ "@suss/datalog": "0.21.0",
52
+ "@suss/extractor": "0.21.0",
53
53
  "web-tree-sitter": "^0.26.11"
54
54
  },
55
55
  "devDependencies": {