@suss/adapter-python 0.26.0 → 0.27.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.js CHANGED
@@ -93,6 +93,25 @@ function children(node) {
93
93
  function bodyStatements(body) {
94
94
  return children(body);
95
95
  }
96
+ function nestedStatements(stmt) {
97
+ const found = [];
98
+ for (const child of children(stmt)) {
99
+ if (child.type === "block") {
100
+ for (const inner of children(child)) {
101
+ if (inner.type.endsWith("_clause")) {
102
+ found.push(...nestedStatements(inner));
103
+ continue;
104
+ }
105
+ found.push(inner);
106
+ }
107
+ continue;
108
+ }
109
+ if (child.type.endsWith("_clause")) {
110
+ found.push(...nestedStatements(child));
111
+ }
112
+ }
113
+ return found;
114
+ }
96
115
  function stripDecorators(node) {
97
116
  if (node.type !== "decorated_definition") {
98
117
  return { definition: node, decorators: [] };
@@ -453,8 +472,36 @@ function resolveNonlocal(scope, name) {
453
472
  }
454
473
 
455
474
  // src/annotations.ts
456
- function createAnnotationContext(scopeFor) {
457
- return { scopeFor, definitions: /* @__PURE__ */ new Map() };
475
+ function createAnnotationContext(scopeFor, importedDefinition = null) {
476
+ return {
477
+ scopeMaps: [scopeFor],
478
+ definitions: /* @__PURE__ */ new Map(),
479
+ importedDefinition,
480
+ expanding: /* @__PURE__ */ new Set()
481
+ };
482
+ }
483
+ function scopeOfNode(ctx, node) {
484
+ for (const scopeFor of ctx.scopeMaps) {
485
+ const scope = scopeFor.get(node.id);
486
+ if (scope !== void 0) {
487
+ return scope;
488
+ }
489
+ }
490
+ return void 0;
491
+ }
492
+ function aliasValueOf(name, scope, ctx) {
493
+ const binding = resolveName(scope, name);
494
+ if (binding?.kind === "assignment") {
495
+ return binding.value === null ? null : { node: binding.value, scope };
496
+ }
497
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
498
+ return null;
499
+ }
500
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
501
+ if (imported === null || imported.node.type === "class_definition") {
502
+ return null;
503
+ }
504
+ return { node: imported.node, scope: imported.moduleScope };
458
505
  }
459
506
  function collectedDefinitions(ctx) {
460
507
  const out = {};
@@ -497,6 +544,7 @@ var EXPRESSION_SHAPERS = {
497
544
  identifier: (node, scope, ctx) => shapeFromName(node.text, scope, ctx),
498
545
  none: () => ({ type: "null" }),
499
546
  generic_type: shapeFromGenericType,
547
+ subscript: shapeFromSubscript,
500
548
  binary_operator: shapeFromBinaryOperator
501
549
  };
502
550
  function shapeFromExpression(node, scope, ctx) {
@@ -518,8 +566,46 @@ function shapeFromName(name, scope, ctx) {
518
566
  if (binding?.kind === "classDef") {
519
567
  return recordShapeRef(name, binding.node, ctx);
520
568
  }
569
+ if (binding?.kind === "import" || binding?.kind === "importFrom") {
570
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
571
+ if (imported?.node.type === "class_definition") {
572
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
573
+ ctx.scopeMaps.push(imported.scopeFor);
574
+ }
575
+ return recordShapeRef(name, imported.node, ctx);
576
+ }
577
+ }
578
+ const alias = aliasValueOf(name, scope, ctx);
579
+ if (alias !== null) {
580
+ return shapeFromAliasValue(name, alias, ctx);
581
+ }
521
582
  return { type: "ref", name };
522
583
  }
584
+ function shapeFromAliasValue(name, alias, ctx) {
585
+ if (ctx.expanding.has(alias.node.id)) {
586
+ return { type: "ref", name };
587
+ }
588
+ ctx.expanding.add(alias.node.id);
589
+ try {
590
+ return shapeFromExpression(alias.node, alias.scope, ctx);
591
+ } finally {
592
+ ctx.expanding.delete(alias.node.id);
593
+ }
594
+ }
595
+ function shapeFromSubscript(node, scope, ctx) {
596
+ const base = field(node, "value");
597
+ const args = fields(node, "subscript");
598
+ const baseName = base?.type === "identifier" ? base.text : null;
599
+ if (baseName === "Annotated") {
600
+ const first = args[0];
601
+ return first === void 0 ? { type: "unknown" } : shapeFromExpression(first, scope, ctx);
602
+ }
603
+ return shapeFromGeneric(
604
+ baseName,
605
+ node.text,
606
+ args.map((arg) => shapeFromExpression(arg, scope, ctx))
607
+ );
608
+ }
523
609
  function genericTypeArgs(node) {
524
610
  const typeParameter = node.namedChildren.find(
525
611
  (child) => child !== null && child.type === "type_parameter"
@@ -530,14 +616,20 @@ function genericTypeArgs(node) {
530
616
  }
531
617
  function shapeFromGenericType(node, scope, ctx) {
532
618
  const base = node.namedChild(0);
533
- const args = genericTypeArgs(node);
534
- const shapeOf = (arg) => arg !== void 0 ? annotationToShape(arg, scope, ctx) : { type: "unknown" };
535
619
  const baseName = base?.type === "identifier" ? base.text : null;
620
+ return shapeFromGeneric(
621
+ baseName,
622
+ node.text,
623
+ genericTypeArgs(node).map((arg) => annotationToShape(arg, scope, ctx))
624
+ );
625
+ }
626
+ function shapeFromGeneric(baseName, text2, args) {
627
+ const shapeOf = (arg) => arg ?? { type: "unknown" };
536
628
  if (baseName === "Optional") {
537
629
  return { type: "union", variants: [shapeOf(args[0]), { type: "null" }] };
538
630
  }
539
631
  if (baseName === "Union") {
540
- return { type: "union", variants: args.map((arg) => shapeOf(arg)) };
632
+ return { type: "union", variants: args };
541
633
  }
542
634
  if (baseName !== null && LIST_NAMES.has(baseName)) {
543
635
  return { type: "array", items: shapeOf(args[0]) };
@@ -548,7 +640,7 @@ function shapeFromGenericType(node, scope, ctx) {
548
640
  if (baseName !== null && TUPLE_SET_NAMES.has(baseName)) {
549
641
  return { type: "array", items: shapeOf(args[0]) };
550
642
  }
551
- return { type: "ref", name: baseName ?? node.text };
643
+ return { type: "ref", name: baseName ?? text2 };
552
644
  }
553
645
  function shapeFromBinaryOperator(node, scope, ctx) {
554
646
  const operator = field(node, "operator");
@@ -573,7 +665,7 @@ function recordShapeRef(name, classNode, ctx) {
573
665
  }
574
666
  ctx.definitions.set(key, null);
575
667
  const bodyNode = field(classNode, "body");
576
- const classScope = ctx.scopeFor.get(classNode.id);
668
+ const classScope = scopeOfNode(ctx, classNode);
577
669
  ctx.definitions.set(
578
670
  key,
579
671
  bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
@@ -1382,6 +1474,13 @@ function writtenValueOf(db, key) {
1382
1474
  resolveCalls(db, [key]);
1383
1475
  return sharedWrittenValueOf(db, key, (keys) => resolveCalls(db, keys));
1384
1476
  }
1477
+ function originsOf(db, nameKey) {
1478
+ if (!db.facts("wantedOrigin").some((row) => String(row[0]) === nameKey)) {
1479
+ db.add("wantedOrigin", [nameKey]);
1480
+ evaluate(db, RESOLUTION_PROGRAM.rules);
1481
+ }
1482
+ return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1483
+ }
1385
1484
  function subjectConstructions(db, valueKeys) {
1386
1485
  const found = /* @__PURE__ */ new Map();
1387
1486
  if (valueKeys.length === 0) {
@@ -1717,7 +1816,7 @@ function emitBinding(emitter, assignment2, receiver) {
1717
1816
  emitter,
1718
1817
  "binds",
1719
1818
  nameId(emitter.filePath, left.text),
1720
- valueKey(emitter, right)
1819
+ valueKey(emitter, assignedValue(right))
1721
1820
  );
1722
1821
  return;
1723
1822
  }
@@ -1749,9 +1848,17 @@ function emitAssignment(emitter, assignment2) {
1749
1848
  "exportsAs",
1750
1849
  emitter.filePath,
1751
1850
  left.text,
1752
- valueKey(emitter, right)
1851
+ valueKey(emitter, assignedValue(right))
1753
1852
  );
1754
1853
  }
1854
+ function assignedValue(right) {
1855
+ if (right.type !== "subscript") {
1856
+ return right;
1857
+ }
1858
+ const outer = field(right, "value");
1859
+ const first = fields(right, "subscript")[0];
1860
+ return outer?.text === "Annotated" && first !== void 0 ? first : right;
1861
+ }
1755
1862
  function declaredBy(statement) {
1756
1863
  if (statement.type === "decorated_definition") {
1757
1864
  return field(statement, "definition") ?? statement;
@@ -3347,11 +3454,15 @@ var WALK_DESCENTS = {
3347
3454
  scan,
3348
3455
  visit
3349
3456
  );
3350
- }
3457
+ },
3458
+ if_statement: descendInPlace,
3459
+ try_statement: descendInPlace,
3460
+ while_statement: descendInPlace,
3461
+ with_statement: descendInPlace,
3462
+ match_statement: descendInPlace
3351
3463
  };
3352
- function nestedStatements(stmt) {
3353
- const body = field(stmt, "body");
3354
- return body === null ? [] : bodyStatements(body);
3464
+ function descendInPlace(stmt, position, scan, visit) {
3465
+ walkStatements(nestedStatements(stmt), position, scan, visit);
3355
3466
  }
3356
3467
  function walkStatements(statements, position, scan, visit) {
3357
3468
  for (const stmt of statements) {
@@ -4432,7 +4543,7 @@ function rawSqlEffects(calls, options) {
4432
4543
  binding: storageBinding({
4433
4544
  recognition: `python-${pattern.module}`,
4434
4545
  storageSystem: pattern.storageSystem,
4435
- scope: pattern.module,
4546
+ scope: "default",
4436
4547
  container: access.table
4437
4548
  }),
4438
4549
  callee: field(call, "function")?.text ?? "",
@@ -4576,16 +4687,23 @@ function typeNameOf(annotation) {
4576
4687
  if (quoted !== null) {
4577
4688
  return quoted;
4578
4689
  }
4690
+ if (annotation.type === "binary_operator") {
4691
+ const named = [field(annotation, "left"), field(annotation, "right")].find(
4692
+ (side) => side !== null && side.type !== "none"
4693
+ );
4694
+ return named === void 0 || named === null ? null : typeNameOf(named);
4695
+ }
4579
4696
  if (annotation.type === "generic_type") {
4580
4697
  const outer = annotation.namedChildren[0];
4581
4698
  const first = genericTypeArgs(annotation)[0];
4582
4699
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4583
4700
  return typeNameOf(first);
4584
4701
  }
4702
+ return outer?.type === "identifier" ? outer.text : null;
4585
4703
  }
4586
4704
  return null;
4587
4705
  }
4588
- function typeGivenBy(statement, name) {
4706
+ function typeGivenBy(statement, name, options) {
4589
4707
  if (statement.type === "assignment") {
4590
4708
  if (field(statement, "left")?.text !== name) {
4591
4709
  return null;
@@ -4595,8 +4713,7 @@ function typeGivenBy(statement, name) {
4595
4713
  return typeNameOf(annotation);
4596
4714
  }
4597
4715
  const right = field(statement, "right");
4598
- const callee = right?.type === "call" ? field(right, "function") : null;
4599
- return callee?.type === "identifier" ? callee.text : null;
4716
+ return right?.type === "call" ? builtType(right, options) : null;
4600
4717
  }
4601
4718
  if (statement.type === "as_pattern") {
4602
4719
  const alias = field(statement, "alias");
@@ -4606,7 +4723,34 @@ function typeGivenBy(statement, name) {
4606
4723
  }
4607
4724
  return null;
4608
4725
  }
4609
- function declaredTypeName(name, from) {
4726
+ function builtType(call, options) {
4727
+ const root = rootOf(call);
4728
+ const callee = field(root, "function");
4729
+ if (callee?.type === "identifier") {
4730
+ return isClassName(callee.text) ? callee.text : resolvedReturnType(options, callee);
4731
+ }
4732
+ const object = callee?.type === "attribute" ? field(callee, "object") : null;
4733
+ if (object?.type === "identifier" && isClassName(object.text)) {
4734
+ return object.text;
4735
+ }
4736
+ const args = field(root, "arguments");
4737
+ const first = (args === null ? [] : children(args))[0];
4738
+ if (first?.type === "identifier" && isClassName(first.text)) {
4739
+ return first.text;
4740
+ }
4741
+ return callee === null ? null : resolvedReturnType(options, callee);
4742
+ }
4743
+ function resolvedReturnType(options, callee) {
4744
+ const settled = settledCallee(options, callee);
4745
+ return settled === void 0 ? null : returnTypeName(options.definitionAt(settled));
4746
+ }
4747
+ function settledCallee(options, callee) {
4748
+ const key = readKey(options.filePath, callee, enclosingFunction(callee));
4749
+ resolveCalls(options.facts, [key]);
4750
+ const resolved = options.facts.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
4751
+ return resolved.length === 1 ? resolved[0] : void 0;
4752
+ }
4753
+ function declaredTypeName(name, from, options) {
4610
4754
  const fn = enclosingFunction(from);
4611
4755
  if (fn === null) {
4612
4756
  return null;
@@ -4619,7 +4763,7 @@ function declaredTypeName(name, from) {
4619
4763
  }
4620
4764
  }
4621
4765
  const visit = (node) => {
4622
- const given = typeGivenBy(node, name);
4766
+ const given = typeGivenBy(node, name, options);
4623
4767
  if (given !== null) {
4624
4768
  return given;
4625
4769
  }
@@ -4648,16 +4792,27 @@ function typedReceiverPattern(options, chain) {
4648
4792
  if (receiver === null) {
4649
4793
  return void 0;
4650
4794
  }
4651
- const typeName = declaredTypeName(receiver, chain.root);
4795
+ const typeName = declaredTypeName(receiver, chain.root, options);
4652
4796
  if (typeName === null) {
4653
4797
  return void 0;
4654
4798
  }
4655
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${typeName}`);
4656
- if (from === void 0) {
4657
- return void 0;
4799
+ const typeKey = `${options.filePath}#${typeName}`;
4800
+ const imported = options.facts.facts("pyImportedName").find((row) => String(row[0]) === typeKey);
4801
+ const direct = imported === void 0 ? void 0 : queryTypePattern(options, String(imported[1]), String(imported[2]));
4802
+ if (direct !== void 0) {
4803
+ return direct;
4804
+ }
4805
+ for (const origin of originsOf(options.facts, typeKey)) {
4806
+ const pattern = queryTypePattern(options, origin.module, origin.name);
4807
+ if (pattern !== void 0) {
4808
+ return pattern;
4809
+ }
4658
4810
  }
4811
+ return void 0;
4812
+ }
4813
+ function queryTypePattern(options, module, name) {
4659
4814
  return options.patterns.find(
4660
- (pattern) => String(from[1]) === pattern.module && pattern.queryTypes.includes(String(from[2]))
4815
+ (pattern) => module === pattern.module && pattern.queryTypes.includes(name)
4661
4816
  );
4662
4817
  }
4663
4818
  function fieldsOf(chain, valueMethods) {
@@ -4712,7 +4867,71 @@ function selectorOf(chain, valueMethods) {
4712
4867
  )
4713
4868
  ];
4714
4869
  }
4715
- function effectFor(pattern, chain, operation) {
4870
+ var TABLE_METHODS = ["select_from", "join"];
4871
+ function modelOf(chain, options) {
4872
+ const fromRoot = modelArgument(chain.root, chain.root, options);
4873
+ if (fromRoot !== null) {
4874
+ return fromRoot;
4875
+ }
4876
+ for (const call of laterCalls(chain)) {
4877
+ if (TABLE_METHODS.includes(methodNameOf(call))) {
4878
+ const named = modelArgument(call, chain.root, options);
4879
+ if (named !== null) {
4880
+ return named;
4881
+ }
4882
+ }
4883
+ }
4884
+ return isClassName(chain.subject) ? chain.subject : null;
4885
+ }
4886
+ function modelArgument(call, from, options) {
4887
+ const args = field(call, "arguments");
4888
+ const first = (args === null ? [] : children(args)).find(
4889
+ (argument) => argument.type !== "keyword_argument"
4890
+ );
4891
+ return first === void 0 ? null : modelNamed(first, from, options);
4892
+ }
4893
+ function modelNamed(node, from, options) {
4894
+ if (node.type === "identifier") {
4895
+ if (isClassName(node.text)) {
4896
+ return node.text;
4897
+ }
4898
+ const declared = declaredTypeName(node.text, from, options);
4899
+ return declared === null ? null : classBehind(declared, options);
4900
+ }
4901
+ if (node.type === "attribute") {
4902
+ const property = field(node, "attribute");
4903
+ if (property !== null && isClassName(property.text)) {
4904
+ return property.text;
4905
+ }
4906
+ const object = field(node, "object");
4907
+ return object === null ? null : modelNamed(object, from, options);
4908
+ }
4909
+ if (node.type === "call") {
4910
+ const callee = field(node, "function");
4911
+ const built = callee === null ? null : modelNamed(callee, from, options);
4912
+ return built ?? modelArgument(node, from, options);
4913
+ }
4914
+ return null;
4915
+ }
4916
+ function classBehind(typeName, options) {
4917
+ const key = `${options.filePath}#${typeName}`;
4918
+ originsOf(options.facts, key);
4919
+ const reached = new Set(
4920
+ options.facts.facts("wantedComesTo").filter((row) => String(row[0]) === key).map((row) => String(row[1]))
4921
+ );
4922
+ const classes = new Set(
4923
+ options.facts.facts("objectValue").map((row) => String(row[0])).filter((value) => reached.has(value))
4924
+ );
4925
+ const exported = options.facts.facts("exportsAs").find((row) => classes.has(String(row[2])));
4926
+ if (exported !== void 0) {
4927
+ return String(exported[1]);
4928
+ }
4929
+ return isClassName(typeName) ? typeName : null;
4930
+ }
4931
+ function isClassName(name) {
4932
+ return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
4933
+ }
4934
+ function effectFor(pattern, chain, operation, options) {
4716
4935
  const valueMethods = pattern.valueMethods ?? [];
4717
4936
  const picked = selectorOf(chain, valueMethods);
4718
4937
  return {
@@ -4720,8 +4939,8 @@ function effectFor(pattern, chain, operation) {
4720
4939
  binding: storageBinding2({
4721
4940
  recognition: "python-storage",
4722
4941
  storageSystem: pattern.storageSystem,
4723
- scope: pattern.module,
4724
- container: chain.subject
4942
+ scope: "default",
4943
+ container: modelOf(chain, options)
4725
4944
  }),
4726
4945
  callee: chain.last.text,
4727
4946
  interaction: {
@@ -4738,23 +4957,14 @@ function fileOf(key) {
4738
4957
  return at === -1 ? key : key.slice(0, at);
4739
4958
  }
4740
4959
  function returnTypeName(node) {
4741
- if (node === void 0) {
4742
- return null;
4743
- }
4744
- const annotation = field(node, "return_type");
4745
- if (annotation === null) {
4746
- return null;
4747
- }
4748
- return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
4960
+ const annotation = node === void 0 ? null : field(node, "return_type");
4961
+ return annotation === null ? null : typeNameOf(annotation);
4749
4962
  }
4750
4963
  function storageCallees(calls, filePath, couldMatch) {
4751
4964
  return chainsIn(calls).filter((chain) => couldMatch.has(methodName(chain.root))).map((chain) => field(chain.root, "function")).filter((callee) => callee !== null).map((callee) => readKey(filePath, callee, enclosingFunction(callee)));
4752
4965
  }
4753
4966
  function resolvedMethodPattern(options, callee) {
4754
- const resolved = options.facts.facts("wantedResolves").filter(
4755
- (row) => String(row[0]) === readKey(options.filePath, callee, enclosingFunction(callee))
4756
- ).map((row) => String(row[1]));
4757
- const settled = resolved.length === 1 ? resolved[0] : void 0;
4967
+ const settled = settledCallee(options, callee);
4758
4968
  if (settled === void 0) {
4759
4969
  return void 0;
4760
4970
  }
@@ -4831,7 +5041,7 @@ function storageCallIds(calls, options) {
4831
5041
  }
4832
5042
  function storageEffects(calls, options) {
4833
5043
  return matchedChains(calls, options).flatMap(
4834
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation)]
5044
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4835
5045
  );
4836
5046
  }
4837
5047
 
@@ -5127,6 +5337,7 @@ function classRouteUnits(pattern, pack, classification, classNode, module, optio
5127
5337
  pattern
5128
5338
  ),
5129
5339
  storage: options.storage,
5340
+ importedDefinition: options.importedDefinition,
5130
5341
  wrappers: wrappersAround(
5131
5342
  pattern,
5132
5343
  pack,
@@ -5456,7 +5667,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
5456
5667
  if (functionName === void 0) {
5457
5668
  return [];
5458
5669
  }
5459
- const ctx = createAnnotationContext(module.scopeFor);
5670
+ const ctx = createAnnotationContext(
5671
+ module.scopeFor,
5672
+ options.importedDefinition ?? null
5673
+ );
5460
5674
  return readRoutePaths(
5461
5675
  pattern,
5462
5676
  readPathArgument(classification, options),
@@ -5495,6 +5709,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
5495
5709
  ),
5496
5710
  definitionsCtx: ctx,
5497
5711
  storage: options.storage,
5712
+ importedDefinition: options.importedDefinition,
5498
5713
  wrappers: wrappersAround(
5499
5714
  pattern,
5500
5715
  pack,
@@ -5592,7 +5807,10 @@ function buildRouteUnit(options) {
5592
5807
  storage: storageLookup
5593
5808
  } = options;
5594
5809
  const template = valueToReadFurtherFrom(routePath);
5595
- const ctx = definitionsCtx ?? createAnnotationContext(module.scopeFor);
5810
+ const ctx = definitionsCtx ?? createAnnotationContext(
5811
+ module.scopeFor,
5812
+ options.importedDefinition ?? null
5813
+ );
5596
5814
  const parameters = readParameters(
5597
5815
  definitionNode,
5598
5816
  enclosingScope,
@@ -5673,7 +5891,7 @@ function buildRouteUnit(options) {
5673
5891
  declaredContract: null,
5674
5892
  readings: [
5675
5893
  routePath,
5676
- ...unreadRoleReadings(parameters, rangeOf(definitionNode)),
5894
+ ...template === null ? unreadRoleReadings(parameters, rangeOf(definitionNode)) : [],
5677
5895
  ...responseShape.passedOver
5678
5896
  ],
5679
5897
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
@@ -5768,7 +5986,7 @@ function readParameter(param, scope, ctx, pathParamNames, requestBodyFromAnnotat
5768
5986
  }
5769
5987
  const { name, typeNode } = info;
5770
5988
  const shape = typeNode !== null ? annotationToShape(typeNode, scope, ctx) : null;
5771
- const role = isInjectedParameter(param, injectedCallees) ? null : roleOf(name, shape, pathParamNames, requestBodyFromAnnotatedClass);
5989
+ const role = isInjectedParameter(param, injectedCallees, scope, ctx) ? null : roleOf(name, shape, pathParamNames, requestBodyFromAnnotatedClass);
5772
5990
  return {
5773
5991
  name,
5774
5992
  position,
@@ -5802,7 +6020,7 @@ function calleeName2(call) {
5802
6020
  }
5803
6021
  return null;
5804
6022
  }
5805
- function isInjectedParameter(param, injectedCallees) {
6023
+ function isInjectedParameter(param, injectedCallees, scope, ctx) {
5806
6024
  if (injectedCallees.size === 0) {
5807
6025
  return false;
5808
6026
  }
@@ -5816,18 +6034,26 @@ function isInjectedParameter(param, injectedCallees) {
5816
6034
  if (callsAnInjector(field(param, "value"))) {
5817
6035
  return true;
5818
6036
  }
5819
- const containsInjectorCall = (node, depth) => {
6037
+ const followed = /* @__PURE__ */ new Set();
6038
+ const containsInjectorCall = (node, nodeScope, depth) => {
5820
6039
  if (node === null || depth > MAX_ANNOTATION_DEPTH) {
5821
6040
  return false;
5822
6041
  }
5823
6042
  if (callsAnInjector(node)) {
5824
6043
  return true;
5825
6044
  }
6045
+ if (node.type === "identifier" && !followed.has(node.id)) {
6046
+ followed.add(node.id);
6047
+ const alias = aliasValueOf(node.text, nodeScope, ctx);
6048
+ if (alias !== null && containsInjectorCall(alias.node, alias.scope, 0)) {
6049
+ return true;
6050
+ }
6051
+ }
5826
6052
  return node.namedChildren.some(
5827
- (child) => containsInjectorCall(child, depth + 1)
6053
+ (child) => containsInjectorCall(child, nodeScope, depth + 1)
5828
6054
  );
5829
6055
  };
5830
- return containsInjectorCall(field(param, "type"), 0);
6056
+ return containsInjectorCall(field(param, "type"), scope, 0);
5831
6057
  }
5832
6058
 
5833
6059
  // src/facts/why.ts
@@ -5985,6 +6211,58 @@ function buildPythonExtractionReport(args) {
5985
6211
  });
5986
6212
  }
5987
6213
 
6214
+ // src/importedDefinitions.ts
6215
+ function importedDefinitionLookup(db, files) {
6216
+ const fileOfModuleScope = /* @__PURE__ */ new Map();
6217
+ const byFile = /* @__PURE__ */ new Map();
6218
+ for (const file of files) {
6219
+ fileOfModuleScope.set(file.module.moduleScope.node.id, file.file);
6220
+ byFile.set(file.file, file);
6221
+ }
6222
+ const found = /* @__PURE__ */ new Map();
6223
+ return (scope, name) => {
6224
+ const fromFile = fileOfModuleScope.get(moduleScopeOf(scope).node.id);
6225
+ if (fromFile === void 0) {
6226
+ return null;
6227
+ }
6228
+ const key = `${fromFile}#${name}`;
6229
+ const known = found.get(key);
6230
+ if (known !== void 0) {
6231
+ return known;
6232
+ }
6233
+ const definition = definitionAmong(originsOf(db, key), byFile);
6234
+ found.set(key, definition);
6235
+ return definition;
6236
+ };
6237
+ }
6238
+ function definitionAmong(origins, byFile) {
6239
+ for (const origin of origins) {
6240
+ const file = byFile.get(origin.module);
6241
+ const binding = file?.module.moduleScope.bindings.get(origin.name);
6242
+ if (file === void 0 || binding === void 0) {
6243
+ continue;
6244
+ }
6245
+ const node = definedNodeOf(binding);
6246
+ if (node !== null) {
6247
+ return {
6248
+ node,
6249
+ moduleScope: file.module.moduleScope,
6250
+ scopeFor: file.module.scopeFor
6251
+ };
6252
+ }
6253
+ }
6254
+ return null;
6255
+ }
6256
+ function definedNodeOf(binding) {
6257
+ if (binding.kind === "classDef") {
6258
+ return binding.node;
6259
+ }
6260
+ if (binding.kind === "assignment") {
6261
+ return binding.value;
6262
+ }
6263
+ return null;
6264
+ }
6265
+
5988
6266
  // src/reach/closure.ts
5989
6267
  import {
5990
6268
  functionCallBinding,
@@ -6055,14 +6333,9 @@ var EXPRESSION_RESOLVERS = {
6055
6333
  }
6056
6334
  return memberOf(resolveExpression(object, site, ctx), attribute.text, ctx);
6057
6335
  },
6058
- // `Service().run()` runs a method on whatever the constructor built.
6059
6336
  call: (node, site, ctx) => {
6060
6337
  const callee = field(node, "function");
6061
- if (callee === null) {
6062
- return stop("noDeclaration");
6063
- }
6064
- const built = resolveExpression(callee, site, ctx);
6065
- return built.kind === "class" ? built : stop("noDeclaration");
6338
+ return callee === null ? stop("noDeclaration") : instanceBuiltBy(resolveExpression(callee, site, ctx), "noDeclaration");
6066
6339
  },
6067
6340
  parenthesized_expression: (node, site, ctx) => {
6068
6341
  const inner = node.namedChildren[0];
@@ -6073,6 +6346,12 @@ function resolveExpression(node, site, ctx) {
6073
6346
  const resolver = EXPRESSION_RESOLVERS[node.type];
6074
6347
  return resolver === void 0 ? stop("noDeclaration") : resolver(node, site, ctx);
6075
6348
  }
6349
+ function instanceBuiltBy(built, otherwise) {
6350
+ if (built.kind === "class" || built.kind === "stop") {
6351
+ return built;
6352
+ }
6353
+ return built.kind === "package" ? stop("outsideRun") : stop(otherwise);
6354
+ }
6076
6355
  function resolveIdentifier(name, site, ctx) {
6077
6356
  if (site.rebound.has(name)) {
6078
6357
  return stop("unsettledValue");
@@ -6138,8 +6417,10 @@ function aliasedValue(value, file, scope, ctx, visited) {
6138
6417
  }
6139
6418
  if (value.type === "call") {
6140
6419
  const callee = field(value, "function");
6141
- const built = callee === null ? stop("unsettledValue") : aliasedValue(callee, file, scope, ctx, visited);
6142
- return built.kind === "class" ? built : stop("unsettledValue");
6420
+ return callee === null ? stop("unsettledValue") : instanceBuiltBy(
6421
+ aliasedValue(callee, file, scope, ctx, visited),
6422
+ "unsettledValue"
6423
+ );
6143
6424
  }
6144
6425
  if (value.type === "attribute") {
6145
6426
  const object = field(value, "object");
@@ -7229,6 +7510,7 @@ async function extractPythonProject(options) {
7229
7510
  storageFor
7230
7511
  })
7231
7512
  );
7513
+ const importedDefinition = importedDefinitionLookup(db, bound);
7232
7514
  const discovered = /* @__PURE__ */ new Map();
7233
7515
  for (const boundFile of bound) {
7234
7516
  const { file, root, module: moduleBinding } = boundFile;
@@ -7242,6 +7524,7 @@ async function extractPythonProject(options) {
7242
7524
  routerIndex,
7243
7525
  gapHandling,
7244
7526
  wrappers: wrapperIndex,
7527
+ importedDefinition,
7245
7528
  ...needsValues ? { facts: db } : {},
7246
7529
  ...storage === void 0 ? {} : { storage }
7247
7530
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-python",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "description": "Read a Python codebase with suss and check what each FastAPI or Flask route returns against the clients, specs and infrastructure that depend on it.",
6
6
  "keywords": [
@@ -49,13 +49,13 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@suss/behavioral-ir": "0.26.0",
53
- "@suss/datalog": "0.26.0",
54
- "@suss/extractor": "0.26.0",
55
- "@suss/ir-core": "0.26.0",
56
- "@suss/resolution": "0.26.0",
57
- "@suss/sql": "0.26.0",
58
- "@suss/values": "0.26.0",
52
+ "@suss/behavioral-ir": "0.27.0",
53
+ "@suss/datalog": "0.27.0",
54
+ "@suss/extractor": "0.27.0",
55
+ "@suss/ir-core": "0.27.0",
56
+ "@suss/resolution": "0.27.0",
57
+ "@suss/sql": "0.27.0",
58
+ "@suss/values": "0.27.0",
59
59
  "web-tree-sitter": "^0.26.11"
60
60
  },
61
61
  "devDependencies": {