@suss/adapter-typescript 0.7.1 → 0.8.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
@@ -3985,6 +3985,7 @@ var import_ts_morph19 = require("ts-morph");
3985
3985
  var import_ts_morph18 = require("ts-morph");
3986
3986
  var byFile = createPerFileCache();
3987
3987
  var byDeclaration = /* @__PURE__ */ new WeakMap();
3988
+ var byField = /* @__PURE__ */ new WeakMap();
3988
3989
  function writesToBinding(declaration) {
3989
3990
  const remembered = byDeclaration.get(declaration);
3990
3991
  if (remembered !== void 0) {
@@ -4017,22 +4018,19 @@ function writesToBindingUncached(declaration) {
4017
4018
  return { values, inOrder: writesRunInOrder(declaration, writes) };
4018
4019
  }
4019
4020
  function writesToField(declaration) {
4020
- const remembered = byDeclaration.get(declaration);
4021
+ const remembered = byField.get(declaration);
4021
4022
  if (remembered !== void 0) {
4022
4023
  return remembered;
4023
4024
  }
4024
4025
  const answer = writesToFieldUncached(declaration);
4025
- byDeclaration.set(declaration, answer);
4026
+ byField.set(declaration, answer);
4026
4027
  return answer;
4027
4028
  }
4028
4029
  function writesToFieldUncached(declaration) {
4029
- const initializer = declaration.getInitializer();
4030
+ const first = startingValueOf(declaration);
4030
4031
  const assignments = assignmentsTo(declaration);
4031
4032
  if (assignments.length === 0) {
4032
- return {
4033
- values: initializer === void 0 ? [] : [initializer],
4034
- inOrder: true
4035
- };
4033
+ return { values: first === void 0 ? [] : [first], inOrder: true };
4036
4034
  }
4037
4035
  const values = [];
4038
4036
  for (const write of assignments) {
@@ -4041,14 +4039,16 @@ function writesToFieldUncached(declaration) {
4041
4039
  }
4042
4040
  values.push(write.value);
4043
4041
  }
4044
- const withInitializer = initializer === void 0 ? values : [initializer, ...values];
4045
4042
  return {
4046
- values: withInitializer,
4043
+ values: first === void 0 ? values : [first, ...values],
4047
4044
  inOrder: assignments.every(
4048
4045
  (write) => writeRunsInConstructor(declaration, write)
4049
4046
  )
4050
4047
  };
4051
4048
  }
4049
+ function startingValueOf(declaration) {
4050
+ return import_ts_morph18.Node.isParameterDeclaration(declaration) ? declaration : declaration.getInitializer();
4051
+ }
4052
4052
  function writeRunsInConstructor(declaration, write) {
4053
4053
  const statement = statementOf(write.node);
4054
4054
  if (statement === null) {
@@ -4059,13 +4059,16 @@ function writeRunsInConstructor(declaration, write) {
4059
4059
  return false;
4060
4060
  }
4061
4061
  const owner = body.getParent();
4062
- return owner !== void 0 && import_ts_morph18.Node.isConstructorDeclaration(owner) && owner.getParent() === declaration.getParent();
4062
+ return owner !== void 0 && import_ts_morph18.Node.isConstructorDeclaration(owner) && owner.getParent() === classHolding(declaration);
4063
+ }
4064
+ function classHolding(declaration) {
4065
+ return import_ts_morph18.Node.isParameterDeclaration(declaration) ? declaration.getParent().getParent() : declaration.getParent();
4063
4066
  }
4064
4067
  function isWrittenAgain(declaration) {
4065
4068
  return assignmentsTo(declaration).length > 0;
4066
4069
  }
4067
4070
  function assignmentsTo(declaration) {
4068
- if (import_ts_morph18.Node.isPropertyDeclaration(declaration)) {
4071
+ if (import_ts_morph18.Node.isPropertyDeclaration(declaration) || import_ts_morph18.Node.isParameterDeclaration(declaration)) {
4069
4072
  const sourceFile2 = declaration.getSourceFile();
4070
4073
  return sourceFile2.isDeclarationFile() ? [] : assignmentsInFile(sourceFile2).get(declaration) ?? [];
4071
4074
  }
@@ -4132,7 +4135,9 @@ function declarationsOf(name) {
4132
4135
  }
4133
4136
  const declarations = [];
4134
4137
  for (const declaration of symbol.getDeclarations()) {
4135
- if (import_ts_morph18.Node.isVariableDeclaration(declaration) || import_ts_morph18.Node.isPropertyDeclaration(declaration)) {
4138
+ if (import_ts_morph18.Node.isVariableDeclaration(declaration) || import_ts_morph18.Node.isPropertyDeclaration(declaration) || // A parameter property is the field's only declaration, so
4139
+ // `this.dao = other` in the constructor writes to it.
4140
+ import_ts_morph18.Node.isParameterDeclaration(declaration) && declaration.isParameterProperty()) {
4136
4141
  declarations.push(declaration);
4137
4142
  }
4138
4143
  }
@@ -5909,7 +5914,8 @@ function createNodeTable() {
5909
5914
  byId: /* @__PURE__ */ new Map(),
5910
5915
  seenFunctions: /* @__PURE__ */ new Set(),
5911
5916
  seenBindings: /* @__PURE__ */ new Set(),
5912
- seenValues: /* @__PURE__ */ new Set()
5917
+ seenValues: /* @__PURE__ */ new Set(),
5918
+ seenClasses: /* @__PURE__ */ new Set()
5913
5919
  };
5914
5920
  }
5915
5921
  function nodeId(node) {
@@ -6130,7 +6136,7 @@ function emitValue(db, table, value) {
6130
6136
  }
6131
6137
  return id;
6132
6138
  }
6133
- if (import_ts_morph28.Node.isCallExpression(expression)) {
6139
+ if (import_ts_morph28.Node.isCallExpression(expression) || import_ts_morph28.Node.isNewExpression(expression)) {
6134
6140
  emitCallFacts(db, table, expression);
6135
6141
  fact(db, "writtenValue", id);
6136
6142
  return id;
@@ -6157,11 +6163,24 @@ function emitBindingValues(db, table, declaration) {
6157
6163
  function emitFieldValues(db, table, declaration) {
6158
6164
  const { values, inOrder } = writesToField(declaration);
6159
6165
  const last = values[values.length - 1];
6160
- if (!inOrder || last === void 0) {
6166
+ if (!inOrder || last === void 0 || !import_ts_morph28.Node.isExpression(last)) {
6161
6167
  return;
6162
6168
  }
6163
6169
  fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
6164
6170
  }
6171
+ function emitParameterPropertyRead(db, table, referenceId, declaration) {
6172
+ const { values, inOrder } = writesToField(declaration);
6173
+ const last = values[values.length - 1];
6174
+ if (!inOrder || last === void 0) {
6175
+ return;
6176
+ }
6177
+ if (!import_ts_morph28.Node.isExpression(last)) {
6178
+ table.byId.set(nodeId(last), last);
6179
+ fact(db, "binds", referenceId, nodeId(last));
6180
+ return;
6181
+ }
6182
+ fact(db, "binds", referenceId, emitValue(db, table, last));
6183
+ }
6165
6184
  function emitBindingElementFacts(db, table, element) {
6166
6185
  const id = nodeId(element);
6167
6186
  table.byId.set(id, element);
@@ -6243,7 +6262,16 @@ function emitReferenceFacts(db, table, reference) {
6243
6262
  continue;
6244
6263
  }
6245
6264
  if (import_ts_morph28.Node.isParameterDeclaration(declaration)) {
6265
+ if (declaration.isParameterProperty() && import_ts_morph28.Node.isPropertyAccessExpression(reference)) {
6266
+ emitParameterPropertyRead(db, table, referenceId, declaration);
6267
+ } else {
6268
+ fact(db, "binds", referenceId, declarationId);
6269
+ }
6270
+ continue;
6271
+ }
6272
+ if (import_ts_morph28.Node.isClassDeclaration(declaration)) {
6246
6273
  fact(db, "binds", referenceId, declarationId);
6274
+ emitClassFacts(db, table, declaration);
6247
6275
  continue;
6248
6276
  }
6249
6277
  if (import_ts_morph28.Node.isBindingElement(declaration)) {
@@ -6288,9 +6316,14 @@ function emitCallFacts(db, table, call) {
6288
6316
  }
6289
6317
  }
6290
6318
  function emitClassFacts(db, table, declaration) {
6319
+ if (table.seenClasses.has(declaration)) {
6320
+ return;
6321
+ }
6322
+ table.seenClasses.add(declaration);
6291
6323
  const id = nodeId(declaration);
6292
6324
  table.byId.set(id, declaration);
6293
6325
  fact(db, "objectValue", id);
6326
+ emitConstructorParameters(db, table, declaration, id);
6294
6327
  for (const method of declaration.getMethods()) {
6295
6328
  const methodId = nodeId(method);
6296
6329
  table.byId.set(methodId, method);
@@ -6311,6 +6344,25 @@ function emitClassFacts(db, table, declaration) {
6311
6344
  }
6312
6345
  }
6313
6346
  }
6347
+ function emitConstructorParameters(db, table, declaration, classId) {
6348
+ const implementation = declaration.getConstructors().find((candidate) => candidate.getBody() !== void 0);
6349
+ if (implementation === void 0) {
6350
+ return;
6351
+ }
6352
+ emitParameters(db, table, classId, implementation.getParameters());
6353
+ }
6354
+ function emitParameters(db, table, ownerId, parameters) {
6355
+ for (let position = 0; position < parameters.length; position++) {
6356
+ const parameter = parameters[position];
6357
+ if (parameter === void 0) {
6358
+ continue;
6359
+ }
6360
+ const parameterId = nodeId(parameter);
6361
+ table.byId.set(parameterId, parameter);
6362
+ fact(db, "paramOf", ownerId, String(position), parameterId);
6363
+ fact(db, "paramNamed", ownerId, parameter.getName(), parameterId);
6364
+ }
6365
+ }
6314
6366
  function emitFunctionFacts(db, table, fn) {
6315
6367
  if (table.seenFunctions.has(fn)) {
6316
6368
  return;
@@ -6319,17 +6371,7 @@ function emitFunctionFacts(db, table, fn) {
6319
6371
  const fnId = nodeId(fn);
6320
6372
  table.byId.set(fnId, fn);
6321
6373
  if (import_ts_morph28.Node.isFunctionDeclaration(fn) || import_ts_morph28.Node.isFunctionExpression(fn) || import_ts_morph28.Node.isArrowFunction(fn) || import_ts_morph28.Node.isMethodDeclaration(fn)) {
6322
- const parameters = fn.getParameters();
6323
- for (let position = 0; position < parameters.length; position++) {
6324
- const parameter = parameters[position];
6325
- if (parameter === void 0) {
6326
- continue;
6327
- }
6328
- const parameterId = nodeId(parameter);
6329
- table.byId.set(parameterId, parameter);
6330
- fact(db, "paramOf", fnId, String(position), parameterId);
6331
- fact(db, "paramNamed", fnId, parameter.getName(), parameterId);
6332
- }
6374
+ emitParameters(db, table, fnId, fn.getParameters());
6333
6375
  const body = fn.getBody?.();
6334
6376
  if (body !== void 0) {
6335
6377
  if (import_ts_morph28.Node.isExpression(body)) {
@@ -10055,9 +10097,21 @@ var ResolutionStore = class {
10055
10097
  this.db.add("wrapperModule", [wrapper.callee, wrapper.module]);
10056
10098
  }
10057
10099
  }
10058
- resolveCallable(value) {
10100
+ /**
10101
+ * `alsoFrom` is a second file to walk out from, for a caller that
10102
+ * knows where the value it is asking about was set up. The walk
10103
+ * follows imports outward from the value's own file, so a file that
10104
+ * imports it, and passed something to the constructor, is somewhere
10105
+ * the walk never reaches on its own.
10106
+ */
10107
+ resolveCallable(value, alsoFrom) {
10059
10108
  const target = factKeyOf(value);
10060
- return this.resolveByWaves(target, "wanted", () => this.lookup(target));
10109
+ return this.resolveByWaves(
10110
+ target,
10111
+ "wanted",
10112
+ () => this.lookup(target),
10113
+ alsoFrom
10114
+ );
10061
10115
  }
10062
10116
  resolveObject(value) {
10063
10117
  const target = factKeyOf(value);
@@ -10125,18 +10179,18 @@ var ResolutionStore = class {
10125
10179
  }
10126
10180
  return origins.some((pair) => namesAPackage(pairHalves(pair).module)) ? [] : null;
10127
10181
  }
10128
- resolveByWaves(value, question, ask) {
10182
+ resolveByWaves(value, question, ask, alsoFrom) {
10129
10183
  try {
10130
- return this.walkForAnswer(value, question, ask);
10184
+ return this.walkForAnswer(value, question, ask, alsoFrom);
10131
10185
  } finally {
10132
10186
  this.forgetQuery();
10133
10187
  }
10134
10188
  }
10135
- walkForAnswer(value, question, ask) {
10189
+ walkForAnswer(value, question, ask, alsoFrom) {
10136
10190
  this.wantValue(question, value);
10137
10191
  this.seedValue(value);
10138
10192
  const walked = /* @__PURE__ */ new Set();
10139
- let frontier = [value.getSourceFile()];
10193
+ let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10140
10194
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10141
10195
  const next = [];
10142
10196
  for (const sourceFile of frontier) {
@@ -10657,24 +10711,41 @@ function declName(decl) {
10657
10711
  }
10658
10712
  return null;
10659
10713
  }
10660
- function resolveCallee(call, calleeName) {
10714
+ function resolveCallee(call, calleeName, scan) {
10661
10715
  if (!import_ts_morph51.Node.isCallExpression(call)) {
10662
10716
  return null;
10663
10717
  }
10664
10718
  const callee = call.getExpression();
10665
- const symbol = callee.getSymbol();
10666
- if (symbol === void 0) {
10667
- return null;
10668
- }
10669
- for (const decl of symbol.getDeclarations()) {
10719
+ const declarations = callee.getSymbol()?.getDeclarations() ?? [];
10720
+ for (const decl of declarations) {
10670
10721
  const resolved = resolveDecl(decl, calleeName);
10671
10722
  if (resolved !== null) {
10672
10723
  return resolved;
10673
10724
  }
10674
10725
  }
10675
- return null;
10726
+ if (scan.resolveCallable === void 0 || !readsAField(callee)) {
10727
+ return null;
10728
+ }
10729
+ if (!declarations.some(isDeclaredShape)) {
10730
+ return null;
10731
+ }
10732
+ const value = scan.resolveCallable(callee, scan.reachedFrom);
10733
+ return value === null ? null : resolveDecl(value, calleeName);
10676
10734
  }
10677
- function collectReachable(func) {
10735
+ function readsAField(callee) {
10736
+ if (!import_ts_morph51.Node.isPropertyAccessExpression(callee)) {
10737
+ return false;
10738
+ }
10739
+ let receiver = callee.getExpression();
10740
+ while (import_ts_morph51.Node.isPropertyAccessExpression(receiver)) {
10741
+ receiver = receiver.getExpression();
10742
+ }
10743
+ return import_ts_morph51.Node.isThisExpression(receiver);
10744
+ }
10745
+ function isDeclaredShape(declaration) {
10746
+ return (import_ts_morph51.Node.isMethodSignature(declaration) || import_ts_morph51.Node.isPropertySignature(declaration)) && !isInExternalCode(declaration.getSourceFile());
10747
+ }
10748
+ function collectReachable(func, scan) {
10678
10749
  const found = [];
10679
10750
  const seen = /* @__PURE__ */ new Set();
10680
10751
  func.forEachDescendant((node) => {
@@ -10682,7 +10753,7 @@ function collectReachable(func) {
10682
10753
  return;
10683
10754
  }
10684
10755
  const calleeText = node.getExpression().getText();
10685
- const resolved = resolveCallee(node, calleeText);
10756
+ const resolved = resolveCallee(node, calleeText, scan);
10686
10757
  if (resolved === null) {
10687
10758
  return;
10688
10759
  }
@@ -10733,6 +10804,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10733
10804
  const functionByKey = /* @__PURE__ */ new Map();
10734
10805
  const seedKeys = /* @__PURE__ */ new Set();
10735
10806
  const scanned = /* @__PURE__ */ new Set();
10807
+ const reachedFrom = /* @__PURE__ */ new Map();
10736
10808
  for (const seed of seeds) {
10737
10809
  const func = lookup.functionAt(seed.location);
10738
10810
  if (func !== null) {
@@ -10755,11 +10827,19 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10755
10827
  if (source === void 0) {
10756
10828
  continue;
10757
10829
  }
10758
- for (const candidate of collectReachable(source.func)) {
10830
+ const cameFrom = reachedFrom.get(key);
10831
+ const scan = {
10832
+ ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
10833
+ ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10834
+ };
10835
+ for (const candidate of collectReachable(source.func, scan)) {
10759
10836
  const calleeKey = nodeKey2(candidate.func);
10760
10837
  if (!functionByKey.has(calleeKey)) {
10761
10838
  functionByKey.set(calleeKey, candidate);
10762
10839
  }
10840
+ if (!reachedFrom.has(calleeKey)) {
10841
+ reachedFrom.set(calleeKey, source.func.getSourceFile());
10842
+ }
10763
10843
  db.add("calls", [key, calleeKey]);
10764
10844
  }
10765
10845
  }
@@ -13148,7 +13228,8 @@ function createTypeScriptAdapter(config) {
13148
13228
  tallies
13149
13229
  ),
13150
13230
  access: collectAccessRecognizers(config.frameworks, tallies),
13151
- resolveWrittenValue: (value) => resolution.resolveWrittenValue(value)
13231
+ resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13232
+ resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13152
13233
  }
13153
13234
  )
13154
13235
  ) : withSubUnits;
package/dist/index.d.cts CHANGED
@@ -184,7 +184,14 @@ declare class ResolutionStore {
184
184
  private readonly graph;
185
185
  private stale;
186
186
  constructor(wrappers?: TransparentWrapper[]);
187
- resolveCallable(value: Node): Node | null;
187
+ /**
188
+ * `alsoFrom` is a second file to walk out from, for a caller that
189
+ * knows where the value it is asking about was set up. The walk
190
+ * follows imports outward from the value's own file, so a file that
191
+ * imports it, and passed something to the constructor, is somewhere
192
+ * the walk never reaches on its own.
193
+ */
194
+ resolveCallable(value: Node, alsoFrom?: SourceFile): Node | null;
188
195
  resolveObject(value: Node): Node | null;
189
196
  /**
190
197
  * For a value that is neither a function nor an object, such as a
@@ -382,8 +389,8 @@ interface DiscoveredUnit {
382
389
  * shape for decorator-driven controllers).
383
390
  */
384
391
  routeInfo?: {
385
- method: string;
386
- path: string;
392
+ method: string | null;
393
+ path: string | null;
387
394
  };
388
395
  /**
389
396
  * Populated by a pack's `discoverUnits` callback for a message-bus
package/dist/index.d.ts CHANGED
@@ -184,7 +184,14 @@ declare class ResolutionStore {
184
184
  private readonly graph;
185
185
  private stale;
186
186
  constructor(wrappers?: TransparentWrapper[]);
187
- resolveCallable(value: Node): Node | null;
187
+ /**
188
+ * `alsoFrom` is a second file to walk out from, for a caller that
189
+ * knows where the value it is asking about was set up. The walk
190
+ * follows imports outward from the value's own file, so a file that
191
+ * imports it, and passed something to the constructor, is somewhere
192
+ * the walk never reaches on its own.
193
+ */
194
+ resolveCallable(value: Node, alsoFrom?: SourceFile): Node | null;
188
195
  resolveObject(value: Node): Node | null;
189
196
  /**
190
197
  * For a value that is neither a function nor an object, such as a
@@ -382,8 +389,8 @@ interface DiscoveredUnit {
382
389
  * shape for decorator-driven controllers).
383
390
  */
384
391
  routeInfo?: {
385
- method: string;
386
- path: string;
392
+ method: string | null;
393
+ path: string | null;
387
394
  };
388
395
  /**
389
396
  * Populated by a pack's `discoverUnits` callback for a message-bus
package/dist/index.js CHANGED
@@ -3953,6 +3953,7 @@ import { Node as Node18 } from "ts-morph";
3953
3953
  import { Node as Node17, SyntaxKind as SyntaxKind5, VariableDeclarationKind } from "ts-morph";
3954
3954
  var byFile = createPerFileCache();
3955
3955
  var byDeclaration = /* @__PURE__ */ new WeakMap();
3956
+ var byField = /* @__PURE__ */ new WeakMap();
3956
3957
  function writesToBinding(declaration) {
3957
3958
  const remembered = byDeclaration.get(declaration);
3958
3959
  if (remembered !== void 0) {
@@ -3985,22 +3986,19 @@ function writesToBindingUncached(declaration) {
3985
3986
  return { values, inOrder: writesRunInOrder(declaration, writes) };
3986
3987
  }
3987
3988
  function writesToField(declaration) {
3988
- const remembered = byDeclaration.get(declaration);
3989
+ const remembered = byField.get(declaration);
3989
3990
  if (remembered !== void 0) {
3990
3991
  return remembered;
3991
3992
  }
3992
3993
  const answer = writesToFieldUncached(declaration);
3993
- byDeclaration.set(declaration, answer);
3994
+ byField.set(declaration, answer);
3994
3995
  return answer;
3995
3996
  }
3996
3997
  function writesToFieldUncached(declaration) {
3997
- const initializer = declaration.getInitializer();
3998
+ const first = startingValueOf(declaration);
3998
3999
  const assignments = assignmentsTo(declaration);
3999
4000
  if (assignments.length === 0) {
4000
- return {
4001
- values: initializer === void 0 ? [] : [initializer],
4002
- inOrder: true
4003
- };
4001
+ return { values: first === void 0 ? [] : [first], inOrder: true };
4004
4002
  }
4005
4003
  const values = [];
4006
4004
  for (const write of assignments) {
@@ -4009,14 +4007,16 @@ function writesToFieldUncached(declaration) {
4009
4007
  }
4010
4008
  values.push(write.value);
4011
4009
  }
4012
- const withInitializer = initializer === void 0 ? values : [initializer, ...values];
4013
4010
  return {
4014
- values: withInitializer,
4011
+ values: first === void 0 ? values : [first, ...values],
4015
4012
  inOrder: assignments.every(
4016
4013
  (write) => writeRunsInConstructor(declaration, write)
4017
4014
  )
4018
4015
  };
4019
4016
  }
4017
+ function startingValueOf(declaration) {
4018
+ return Node17.isParameterDeclaration(declaration) ? declaration : declaration.getInitializer();
4019
+ }
4020
4020
  function writeRunsInConstructor(declaration, write) {
4021
4021
  const statement = statementOf(write.node);
4022
4022
  if (statement === null) {
@@ -4027,13 +4027,16 @@ function writeRunsInConstructor(declaration, write) {
4027
4027
  return false;
4028
4028
  }
4029
4029
  const owner = body.getParent();
4030
- return owner !== void 0 && Node17.isConstructorDeclaration(owner) && owner.getParent() === declaration.getParent();
4030
+ return owner !== void 0 && Node17.isConstructorDeclaration(owner) && owner.getParent() === classHolding(declaration);
4031
+ }
4032
+ function classHolding(declaration) {
4033
+ return Node17.isParameterDeclaration(declaration) ? declaration.getParent().getParent() : declaration.getParent();
4031
4034
  }
4032
4035
  function isWrittenAgain(declaration) {
4033
4036
  return assignmentsTo(declaration).length > 0;
4034
4037
  }
4035
4038
  function assignmentsTo(declaration) {
4036
- if (Node17.isPropertyDeclaration(declaration)) {
4039
+ if (Node17.isPropertyDeclaration(declaration) || Node17.isParameterDeclaration(declaration)) {
4037
4040
  const sourceFile2 = declaration.getSourceFile();
4038
4041
  return sourceFile2.isDeclarationFile() ? [] : assignmentsInFile(sourceFile2).get(declaration) ?? [];
4039
4042
  }
@@ -4100,7 +4103,9 @@ function declarationsOf(name) {
4100
4103
  }
4101
4104
  const declarations = [];
4102
4105
  for (const declaration of symbol.getDeclarations()) {
4103
- if (Node17.isVariableDeclaration(declaration) || Node17.isPropertyDeclaration(declaration)) {
4106
+ if (Node17.isVariableDeclaration(declaration) || Node17.isPropertyDeclaration(declaration) || // A parameter property is the field's only declaration, so
4107
+ // `this.dao = other` in the constructor writes to it.
4108
+ Node17.isParameterDeclaration(declaration) && declaration.isParameterProperty()) {
4104
4109
  declarations.push(declaration);
4105
4110
  }
4106
4111
  }
@@ -5884,7 +5889,8 @@ function createNodeTable() {
5884
5889
  byId: /* @__PURE__ */ new Map(),
5885
5890
  seenFunctions: /* @__PURE__ */ new Set(),
5886
5891
  seenBindings: /* @__PURE__ */ new Set(),
5887
- seenValues: /* @__PURE__ */ new Set()
5892
+ seenValues: /* @__PURE__ */ new Set(),
5893
+ seenClasses: /* @__PURE__ */ new Set()
5888
5894
  };
5889
5895
  }
5890
5896
  function nodeId(node) {
@@ -6105,7 +6111,7 @@ function emitValue(db, table, value) {
6105
6111
  }
6106
6112
  return id;
6107
6113
  }
6108
- if (Node26.isCallExpression(expression)) {
6114
+ if (Node26.isCallExpression(expression) || Node26.isNewExpression(expression)) {
6109
6115
  emitCallFacts(db, table, expression);
6110
6116
  fact(db, "writtenValue", id);
6111
6117
  return id;
@@ -6132,11 +6138,24 @@ function emitBindingValues(db, table, declaration) {
6132
6138
  function emitFieldValues(db, table, declaration) {
6133
6139
  const { values, inOrder } = writesToField(declaration);
6134
6140
  const last = values[values.length - 1];
6135
- if (!inOrder || last === void 0) {
6141
+ if (!inOrder || last === void 0 || !Node26.isExpression(last)) {
6136
6142
  return;
6137
6143
  }
6138
6144
  fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
6139
6145
  }
6146
+ function emitParameterPropertyRead(db, table, referenceId, declaration) {
6147
+ const { values, inOrder } = writesToField(declaration);
6148
+ const last = values[values.length - 1];
6149
+ if (!inOrder || last === void 0) {
6150
+ return;
6151
+ }
6152
+ if (!Node26.isExpression(last)) {
6153
+ table.byId.set(nodeId(last), last);
6154
+ fact(db, "binds", referenceId, nodeId(last));
6155
+ return;
6156
+ }
6157
+ fact(db, "binds", referenceId, emitValue(db, table, last));
6158
+ }
6140
6159
  function emitBindingElementFacts(db, table, element) {
6141
6160
  const id = nodeId(element);
6142
6161
  table.byId.set(id, element);
@@ -6218,7 +6237,16 @@ function emitReferenceFacts(db, table, reference) {
6218
6237
  continue;
6219
6238
  }
6220
6239
  if (Node26.isParameterDeclaration(declaration)) {
6240
+ if (declaration.isParameterProperty() && Node26.isPropertyAccessExpression(reference)) {
6241
+ emitParameterPropertyRead(db, table, referenceId, declaration);
6242
+ } else {
6243
+ fact(db, "binds", referenceId, declarationId);
6244
+ }
6245
+ continue;
6246
+ }
6247
+ if (Node26.isClassDeclaration(declaration)) {
6221
6248
  fact(db, "binds", referenceId, declarationId);
6249
+ emitClassFacts(db, table, declaration);
6222
6250
  continue;
6223
6251
  }
6224
6252
  if (Node26.isBindingElement(declaration)) {
@@ -6263,9 +6291,14 @@ function emitCallFacts(db, table, call) {
6263
6291
  }
6264
6292
  }
6265
6293
  function emitClassFacts(db, table, declaration) {
6294
+ if (table.seenClasses.has(declaration)) {
6295
+ return;
6296
+ }
6297
+ table.seenClasses.add(declaration);
6266
6298
  const id = nodeId(declaration);
6267
6299
  table.byId.set(id, declaration);
6268
6300
  fact(db, "objectValue", id);
6301
+ emitConstructorParameters(db, table, declaration, id);
6269
6302
  for (const method of declaration.getMethods()) {
6270
6303
  const methodId = nodeId(method);
6271
6304
  table.byId.set(methodId, method);
@@ -6286,6 +6319,25 @@ function emitClassFacts(db, table, declaration) {
6286
6319
  }
6287
6320
  }
6288
6321
  }
6322
+ function emitConstructorParameters(db, table, declaration, classId) {
6323
+ const implementation = declaration.getConstructors().find((candidate) => candidate.getBody() !== void 0);
6324
+ if (implementation === void 0) {
6325
+ return;
6326
+ }
6327
+ emitParameters(db, table, classId, implementation.getParameters());
6328
+ }
6329
+ function emitParameters(db, table, ownerId, parameters) {
6330
+ for (let position = 0; position < parameters.length; position++) {
6331
+ const parameter = parameters[position];
6332
+ if (parameter === void 0) {
6333
+ continue;
6334
+ }
6335
+ const parameterId = nodeId(parameter);
6336
+ table.byId.set(parameterId, parameter);
6337
+ fact(db, "paramOf", ownerId, String(position), parameterId);
6338
+ fact(db, "paramNamed", ownerId, parameter.getName(), parameterId);
6339
+ }
6340
+ }
6289
6341
  function emitFunctionFacts(db, table, fn) {
6290
6342
  if (table.seenFunctions.has(fn)) {
6291
6343
  return;
@@ -6294,17 +6346,7 @@ function emitFunctionFacts(db, table, fn) {
6294
6346
  const fnId = nodeId(fn);
6295
6347
  table.byId.set(fnId, fn);
6296
6348
  if (Node26.isFunctionDeclaration(fn) || Node26.isFunctionExpression(fn) || Node26.isArrowFunction(fn) || Node26.isMethodDeclaration(fn)) {
6297
- const parameters = fn.getParameters();
6298
- for (let position = 0; position < parameters.length; position++) {
6299
- const parameter = parameters[position];
6300
- if (parameter === void 0) {
6301
- continue;
6302
- }
6303
- const parameterId = nodeId(parameter);
6304
- table.byId.set(parameterId, parameter);
6305
- fact(db, "paramOf", fnId, String(position), parameterId);
6306
- fact(db, "paramNamed", fnId, parameter.getName(), parameterId);
6307
- }
6349
+ emitParameters(db, table, fnId, fn.getParameters());
6308
6350
  const body = fn.getBody?.();
6309
6351
  if (body !== void 0) {
6310
6352
  if (Node26.isExpression(body)) {
@@ -10050,9 +10092,21 @@ var ResolutionStore = class {
10050
10092
  this.db.add("wrapperModule", [wrapper.callee, wrapper.module]);
10051
10093
  }
10052
10094
  }
10053
- resolveCallable(value) {
10095
+ /**
10096
+ * `alsoFrom` is a second file to walk out from, for a caller that
10097
+ * knows where the value it is asking about was set up. The walk
10098
+ * follows imports outward from the value's own file, so a file that
10099
+ * imports it, and passed something to the constructor, is somewhere
10100
+ * the walk never reaches on its own.
10101
+ */
10102
+ resolveCallable(value, alsoFrom) {
10054
10103
  const target = factKeyOf(value);
10055
- return this.resolveByWaves(target, "wanted", () => this.lookup(target));
10104
+ return this.resolveByWaves(
10105
+ target,
10106
+ "wanted",
10107
+ () => this.lookup(target),
10108
+ alsoFrom
10109
+ );
10056
10110
  }
10057
10111
  resolveObject(value) {
10058
10112
  const target = factKeyOf(value);
@@ -10120,18 +10174,18 @@ var ResolutionStore = class {
10120
10174
  }
10121
10175
  return origins.some((pair) => namesAPackage(pairHalves(pair).module)) ? [] : null;
10122
10176
  }
10123
- resolveByWaves(value, question, ask) {
10177
+ resolveByWaves(value, question, ask, alsoFrom) {
10124
10178
  try {
10125
- return this.walkForAnswer(value, question, ask);
10179
+ return this.walkForAnswer(value, question, ask, alsoFrom);
10126
10180
  } finally {
10127
10181
  this.forgetQuery();
10128
10182
  }
10129
10183
  }
10130
- walkForAnswer(value, question, ask) {
10184
+ walkForAnswer(value, question, ask, alsoFrom) {
10131
10185
  this.wantValue(question, value);
10132
10186
  this.seedValue(value);
10133
10187
  const walked = /* @__PURE__ */ new Set();
10134
- let frontier = [value.getSourceFile()];
10188
+ let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10135
10189
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10136
10190
  const next = [];
10137
10191
  for (const sourceFile of frontier) {
@@ -10655,24 +10709,41 @@ function declName(decl) {
10655
10709
  }
10656
10710
  return null;
10657
10711
  }
10658
- function resolveCallee(call, calleeName) {
10712
+ function resolveCallee(call, calleeName, scan) {
10659
10713
  if (!Node49.isCallExpression(call)) {
10660
10714
  return null;
10661
10715
  }
10662
10716
  const callee = call.getExpression();
10663
- const symbol = callee.getSymbol();
10664
- if (symbol === void 0) {
10665
- return null;
10666
- }
10667
- for (const decl of symbol.getDeclarations()) {
10717
+ const declarations = callee.getSymbol()?.getDeclarations() ?? [];
10718
+ for (const decl of declarations) {
10668
10719
  const resolved = resolveDecl(decl, calleeName);
10669
10720
  if (resolved !== null) {
10670
10721
  return resolved;
10671
10722
  }
10672
10723
  }
10673
- return null;
10724
+ if (scan.resolveCallable === void 0 || !readsAField(callee)) {
10725
+ return null;
10726
+ }
10727
+ if (!declarations.some(isDeclaredShape)) {
10728
+ return null;
10729
+ }
10730
+ const value = scan.resolveCallable(callee, scan.reachedFrom);
10731
+ return value === null ? null : resolveDecl(value, calleeName);
10674
10732
  }
10675
- function collectReachable(func) {
10733
+ function readsAField(callee) {
10734
+ if (!Node49.isPropertyAccessExpression(callee)) {
10735
+ return false;
10736
+ }
10737
+ let receiver = callee.getExpression();
10738
+ while (Node49.isPropertyAccessExpression(receiver)) {
10739
+ receiver = receiver.getExpression();
10740
+ }
10741
+ return Node49.isThisExpression(receiver);
10742
+ }
10743
+ function isDeclaredShape(declaration) {
10744
+ return (Node49.isMethodSignature(declaration) || Node49.isPropertySignature(declaration)) && !isInExternalCode(declaration.getSourceFile());
10745
+ }
10746
+ function collectReachable(func, scan) {
10676
10747
  const found = [];
10677
10748
  const seen = /* @__PURE__ */ new Set();
10678
10749
  func.forEachDescendant((node) => {
@@ -10680,7 +10751,7 @@ function collectReachable(func) {
10680
10751
  return;
10681
10752
  }
10682
10753
  const calleeText = node.getExpression().getText();
10683
- const resolved = resolveCallee(node, calleeText);
10754
+ const resolved = resolveCallee(node, calleeText, scan);
10684
10755
  if (resolved === null) {
10685
10756
  return;
10686
10757
  }
@@ -10731,6 +10802,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10731
10802
  const functionByKey = /* @__PURE__ */ new Map();
10732
10803
  const seedKeys = /* @__PURE__ */ new Set();
10733
10804
  const scanned = /* @__PURE__ */ new Set();
10805
+ const reachedFrom = /* @__PURE__ */ new Map();
10734
10806
  for (const seed of seeds) {
10735
10807
  const func = lookup.functionAt(seed.location);
10736
10808
  if (func !== null) {
@@ -10753,11 +10825,19 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10753
10825
  if (source === void 0) {
10754
10826
  continue;
10755
10827
  }
10756
- for (const candidate of collectReachable(source.func)) {
10828
+ const cameFrom = reachedFrom.get(key);
10829
+ const scan = {
10830
+ ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
10831
+ ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10832
+ };
10833
+ for (const candidate of collectReachable(source.func, scan)) {
10757
10834
  const calleeKey = nodeKey2(candidate.func);
10758
10835
  if (!functionByKey.has(calleeKey)) {
10759
10836
  functionByKey.set(calleeKey, candidate);
10760
10837
  }
10838
+ if (!reachedFrom.has(calleeKey)) {
10839
+ reachedFrom.set(calleeKey, source.func.getSourceFile());
10840
+ }
10761
10841
  db.add("calls", [key, calleeKey]);
10762
10842
  }
10763
10843
  }
@@ -13152,7 +13232,8 @@ function createTypeScriptAdapter(config) {
13152
13232
  tallies
13153
13233
  ),
13154
13234
  access: collectAccessRecognizers(config.frameworks, tallies),
13155
- resolveWrittenValue: (value) => resolution.resolveWrittenValue(value)
13235
+ resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13236
+ resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13156
13237
  }
13157
13238
  )
13158
13239
  ) : withSubUnits;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-typescript",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "TypeScript adapter for suss — walks source files with ts-morph and emits raw extractions for the suss pipeline.",
6
6
  "keywords": [
@@ -46,10 +46,10 @@
46
46
  "typecheck": "tsc --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@suss/behavioral-ir": "0.7.1",
50
- "@suss/datalog": "0.7.1",
51
- "@suss/extractor": "0.7.1",
52
- "@suss/resolution": "0.7.1",
49
+ "@suss/behavioral-ir": "0.8.0",
50
+ "@suss/datalog": "0.8.0",
51
+ "@suss/extractor": "0.8.0",
52
+ "@suss/resolution": "0.8.0",
53
53
  "graphql": "^16.8.0",
54
54
  "picomatch": "^4.0.4",
55
55
  "ts-morph": "^23.0.0"