@suss/adapter-typescript 0.10.0 → 0.12.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,6 +1,10 @@
1
1
  import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, Project, Symbol, Expression } from 'ts-morph';
2
- import { TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, ResponsePropertyMapping } from '@suss/extractor';
2
+ import { PackDeclarations, TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, ResponsePropertyMapping } from '@suss/extractor';
3
3
  import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
4
+ import { Proof, Atom } from '@suss/datalog';
5
+ import { ResolutionExplanation } from '@suss/resolution';
6
+ import { AstCapableOps } from '@suss/recognize/ast';
7
+ import { CallOps } from '@suss/recognize';
4
8
 
5
9
  /**
6
10
  * cache.ts: the on-disk extraction cache.
@@ -45,7 +49,6 @@ interface CacheDiagnostic {
45
49
  type CacheLookup = {
46
50
  kind: "hit";
47
51
  summaries: BehavioralSummary[];
48
- projectRoot?: string;
49
52
  diagnostic: CacheDiagnostic;
50
53
  } | {
51
54
  kind: "miss";
@@ -122,20 +125,30 @@ interface PackFunnel {
122
125
  /**
123
126
  * Bound summaries on the provider side of their boundary.
124
127
  *
125
- * Transitions say what a unit does with a request, which only a
126
- * provider can say. A consumer records what it reads back from the
127
- * response instead, and that goes on the summary's metadata. Measuring
128
- * a client pack against transitions would report every working one as
129
- * having extracted nothing.
128
+ * Kept apart from the count below because a provider that produced
129
+ * no transitions is a different problem from a client that produced
130
+ * none.
130
131
  */
131
132
  providerSummaries: number;
132
- /** Provider summaries with at least one transition. */
133
+ /**
134
+ * Summaries with at least one transition, of any role. A provider's
135
+ * transitions say what it does with a request and a client's say what
136
+ * it expects back, so a pack whose summaries have none has bound
137
+ * something and described nothing either way.
138
+ */
133
139
  summariesWithBehavior: number;
134
140
  /**
135
141
  * Where one of this pack's hooks threw. Every count above is a floor
136
142
  * while this is non-empty.
137
143
  */
138
144
  failures: PackFailure[];
145
+ /**
146
+ * What the pack wrote as data rather than as code, or null for a pack
147
+ * written as a hand-rolled walk. This is the one thing in the funnel
148
+ * that no run produces: it is the pack's own shape, and it is here so
149
+ * the migration onto the declared surface can be measured.
150
+ */
151
+ declarations: PackDeclarations | null;
139
152
  }
140
153
  interface ExtractionReport {
141
154
  /** Files in the tsconfig include set, or null when the caller supplied a Project. */
@@ -199,6 +212,28 @@ interface FileSetQuery {
199
212
  * edges a query follows, so cost tracks how indirect the code is.
200
213
  */
201
214
 
215
+ interface ExplainCallableOptions {
216
+ /** A second file to walk out from; see `resolveCallable`. */
217
+ alsoFrom?: SourceFile;
218
+ /** How many derived nodes deep the proof walk goes; see `proofOf`. */
219
+ maxDepth?: number;
220
+ }
221
+ /** What one witness re-evaluation cost, said rather than hidden. */
222
+ interface ExplainStats {
223
+ /** Facts the demand walk had extracted, which the proof pass reran. */
224
+ baseFacts: number;
225
+ /** Facts the exhaustive pass derived on top of those. */
226
+ derivedFacts: number;
227
+ evaluateMs: number;
228
+ }
229
+ interface ExplainedResolution {
230
+ /** The node the value comes down to. */
231
+ target: Node;
232
+ proof: Proof;
233
+ /** The source node behind a proof atom, when the atom is a node id. */
234
+ nodeFor: (atom: Atom) => Node | undefined;
235
+ stats: ExplainStats;
236
+ }
202
237
  declare class ResolutionStore {
203
238
  private readonly db;
204
239
  private readonly table;
@@ -219,6 +254,21 @@ declare class ResolutionStore {
219
254
  * the walk never reaches on its own.
220
255
  */
221
256
  resolveCallable(value: Node, alsoFrom?: SourceFile): Node | null;
257
+ /**
258
+ * Every function the value comes down to. One is the answer
259
+ * `resolveCallable` gives; several is a value with more than one
260
+ * possible source, which a caller says at the site rather than
261
+ * folding into the same nothing as a chain that went nowhere.
262
+ */
263
+ resolveCallableSources(value: Node, alsoFrom?: SourceFile): Node[];
264
+ /**
265
+ * The witness proof behind `resolveCallable`'s answer. Resolves the
266
+ * value first, which walks files and extracts facts in the usual
267
+ * waves, then re-evaluates the rules over those base facts under the
268
+ * witness algebra and rebuilds the proof of the answer. Null when
269
+ * the value does not resolve at all.
270
+ */
271
+ explainCallable(value: Node, options?: ExplainCallableOptions): ExplainedResolution | null;
222
272
  resolveObject(value: Node): Node | null;
223
273
  /**
224
274
  * For a value that is neither a function nor an object, such as a
@@ -266,10 +316,12 @@ declare class ResolutionStore {
266
316
  */
267
317
  filesImportingTransitively(fileSets: ReadonlyArray<FileSetQuery>): ReadonlyArray<ReadonlySet<SourceFile>>;
268
318
  /**
269
- * Two candidates give null. Picking whichever arrived first would make
270
- * the result depend on the order the facts came in.
319
+ * Null keeps the wave walk widening; any candidate at all ends it,
320
+ * since more files can only add candidates, never take one away. The
321
+ * single-answer policy stays with the callers: picking one of two
322
+ * would make the result depend on the order the facts came in.
271
323
  */
272
- private lookup;
324
+ private lookupSources;
273
325
  private derive;
274
326
  private answersFor;
275
327
  /**
@@ -394,6 +446,8 @@ interface DiscoveredUnit {
394
446
  required: boolean;
395
447
  }>;
396
448
  rootFields: string[];
449
+ /** Spreads in `document` with no definition in it: read partially. */
450
+ unresolvedFragments?: string[];
397
451
  unresolved?: {
398
452
  reference: string;
399
453
  reason: string;
@@ -523,6 +577,11 @@ type DescentBarriers = ReadonlySet<Node>;
523
577
  declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): RawCodeStructure;
524
578
  interface TypeScriptAdapterConfig {
525
579
  tsConfigFilePath?: string;
580
+ /**
581
+ * Absolute. Every id's file path is relative to this directory. When
582
+ * absent, settled by workspaceRootFor from the tsconfig's directory.
583
+ */
584
+ projectRoot?: string;
526
585
  /** Separates two services in one repository whose file names match. */
527
586
  workspace?: string;
528
587
  project?: Project;
@@ -774,6 +833,71 @@ interface TsDiscoveryContext {
774
833
  }
775
834
  declare function createTsDiscoveryContext(resolution?: ResolutionStore): TsDiscoveryContext;
776
835
 
836
+ /**
837
+ * The session behind `suss ask why`: a ts-morph project over the
838
+ * asked-about source, a way to point at the expression somebody
839
+ * spelled, and the witness proof of what it resolved to, rendered
840
+ * through `@suss/resolution`'s phrases.
841
+ *
842
+ * The CLI points at values with a file, a line, and the text as
843
+ * written, because that is what a person has in hand. Everything
844
+ * ts-morph stays behind this class; callers treat the found nodes as
845
+ * opaque handles to pass back in.
846
+ */
847
+
848
+ interface WhySessionOptions {
849
+ /** The project root, which paths in every answer come out relative to. */
850
+ dir: string;
851
+ }
852
+ /** A value or function said the way an answer prints it. */
853
+ interface ValueLocation {
854
+ name: string;
855
+ /** Relative to the session root when under it, absolute otherwise. */
856
+ file: string;
857
+ line: number;
858
+ }
859
+ interface WhyExplained {
860
+ explanation: ResolutionExplanation;
861
+ /** The chain's atoms, each said in source terms. */
862
+ chain: string[];
863
+ /** The chain and its reasons as printable lines. */
864
+ lines: string[];
865
+ target: ValueLocation;
866
+ stats: ExplainStats;
867
+ }
868
+ declare class WhySession {
869
+ private readonly project;
870
+ private readonly store;
871
+ private readonly root;
872
+ constructor(options: WhySessionOptions);
873
+ /**
874
+ * The smallest expression on `line` of `file` whose text is exactly
875
+ * `text`, or null. Spelled from the person's side: the name as
876
+ * written, not a node id.
877
+ */
878
+ findExpression(file: string, line: number, text: string): Node | null;
879
+ /**
880
+ * The callee of the call written as `calleeText` between `startLine`
881
+ * and `endLine` of `file`, or null. This is how a summary's recorded
882
+ * callee gets back to its node: the summary keeps source text and a
883
+ * range, not ids.
884
+ */
885
+ findCallee(file: string, startLine: number, endLine: number, calleeText: string): Node | null;
886
+ /**
887
+ * Why `value` resolves to what it does: the witness proof, flattened
888
+ * to the chain and rendered. Null when the value does not resolve,
889
+ * which the caller says in its own words.
890
+ */
891
+ explain(value: Node, options?: {
892
+ maxDepth?: number;
893
+ }): WhyExplained | null;
894
+ /** Where a found node is, in the terms an answer prints. */
895
+ locate(node: Node): ValueLocation;
896
+ private sourceFileAt;
897
+ /** A module key: a file path said relative to the root, or a package name. */
898
+ private displayPath;
899
+ }
900
+
777
901
  type ExportedDeclarationMap = ReturnType<SourceFile["getExportedDeclarations"]>;
778
902
  /**
779
903
  * What a module exports, by exported name. Cached, because ts-morph
@@ -814,13 +938,14 @@ interface HealthCheck {
814
938
  /** The property being checked, as something either true or false. */
815
939
  name: string;
816
940
  /**
817
- * The heading printed when this check finds something.
941
+ * The short word this check reports under, kebab-case.
818
942
  *
819
- * `name` is written as an assertion, so printing it above the
820
- * violations would say "no pack collides with itself" and then list a
821
- * pack colliding with itself.
943
+ * It is the first column of every line the check prints, so a reader
944
+ * works out what it means once and recognises it after that. `name`
945
+ * is written as an assertion and would read backwards over a list of
946
+ * things failing it.
822
947
  */
823
- whenBroken: string;
948
+ code: string;
824
949
  /**
825
950
  * Who the finding is addressed to.
826
951
  *
@@ -834,6 +959,26 @@ interface HealthCheck {
834
959
  audience: "run" | "pack";
835
960
  violations: HealthViolation[];
836
961
  }
962
+ /**
963
+ * What one pack paid for what it matches.
964
+ *
965
+ * Expressiveness is bought link by link, and #542 asks for the price to
966
+ * be printed. A pack with every link written as data runs on any
967
+ * adapter with the executor ops. A link written as a function runs only
968
+ * where its own language does, and one that reads the syntax tree is
969
+ * the floor. All three are allowed, and this says which is which.
970
+ */
971
+ interface PackGradient {
972
+ pack: string;
973
+ /** Links written as data, across every declaration. */
974
+ dataLinks: number;
975
+ /** Links written as a function, as "declaration.question". */
976
+ functionLinks: string[];
977
+ /** Links whose function reads the adapter's own syntax tree. */
978
+ astLinks: string[];
979
+ /** Declarations shipped without a line of code to run against them. */
980
+ withoutExample: string[];
981
+ }
837
982
  /**
838
983
  * Run every health check over one extraction report and return what
839
984
  * fired, grouped by which check caught it.
@@ -861,14 +1006,91 @@ declare function formatPackHealth(checks: ReadonlyArray<HealthCheck>, audiences:
861
1006
  */
862
1007
  declare function parseConditionExpression(expr: Expression, depth?: number): Predicate | null;
863
1008
 
1009
+ /**
1010
+ * What this adapter can tell a declared pack about a call.
1011
+ *
1012
+ * Every question here was already worked out somewhere in this adapter,
1013
+ * once per pack that needed it. Collecting them behind the one
1014
+ * interface `@suss/recognize` declares is what lets a pack stop being a
1015
+ * walk over ts-morph and start being data.
1016
+ *
1017
+ * Two of the receiver origins are implemented. The migration plan for
1018
+ * #542 lists four more, and each one adds an entry to `ORIGIN` beside
1019
+ * the union member it matches.
1020
+ */
1021
+
1022
+ /** One-hop lookup from a written name to the value it was bound to. */
1023
+ type Resolve = (value: Node) => Node | null;
1024
+ /** What a declared pack can ask about one TypeScript call. */
1025
+ declare function callOpsFor(call: CallExpression, resolveWrittenValue?: Resolve): AstCapableOps;
1026
+
1027
+ /**
1028
+ * Context handed to TypeScript-adapter recognizers. Recognizers in
1029
+ * `@suss/framework-prisma` (and other packs) receive this and use
1030
+ * the type-checker / source-file primitives to decide whether the
1031
+ * call site matches their semantics. `extractArgs` reuses the same
1032
+ * `EffectArg` builder the adapter uses for `invocation` effects, so
1033
+ * recognizers don't have to re-implement literal/object/identifier
1034
+ * shape extraction.
1035
+ */
1036
+ interface TsInvocationRecognizerContext {
1037
+ /** The call expression itself (also passed as the first arg). */
1038
+ call: CallExpression;
1039
+ /** Source file the call lives in. Useful for import resolution. */
1040
+ sourceFile: SourceFile;
1041
+ /**
1042
+ * Convert the call's arguments to `EffectArg[]` using the same
1043
+ * literal / object / identifier extraction the adapter applies to
1044
+ * `invocation` effects. Recognizers call this when they want
1045
+ * field-level shape (e.g. Prisma's `select: { id: true }`).
1046
+ */
1047
+ extractArgs(): EffectArg[];
1048
+ /**
1049
+ * Whether `identifier` was introduced by an import of
1050
+ * `expectedModule`. Recognizers use this to tell a library's export
1051
+ * apart from a local class that happens to share its name.
1052
+ *
1053
+ * Named, default, and namespace imports all match. So does an import
1054
+ * through a project-local barrel that re-exports the module: the
1055
+ * aliased symbol is still in the package that declared it.
1056
+ */
1057
+ isImportedFrom(identifier: Node, expectedModule: string): boolean;
1058
+ /**
1059
+ * The expression `value` is written as, followed through const
1060
+ * bindings, imports, and re-export barrels. A recognizer asks this
1061
+ * before claiming the source does not name something:
1062
+ *
1063
+ * const url = "https://sqs/.../orders";
1064
+ * client.send(new SendMessageCommand({ QueueUrl: url }));
1065
+ *
1066
+ * Resolving `url` gives back the string literal, and the recognizer's
1067
+ * own pattern match runs on that. Null when the value has no written
1068
+ * form (a parameter, a call result), which is exactly when null on the
1069
+ * binding is correct.
1070
+ */
1071
+ resolveWrittenValue(value: Node): Node | null;
1072
+ /**
1073
+ * What a declared pack asks about this call, in the vocabulary
1074
+ * `@suss/recognize` defines. A pack written as a chain of data links
1075
+ * reads only this; the members above are what a pack written as code
1076
+ * reaches for.
1077
+ */
1078
+ ops: CallOps;
1079
+ }
864
1080
  declare function isImportedFrom(identifier: Node, expectedModule: string): boolean;
865
1081
  /**
866
1082
  * Whether `expectedModule` declares the method a call goes to. A pack
867
1083
  * asks this when the receiver came from somewhere the source does not
868
1084
  * spell out: `const redis = await this.getClient()` says nothing about
869
1085
  * ioredis, and the type of `redis.get` says everything.
1086
+ *
1087
+ * A client cached on an untyped global, `global.x || new Redis()`,
1088
+ * types as `any`, so the method has no symbol. With `resolve` supplied,
1089
+ * what the receiver was written as still says which library: the value
1090
+ * comes down to a construction or a factory call of something imported
1091
+ * from it.
870
1092
  */
871
- declare function methodDeclaredIn(callee: Node, expectedModule: string): boolean;
1093
+ declare function methodDeclaredIn(callee: Node, expectedModule: string, resolve?: (value: Node) => Node | null): boolean;
872
1094
 
873
1095
  /**
874
1096
  * What a name-valued expression says, as fixed text with a hole for
@@ -995,6 +1217,37 @@ interface TsJsxAttributeLocation {
995
1217
  }
996
1218
  declare function createTsSubUnitContext(): TsSubUnitContext;
997
1219
 
1220
+ /**
1221
+ * Naming a summary, so that something else can point at it.
1222
+ *
1223
+ * A bare function name is not enough. Reading suss's own source turns up
1224
+ * 404 summaries that share a name with another one, and a large
1225
+ * repository is worse: two services with the same `src/handlers.ts` give
1226
+ * every summary in one an exact twin in the other. Following a call by
1227
+ * matching names was always a guess about which one was meant.
1228
+ *
1229
+ * So the name includes the project the summary came from, the file it is
1230
+ * in, and the path its export is reached by. That is unique across
1231
+ * everything one run can see, and it still looks like something a person
1232
+ * would have written down.
1233
+ */
1234
+
1235
+ /**
1236
+ * The one root a run measures its file paths from: the nearest
1237
+ * directory at or above the run's anchor whose package.json has a
1238
+ * name. The anchor is the tsconfig's directory, or the directory a
1239
+ * run without one was pointed at. When no package.json declares a
1240
+ * name above the anchor, the anchor itself is the root.
1241
+ *
1242
+ * The same directory supplies the workspace segment of an id, so both
1243
+ * parts of an id are measured from one place. The walk starts from the
1244
+ * run's configuration, not from whichever files the run loaded, so a
1245
+ * run over one nested tsconfig spells a file the same way a run over
1246
+ * the whole workspace does, and the same command spells it the same
1247
+ * way twice.
1248
+ */
1249
+ declare function workspaceRootFor(anchor: string): string;
1250
+
998
1251
  declare const ADAPTER_VERSION = "0.2.1";
999
1252
  /**
1000
1253
  * Content hash of the given files, in the order they arrive. Empty when
@@ -1026,4 +1279,4 @@ declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
1026
1279
  version?: string;
1027
1280
  }>): string;
1028
1281
 
1029
- export { ADAPTER_VERSION, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type PackFunnel, type ReadNameOptions, ResolutionStore, type Timer, type TimingReport, type TsDiscoveryContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, isImportedFrom, methodDeclaredIn, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier };
1282
+ export { ADAPTER_VERSION, 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, callOpsFor, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, isImportedFrom, methodDeclaredIn, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, workspaceRootFor };