@suss/adapter-python 0.5.2 → 0.5.4

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
@@ -564,6 +564,18 @@ function readArg(node) {
564
564
  if (node.type === "identifier") {
565
565
  return { kind: "identifier", name: node.text };
566
566
  }
567
+ if (node.type === "attribute") {
568
+ const object2 = field(node, "object");
569
+ const attribute = field(node, "attribute");
570
+ if (object2?.type === "identifier" && attribute?.type === "identifier") {
571
+ return {
572
+ kind: "attribute",
573
+ objectName: object2.text,
574
+ attributeName: attribute.text
575
+ };
576
+ }
577
+ return { kind: "other" };
578
+ }
567
579
  if (node.type === "list") {
568
580
  return {
569
581
  kind: "list",
@@ -17248,45 +17260,64 @@ function directStorageEffects(calls, options) {
17248
17260
  function discoverUnits(root, module2, options) {
17249
17261
  const units = [];
17250
17262
  for (const stmt of bodyStatements(root)) {
17251
- if (stmt.type !== "decorated_definition") {
17263
+ if (stmt.type === "decorated_definition") {
17264
+ units.push(...decoratedUnits(stmt, module2.moduleScope, module2, options));
17252
17265
  continue;
17253
17266
  }
17254
- const { definition, decorators } = stripDecorators(stmt);
17255
- for (const decoratorNode of decorators) {
17256
- const direct = classifyDecorator(decoratorNode, module2.moduleScope);
17257
- const classifications = direct.module !== null && acceptedByAnyPattern(direct.module, options) ? [direct] : [
17258
- unwrapDecorator(
17259
- decoratorNode,
17260
- module2.moduleScope,
17261
- module2,
17262
- (spec, name) => options.routerIndex?.moduleDef(
17263
- options.absoluteFile ?? options.filePath,
17264
- spec,
17265
- name
17266
- ) ?? null
17267
- )
17268
- ].filter(
17269
- (candidate) => candidate !== null
17270
- );
17271
- for (const classification of classifications) {
17272
- if (classification.module === null) {
17273
- continue;
17274
- }
17275
- const decoratorModule = classification.module;
17276
- for (const pack of options.packs) {
17277
- for (const pattern of pack.discovery) {
17278
- units.push(
17279
- ...unitsFor(
17280
- pattern,
17281
- pack,
17282
- decoratorModule,
17283
- classification,
17284
- definition,
17285
- module2,
17286
- options
17287
- )
17288
- );
17289
- }
17267
+ const { definition } = stripDecorators(stmt);
17268
+ if (definition.type !== "function_definition") {
17269
+ continue;
17270
+ }
17271
+ const body = field(definition, "body");
17272
+ const scope = module2.scopeFor.get(definition.id);
17273
+ if (body === null || scope === void 0) {
17274
+ continue;
17275
+ }
17276
+ for (const inner of bodyStatements(body)) {
17277
+ if (inner.type === "decorated_definition") {
17278
+ units.push(...decoratedUnits(inner, scope, module2, options));
17279
+ }
17280
+ }
17281
+ }
17282
+ return units;
17283
+ }
17284
+ function decoratedUnits(stmt, scope, module2, options) {
17285
+ const units = [];
17286
+ const { definition, decorators } = stripDecorators(stmt);
17287
+ for (const decoratorNode of decorators) {
17288
+ const direct = classifyDecorator(decoratorNode, scope);
17289
+ const classifications = direct.module !== null && acceptedByAnyPattern(direct.module, options) ? [direct] : [
17290
+ unwrapDecorator(
17291
+ decoratorNode,
17292
+ scope,
17293
+ module2,
17294
+ (spec, name) => options.routerIndex?.moduleDef(
17295
+ options.absoluteFile ?? options.filePath,
17296
+ spec,
17297
+ name
17298
+ ) ?? null
17299
+ )
17300
+ ].filter(
17301
+ (candidate) => candidate !== null
17302
+ );
17303
+ for (const classification of classifications) {
17304
+ if (classification.module === null) {
17305
+ continue;
17306
+ }
17307
+ const decoratorModule = classification.module;
17308
+ for (const pack of options.packs) {
17309
+ for (const pattern of pack.discovery) {
17310
+ units.push(
17311
+ ...unitsFor(
17312
+ pattern,
17313
+ pack,
17314
+ decoratorModule,
17315
+ classification,
17316
+ definition,
17317
+ module2,
17318
+ options
17319
+ )
17320
+ );
17290
17321
  }
17291
17322
  }
17292
17323
  }
@@ -18221,8 +18252,10 @@ function buildRouterIndex(files, packs, resolverOptions) {
18221
18252
  }
18222
18253
  }
18223
18254
  const byFile = /* @__PURE__ */ new Map();
18255
+ const displayPaths = /* @__PURE__ */ new Map();
18224
18256
  for (const file2 of files) {
18225
18257
  byFile.set(file2.file, file2);
18258
+ displayPaths.set(file2.module, file2.displayPath);
18226
18259
  }
18227
18260
  return {
18228
18261
  moduleDef(fromFile, spec, name) {
@@ -18262,14 +18295,15 @@ function buildRouterIndex(files, packs, resolverOptions) {
18262
18295
  if (ownPrefix.kind === "abstain") {
18263
18296
  return ownPrefix;
18264
18297
  }
18298
+ const routerPath = displayPaths.get(module2);
18265
18299
  const mount = index.mounts.get(construction);
18266
18300
  if (mount === void 0) {
18267
- return { kind: "abstain", reason: unmountedReason(index) };
18301
+ return { kind: "abstain", reason: unmountedReason(index, routerPath) };
18268
18302
  }
18269
18303
  if (mount.kind === "abstain") {
18270
18304
  return { kind: "abstain", reason: mount.reason };
18271
18305
  }
18272
- const rivalled = rivalRegistration(index, mount.site);
18306
+ const rivalled = rivalRegistration(index, mount.site, routerPath);
18273
18307
  if (rivalled !== null) {
18274
18308
  return { kind: "abstain", reason: rivalled };
18275
18309
  }
@@ -18303,24 +18337,32 @@ function loopsClause(loops, verb) {
18303
18337
  }
18304
18338
  return `loops at ${locations.join(", ")} ${verb}`;
18305
18339
  }
18306
- function unmountedReason(index) {
18307
- const loops = [...index.unenumerableLoops.values()];
18308
- if (loops.length === 0) {
18309
- return "is never mounted through a single variable binding in the files read";
18340
+ function couldReach(loop, routerPath) {
18341
+ if (routerPath === void 0) {
18342
+ return true;
18310
18343
  }
18344
+ const directory = loop.file.slice(0, loop.file.lastIndexOf("/") + 1);
18345
+ return routerPath.startsWith(directory);
18346
+ }
18347
+ function unmountedReason(index, routerPath) {
18348
+ const loops = [...index.unenumerableLoops.values()];
18311
18349
  const stopped = loops.find((loop) => loop.unmatched !== void 0);
18312
18350
  if (stopped?.unmatched !== void 0) {
18313
18351
  const { name, resolvedTo, matched, of } = stopped.unmatched;
18314
18352
  return `is not mounted by name in the files read, and a loop at ${stopped.location} mounts a list this reading resolved, where ${matched} of ${of} entries matched a router and ${name} did not (it resolved to ${resolvedTo}), so the whole list is declined rather than most of it mounted`;
18315
18353
  }
18316
- return `is not mounted by name in the files read, and ${loopsClause(loops, "mount")} routers read out of a call this reading does not follow`;
18354
+ const reachable = loops.filter((loop) => couldReach(loop, routerPath));
18355
+ if (reachable.length === 0) {
18356
+ return "is never mounted through a single variable binding in the files read";
18357
+ }
18358
+ return `is not mounted by name in the files read, and ${loopsClause(reachable, "mount")} routers read out of a call this reading does not follow`;
18317
18359
  }
18318
- function rivalRegistration(index, site) {
18360
+ function rivalRegistration(index, site, routerPath) {
18319
18361
  if (site.kind === "module") {
18320
18362
  return null;
18321
18363
  }
18322
18364
  const rivals = [...index.unenumerableLoops.values()].filter(
18323
- (loop) => !sameSite(loop.site, site)
18365
+ (loop) => !sameSite(loop.site, site) && couldReach(loop, routerPath)
18324
18366
  );
18325
18367
  if (rivals.length === 0) {
18326
18368
  return null;
@@ -18772,6 +18814,10 @@ function resolvedValueKey(facts, value) {
18772
18814
  return row === void 0 ? value : String(row[1]);
18773
18815
  }
18774
18816
  function mountedConstructions(arg, position, scan) {
18817
+ if (arg?.kind === "attribute") {
18818
+ const construction = constructionThroughModule(arg, position.scope, scan);
18819
+ return construction === null ? [] : [construction];
18820
+ }
18775
18821
  if (arg?.kind !== "identifier") {
18776
18822
  return [];
18777
18823
  }
@@ -18793,6 +18839,7 @@ function mountedConstructions(arg, position, scan) {
18793
18839
  }
18794
18840
  scan.index.unenumerableLoops.set(target.location, {
18795
18841
  site: position.site,
18842
+ file: scan.bound.displayPath,
18796
18843
  location: target.location,
18797
18844
  ...returned.kind === "unmatched" ? { unmatched: returned } : {}
18798
18845
  });
@@ -18800,10 +18847,42 @@ function mountedConstructions(arg, position, scan) {
18800
18847
  }
18801
18848
  scan.index.unenumerableLoops.set(target.location, {
18802
18849
  site: position.site,
18850
+ file: scan.bound.displayPath,
18803
18851
  location: target.location
18804
18852
  });
18805
18853
  return [];
18806
18854
  }
18855
+ function constructionThroughModule(arg, scope, scan) {
18856
+ const binding = resolveName(scope, arg.objectName);
18857
+ if (binding === null) {
18858
+ return null;
18859
+ }
18860
+ const spec = importedModuleSpec(binding);
18861
+ if (spec === null) {
18862
+ return null;
18863
+ }
18864
+ const resolution = resolveModule(scan.bound.file, spec, scan.resolverOptions);
18865
+ if (resolution.status !== "resolved") {
18866
+ return null;
18867
+ }
18868
+ const target = scan.byFile.get(resolution.file);
18869
+ if (target === void 0) {
18870
+ return null;
18871
+ }
18872
+ return scan.index.constructions.get(target.module)?.get(arg.attributeName) ?? null;
18873
+ }
18874
+ function importedModuleSpec(binding) {
18875
+ if (binding.kind === "import") {
18876
+ return { module: binding.module, relativeLevel: binding.relativeLevel };
18877
+ }
18878
+ if (binding.kind === "importFrom") {
18879
+ return {
18880
+ module: binding.module === "" ? binding.importedName : `${binding.module}.${binding.importedName}`,
18881
+ relativeLevel: binding.relativeLevel
18882
+ };
18883
+ }
18884
+ return null;
18885
+ }
18807
18886
  function recordMountStatement(stmt, position, scan) {
18808
18887
  const mountCall = mountCallOf(stmt, position.scope, scan);
18809
18888
  if (mountCall === null) {
package/dist/index.d.cts CHANGED
@@ -128,6 +128,12 @@ type DecoratorArg = {
128
128
  | {
129
129
  kind: "identifier";
130
130
  name: string;
131
+ }
132
+ /** One dotted hop, `routers.orders`. The caller decides what the object is. */
133
+ | {
134
+ kind: "attribute";
135
+ objectName: string;
136
+ attributeName: string;
131
137
  } | {
132
138
  kind: "list";
133
139
  items: DecoratorArg[];
package/dist/index.d.ts CHANGED
@@ -128,6 +128,12 @@ type DecoratorArg = {
128
128
  | {
129
129
  kind: "identifier";
130
130
  name: string;
131
+ }
132
+ /** One dotted hop, `routers.orders`. The caller decides what the object is. */
133
+ | {
134
+ kind: "attribute";
135
+ objectName: string;
136
+ attributeName: string;
131
137
  } | {
132
138
  kind: "list";
133
139
  items: DecoratorArg[];
package/dist/index.js CHANGED
@@ -510,6 +510,18 @@ function readArg(node) {
510
510
  if (node.type === "identifier") {
511
511
  return { kind: "identifier", name: node.text };
512
512
  }
513
+ if (node.type === "attribute") {
514
+ const object2 = field(node, "object");
515
+ const attribute = field(node, "attribute");
516
+ if (object2?.type === "identifier" && attribute?.type === "identifier") {
517
+ return {
518
+ kind: "attribute",
519
+ objectName: object2.text,
520
+ attributeName: attribute.text
521
+ };
522
+ }
523
+ return { kind: "other" };
524
+ }
513
525
  if (node.type === "list") {
514
526
  return {
515
527
  kind: "list",
@@ -17204,45 +17216,64 @@ function directStorageEffects(calls, options) {
17204
17216
  function discoverUnits(root, module, options) {
17205
17217
  const units = [];
17206
17218
  for (const stmt of bodyStatements(root)) {
17207
- if (stmt.type !== "decorated_definition") {
17219
+ if (stmt.type === "decorated_definition") {
17220
+ units.push(...decoratedUnits(stmt, module.moduleScope, module, options));
17208
17221
  continue;
17209
17222
  }
17210
- const { definition, decorators } = stripDecorators(stmt);
17211
- for (const decoratorNode of decorators) {
17212
- const direct = classifyDecorator(decoratorNode, module.moduleScope);
17213
- const classifications = direct.module !== null && acceptedByAnyPattern(direct.module, options) ? [direct] : [
17214
- unwrapDecorator(
17215
- decoratorNode,
17216
- module.moduleScope,
17217
- module,
17218
- (spec, name) => options.routerIndex?.moduleDef(
17219
- options.absoluteFile ?? options.filePath,
17220
- spec,
17221
- name
17222
- ) ?? null
17223
- )
17224
- ].filter(
17225
- (candidate) => candidate !== null
17226
- );
17227
- for (const classification of classifications) {
17228
- if (classification.module === null) {
17229
- continue;
17230
- }
17231
- const decoratorModule = classification.module;
17232
- for (const pack of options.packs) {
17233
- for (const pattern of pack.discovery) {
17234
- units.push(
17235
- ...unitsFor(
17236
- pattern,
17237
- pack,
17238
- decoratorModule,
17239
- classification,
17240
- definition,
17241
- module,
17242
- options
17243
- )
17244
- );
17245
- }
17223
+ const { definition } = stripDecorators(stmt);
17224
+ if (definition.type !== "function_definition") {
17225
+ continue;
17226
+ }
17227
+ const body = field(definition, "body");
17228
+ const scope = module.scopeFor.get(definition.id);
17229
+ if (body === null || scope === void 0) {
17230
+ continue;
17231
+ }
17232
+ for (const inner of bodyStatements(body)) {
17233
+ if (inner.type === "decorated_definition") {
17234
+ units.push(...decoratedUnits(inner, scope, module, options));
17235
+ }
17236
+ }
17237
+ }
17238
+ return units;
17239
+ }
17240
+ function decoratedUnits(stmt, scope, module, options) {
17241
+ const units = [];
17242
+ const { definition, decorators } = stripDecorators(stmt);
17243
+ for (const decoratorNode of decorators) {
17244
+ const direct = classifyDecorator(decoratorNode, scope);
17245
+ const classifications = direct.module !== null && acceptedByAnyPattern(direct.module, options) ? [direct] : [
17246
+ unwrapDecorator(
17247
+ decoratorNode,
17248
+ scope,
17249
+ module,
17250
+ (spec, name) => options.routerIndex?.moduleDef(
17251
+ options.absoluteFile ?? options.filePath,
17252
+ spec,
17253
+ name
17254
+ ) ?? null
17255
+ )
17256
+ ].filter(
17257
+ (candidate) => candidate !== null
17258
+ );
17259
+ for (const classification of classifications) {
17260
+ if (classification.module === null) {
17261
+ continue;
17262
+ }
17263
+ const decoratorModule = classification.module;
17264
+ for (const pack of options.packs) {
17265
+ for (const pattern of pack.discovery) {
17266
+ units.push(
17267
+ ...unitsFor(
17268
+ pattern,
17269
+ pack,
17270
+ decoratorModule,
17271
+ classification,
17272
+ definition,
17273
+ module,
17274
+ options
17275
+ )
17276
+ );
17246
17277
  }
17247
17278
  }
17248
17279
  }
@@ -18177,8 +18208,10 @@ function buildRouterIndex(files, packs, resolverOptions) {
18177
18208
  }
18178
18209
  }
18179
18210
  const byFile = /* @__PURE__ */ new Map();
18211
+ const displayPaths = /* @__PURE__ */ new Map();
18180
18212
  for (const file2 of files) {
18181
18213
  byFile.set(file2.file, file2);
18214
+ displayPaths.set(file2.module, file2.displayPath);
18182
18215
  }
18183
18216
  return {
18184
18217
  moduleDef(fromFile, spec, name) {
@@ -18218,14 +18251,15 @@ function buildRouterIndex(files, packs, resolverOptions) {
18218
18251
  if (ownPrefix.kind === "abstain") {
18219
18252
  return ownPrefix;
18220
18253
  }
18254
+ const routerPath = displayPaths.get(module);
18221
18255
  const mount = index.mounts.get(construction);
18222
18256
  if (mount === void 0) {
18223
- return { kind: "abstain", reason: unmountedReason(index) };
18257
+ return { kind: "abstain", reason: unmountedReason(index, routerPath) };
18224
18258
  }
18225
18259
  if (mount.kind === "abstain") {
18226
18260
  return { kind: "abstain", reason: mount.reason };
18227
18261
  }
18228
- const rivalled = rivalRegistration(index, mount.site);
18262
+ const rivalled = rivalRegistration(index, mount.site, routerPath);
18229
18263
  if (rivalled !== null) {
18230
18264
  return { kind: "abstain", reason: rivalled };
18231
18265
  }
@@ -18259,24 +18293,32 @@ function loopsClause(loops, verb) {
18259
18293
  }
18260
18294
  return `loops at ${locations.join(", ")} ${verb}`;
18261
18295
  }
18262
- function unmountedReason(index) {
18263
- const loops = [...index.unenumerableLoops.values()];
18264
- if (loops.length === 0) {
18265
- return "is never mounted through a single variable binding in the files read";
18296
+ function couldReach(loop, routerPath) {
18297
+ if (routerPath === void 0) {
18298
+ return true;
18266
18299
  }
18300
+ const directory = loop.file.slice(0, loop.file.lastIndexOf("/") + 1);
18301
+ return routerPath.startsWith(directory);
18302
+ }
18303
+ function unmountedReason(index, routerPath) {
18304
+ const loops = [...index.unenumerableLoops.values()];
18267
18305
  const stopped = loops.find((loop) => loop.unmatched !== void 0);
18268
18306
  if (stopped?.unmatched !== void 0) {
18269
18307
  const { name, resolvedTo, matched, of } = stopped.unmatched;
18270
18308
  return `is not mounted by name in the files read, and a loop at ${stopped.location} mounts a list this reading resolved, where ${matched} of ${of} entries matched a router and ${name} did not (it resolved to ${resolvedTo}), so the whole list is declined rather than most of it mounted`;
18271
18309
  }
18272
- return `is not mounted by name in the files read, and ${loopsClause(loops, "mount")} routers read out of a call this reading does not follow`;
18310
+ const reachable = loops.filter((loop) => couldReach(loop, routerPath));
18311
+ if (reachable.length === 0) {
18312
+ return "is never mounted through a single variable binding in the files read";
18313
+ }
18314
+ return `is not mounted by name in the files read, and ${loopsClause(reachable, "mount")} routers read out of a call this reading does not follow`;
18273
18315
  }
18274
- function rivalRegistration(index, site) {
18316
+ function rivalRegistration(index, site, routerPath) {
18275
18317
  if (site.kind === "module") {
18276
18318
  return null;
18277
18319
  }
18278
18320
  const rivals = [...index.unenumerableLoops.values()].filter(
18279
- (loop) => !sameSite(loop.site, site)
18321
+ (loop) => !sameSite(loop.site, site) && couldReach(loop, routerPath)
18280
18322
  );
18281
18323
  if (rivals.length === 0) {
18282
18324
  return null;
@@ -18728,6 +18770,10 @@ function resolvedValueKey(facts, value) {
18728
18770
  return row === void 0 ? value : String(row[1]);
18729
18771
  }
18730
18772
  function mountedConstructions(arg, position, scan) {
18773
+ if (arg?.kind === "attribute") {
18774
+ const construction = constructionThroughModule(arg, position.scope, scan);
18775
+ return construction === null ? [] : [construction];
18776
+ }
18731
18777
  if (arg?.kind !== "identifier") {
18732
18778
  return [];
18733
18779
  }
@@ -18749,6 +18795,7 @@ function mountedConstructions(arg, position, scan) {
18749
18795
  }
18750
18796
  scan.index.unenumerableLoops.set(target.location, {
18751
18797
  site: position.site,
18798
+ file: scan.bound.displayPath,
18752
18799
  location: target.location,
18753
18800
  ...returned.kind === "unmatched" ? { unmatched: returned } : {}
18754
18801
  });
@@ -18756,10 +18803,42 @@ function mountedConstructions(arg, position, scan) {
18756
18803
  }
18757
18804
  scan.index.unenumerableLoops.set(target.location, {
18758
18805
  site: position.site,
18806
+ file: scan.bound.displayPath,
18759
18807
  location: target.location
18760
18808
  });
18761
18809
  return [];
18762
18810
  }
18811
+ function constructionThroughModule(arg, scope, scan) {
18812
+ const binding = resolveName(scope, arg.objectName);
18813
+ if (binding === null) {
18814
+ return null;
18815
+ }
18816
+ const spec = importedModuleSpec(binding);
18817
+ if (spec === null) {
18818
+ return null;
18819
+ }
18820
+ const resolution = resolveModule(scan.bound.file, spec, scan.resolverOptions);
18821
+ if (resolution.status !== "resolved") {
18822
+ return null;
18823
+ }
18824
+ const target = scan.byFile.get(resolution.file);
18825
+ if (target === void 0) {
18826
+ return null;
18827
+ }
18828
+ return scan.index.constructions.get(target.module)?.get(arg.attributeName) ?? null;
18829
+ }
18830
+ function importedModuleSpec(binding) {
18831
+ if (binding.kind === "import") {
18832
+ return { module: binding.module, relativeLevel: binding.relativeLevel };
18833
+ }
18834
+ if (binding.kind === "importFrom") {
18835
+ return {
18836
+ module: binding.module === "" ? binding.importedName : `${binding.module}.${binding.importedName}`,
18837
+ relativeLevel: binding.relativeLevel
18838
+ };
18839
+ }
18840
+ return null;
18841
+ }
18763
18842
  function recordMountStatement(stmt, position, scan) {
18764
18843
  const mountCall = mountCallOf(stmt, position.scope, scan);
18765
18844
  if (mountCall === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-python",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "type": "module",
5
5
  "description": "Python language adapter for suss — parses source with tree-sitter (WASM), resolves names with a lexical binder, and emits 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.5.2",
51
- "@suss/datalog": "0.5.2",
52
- "@suss/extractor": "0.5.2",
50
+ "@suss/behavioral-ir": "0.5.4",
51
+ "@suss/datalog": "0.5.4",
52
+ "@suss/extractor": "0.5.4",
53
53
  "web-tree-sitter": "^0.26.11"
54
54
  },
55
55
  "devDependencies": {