@suss/adapter-typescript 0.31.1 → 0.32.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.cjs CHANGED
@@ -1498,13 +1498,14 @@ function isWriteTarget(name) {
1498
1498
  function fact(db, relation, ...tuple) {
1499
1499
  db.add(relation, tuple);
1500
1500
  }
1501
- function createNodeTable() {
1501
+ function createNodeTable(environmentObjects = []) {
1502
1502
  return {
1503
1503
  byId: /* @__PURE__ */ new Map(),
1504
1504
  seenFunctions: /* @__PURE__ */ new Set(),
1505
1505
  seenBindings: /* @__PURE__ */ new Set(),
1506
1506
  seenValues: /* @__PURE__ */ new Set(),
1507
- seenClasses: /* @__PURE__ */ new Set()
1507
+ seenClasses: /* @__PURE__ */ new Set(),
1508
+ environmentObjects
1508
1509
  };
1509
1510
  }
1510
1511
  function nodeId(node) {
@@ -1649,6 +1650,40 @@ function literalIndexOf(index) {
1649
1650
  }
1650
1651
  return null;
1651
1652
  }
1653
+ function readsEnvironment(table, access) {
1654
+ if (table.environmentObjects.length === 0) {
1655
+ return false;
1656
+ }
1657
+ const path14 = dottedPathOf(access.getExpression());
1658
+ return path14 !== null && table.environmentObjects.includes(path14);
1659
+ }
1660
+ function isEnvironmentNameRead(table, node) {
1661
+ if (!import_ts_morph6.Node.isElementAccessExpression(node)) {
1662
+ return false;
1663
+ }
1664
+ const argument = node.getArgumentExpression();
1665
+ return argument !== void 0 && literalIndexOf(argument) === null && readsEnvironment(table, node);
1666
+ }
1667
+ function environmentNameReadsIn(table, sourceFile) {
1668
+ if (table.environmentObjects.length === 0) {
1669
+ return [];
1670
+ }
1671
+ const text = sourceFile.getFullText();
1672
+ if (!table.environmentObjects.some((path14) => text.includes(path14))) {
1673
+ return [];
1674
+ }
1675
+ return sourceFile.getDescendantsOfKind(import_ts_morph6.SyntaxKind.ElementAccessExpression).filter((access) => isEnvironmentNameRead(table, access));
1676
+ }
1677
+ function dottedPathOf(expression) {
1678
+ if (import_ts_morph6.Node.isIdentifier(expression)) {
1679
+ return expression.getText();
1680
+ }
1681
+ if (!import_ts_morph6.Node.isPropertyAccessExpression(expression)) {
1682
+ return null;
1683
+ }
1684
+ const object = dottedPathOf(expression.getExpression());
1685
+ return object === null ? null : `${object}.${expression.getName()}`;
1686
+ }
1652
1687
  function asFallbackExpression(expression) {
1653
1688
  if (!import_ts_morph6.Node.isBinaryExpression(expression)) {
1654
1689
  return null;
@@ -1720,6 +1755,10 @@ function emitValue(db, table, value) {
1720
1755
  );
1721
1756
  return id;
1722
1757
  }
1758
+ const argument = expression.getArgumentExpression();
1759
+ if (argument !== void 0 && readsEnvironment(table, expression)) {
1760
+ fact(db, "readsEnvNamed", id, emitValue(db, table, argument));
1761
+ }
1723
1762
  fact(db, "writtenValue", id);
1724
1763
  return id;
1725
1764
  }
@@ -2184,6 +2223,9 @@ function recordBodyCalls(db, table, fnId, node) {
2184
2223
  if (!import_ts_morph6.Node.isExpression(node)) {
2185
2224
  return false;
2186
2225
  }
2226
+ if (isEnvironmentNameRead(table, node)) {
2227
+ emitValue(db, table, node);
2228
+ }
2187
2229
  const call = unwrapExpression(node);
2188
2230
  if (import_ts_morph6.Node.isCallExpression(call)) {
2189
2231
  const callee = unwrapExpression(call.getExpression());
@@ -2409,9 +2451,10 @@ var QUERY_FACTS = (0, import_resolution2.queryFacts)(RESOLUTION_PROGRAM);
2409
2451
  var MODULE_DEMANDS = RESOLUTION_PROGRAM.demands.filter(
2410
2452
  (one) => (one.relation === "moduleExport" || one.relation === "moduleForwards") && one.bound[0] === true
2411
2453
  ).map((one) => one.demand);
2454
+ var NO_NAMERS = { sitesNaming: () => [] };
2412
2455
  var ResolutionStore = class {
2413
2456
  db = new import_datalog2.Database();
2414
- table = createNodeTable();
2457
+ table;
2415
2458
  fullyExtracted = /* @__PURE__ */ new Set();
2416
2459
  seededValues = /* @__PURE__ */ new Set();
2417
2460
  importedNames = /* @__PURE__ */ new Map();
@@ -2429,13 +2472,18 @@ var ResolutionStore = class {
2429
2472
  constructionSites = /* @__PURE__ */ new Map();
2430
2473
  /** Files the most recent query read, for the memo to keep. */
2431
2474
  lastQueryWalked = [];
2475
+ /** See `environmentSiteFiles`; null until the first env question. */
2476
+ envSiteFiles = null;
2477
+ envNamersAnswer = NO_NAMERS;
2478
+ envNamersExtractedAt = -1;
2432
2479
  declarations = /* @__PURE__ */ new Map();
2433
2480
  graph = new ModuleGraph();
2434
2481
  /** See `notePossibleCallers`. */
2435
2482
  possibleCallers = /* @__PURE__ */ new Set();
2436
2483
  callerFilesRead = /* @__PURE__ */ new Set();
2437
2484
  stale = true;
2438
- constructor(wrappers = []) {
2485
+ constructor(wrappers = [], environmentObjects = []) {
2486
+ this.table = createNodeTable(environmentObjects);
2439
2487
  (0, import_resolution2.addPackWords)(this.db, { unwrapsByName: wrappers });
2440
2488
  }
2441
2489
  /**
@@ -2648,6 +2696,89 @@ var ResolutionStore = class {
2648
2696
  }
2649
2697
  return found;
2650
2698
  }
2699
+ /**
2700
+ * Which parameters an environment read takes its variable's name
2701
+ * from, for a reader standing at a call. One question covers the
2702
+ * whole project: a project has a handful of environment reads and
2703
+ * thousands of parameters, and with the read bound the rules run from
2704
+ * each callee to its callers.
2705
+ *
2706
+ * `definedIn` is the callee's own file, read into the store before
2707
+ * the question so a helper nothing had extracted yet is among the
2708
+ * facts the rules run over.
2709
+ */
2710
+ envNamers(definedIn) {
2711
+ const siteFiles = this.environmentSiteFiles(definedIn.getProject());
2712
+ if (siteFiles.length === 0) {
2713
+ return NO_NAMERS;
2714
+ }
2715
+ this.extractFile(definedIn);
2716
+ if (this.envNamersExtractedAt === this.fullyExtracted.size) {
2717
+ return this.envNamersAnswer;
2718
+ }
2719
+ const answered = this.askEnvNamers([...siteFiles, definedIn]);
2720
+ const siteFilePaths = new Set(siteFiles.map((one) => one.getFilePath()));
2721
+ this.envNamersAnswer = {
2722
+ sitesNaming: (parameter) => this.sitesNamedBy(answered, siteFilePaths, parameter)
2723
+ };
2724
+ this.envNamersExtractedAt = this.fullyExtracted.size;
2725
+ return this.envNamersAnswer;
2726
+ }
2727
+ sitesNamedBy(answered, siteFilePaths, parameter) {
2728
+ const key = nodeId(parameter);
2729
+ const sites = answered.get(key);
2730
+ if (sites !== void 0) {
2731
+ return sites;
2732
+ }
2733
+ if (!siteFilePaths.has(parameter.getSourceFile().getFilePath())) {
2734
+ return [];
2735
+ }
2736
+ return this.db.lookup("paramOf", 2, key).length > 0 ? [] : null;
2737
+ }
2738
+ /**
2739
+ * Seed every read the store knows of, derive, and take the sites back
2740
+ * per parameter. The question is dropped afterwards, so a later ask
2741
+ * over a larger fact set derives it again.
2742
+ */
2743
+ askEnvNamers(seeds) {
2744
+ const byParameter = /* @__PURE__ */ new Map();
2745
+ try {
2746
+ for (const [site] of this.db.facts("readsEnvNamed")) {
2747
+ this.wantKey("wantedEnvSite", String(site));
2748
+ }
2749
+ this.extractDemanded(seeds);
2750
+ this.derive();
2751
+ for (const [parameter, site] of this.db.facts("wantedParamNamesEnv")) {
2752
+ const node = this.table.byId.get(String(site));
2753
+ if (node === void 0) {
2754
+ continue;
2755
+ }
2756
+ const sites = byParameter.get(String(parameter)) ?? [];
2757
+ sites.push(node);
2758
+ byParameter.set(String(parameter), sites);
2759
+ }
2760
+ } finally {
2761
+ this.forgetQuery();
2762
+ }
2763
+ return byParameter;
2764
+ }
2765
+ /**
2766
+ * The project's files that spell an environment read with a computed
2767
+ * index, read into the store. Which files those are cannot depend on
2768
+ * what a reader happened to ask about first, so the scan is over the
2769
+ * project's own sources and it happens once.
2770
+ */
2771
+ environmentSiteFiles(project) {
2772
+ if (this.envSiteFiles !== null) {
2773
+ return this.envSiteFiles;
2774
+ }
2775
+ const found = project.getSourceFiles().filter(
2776
+ (one) => !one.isInNodeModules() && environmentNameReadsIn(this.table, one).length > 0
2777
+ );
2778
+ this.envSiteFiles = found;
2779
+ this.extractFiles(found);
2780
+ return found;
2781
+ }
2651
2782
  /**
2652
2783
  * Whether running this function hands back that call: returned
2653
2784
  * outright, written into a name and returned, or a shorthand body.
@@ -3058,7 +3189,11 @@ var ResolutionStore = class {
3058
3189
  this.stale = false;
3059
3190
  }
3060
3191
  wantValue(question, value) {
3061
- if (this.db.add(question, [nodeId(value)]) === "added") {
3192
+ this.wantKey(question, nodeId(value));
3193
+ }
3194
+ /** For a question seeded with a fact key the store read out of the database. */
3195
+ wantKey(question, key) {
3196
+ if (this.db.add(question, [key]) === "added") {
3062
3197
  this.stale = true;
3063
3198
  }
3064
3199
  }
@@ -17623,11 +17758,14 @@ function createTypeScriptAdapter(suppliedConfig) {
17623
17758
  const packWrappers = config.frameworks.flatMap(
17624
17759
  (pack) => pack.transparentWrappers ?? []
17625
17760
  );
17761
+ const packEnvironments = config.frameworks.flatMap(
17762
+ (pack) => pack.environmentObjects ?? []
17763
+ );
17626
17764
  return {
17627
17765
  tsProject: project,
17628
17766
  async extractFromFiles(filePaths) {
17629
17767
  const summaries = [];
17630
- const resolution = new ResolutionStore(packWrappers);
17768
+ const resolution = new ResolutionStore(packWrappers, packEnvironments);
17631
17769
  const claimedUnits = /* @__PURE__ */ new Map();
17632
17770
  for (const fp of filePaths) {
17633
17771
  let sourceFile = project.getSourceFile(fp);
@@ -17730,7 +17868,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17730
17868
  timer
17731
17869
  );
17732
17870
  timer.time("warmExportChains", () => warmExportChains(deep.deepRoots));
17733
- const resolution = new ResolutionStore(packWrappers);
17871
+ const resolution = new ResolutionStore(packWrappers, packEnvironments);
17734
17872
  const packsByFile = timer.time(
17735
17873
  "preFilter",
17736
17874
  () => computePackApplicability(sourceFiles, config.frameworks, resolution)
package/dist/index.d.cts CHANGED
@@ -46,6 +46,18 @@ interface PassedArgument {
46
46
  call: Node;
47
47
  argument: Node;
48
48
  }
49
+ /** See `ResolutionStore.envNamers`. */
50
+ interface EnvironmentNamers {
51
+ /**
52
+ * The environment reads that take their variable's name from this
53
+ * parameter, however many helpers forward it along the way. Empty for
54
+ * a parameter no read ever takes a name from, and null where the
55
+ * rules had nothing to go on: a file that exports nothing states no
56
+ * facts, so their silence about its parameters means neither yes nor
57
+ * no, and the asker settles what it can by reading the syntax.
58
+ */
59
+ sitesNaming(parameter: Node): readonly Node[] | null;
60
+ }
49
61
  declare class ResolutionStore {
50
62
  private readonly db;
51
63
  private readonly table;
@@ -66,13 +78,17 @@ declare class ResolutionStore {
66
78
  private readonly constructionSites;
67
79
  /** Files the most recent query read, for the memo to keep. */
68
80
  private lastQueryWalked;
81
+ /** See `environmentSiteFiles`; null until the first env question. */
82
+ private envSiteFiles;
83
+ private envNamersAnswer;
84
+ private envNamersExtractedAt;
69
85
  private readonly declarations;
70
86
  private readonly graph;
71
87
  /** See `notePossibleCallers`. */
72
88
  private readonly possibleCallers;
73
89
  private readonly callerFilesRead;
74
90
  private stale;
75
- constructor(wrappers?: TransparentWrapper[]);
91
+ constructor(wrappers?: TransparentWrapper[], environmentObjects?: readonly string[]);
76
92
  /**
77
93
  * `alsoFrom` is a second file to walk out from, for a caller that
78
94
  * knows where the value it is asking about was set up. The walk
@@ -144,6 +160,32 @@ declare class ResolutionStore {
144
160
  * parameter's own function with no join against `bodyCalls` needed.
145
161
  */
146
162
  callsPassing(parameter: Node): PassedArgument[];
163
+ /**
164
+ * Which parameters an environment read takes its variable's name
165
+ * from, for a reader standing at a call. One question covers the
166
+ * whole project: a project has a handful of environment reads and
167
+ * thousands of parameters, and with the read bound the rules run from
168
+ * each callee to its callers.
169
+ *
170
+ * `definedIn` is the callee's own file, read into the store before
171
+ * the question so a helper nothing had extracted yet is among the
172
+ * facts the rules run over.
173
+ */
174
+ envNamers(definedIn: SourceFile): EnvironmentNamers;
175
+ private sitesNamedBy;
176
+ /**
177
+ * Seed every read the store knows of, derive, and take the sites back
178
+ * per parameter. The question is dropped afterwards, so a later ask
179
+ * over a larger fact set derives it again.
180
+ */
181
+ private askEnvNamers;
182
+ /**
183
+ * The project's files that spell an environment read with a computed
184
+ * index, read into the store. Which files those are cannot depend on
185
+ * what a reader happened to ask about first, so the scan is over the
186
+ * project's own sources and it happens once.
187
+ */
188
+ private environmentSiteFiles;
147
189
  /**
148
190
  * Whether running this function hands back that call: returned
149
191
  * outright, written into a name and returned, or a shorthand body.
@@ -247,6 +289,8 @@ declare class ResolutionStore {
247
289
  */
248
290
  private forgetQuery;
249
291
  private wantValue;
292
+ /** For a question seeded with a fact key the store read out of the database. */
293
+ private wantKey;
250
294
  private wantExportsOf;
251
295
  /**
252
296
  * Discovery walks the table in order, and the rounds derive it in
@@ -1579,4 +1623,4 @@ declare function climbSyntax(node: Node): Node;
1579
1623
  */
1580
1624
  declare function peelValue(node: Node): Node;
1581
1625
 
1582
- export { ADAPTER_VERSION, type Accessed, type CallAccountingEntry, type CallAccountingOutcome, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DescentBarriers, type DiscoveredUnit, type ExplainCallableOptions, type ExplainedResolution, type FoundTerminal, type FunctionRoot, GENERIC_TERMINALS, type ImportScanOptions, NO_BARRIERS, type ObservedStubCall, type RawBranchResult, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, TSCONFIG_NAMES, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, TypeScriptWhySession, type WhySessionOptions, accessContextFor, arrayLiteralOf, buildProjectHelperIndex, callOpsFor, climbSyntax, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, couldNameAValue, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findEnclosingFunction, findNearestTsconfig, findTerminals, functionTargetOf, functionValueOf, hasBody, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isDefaultedAt, isImportedFrom, methodDeclaredIn, namedImportsOf, objectLiteralOf, parseConditionExpression, peelSyntax, peelValue, propertiesOf, propertyFunctionOf, propertyNameOf, propertyOf, propertyValueOf, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, startsItsOwnScope, stringPropertyOf, stringValueOf, stubEvidence, stubEvidenceIn, symbolBehind, workspaceRootFor, writtenNodeOf };
1626
+ export { ADAPTER_VERSION, type Accessed, type CallAccountingEntry, type CallAccountingOutcome, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DescentBarriers, type DiscoveredUnit, type EnvironmentNamers, type ExplainCallableOptions, type ExplainedResolution, type FoundTerminal, type FunctionRoot, GENERIC_TERMINALS, type ImportScanOptions, NO_BARRIERS, type ObservedStubCall, type RawBranchResult, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, TSCONFIG_NAMES, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, TypeScriptWhySession, type WhySessionOptions, accessContextFor, arrayLiteralOf, buildProjectHelperIndex, callOpsFor, climbSyntax, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, couldNameAValue, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findEnclosingFunction, findNearestTsconfig, findTerminals, functionTargetOf, functionValueOf, hasBody, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isDefaultedAt, isImportedFrom, methodDeclaredIn, namedImportsOf, objectLiteralOf, parseConditionExpression, peelSyntax, peelValue, propertiesOf, propertyFunctionOf, propertyNameOf, propertyOf, propertyValueOf, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, startsItsOwnScope, stringPropertyOf, stringValueOf, stubEvidence, stubEvidenceIn, symbolBehind, workspaceRootFor, writtenNodeOf };
package/dist/index.d.ts CHANGED
@@ -46,6 +46,18 @@ interface PassedArgument {
46
46
  call: Node;
47
47
  argument: Node;
48
48
  }
49
+ /** See `ResolutionStore.envNamers`. */
50
+ interface EnvironmentNamers {
51
+ /**
52
+ * The environment reads that take their variable's name from this
53
+ * parameter, however many helpers forward it along the way. Empty for
54
+ * a parameter no read ever takes a name from, and null where the
55
+ * rules had nothing to go on: a file that exports nothing states no
56
+ * facts, so their silence about its parameters means neither yes nor
57
+ * no, and the asker settles what it can by reading the syntax.
58
+ */
59
+ sitesNaming(parameter: Node): readonly Node[] | null;
60
+ }
49
61
  declare class ResolutionStore {
50
62
  private readonly db;
51
63
  private readonly table;
@@ -66,13 +78,17 @@ declare class ResolutionStore {
66
78
  private readonly constructionSites;
67
79
  /** Files the most recent query read, for the memo to keep. */
68
80
  private lastQueryWalked;
81
+ /** See `environmentSiteFiles`; null until the first env question. */
82
+ private envSiteFiles;
83
+ private envNamersAnswer;
84
+ private envNamersExtractedAt;
69
85
  private readonly declarations;
70
86
  private readonly graph;
71
87
  /** See `notePossibleCallers`. */
72
88
  private readonly possibleCallers;
73
89
  private readonly callerFilesRead;
74
90
  private stale;
75
- constructor(wrappers?: TransparentWrapper[]);
91
+ constructor(wrappers?: TransparentWrapper[], environmentObjects?: readonly string[]);
76
92
  /**
77
93
  * `alsoFrom` is a second file to walk out from, for a caller that
78
94
  * knows where the value it is asking about was set up. The walk
@@ -144,6 +160,32 @@ declare class ResolutionStore {
144
160
  * parameter's own function with no join against `bodyCalls` needed.
145
161
  */
146
162
  callsPassing(parameter: Node): PassedArgument[];
163
+ /**
164
+ * Which parameters an environment read takes its variable's name
165
+ * from, for a reader standing at a call. One question covers the
166
+ * whole project: a project has a handful of environment reads and
167
+ * thousands of parameters, and with the read bound the rules run from
168
+ * each callee to its callers.
169
+ *
170
+ * `definedIn` is the callee's own file, read into the store before
171
+ * the question so a helper nothing had extracted yet is among the
172
+ * facts the rules run over.
173
+ */
174
+ envNamers(definedIn: SourceFile): EnvironmentNamers;
175
+ private sitesNamedBy;
176
+ /**
177
+ * Seed every read the store knows of, derive, and take the sites back
178
+ * per parameter. The question is dropped afterwards, so a later ask
179
+ * over a larger fact set derives it again.
180
+ */
181
+ private askEnvNamers;
182
+ /**
183
+ * The project's files that spell an environment read with a computed
184
+ * index, read into the store. Which files those are cannot depend on
185
+ * what a reader happened to ask about first, so the scan is over the
186
+ * project's own sources and it happens once.
187
+ */
188
+ private environmentSiteFiles;
147
189
  /**
148
190
  * Whether running this function hands back that call: returned
149
191
  * outright, written into a name and returned, or a shorthand body.
@@ -247,6 +289,8 @@ declare class ResolutionStore {
247
289
  */
248
290
  private forgetQuery;
249
291
  private wantValue;
292
+ /** For a question seeded with a fact key the store read out of the database. */
293
+ private wantKey;
250
294
  private wantExportsOf;
251
295
  /**
252
296
  * Discovery walks the table in order, and the rounds derive it in
@@ -1579,4 +1623,4 @@ declare function climbSyntax(node: Node): Node;
1579
1623
  */
1580
1624
  declare function peelValue(node: Node): Node;
1581
1625
 
1582
- export { ADAPTER_VERSION, type Accessed, type CallAccountingEntry, type CallAccountingOutcome, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DescentBarriers, type DiscoveredUnit, type ExplainCallableOptions, type ExplainedResolution, type FoundTerminal, type FunctionRoot, GENERIC_TERMINALS, type ImportScanOptions, NO_BARRIERS, type ObservedStubCall, type RawBranchResult, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, TSCONFIG_NAMES, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, TypeScriptWhySession, type WhySessionOptions, accessContextFor, arrayLiteralOf, buildProjectHelperIndex, callOpsFor, climbSyntax, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, couldNameAValue, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findEnclosingFunction, findNearestTsconfig, findTerminals, functionTargetOf, functionValueOf, hasBody, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isDefaultedAt, isImportedFrom, methodDeclaredIn, namedImportsOf, objectLiteralOf, parseConditionExpression, peelSyntax, peelValue, propertiesOf, propertyFunctionOf, propertyNameOf, propertyOf, propertyValueOf, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, startsItsOwnScope, stringPropertyOf, stringValueOf, stubEvidence, stubEvidenceIn, symbolBehind, workspaceRootFor, writtenNodeOf };
1626
+ export { ADAPTER_VERSION, type Accessed, type CallAccountingEntry, type CallAccountingOutcome, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DescentBarriers, type DiscoveredUnit, type EnvironmentNamers, type ExplainCallableOptions, type ExplainedResolution, type FoundTerminal, type FunctionRoot, GENERIC_TERMINALS, type ImportScanOptions, NO_BARRIERS, type ObservedStubCall, type RawBranchResult, type ReadNameOptions, ResolutionStore, type StubCallEvidence, type StubEvidenceOptions, TSCONFIG_NAMES, type TsDiscoveryContext, type TsInvocationRecognizerContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, TypeScriptWhySession, type WhySessionOptions, accessContextFor, arrayLiteralOf, buildProjectHelperIndex, callOpsFor, climbSyntax, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, couldNameAValue, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findEnclosingFunction, findNearestTsconfig, findTerminals, functionTargetOf, functionValueOf, hasBody, importDeclarationsOf, importedNamesOf, importedRootsOf, invocationContextFor, isDefaultedAt, isImportedFrom, methodDeclaredIn, namedImportsOf, objectLiteralOf, parseConditionExpression, peelSyntax, peelValue, propertiesOf, propertyFunctionOf, propertyNameOf, propertyOf, propertyValueOf, readConfiguredCall, readContract, readContractForClientCall, readName, resolveAliasedSymbol, resolveSubject, rootIdentifier, startsItsOwnScope, stringPropertyOf, stringValueOf, stubEvidence, stubEvidenceIn, symbolBehind, workspaceRootFor, writtenNodeOf };
package/dist/index.js CHANGED
@@ -1454,13 +1454,14 @@ function isWriteTarget(name) {
1454
1454
  function fact(db, relation, ...tuple) {
1455
1455
  db.add(relation, tuple);
1456
1456
  }
1457
- function createNodeTable() {
1457
+ function createNodeTable(environmentObjects = []) {
1458
1458
  return {
1459
1459
  byId: /* @__PURE__ */ new Map(),
1460
1460
  seenFunctions: /* @__PURE__ */ new Set(),
1461
1461
  seenBindings: /* @__PURE__ */ new Set(),
1462
1462
  seenValues: /* @__PURE__ */ new Set(),
1463
- seenClasses: /* @__PURE__ */ new Set()
1463
+ seenClasses: /* @__PURE__ */ new Set(),
1464
+ environmentObjects
1464
1465
  };
1465
1466
  }
1466
1467
  function nodeId(node) {
@@ -1605,6 +1606,40 @@ function literalIndexOf(index) {
1605
1606
  }
1606
1607
  return null;
1607
1608
  }
1609
+ function readsEnvironment(table, access) {
1610
+ if (table.environmentObjects.length === 0) {
1611
+ return false;
1612
+ }
1613
+ const path14 = dottedPathOf(access.getExpression());
1614
+ return path14 !== null && table.environmentObjects.includes(path14);
1615
+ }
1616
+ function isEnvironmentNameRead(table, node) {
1617
+ if (!Node5.isElementAccessExpression(node)) {
1618
+ return false;
1619
+ }
1620
+ const argument = node.getArgumentExpression();
1621
+ return argument !== void 0 && literalIndexOf(argument) === null && readsEnvironment(table, node);
1622
+ }
1623
+ function environmentNameReadsIn(table, sourceFile) {
1624
+ if (table.environmentObjects.length === 0) {
1625
+ return [];
1626
+ }
1627
+ const text = sourceFile.getFullText();
1628
+ if (!table.environmentObjects.some((path14) => text.includes(path14))) {
1629
+ return [];
1630
+ }
1631
+ return sourceFile.getDescendantsOfKind(SyntaxKind2.ElementAccessExpression).filter((access) => isEnvironmentNameRead(table, access));
1632
+ }
1633
+ function dottedPathOf(expression) {
1634
+ if (Node5.isIdentifier(expression)) {
1635
+ return expression.getText();
1636
+ }
1637
+ if (!Node5.isPropertyAccessExpression(expression)) {
1638
+ return null;
1639
+ }
1640
+ const object = dottedPathOf(expression.getExpression());
1641
+ return object === null ? null : `${object}.${expression.getName()}`;
1642
+ }
1608
1643
  function asFallbackExpression(expression) {
1609
1644
  if (!Node5.isBinaryExpression(expression)) {
1610
1645
  return null;
@@ -1676,6 +1711,10 @@ function emitValue(db, table, value) {
1676
1711
  );
1677
1712
  return id;
1678
1713
  }
1714
+ const argument = expression.getArgumentExpression();
1715
+ if (argument !== void 0 && readsEnvironment(table, expression)) {
1716
+ fact(db, "readsEnvNamed", id, emitValue(db, table, argument));
1717
+ }
1679
1718
  fact(db, "writtenValue", id);
1680
1719
  return id;
1681
1720
  }
@@ -2140,6 +2179,9 @@ function recordBodyCalls(db, table, fnId, node) {
2140
2179
  if (!Node5.isExpression(node)) {
2141
2180
  return false;
2142
2181
  }
2182
+ if (isEnvironmentNameRead(table, node)) {
2183
+ emitValue(db, table, node);
2184
+ }
2143
2185
  const call = unwrapExpression(node);
2144
2186
  if (Node5.isCallExpression(call)) {
2145
2187
  const callee = unwrapExpression(call.getExpression());
@@ -2365,9 +2407,10 @@ var QUERY_FACTS = queryFacts(RESOLUTION_PROGRAM);
2365
2407
  var MODULE_DEMANDS = RESOLUTION_PROGRAM.demands.filter(
2366
2408
  (one) => (one.relation === "moduleExport" || one.relation === "moduleForwards") && one.bound[0] === true
2367
2409
  ).map((one) => one.demand);
2410
+ var NO_NAMERS = { sitesNaming: () => [] };
2368
2411
  var ResolutionStore = class {
2369
2412
  db = new Database2();
2370
- table = createNodeTable();
2413
+ table;
2371
2414
  fullyExtracted = /* @__PURE__ */ new Set();
2372
2415
  seededValues = /* @__PURE__ */ new Set();
2373
2416
  importedNames = /* @__PURE__ */ new Map();
@@ -2385,13 +2428,18 @@ var ResolutionStore = class {
2385
2428
  constructionSites = /* @__PURE__ */ new Map();
2386
2429
  /** Files the most recent query read, for the memo to keep. */
2387
2430
  lastQueryWalked = [];
2431
+ /** See `environmentSiteFiles`; null until the first env question. */
2432
+ envSiteFiles = null;
2433
+ envNamersAnswer = NO_NAMERS;
2434
+ envNamersExtractedAt = -1;
2388
2435
  declarations = /* @__PURE__ */ new Map();
2389
2436
  graph = new ModuleGraph();
2390
2437
  /** See `notePossibleCallers`. */
2391
2438
  possibleCallers = /* @__PURE__ */ new Set();
2392
2439
  callerFilesRead = /* @__PURE__ */ new Set();
2393
2440
  stale = true;
2394
- constructor(wrappers = []) {
2441
+ constructor(wrappers = [], environmentObjects = []) {
2442
+ this.table = createNodeTable(environmentObjects);
2395
2443
  addPackWords(this.db, { unwrapsByName: wrappers });
2396
2444
  }
2397
2445
  /**
@@ -2604,6 +2652,89 @@ var ResolutionStore = class {
2604
2652
  }
2605
2653
  return found;
2606
2654
  }
2655
+ /**
2656
+ * Which parameters an environment read takes its variable's name
2657
+ * from, for a reader standing at a call. One question covers the
2658
+ * whole project: a project has a handful of environment reads and
2659
+ * thousands of parameters, and with the read bound the rules run from
2660
+ * each callee to its callers.
2661
+ *
2662
+ * `definedIn` is the callee's own file, read into the store before
2663
+ * the question so a helper nothing had extracted yet is among the
2664
+ * facts the rules run over.
2665
+ */
2666
+ envNamers(definedIn) {
2667
+ const siteFiles = this.environmentSiteFiles(definedIn.getProject());
2668
+ if (siteFiles.length === 0) {
2669
+ return NO_NAMERS;
2670
+ }
2671
+ this.extractFile(definedIn);
2672
+ if (this.envNamersExtractedAt === this.fullyExtracted.size) {
2673
+ return this.envNamersAnswer;
2674
+ }
2675
+ const answered = this.askEnvNamers([...siteFiles, definedIn]);
2676
+ const siteFilePaths = new Set(siteFiles.map((one) => one.getFilePath()));
2677
+ this.envNamersAnswer = {
2678
+ sitesNaming: (parameter) => this.sitesNamedBy(answered, siteFilePaths, parameter)
2679
+ };
2680
+ this.envNamersExtractedAt = this.fullyExtracted.size;
2681
+ return this.envNamersAnswer;
2682
+ }
2683
+ sitesNamedBy(answered, siteFilePaths, parameter) {
2684
+ const key = nodeId(parameter);
2685
+ const sites = answered.get(key);
2686
+ if (sites !== void 0) {
2687
+ return sites;
2688
+ }
2689
+ if (!siteFilePaths.has(parameter.getSourceFile().getFilePath())) {
2690
+ return [];
2691
+ }
2692
+ return this.db.lookup("paramOf", 2, key).length > 0 ? [] : null;
2693
+ }
2694
+ /**
2695
+ * Seed every read the store knows of, derive, and take the sites back
2696
+ * per parameter. The question is dropped afterwards, so a later ask
2697
+ * over a larger fact set derives it again.
2698
+ */
2699
+ askEnvNamers(seeds) {
2700
+ const byParameter = /* @__PURE__ */ new Map();
2701
+ try {
2702
+ for (const [site] of this.db.facts("readsEnvNamed")) {
2703
+ this.wantKey("wantedEnvSite", String(site));
2704
+ }
2705
+ this.extractDemanded(seeds);
2706
+ this.derive();
2707
+ for (const [parameter, site] of this.db.facts("wantedParamNamesEnv")) {
2708
+ const node = this.table.byId.get(String(site));
2709
+ if (node === void 0) {
2710
+ continue;
2711
+ }
2712
+ const sites = byParameter.get(String(parameter)) ?? [];
2713
+ sites.push(node);
2714
+ byParameter.set(String(parameter), sites);
2715
+ }
2716
+ } finally {
2717
+ this.forgetQuery();
2718
+ }
2719
+ return byParameter;
2720
+ }
2721
+ /**
2722
+ * The project's files that spell an environment read with a computed
2723
+ * index, read into the store. Which files those are cannot depend on
2724
+ * what a reader happened to ask about first, so the scan is over the
2725
+ * project's own sources and it happens once.
2726
+ */
2727
+ environmentSiteFiles(project) {
2728
+ if (this.envSiteFiles !== null) {
2729
+ return this.envSiteFiles;
2730
+ }
2731
+ const found = project.getSourceFiles().filter(
2732
+ (one) => !one.isInNodeModules() && environmentNameReadsIn(this.table, one).length > 0
2733
+ );
2734
+ this.envSiteFiles = found;
2735
+ this.extractFiles(found);
2736
+ return found;
2737
+ }
2607
2738
  /**
2608
2739
  * Whether running this function hands back that call: returned
2609
2740
  * outright, written into a name and returned, or a shorthand body.
@@ -3014,7 +3145,11 @@ var ResolutionStore = class {
3014
3145
  this.stale = false;
3015
3146
  }
3016
3147
  wantValue(question, value) {
3017
- if (this.db.add(question, [nodeId(value)]) === "added") {
3148
+ this.wantKey(question, nodeId(value));
3149
+ }
3150
+ /** For a question seeded with a fact key the store read out of the database. */
3151
+ wantKey(question, key) {
3152
+ if (this.db.add(question, [key]) === "added") {
3018
3153
  this.stale = true;
3019
3154
  }
3020
3155
  }
@@ -17654,11 +17789,14 @@ function createTypeScriptAdapter(suppliedConfig) {
17654
17789
  const packWrappers = config.frameworks.flatMap(
17655
17790
  (pack) => pack.transparentWrappers ?? []
17656
17791
  );
17792
+ const packEnvironments = config.frameworks.flatMap(
17793
+ (pack) => pack.environmentObjects ?? []
17794
+ );
17657
17795
  return {
17658
17796
  tsProject: project,
17659
17797
  async extractFromFiles(filePaths) {
17660
17798
  const summaries = [];
17661
- const resolution = new ResolutionStore(packWrappers);
17799
+ const resolution = new ResolutionStore(packWrappers, packEnvironments);
17662
17800
  const claimedUnits = /* @__PURE__ */ new Map();
17663
17801
  for (const fp of filePaths) {
17664
17802
  let sourceFile = project.getSourceFile(fp);
@@ -17761,7 +17899,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17761
17899
  timer
17762
17900
  );
17763
17901
  timer.time("warmExportChains", () => warmExportChains(deep.deepRoots));
17764
- const resolution = new ResolutionStore(packWrappers);
17902
+ const resolution = new ResolutionStore(packWrappers, packEnvironments);
17765
17903
  const packsByFile = timer.time(
17766
17904
  "preFilter",
17767
17905
  () => computePackApplicability(sourceFiles, config.frameworks, resolution)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-typescript",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
4
4
  "type": "module",
5
5
  "description": "Read a TypeScript or JavaScript codebase with suss and check what each handler returns against every caller, spec and template it can see.",
6
6
  "keywords": [
@@ -48,11 +48,11 @@
48
48
  "typecheck": "tsc --noEmit"
49
49
  },
50
50
  "dependencies": {
51
- "@suss/behavioral-ir": "0.31.1",
52
- "@suss/datalog": "0.31.1",
53
- "@suss/extractor": "0.31.1",
54
- "@suss/resolution": "0.31.1",
55
- "@suss/values": "0.31.1",
51
+ "@suss/behavioral-ir": "0.32.0",
52
+ "@suss/datalog": "0.32.0",
53
+ "@suss/extractor": "0.32.0",
54
+ "@suss/resolution": "0.32.0",
55
+ "@suss/values": "0.32.0",
56
56
  "graphql": "^16.8.0",
57
57
  "picomatch": "^4.0.4",
58
58
  "ts-morph": "^23.0.0",