@suss/adapter-typescript 0.18.0 → 0.20.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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, Project, Symbol, Expression, NewExpression, TaggedTemplateExpression, PropertyAccessExpression } from 'ts-morph';
2
- import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, AstCapableOps, CallOps, ResponsePropertyMapping } from '@suss/extractor';
1
+ import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, PropertyAccessExpression, TaggedTemplateExpression, Project, ImportDeclaration, Symbol, Expression, NewExpression, Identifier } from 'ts-morph';
2
+ import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, EffectArg, CallOps, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, RawDeclaredContract, ContractPattern, AstCapableOps, ResponsePropertyMapping } from '@suss/extractor';
3
3
  import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
4
4
  import { Proof, Atom } from '@suss/datalog';
5
5
  import { ResolutionExplanation } from '@suss/resolution';
@@ -171,6 +171,13 @@ interface ExtractionReport {
171
171
  * nothing. Null when the run produced summaries.
172
172
  */
173
173
  emptyStage: EmptyStage | null;
174
+ /**
175
+ * Reassigned names the run stated nothing for, because control flow
176
+ * decides which write a reader sees. Each is a value resolution the
177
+ * facts decline; the count across a large corpus says whether scoped
178
+ * reaching definitions is worth writing.
179
+ */
180
+ reassignedNamesUnstated: number;
174
181
  }
175
182
  type EmptyStage = "tsconfig" | "gateResolution" | "candidateFiles" | "discovery" | "assembly";
176
183
  /**
@@ -238,6 +245,14 @@ declare class ResolutionStore {
238
245
  private readonly fullyExtracted;
239
246
  private readonly seededValues;
240
247
  private readonly importedNames;
248
+ private readonly writtenValues;
249
+ private readonly importOrigins;
250
+ /**
251
+ * A file's table depends only on its re-export closure, which
252
+ * `collectExports` extracts whole before answering, so later
253
+ * extraction elsewhere cannot change an entry.
254
+ */
255
+ private readonly exportTables;
241
256
  /** Files the most recent wave walk entered, for the memo to keep. */
242
257
  private lastQueryWalked;
243
258
  private readonly declarations;
@@ -280,6 +295,36 @@ declare class ResolutionStore {
280
295
  * decorators.
281
296
  */
282
297
  importedNamesOf(value: Node, modules: string[]): string[];
298
+ /**
299
+ * Which module export this value comes down to, with what made it
300
+ * included: a value written as `createClient()` is made from the
301
+ * module exporting `createClient`, and a member destructured off
302
+ * that result is one path segment further. The module half is the
303
+ * specifier as the source wrote it, so a subpath import keeps its
304
+ * subpath.
305
+ */
306
+ importOriginsOf(value: Node, modules: string[]): Array<{
307
+ module: string;
308
+ path: string[];
309
+ }>;
310
+ /**
311
+ * The batched form: one demand set, one derivation, one widening
312
+ * walk shared by every value. A discovery pass asks about every
313
+ * callee in a file, and per-value queries would re-pay demand
314
+ * clearing and re-derivation once per call site.
315
+ */
316
+ importOriginsOfMany(values: readonly Node[], modules: string[]): Map<Node, Array<{
317
+ module: string;
318
+ path: string[];
319
+ }>>;
320
+ /**
321
+ * The node a memo shares across references: a plain name's
322
+ * declaration, so a hundred call sites of one import pay one walk.
323
+ * Anything else keys as itself, since `a.foo` and `b.foo` share a
324
+ * declared `foo` without sharing a value.
325
+ */
326
+ private sharedDeclarationFor;
327
+ private lookupImportOrigins;
283
328
  /**
284
329
  * Null tells the walk to keep widening. A value that landed in a
285
330
  * package returns empty instead, because a library's own body is not
@@ -316,6 +361,24 @@ declare class ResolutionStore {
316
361
  * SDK defeats a per-file import check and does not defeat this.
317
362
  */
318
363
  filesImportingTransitively(fileSets: ReadonlyArray<FileSetQuery>): ReadonlyArray<ReadonlySet<SourceFile>>;
364
+ /**
365
+ * The calls behind a receiver that `matches` accepts, walked out in
366
+ * waves the way every question is. The wave ends as soon as one file
367
+ * yields a match, and the caller applies the single-answer policy to
368
+ * the set; the resolution README's anchor section says why the rules
369
+ * cannot rank a nearer call above a farther one.
370
+ */
371
+ anchorCallsOf(value: Node, matches: (call: Node) => boolean): Node[];
372
+ /**
373
+ * Every name a module exports and the values behind each, with
374
+ * re-export chains flattened by the rules. The frontier follows
375
+ * re-export targets only, so a barrel of barrels extracts its own
376
+ * chain and nothing beside it, to any depth.
377
+ */
378
+ exportsOf(sourceFile: SourceFile): Map<string, Node[]>;
379
+ private collectExports;
380
+ /** Module keys this file re-exports from, read off its own facts. */
381
+ private reExportTargetsOf;
319
382
  /**
320
383
  * Null keeps the wave walk widening; any candidate at all ends it,
321
384
  * since more files can only add candidates, never take one away. The
@@ -464,6 +527,11 @@ interface DiscoveredUnit {
464
527
  packageName: string;
465
528
  exportPath: string[];
466
529
  };
530
+ /** A callable identity the pack stated: module and export name. */
531
+ functionCallInfo?: {
532
+ module: string;
533
+ exportName: string;
534
+ };
467
535
  /**
468
536
  * Populated by `decoratedRoute` discovery (NestJS-style REST
469
537
  * controllers). The adapter uses it to build a `rest` binding
@@ -560,6 +628,127 @@ interface Timer {
560
628
  */
561
629
  type DescentBarriers = ReadonlySet<Node>;
562
630
 
631
+ /**
632
+ * Context handed to TypeScript-adapter access recognizers: sister
633
+ * to TsInvocationRecognizerContext but for property-access nodes.
634
+ * No `extractArgs` since property accesses don't take arguments.
635
+ */
636
+ interface TsAccessRecognizerContext {
637
+ /** The property-access expression itself. */
638
+ access: Node;
639
+ /** Source file the access lives in. */
640
+ sourceFile: SourceFile;
641
+ /**
642
+ * One-hop lookup from a written name to the value it was bound to,
643
+ * from the run's facts (#300). Gives null when the run has no store,
644
+ * and the recognizer's own pattern match runs on the raw node.
645
+ */
646
+ resolveWrittenValue: (value: Node) => Node | null;
647
+ /**
648
+ * What a declared pack asks about this node, when the node is a call
649
+ * or a tagged template. This walk visits property accesses as well,
650
+ * and there is no call there to ask about, so a declared pack
651
+ * standing on one is handed nothing and matches nothing.
652
+ */
653
+ ops?: CallOps;
654
+ }
655
+ /**
656
+ * Context handed to TypeScript-adapter recognizers. Recognizers in
657
+ * `@suss/framework-prisma` (and other packs) receive this and use
658
+ * the type-checker / source-file primitives to decide whether the
659
+ * call site matches their semantics. `extractArgs` reuses the same
660
+ * `EffectArg` builder the adapter uses for `invocation` effects, so
661
+ * recognizers don't have to re-implement literal/object/identifier
662
+ * shape extraction.
663
+ */
664
+ interface TsInvocationRecognizerContext {
665
+ /** The call expression itself (also passed as the first arg). */
666
+ call: CallExpression;
667
+ /** Source file the call lives in. Useful for import resolution. */
668
+ sourceFile: SourceFile;
669
+ /**
670
+ * Convert the call's arguments to `EffectArg[]` using the same
671
+ * literal / object / identifier extraction the adapter applies to
672
+ * `invocation` effects. Recognizers call this when they want
673
+ * field-level shape (e.g. Prisma's `select: { id: true }`).
674
+ */
675
+ extractArgs(): EffectArg[];
676
+ /**
677
+ * Whether `identifier` was introduced by an import of
678
+ * `expectedModule`. Recognizers use this to tell a library's export
679
+ * apart from a local class that happens to share its name.
680
+ *
681
+ * Named, default, and namespace imports all match. So does an import
682
+ * through a project-local barrel that re-exports the module: the
683
+ * aliased symbol is still in the package that declared it.
684
+ */
685
+ isImportedFrom(identifier: Node, expectedModule: string): boolean;
686
+ /**
687
+ * The expression `value` is written as, followed through const
688
+ * bindings, imports, and re-export barrels. A recognizer asks this
689
+ * before claiming the source does not name something:
690
+ *
691
+ * const url = "https://sqs/.../orders";
692
+ * client.send(new SendMessageCommand({ QueueUrl: url }));
693
+ *
694
+ * Resolving `url` gives back the string literal, and the recognizer's
695
+ * own pattern match runs on that. Null when the value has no written
696
+ * form (a parameter, a call result), which is exactly when null on the
697
+ * binding is correct.
698
+ */
699
+ resolveWrittenValue(value: Node): Node | null;
700
+ /**
701
+ * What a declared pack asks about this call, in the vocabulary
702
+ * `@suss/extractor` defines. A pack written as a chain of data links
703
+ * reads only this; the members above are what a pack written as code
704
+ * reaches for.
705
+ */
706
+ ops: CallOps;
707
+ }
708
+ /**
709
+ * Everything a recognizer may ask about one call site.
710
+ *
711
+ * A pack's tests build it here too, so a pack that passes them has run
712
+ * against the context extraction gives it.
713
+ */
714
+ declare function invocationContextFor(call: CallExpression, resolveWrittenValue?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom, anchorCallsOf?: AnchorCallsOf): TsInvocationRecognizerContext;
715
+ /**
716
+ * Whether a value's chain reaches the module, asked of the store. The
717
+ * adapter binds it; a context built bare falls back to the checker's
718
+ * alias resolution.
719
+ */
720
+ type OriginatesFrom = (value: Node, module: string) => boolean;
721
+ /** The calls behind a value that a caller's own check accepts, from the store. */
722
+ type AnchorCallsOf = (value: Node, matches: (call: Node) => boolean) => Node[];
723
+ declare function isImportedFrom(identifier: Node, expectedModule: string, originatesFrom?: OriginatesFrom): boolean;
724
+ /**
725
+ * Whether `expectedModule` declares the method a call goes to. A pack
726
+ * asks this when the receiver came from somewhere the source does not
727
+ * spell out: `const redis = await this.getClient()` says nothing about
728
+ * ioredis, and the type of `redis.get` says everything.
729
+ *
730
+ * A client cached on an untyped global, `global.x || new Redis()`,
731
+ * types as `any`, so the method has no symbol. With `resolve` supplied,
732
+ * what the receiver was written as still says which library: the value
733
+ * comes down to a construction or a factory call of something imported
734
+ * from it.
735
+ */
736
+ declare function methodDeclaredIn(callee: Node, expectedModule: string, resolve?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom): boolean;
737
+ /** The nodes this walk stops at, which is every shape a pack can guard. */
738
+ type Accessed = PropertyAccessExpression | CallExpression | TaggedTemplateExpression;
739
+ /**
740
+ * What one node is handed. The ops are built on the first read rather
741
+ * than up front, the way the invocation walk builds them: most nodes
742
+ * reach no declared pack, and a pack written as code never asks.
743
+ *
744
+ * A pack's tests build it here too, so a pack that passes them has run
745
+ * against the context extraction gives it.
746
+ */
747
+ declare function accessContextFor(node: Accessed, sourceFile: SourceFile, resolveWrittenValue?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom, anchorCallsOf?: AnchorCallsOf): TsAccessRecognizerContext;
748
+
749
+ /** Resolves a helper callee to its declaration; the adapter binds the store's ask. */
750
+ type ResolveCallee = (callee: Node) => Node | null;
751
+
563
752
  /**
564
753
  * The whole extraction, from a tsconfig to a set of behavioral
565
754
  * summaries. `createTypeScriptAdapter` is what a caller uses;
@@ -575,7 +764,7 @@ type DescentBarriers = ReadonlySet<Node>;
575
764
  * framework or a runtime belongs to a pack.
576
765
  */
577
766
 
578
- declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): RawCodeStructure;
767
+ declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom, anchorCallsOf?: AnchorCallsOf, resolveCallee?: ResolveCallee): RawCodeStructure;
579
768
  interface TypeScriptAdapterConfig {
580
769
  tsConfigFilePath?: string;
581
770
  /**
@@ -639,7 +828,7 @@ interface FoundTerminal {
639
828
  * discovered. Inside a nested function only escaping terminals are
640
829
  * matched: see `NESTED_ESCAPING_MATCH_TYPES`.
641
830
  */
642
- declare function findTerminals(func: FunctionRoot, patterns: TerminalPattern[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): FoundTerminal[];
831
+ declare function findTerminals(func: FunctionRoot, patterns: TerminalPattern[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom, resolveCallee?: ResolveCallee): FoundTerminal[];
643
832
 
644
833
  /**
645
834
  * Turns one function into the list of branches a summary is built from.
@@ -668,7 +857,7 @@ interface RawBranchResult {
668
857
  * or when every condition on it came from an early return or throw,
669
858
  * since those are the paths that run when no guard fired.
670
859
  */
671
- declare function extractRawBranches(func: FunctionRoot, terminalPatterns: TerminalPattern[], invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): RawBranchResult;
860
+ declare function extractRawBranches(func: FunctionRoot, terminalPatterns: TerminalPattern[], invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null, originatesFrom?: OriginatesFrom, anchorCallsOf?: AnchorCallsOf, resolveCallee?: ResolveCallee): RawBranchResult;
672
861
 
673
862
  /**
674
863
  * A Project covering `rootDir`, for a codebase with no tsconfig to read.
@@ -763,7 +952,7 @@ interface ContractReadResult {
763
952
  * Returns null if the handler wasn't registered with a contract or the contract
764
953
  * can't be resolved.
765
954
  */
766
- declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, framework: string): ContractReadResult | null;
955
+ declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, framework: string, resolution?: ResolutionStore): ContractReadResult | null;
767
956
  /**
768
957
  * Resolve the contract for a consumer call site.
769
958
  *
@@ -771,7 +960,51 @@ declare function readContract(unit: DiscoveredUnit, pattern: ContractPattern, fr
771
960
  * call to find the contract, then extracts the endpoint definition for the
772
961
  * matched method name (e.g. "getUser").
773
962
  */
774
- declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern, framework: string): ContractReadResult | null;
963
+ declare function readContractForClientCall(callExpression: Node, methodName: string, pattern: ContractPattern, framework: string, resolution?: ResolutionStore): ContractReadResult | null;
964
+
965
+ /**
966
+ * The one import scan.
967
+ *
968
+ * Fifteen copies of "which names did this file import from module X"
969
+ * grew across discovery handlers and packs, and two of them disagreed
970
+ * about aliases (#674). Every question about a file's imports of a
971
+ * module is asked here: which declarations name it, what the named
972
+ * imports are called locally, and which local spellings the default
973
+ * and namespace imports go by. Matching semantics are explicit
974
+ * options, so a caller that accepts subpath specifiers or resolves
975
+ * path-shaped specifiers through the store says so, and one that
976
+ * matches the specifier verbatim stays verbatim.
977
+ */
978
+
979
+ interface ImportScanOptions {
980
+ /**
981
+ * Accept `module/subpath` specifiers beside the exact module, the
982
+ * way `react-dom/client` is still react-dom's surface.
983
+ */
984
+ subpaths?: boolean;
985
+ /** Resolve path-shaped specifiers through the store's module graph. */
986
+ resolution?: ResolutionStore;
987
+ }
988
+ /** One named import: how the file spells it, and what it is. */
989
+ interface NamedImport {
990
+ /** The spelling this file binds, the alias when there is one. */
991
+ local: string;
992
+ /** The name the module exports it as. */
993
+ canonical: string;
994
+ /** The specifier as written, for a caller that reads sub-paths. */
995
+ specifier: string;
996
+ }
997
+ /** Every named import of these modules, aliases resolved. */
998
+ declare function namedImportsOf(sourceFile: SourceFile, modules: readonly string[], options?: ImportScanOptions): NamedImport[];
999
+ /**
1000
+ * Local spelling to canonical exported name, for every named import
1001
+ * of these modules. `import { Get as HttpGet }` yields HttpGet -> Get.
1002
+ */
1003
+ declare function importedNamesOf(sourceFile: SourceFile, modules: readonly string[], options?: ImportScanOptions): Map<string, string>;
1004
+ /** The local spellings of default and namespace imports of these modules. */
1005
+ declare function importedRootsOf(sourceFile: SourceFile, modules: readonly string[], options?: ImportScanOptions): Set<string>;
1006
+ /** Every import declaration in the file that names one of `modules`. */
1007
+ declare function importDeclarationsOf(sourceFile: SourceFile, modules: readonly string[], options?: ImportScanOptions): ImportDeclaration[];
775
1008
 
776
1009
  interface TsDiscoveryContext {
777
1010
  /** Full filesystem path of the source file. Useful for excluding
@@ -899,14 +1132,11 @@ declare class WhySession {
899
1132
  private displayPath;
900
1133
  }
901
1134
 
902
- type ExportedDeclarationMap = ReturnType<SourceFile["getExportedDeclarations"]>;
903
1135
  /**
904
- * What a module exports, by exported name. Cached, because ts-morph
905
- * rebuilds the map from scratch on every `getExportedDeclarations` call
906
- * and callee resolution asks the same file the same thing once per
907
- * import site.
1136
+ * What a module exports, by exported name. The store's rules flatten
1137
+ * re-export chains of any length, and the store memoizes per file.
908
1138
  */
909
- declare function exportedDeclarationsOf(sourceFile: SourceFile): ExportedDeclarationMap;
1139
+ declare function exportedDeclarationsOf(sourceFile: SourceFile, resolution: ResolutionStore): Map<string, Node[]>;
910
1140
  /**
911
1141
  * `Symbol.getAliasedSymbol`, with the re-export chains warmed first so
912
1142
  * that an import sitting at the top of a deep chain still resolves
@@ -1031,117 +1261,31 @@ type Called = CallExpression | NewExpression | TaggedTemplateExpression;
1031
1261
  /** One-hop lookup from a written name to the value it was bound to. */
1032
1262
  type Resolve = (value: Node) => Node | null;
1033
1263
  /** What a declared pack can ask about one TypeScript call. */
1034
- declare function callOpsFor(call: Called, resolveWrittenValue?: Resolve): AstCapableOps;
1264
+ declare function callOpsFor(call: Called, resolveWrittenValue?: Resolve, originatesFrom?: OriginatesFrom, anchorCallsOf?: AnchorCallsOf): AstCapableOps;
1035
1265
 
1036
1266
  /**
1037
- * Context handed to TypeScript-adapter access recognizers: sister
1038
- * to TsInvocationRecognizerContext but for property-access nodes.
1039
- * No `extractArgs` since property accesses don't take arguments.
1040
- */
1041
- interface TsAccessRecognizerContext {
1042
- /** The property-access expression itself. */
1043
- access: Node;
1044
- /** Source file the access lives in. */
1045
- sourceFile: SourceFile;
1046
- /**
1047
- * One-hop lookup from a written name to the value it was bound to,
1048
- * from the run's facts (#300). Gives null when the run has no store,
1049
- * and the recognizer's own pattern match runs on the raw node.
1050
- */
1051
- resolveWrittenValue: (value: Node) => Node | null;
1052
- /**
1053
- * What a declared pack asks about this node, when the node is a call
1054
- * or a tagged template. This walk visits property accesses as well,
1055
- * and there is no call there to ask about, so a declared pack
1056
- * standing on one is handed nothing and matches nothing.
1057
- */
1058
- ops?: CallOps;
1059
- }
1060
- /**
1061
- * Context handed to TypeScript-adapter recognizers. Recognizers in
1062
- * `@suss/framework-prisma` (and other packs) receive this and use
1063
- * the type-checker / source-file primitives to decide whether the
1064
- * call site matches their semantics. `extractArgs` reuses the same
1065
- * `EffectArg` builder the adapter uses for `invocation` effects, so
1066
- * recognizers don't have to re-implement literal/object/identifier
1067
- * shape extraction.
1068
- */
1069
- interface TsInvocationRecognizerContext {
1070
- /** The call expression itself (also passed as the first arg). */
1071
- call: CallExpression;
1072
- /** Source file the call lives in. Useful for import resolution. */
1073
- sourceFile: SourceFile;
1074
- /**
1075
- * Convert the call's arguments to `EffectArg[]` using the same
1076
- * literal / object / identifier extraction the adapter applies to
1077
- * `invocation` effects. Recognizers call this when they want
1078
- * field-level shape (e.g. Prisma's `select: { id: true }`).
1079
- */
1080
- extractArgs(): EffectArg[];
1081
- /**
1082
- * Whether `identifier` was introduced by an import of
1083
- * `expectedModule`. Recognizers use this to tell a library's export
1084
- * apart from a local class that happens to share its name.
1085
- *
1086
- * Named, default, and namespace imports all match. So does an import
1087
- * through a project-local barrel that re-exports the module: the
1088
- * aliased symbol is still in the package that declared it.
1089
- */
1090
- isImportedFrom(identifier: Node, expectedModule: string): boolean;
1091
- /**
1092
- * The expression `value` is written as, followed through const
1093
- * bindings, imports, and re-export barrels. A recognizer asks this
1094
- * before claiming the source does not name something:
1095
- *
1096
- * const url = "https://sqs/.../orders";
1097
- * client.send(new SendMessageCommand({ QueueUrl: url }));
1098
- *
1099
- * Resolving `url` gives back the string literal, and the recognizer's
1100
- * own pattern match runs on that. Null when the value has no written
1101
- * form (a parameter, a call result), which is exactly when null on the
1102
- * binding is correct.
1103
- */
1104
- resolveWrittenValue(value: Node): Node | null;
1105
- /**
1106
- * What a declared pack asks about this call, in the vocabulary
1107
- * `@suss/extractor` defines. A pack written as a chain of data links
1108
- * reads only this; the members above are what a pack written as code
1109
- * reaches for.
1110
- */
1111
- ops: CallOps;
1112
- }
1113
- /**
1114
- * Everything a recognizer may ask about one call site.
1267
+ * The one resolver from a reference to the function behind it.
1115
1268
  *
1116
- * A pack's tests build it here too, so a pack that passes them has run
1117
- * against the context extraction gives it.
1269
+ * Four copies of "follow an identifier to its function declaration"
1270
+ * grew across the closure, the render-tree builder, the react pack,
1271
+ * and the parameter-read walk, three of them lossy (#674). The walk
1272
+ * lives here: alias-following through imports, shorthand properties
1273
+ * resolved to the binding they read, overload signatures routed to
1274
+ * the declaration with the body, and external code refused. Callers
1275
+ * that need only the file and name take `functionTargetOf`; the
1276
+ * closure takes `resolveDecl` for the function root itself.
1118
1277
  */
1119
- declare function invocationContextFor(call: CallExpression, resolveWrittenValue?: (value: Node) => Node | null): TsInvocationRecognizerContext;
1120
- declare function isImportedFrom(identifier: Node, expectedModule: string): boolean;
1121
- /**
1122
- * Whether `expectedModule` declares the method a call goes to. A pack
1123
- * asks this when the receiver came from somewhere the source does not
1124
- * spell out: `const redis = await this.getClient()` says nothing about
1125
- * ioredis, and the type of `redis.get` says everything.
1126
- *
1127
- * A client cached on an untyped global, `global.x || new Redis()`,
1128
- * types as `any`, so the method has no symbol. With `resolve` supplied,
1129
- * what the receiver was written as still says which library: the value
1130
- * comes down to a construction or a factory call of something imported
1131
- * from it.
1132
- */
1133
- declare function methodDeclaredIn(callee: Node, expectedModule: string, resolve?: (value: Node) => Node | null): boolean;
1134
- /** The nodes this walk stops at, which is every shape a pack can guard. */
1135
- type Accessed = PropertyAccessExpression | CallExpression | TaggedTemplateExpression;
1278
+
1136
1279
  /**
1137
- * What one node is handed. The ops are built on the first read rather
1138
- * than up front, the way the invocation walk builds them: most nodes
1139
- * reach no declared pack, and a pack written as code never asks.
1140
- *
1141
- * A pack's tests build it here too, so a pack that passes them has run
1142
- * against the context extraction gives it.
1280
+ * The function an identifier refers to, as its declaration's file and
1281
+ * name, for joins that never walk the body. Null when the reference
1282
+ * does not land on a project function.
1143
1283
  */
1144
- declare function accessContextFor(node: Accessed, sourceFile: SourceFile, resolveWrittenValue?: (value: Node) => Node | null): TsAccessRecognizerContext;
1284
+ declare function functionTargetOf(node: Identifier): {
1285
+ func: FunctionRoot;
1286
+ file: string;
1287
+ name: string;
1288
+ } | null;
1145
1289
 
1146
1290
  /**
1147
1291
  * What a name-valued expression says, as fixed text with a hole for
@@ -1192,6 +1336,33 @@ declare function collectClientFieldAccesses(callExpr: CallExpression, func: Node
1192
1336
  end: number;
1193
1337
  }>, responseSemantics?: ResponsePropertyMapping[]): BranchFieldAccesses[];
1194
1338
 
1339
+ /**
1340
+ * Evidence for drafting a dependency stub: every call the project
1341
+ * makes into a named package, grouped by the export it reaches, with
1342
+ * the argument shapes observed at each call site. `suss stub draft`
1343
+ * turns this into a stub skeleton for an author to fill in. The
1344
+ * package's own code is never read, only the project's calls into it,
1345
+ * so this works for packages whose source no adapter can parse.
1346
+ */
1347
+
1348
+ interface ObservedStubCall {
1349
+ readonly file: string;
1350
+ readonly line: number;
1351
+ readonly args: EffectArg[];
1352
+ }
1353
+ interface StubCallEvidence {
1354
+ readonly exportPath: string[];
1355
+ readonly calls: ObservedStubCall[];
1356
+ }
1357
+ declare function stubEvidenceIn(project: Project, packageName: string, root: string): StubCallEvidence[];
1358
+ interface StubEvidenceOptions {
1359
+ packageName: string;
1360
+ tsConfigFilePath?: string;
1361
+ /** Read when no tsconfig is given. */
1362
+ directory?: string;
1363
+ }
1364
+ declare function stubEvidence(options: StubEvidenceOptions): StubCallEvidence[];
1365
+
1195
1366
  /**
1196
1367
  * Resolve a ts-morph Expression node to a structured ValueRef.
1197
1368
  * Uses only expr.getSymbol()?.getDeclarations()[0] for symbol lookup ,
@@ -1330,4 +1501,4 @@ declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
1330
1501
  version?: string;
1331
1502
  }>): string;
1332
1503
 
1333
- export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, invocationContextFor, isImportedFrom, methodDeclaredIn, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, workspaceRootFor };
1504
+ export { ADAPTER_VERSION, type Accessed, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExplainCallableOptions, type ExplainStats, type ExplainedResolution, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type ImportScanOptions, type ObservedStubCall, type PackFunnel, type PackGradient, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, type Timer, type TimingReport, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, type ValueLocation, type WhyExplained, WhySession, type WhySessionOptions, accessContextFor, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, functionTargetOf, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isImportedFrom, methodDeclaredIn, namedImportsOf, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, stubEvidence, stubEvidenceIn, workspaceRootFor };