@suss/adapter-python 0.5.1 → 0.5.3
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 +157 -30
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +157 -30
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -666,6 +666,82 @@ function classifyDecorator(decoratorNode, scope) {
|
|
|
666
666
|
}
|
|
667
667
|
return { ...resolveCallee(expr, scope), args: [], keywordArgs: {}, range };
|
|
668
668
|
}
|
|
669
|
+
function unwrapDecorator(decoratorNode, scope, ownModule, moduleDef) {
|
|
670
|
+
const expr = decoratorNode.namedChild(0);
|
|
671
|
+
if (expr === null || expr.type !== "call") {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
const callee = field(expr, "function");
|
|
675
|
+
if (callee === null || callee.type !== "identifier") {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
const wrapper = wrapperBehind(callee.text, scope, ownModule, moduleDef);
|
|
679
|
+
if (wrapper === null) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
const inner = singleReturnedCall(wrapper.node);
|
|
683
|
+
if (inner === null) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
const innerCallee = field(inner, "function");
|
|
687
|
+
if (innerCallee === null || innerCallee.type !== "attribute") {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
if (!passesParametersThrough(wrapper.node, inner)) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
const resolved = resolveCallee(innerCallee, wrapper.module.moduleScope);
|
|
694
|
+
if (resolved.importedName === null) {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
const argumentList = field(expr, "arguments");
|
|
698
|
+
const { args, keywordArgs } = argumentList?.type === "argument_list" ? readCallArguments(argumentList) : { args: [], keywordArgs: {} };
|
|
699
|
+
return {
|
|
700
|
+
...resolved,
|
|
701
|
+
args,
|
|
702
|
+
keywordArgs,
|
|
703
|
+
range: rangeOf(decoratorNode),
|
|
704
|
+
objectModule: wrapper.module
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function wrapperBehind(name, scope, ownModule, moduleDef) {
|
|
708
|
+
const binding = resolveName(scope, name);
|
|
709
|
+
if (binding?.kind === "functionDef") {
|
|
710
|
+
return { node: binding.node, module: ownModule };
|
|
711
|
+
}
|
|
712
|
+
if (binding?.kind === "importFrom") {
|
|
713
|
+
return moduleDef(
|
|
714
|
+
{ module: binding.module, relativeLevel: binding.relativeLevel },
|
|
715
|
+
binding.importedName
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
function singleReturnedCall(def) {
|
|
721
|
+
const body = field(def, "body");
|
|
722
|
+
if (body === null) {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
const statements = body.namedChildren.filter(
|
|
726
|
+
(child) => child !== null && child.type !== "comment"
|
|
727
|
+
);
|
|
728
|
+
if (statements.length !== 1 || statements[0]?.type !== "return_statement") {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const returned = statements[0].namedChildren[0];
|
|
732
|
+
return returned !== null && returned?.type === "call" ? returned : null;
|
|
733
|
+
}
|
|
734
|
+
function passesParametersThrough(def, inner) {
|
|
735
|
+
const params = field(def, "parameters");
|
|
736
|
+
const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
|
|
737
|
+
const argumentList = field(inner, "arguments");
|
|
738
|
+
const positional = (argumentList?.namedChildren ?? []).filter(
|
|
739
|
+
(child) => child !== null && child.type !== "keyword_argument"
|
|
740
|
+
);
|
|
741
|
+
return positional.length === names.length && positional.every(
|
|
742
|
+
(argument, at) => argument.type === "identifier" && argument.text === names[at]
|
|
743
|
+
);
|
|
744
|
+
}
|
|
669
745
|
|
|
670
746
|
// src/discovery.ts
|
|
671
747
|
var import_behavioral_ir = require("@suss/behavioral-ir");
|
|
@@ -17177,33 +17253,51 @@ function discoverUnits(root, module2, options) {
|
|
|
17177
17253
|
}
|
|
17178
17254
|
const { definition, decorators } = stripDecorators(stmt);
|
|
17179
17255
|
for (const decoratorNode of decorators) {
|
|
17180
|
-
const
|
|
17181
|
-
|
|
17182
|
-
|
|
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
|
|
17183
17270
|
);
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
for (const
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
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
|
+
}
|
|
17201
17290
|
}
|
|
17202
17291
|
}
|
|
17203
17292
|
}
|
|
17204
17293
|
}
|
|
17205
17294
|
return units;
|
|
17206
17295
|
}
|
|
17296
|
+
function acceptedByAnyPattern(module2, options) {
|
|
17297
|
+
return options.packs.some(
|
|
17298
|
+
(pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module2))
|
|
17299
|
+
);
|
|
17300
|
+
}
|
|
17207
17301
|
function unitsFor(pattern, pack, decoratorModule, classification, definition, module2, options) {
|
|
17208
17302
|
if (!pattern.importModule.includes(decoratorModule)) {
|
|
17209
17303
|
return [];
|
|
@@ -17381,7 +17475,7 @@ function readRouterPrefix(pattern, classification, module2, options, range) {
|
|
|
17381
17475
|
}
|
|
17382
17476
|
const resolution = options.routerIndex.resolve(
|
|
17383
17477
|
pattern,
|
|
17384
|
-
module2,
|
|
17478
|
+
classification.objectModule ?? module2,
|
|
17385
17479
|
classification.objectName
|
|
17386
17480
|
);
|
|
17387
17481
|
if (resolution.kind === "abstain") {
|
|
@@ -18126,7 +18220,28 @@ function buildRouterIndex(files, packs, resolverOptions) {
|
|
|
18126
18220
|
}
|
|
18127
18221
|
}
|
|
18128
18222
|
}
|
|
18223
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
18224
|
+
const displayPaths = /* @__PURE__ */ new Map();
|
|
18225
|
+
for (const file2 of files) {
|
|
18226
|
+
byFile.set(file2.file, file2);
|
|
18227
|
+
displayPaths.set(file2.module, file2.displayPath);
|
|
18228
|
+
}
|
|
18129
18229
|
return {
|
|
18230
|
+
moduleDef(fromFile, spec, name) {
|
|
18231
|
+
const resolution = resolveModule(fromFile, spec, resolverOptions);
|
|
18232
|
+
if (resolution.status !== "resolved") {
|
|
18233
|
+
return null;
|
|
18234
|
+
}
|
|
18235
|
+
const bound = byFile.get(resolution.file);
|
|
18236
|
+
if (bound === void 0) {
|
|
18237
|
+
return null;
|
|
18238
|
+
}
|
|
18239
|
+
const binding = bound.module.moduleScope.bindings.get(name);
|
|
18240
|
+
if (binding?.kind !== "functionDef") {
|
|
18241
|
+
return null;
|
|
18242
|
+
}
|
|
18243
|
+
return { node: binding.node, module: bound.module };
|
|
18244
|
+
},
|
|
18130
18245
|
resolve(pattern, module2, objectName) {
|
|
18131
18246
|
const index = byPattern.get(pattern);
|
|
18132
18247
|
if (index === void 0) {
|
|
@@ -18149,14 +18264,15 @@ function buildRouterIndex(files, packs, resolverOptions) {
|
|
|
18149
18264
|
if (ownPrefix.kind === "abstain") {
|
|
18150
18265
|
return ownPrefix;
|
|
18151
18266
|
}
|
|
18267
|
+
const routerPath = displayPaths.get(module2);
|
|
18152
18268
|
const mount = index.mounts.get(construction);
|
|
18153
18269
|
if (mount === void 0) {
|
|
18154
|
-
return { kind: "abstain", reason: unmountedReason(index) };
|
|
18270
|
+
return { kind: "abstain", reason: unmountedReason(index, routerPath) };
|
|
18155
18271
|
}
|
|
18156
18272
|
if (mount.kind === "abstain") {
|
|
18157
18273
|
return { kind: "abstain", reason: mount.reason };
|
|
18158
18274
|
}
|
|
18159
|
-
const rivalled = rivalRegistration(index, mount.site);
|
|
18275
|
+
const rivalled = rivalRegistration(index, mount.site, routerPath);
|
|
18160
18276
|
if (rivalled !== null) {
|
|
18161
18277
|
return { kind: "abstain", reason: rivalled };
|
|
18162
18278
|
}
|
|
@@ -18190,24 +18306,32 @@ function loopsClause(loops, verb) {
|
|
|
18190
18306
|
}
|
|
18191
18307
|
return `loops at ${locations.join(", ")} ${verb}`;
|
|
18192
18308
|
}
|
|
18193
|
-
function
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
return "is never mounted through a single variable binding in the files read";
|
|
18309
|
+
function couldReach(loop, routerPath) {
|
|
18310
|
+
if (routerPath === void 0) {
|
|
18311
|
+
return true;
|
|
18197
18312
|
}
|
|
18313
|
+
const directory = loop.file.slice(0, loop.file.lastIndexOf("/") + 1);
|
|
18314
|
+
return routerPath.startsWith(directory);
|
|
18315
|
+
}
|
|
18316
|
+
function unmountedReason(index, routerPath) {
|
|
18317
|
+
const loops = [...index.unenumerableLoops.values()];
|
|
18198
18318
|
const stopped = loops.find((loop) => loop.unmatched !== void 0);
|
|
18199
18319
|
if (stopped?.unmatched !== void 0) {
|
|
18200
18320
|
const { name, resolvedTo, matched, of } = stopped.unmatched;
|
|
18201
18321
|
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`;
|
|
18202
18322
|
}
|
|
18203
|
-
|
|
18323
|
+
const reachable = loops.filter((loop) => couldReach(loop, routerPath));
|
|
18324
|
+
if (reachable.length === 0) {
|
|
18325
|
+
return "is never mounted through a single variable binding in the files read";
|
|
18326
|
+
}
|
|
18327
|
+
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`;
|
|
18204
18328
|
}
|
|
18205
|
-
function rivalRegistration(index, site) {
|
|
18329
|
+
function rivalRegistration(index, site, routerPath) {
|
|
18206
18330
|
if (site.kind === "module") {
|
|
18207
18331
|
return null;
|
|
18208
18332
|
}
|
|
18209
18333
|
const rivals = [...index.unenumerableLoops.values()].filter(
|
|
18210
|
-
(loop) => !sameSite(loop.site, site)
|
|
18334
|
+
(loop) => !sameSite(loop.site, site) && couldReach(loop, routerPath)
|
|
18211
18335
|
);
|
|
18212
18336
|
if (rivals.length === 0) {
|
|
18213
18337
|
return null;
|
|
@@ -18680,6 +18804,7 @@ function mountedConstructions(arg, position, scan) {
|
|
|
18680
18804
|
}
|
|
18681
18805
|
scan.index.unenumerableLoops.set(target.location, {
|
|
18682
18806
|
site: position.site,
|
|
18807
|
+
file: scan.bound.displayPath,
|
|
18683
18808
|
location: target.location,
|
|
18684
18809
|
...returned.kind === "unmatched" ? { unmatched: returned } : {}
|
|
18685
18810
|
});
|
|
@@ -18687,6 +18812,7 @@ function mountedConstructions(arg, position, scan) {
|
|
|
18687
18812
|
}
|
|
18688
18813
|
scan.index.unenumerableLoops.set(target.location, {
|
|
18689
18814
|
site: position.site,
|
|
18815
|
+
file: scan.bound.displayPath,
|
|
18690
18816
|
location: target.location
|
|
18691
18817
|
});
|
|
18692
18818
|
return [];
|
|
@@ -19015,6 +19141,7 @@ async function extractPythonProject(options) {
|
|
|
19015
19141
|
const rawUnits = discoverUnits(root, moduleBinding, {
|
|
19016
19142
|
packs: options.packs,
|
|
19017
19143
|
filePath: displayPath,
|
|
19144
|
+
absoluteFile: file2,
|
|
19018
19145
|
routerIndex,
|
|
19019
19146
|
gapHandling,
|
|
19020
19147
|
...storagePatterns.length > 0 ? {
|
package/dist/index.d.cts
CHANGED
|
@@ -146,6 +146,12 @@ interface DecoratorClassification {
|
|
|
146
146
|
keywordArgs: Record<string, DecoratorArg>;
|
|
147
147
|
/** Where the decorator is written. Anything we read out of its arguments uses this as its provenance. */
|
|
148
148
|
range: Range;
|
|
149
|
+
/**
|
|
150
|
+
* The module the decorator's object lives in, when the decorator was read
|
|
151
|
+
* through a project wrapper written in another file. The router prefix is
|
|
152
|
+
* resolved there, since that is where the namespace is constructed.
|
|
153
|
+
*/
|
|
154
|
+
objectModule?: ModuleBinding;
|
|
149
155
|
}
|
|
150
156
|
declare function classifyDecorator(decoratorNode: Node, scope: Scope): DecoratorClassification;
|
|
151
157
|
|
|
@@ -417,6 +423,18 @@ type RoutePrefixResolution = {
|
|
|
417
423
|
};
|
|
418
424
|
interface RouterIndex {
|
|
419
425
|
resolve(pattern: PythonDiscoveryPattern, module: ModuleBinding, objectName: string): RoutePrefixResolution;
|
|
426
|
+
/**
|
|
427
|
+
* The module-level def a name in another module refers to, with that
|
|
428
|
+
* module's own bindings, so a decorator written through a project wrapper
|
|
429
|
+
* can be read where the wrapper is written.
|
|
430
|
+
*/
|
|
431
|
+
moduleDef(fromFile: string, spec: {
|
|
432
|
+
module: string;
|
|
433
|
+
relativeLevel: number;
|
|
434
|
+
}, name: string): {
|
|
435
|
+
node: Node;
|
|
436
|
+
module: ModuleBinding;
|
|
437
|
+
} | null;
|
|
420
438
|
}
|
|
421
439
|
interface RouterIndexOptions extends ModuleResolverOptions {
|
|
422
440
|
/** The project's facts, so a loop over a call can be settled by the rules. */
|
|
@@ -448,6 +466,8 @@ interface DiscoveryOptions {
|
|
|
448
466
|
gapHandling?: ExtractorOptions["gapHandling"];
|
|
449
467
|
/** What a pack needs to say a call talks to the database. Absent when no pack does. */
|
|
450
468
|
storage?: StorageLookup | undefined;
|
|
469
|
+
/** The file's absolute path, which module resolution wants; `filePath` may be shortened for display. */
|
|
470
|
+
absoluteFile?: string | undefined;
|
|
451
471
|
}
|
|
452
472
|
declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
|
|
453
473
|
|
package/dist/index.d.ts
CHANGED
|
@@ -146,6 +146,12 @@ interface DecoratorClassification {
|
|
|
146
146
|
keywordArgs: Record<string, DecoratorArg>;
|
|
147
147
|
/** Where the decorator is written. Anything we read out of its arguments uses this as its provenance. */
|
|
148
148
|
range: Range;
|
|
149
|
+
/**
|
|
150
|
+
* The module the decorator's object lives in, when the decorator was read
|
|
151
|
+
* through a project wrapper written in another file. The router prefix is
|
|
152
|
+
* resolved there, since that is where the namespace is constructed.
|
|
153
|
+
*/
|
|
154
|
+
objectModule?: ModuleBinding;
|
|
149
155
|
}
|
|
150
156
|
declare function classifyDecorator(decoratorNode: Node, scope: Scope): DecoratorClassification;
|
|
151
157
|
|
|
@@ -417,6 +423,18 @@ type RoutePrefixResolution = {
|
|
|
417
423
|
};
|
|
418
424
|
interface RouterIndex {
|
|
419
425
|
resolve(pattern: PythonDiscoveryPattern, module: ModuleBinding, objectName: string): RoutePrefixResolution;
|
|
426
|
+
/**
|
|
427
|
+
* The module-level def a name in another module refers to, with that
|
|
428
|
+
* module's own bindings, so a decorator written through a project wrapper
|
|
429
|
+
* can be read where the wrapper is written.
|
|
430
|
+
*/
|
|
431
|
+
moduleDef(fromFile: string, spec: {
|
|
432
|
+
module: string;
|
|
433
|
+
relativeLevel: number;
|
|
434
|
+
}, name: string): {
|
|
435
|
+
node: Node;
|
|
436
|
+
module: ModuleBinding;
|
|
437
|
+
} | null;
|
|
420
438
|
}
|
|
421
439
|
interface RouterIndexOptions extends ModuleResolverOptions {
|
|
422
440
|
/** The project's facts, so a loop over a call can be settled by the rules. */
|
|
@@ -448,6 +466,8 @@ interface DiscoveryOptions {
|
|
|
448
466
|
gapHandling?: ExtractorOptions["gapHandling"];
|
|
449
467
|
/** What a pack needs to say a call talks to the database. Absent when no pack does. */
|
|
450
468
|
storage?: StorageLookup | undefined;
|
|
469
|
+
/** The file's absolute path, which module resolution wants; `filePath` may be shortened for display. */
|
|
470
|
+
absoluteFile?: string | undefined;
|
|
451
471
|
}
|
|
452
472
|
declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
|
|
453
473
|
|
package/dist/index.js
CHANGED
|
@@ -612,6 +612,82 @@ function classifyDecorator(decoratorNode, scope) {
|
|
|
612
612
|
}
|
|
613
613
|
return { ...resolveCallee(expr, scope), args: [], keywordArgs: {}, range };
|
|
614
614
|
}
|
|
615
|
+
function unwrapDecorator(decoratorNode, scope, ownModule, moduleDef) {
|
|
616
|
+
const expr = decoratorNode.namedChild(0);
|
|
617
|
+
if (expr === null || expr.type !== "call") {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
const callee = field(expr, "function");
|
|
621
|
+
if (callee === null || callee.type !== "identifier") {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
const wrapper = wrapperBehind(callee.text, scope, ownModule, moduleDef);
|
|
625
|
+
if (wrapper === null) {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
const inner = singleReturnedCall(wrapper.node);
|
|
629
|
+
if (inner === null) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const innerCallee = field(inner, "function");
|
|
633
|
+
if (innerCallee === null || innerCallee.type !== "attribute") {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
if (!passesParametersThrough(wrapper.node, inner)) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
const resolved = resolveCallee(innerCallee, wrapper.module.moduleScope);
|
|
640
|
+
if (resolved.importedName === null) {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
const argumentList = field(expr, "arguments");
|
|
644
|
+
const { args, keywordArgs } = argumentList?.type === "argument_list" ? readCallArguments(argumentList) : { args: [], keywordArgs: {} };
|
|
645
|
+
return {
|
|
646
|
+
...resolved,
|
|
647
|
+
args,
|
|
648
|
+
keywordArgs,
|
|
649
|
+
range: rangeOf(decoratorNode),
|
|
650
|
+
objectModule: wrapper.module
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function wrapperBehind(name, scope, ownModule, moduleDef) {
|
|
654
|
+
const binding = resolveName(scope, name);
|
|
655
|
+
if (binding?.kind === "functionDef") {
|
|
656
|
+
return { node: binding.node, module: ownModule };
|
|
657
|
+
}
|
|
658
|
+
if (binding?.kind === "importFrom") {
|
|
659
|
+
return moduleDef(
|
|
660
|
+
{ module: binding.module, relativeLevel: binding.relativeLevel },
|
|
661
|
+
binding.importedName
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
function singleReturnedCall(def) {
|
|
667
|
+
const body = field(def, "body");
|
|
668
|
+
if (body === null) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
const statements = body.namedChildren.filter(
|
|
672
|
+
(child) => child !== null && child.type !== "comment"
|
|
673
|
+
);
|
|
674
|
+
if (statements.length !== 1 || statements[0]?.type !== "return_statement") {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
const returned = statements[0].namedChildren[0];
|
|
678
|
+
return returned !== null && returned?.type === "call" ? returned : null;
|
|
679
|
+
}
|
|
680
|
+
function passesParametersThrough(def, inner) {
|
|
681
|
+
const params = field(def, "parameters");
|
|
682
|
+
const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
|
|
683
|
+
const argumentList = field(inner, "arguments");
|
|
684
|
+
const positional = (argumentList?.namedChildren ?? []).filter(
|
|
685
|
+
(child) => child !== null && child.type !== "keyword_argument"
|
|
686
|
+
);
|
|
687
|
+
return positional.length === names.length && positional.every(
|
|
688
|
+
(argument, at) => argument.type === "identifier" && argument.text === names[at]
|
|
689
|
+
);
|
|
690
|
+
}
|
|
615
691
|
|
|
616
692
|
// src/discovery.ts
|
|
617
693
|
import { dispatchByType, restBinding } from "@suss/behavioral-ir";
|
|
@@ -17133,33 +17209,51 @@ function discoverUnits(root, module, options) {
|
|
|
17133
17209
|
}
|
|
17134
17210
|
const { definition, decorators } = stripDecorators(stmt);
|
|
17135
17211
|
for (const decoratorNode of decorators) {
|
|
17136
|
-
const
|
|
17137
|
-
|
|
17138
|
-
|
|
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
|
|
17139
17226
|
);
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
for (const
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
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
|
+
}
|
|
17157
17246
|
}
|
|
17158
17247
|
}
|
|
17159
17248
|
}
|
|
17160
17249
|
}
|
|
17161
17250
|
return units;
|
|
17162
17251
|
}
|
|
17252
|
+
function acceptedByAnyPattern(module, options) {
|
|
17253
|
+
return options.packs.some(
|
|
17254
|
+
(pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module))
|
|
17255
|
+
);
|
|
17256
|
+
}
|
|
17163
17257
|
function unitsFor(pattern, pack, decoratorModule, classification, definition, module, options) {
|
|
17164
17258
|
if (!pattern.importModule.includes(decoratorModule)) {
|
|
17165
17259
|
return [];
|
|
@@ -17337,7 +17431,7 @@ function readRouterPrefix(pattern, classification, module, options, range) {
|
|
|
17337
17431
|
}
|
|
17338
17432
|
const resolution = options.routerIndex.resolve(
|
|
17339
17433
|
pattern,
|
|
17340
|
-
module,
|
|
17434
|
+
classification.objectModule ?? module,
|
|
17341
17435
|
classification.objectName
|
|
17342
17436
|
);
|
|
17343
17437
|
if (resolution.kind === "abstain") {
|
|
@@ -18082,7 +18176,28 @@ function buildRouterIndex(files, packs, resolverOptions) {
|
|
|
18082
18176
|
}
|
|
18083
18177
|
}
|
|
18084
18178
|
}
|
|
18179
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
18180
|
+
const displayPaths = /* @__PURE__ */ new Map();
|
|
18181
|
+
for (const file2 of files) {
|
|
18182
|
+
byFile.set(file2.file, file2);
|
|
18183
|
+
displayPaths.set(file2.module, file2.displayPath);
|
|
18184
|
+
}
|
|
18085
18185
|
return {
|
|
18186
|
+
moduleDef(fromFile, spec, name) {
|
|
18187
|
+
const resolution = resolveModule(fromFile, spec, resolverOptions);
|
|
18188
|
+
if (resolution.status !== "resolved") {
|
|
18189
|
+
return null;
|
|
18190
|
+
}
|
|
18191
|
+
const bound = byFile.get(resolution.file);
|
|
18192
|
+
if (bound === void 0) {
|
|
18193
|
+
return null;
|
|
18194
|
+
}
|
|
18195
|
+
const binding = bound.module.moduleScope.bindings.get(name);
|
|
18196
|
+
if (binding?.kind !== "functionDef") {
|
|
18197
|
+
return null;
|
|
18198
|
+
}
|
|
18199
|
+
return { node: binding.node, module: bound.module };
|
|
18200
|
+
},
|
|
18086
18201
|
resolve(pattern, module, objectName) {
|
|
18087
18202
|
const index = byPattern.get(pattern);
|
|
18088
18203
|
if (index === void 0) {
|
|
@@ -18105,14 +18220,15 @@ function buildRouterIndex(files, packs, resolverOptions) {
|
|
|
18105
18220
|
if (ownPrefix.kind === "abstain") {
|
|
18106
18221
|
return ownPrefix;
|
|
18107
18222
|
}
|
|
18223
|
+
const routerPath = displayPaths.get(module);
|
|
18108
18224
|
const mount = index.mounts.get(construction);
|
|
18109
18225
|
if (mount === void 0) {
|
|
18110
|
-
return { kind: "abstain", reason: unmountedReason(index) };
|
|
18226
|
+
return { kind: "abstain", reason: unmountedReason(index, routerPath) };
|
|
18111
18227
|
}
|
|
18112
18228
|
if (mount.kind === "abstain") {
|
|
18113
18229
|
return { kind: "abstain", reason: mount.reason };
|
|
18114
18230
|
}
|
|
18115
|
-
const rivalled = rivalRegistration(index, mount.site);
|
|
18231
|
+
const rivalled = rivalRegistration(index, mount.site, routerPath);
|
|
18116
18232
|
if (rivalled !== null) {
|
|
18117
18233
|
return { kind: "abstain", reason: rivalled };
|
|
18118
18234
|
}
|
|
@@ -18146,24 +18262,32 @@ function loopsClause(loops, verb) {
|
|
|
18146
18262
|
}
|
|
18147
18263
|
return `loops at ${locations.join(", ")} ${verb}`;
|
|
18148
18264
|
}
|
|
18149
|
-
function
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
return "is never mounted through a single variable binding in the files read";
|
|
18265
|
+
function couldReach(loop, routerPath) {
|
|
18266
|
+
if (routerPath === void 0) {
|
|
18267
|
+
return true;
|
|
18153
18268
|
}
|
|
18269
|
+
const directory = loop.file.slice(0, loop.file.lastIndexOf("/") + 1);
|
|
18270
|
+
return routerPath.startsWith(directory);
|
|
18271
|
+
}
|
|
18272
|
+
function unmountedReason(index, routerPath) {
|
|
18273
|
+
const loops = [...index.unenumerableLoops.values()];
|
|
18154
18274
|
const stopped = loops.find((loop) => loop.unmatched !== void 0);
|
|
18155
18275
|
if (stopped?.unmatched !== void 0) {
|
|
18156
18276
|
const { name, resolvedTo, matched, of } = stopped.unmatched;
|
|
18157
18277
|
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`;
|
|
18158
18278
|
}
|
|
18159
|
-
|
|
18279
|
+
const reachable = loops.filter((loop) => couldReach(loop, routerPath));
|
|
18280
|
+
if (reachable.length === 0) {
|
|
18281
|
+
return "is never mounted through a single variable binding in the files read";
|
|
18282
|
+
}
|
|
18283
|
+
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`;
|
|
18160
18284
|
}
|
|
18161
|
-
function rivalRegistration(index, site) {
|
|
18285
|
+
function rivalRegistration(index, site, routerPath) {
|
|
18162
18286
|
if (site.kind === "module") {
|
|
18163
18287
|
return null;
|
|
18164
18288
|
}
|
|
18165
18289
|
const rivals = [...index.unenumerableLoops.values()].filter(
|
|
18166
|
-
(loop) => !sameSite(loop.site, site)
|
|
18290
|
+
(loop) => !sameSite(loop.site, site) && couldReach(loop, routerPath)
|
|
18167
18291
|
);
|
|
18168
18292
|
if (rivals.length === 0) {
|
|
18169
18293
|
return null;
|
|
@@ -18636,6 +18760,7 @@ function mountedConstructions(arg, position, scan) {
|
|
|
18636
18760
|
}
|
|
18637
18761
|
scan.index.unenumerableLoops.set(target.location, {
|
|
18638
18762
|
site: position.site,
|
|
18763
|
+
file: scan.bound.displayPath,
|
|
18639
18764
|
location: target.location,
|
|
18640
18765
|
...returned.kind === "unmatched" ? { unmatched: returned } : {}
|
|
18641
18766
|
});
|
|
@@ -18643,6 +18768,7 @@ function mountedConstructions(arg, position, scan) {
|
|
|
18643
18768
|
}
|
|
18644
18769
|
scan.index.unenumerableLoops.set(target.location, {
|
|
18645
18770
|
site: position.site,
|
|
18771
|
+
file: scan.bound.displayPath,
|
|
18646
18772
|
location: target.location
|
|
18647
18773
|
});
|
|
18648
18774
|
return [];
|
|
@@ -18971,6 +19097,7 @@ async function extractPythonProject(options) {
|
|
|
18971
19097
|
const rawUnits = discoverUnits(root, moduleBinding, {
|
|
18972
19098
|
packs: options.packs,
|
|
18973
19099
|
filePath: displayPath,
|
|
19100
|
+
absoluteFile: file2,
|
|
18974
19101
|
routerIndex,
|
|
18975
19102
|
gapHandling,
|
|
18976
19103
|
...storagePatterns.length > 0 ? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/adapter-python",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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.
|
|
51
|
-
"@suss/datalog": "0.5.
|
|
52
|
-
"@suss/extractor": "0.5.
|
|
50
|
+
"@suss/behavioral-ir": "0.5.3",
|
|
51
|
+
"@suss/datalog": "0.5.3",
|
|
52
|
+
"@suss/extractor": "0.5.3",
|
|
53
53
|
"web-tree-sitter": "^0.26.11"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|