carrick 0.3.124 → 0.3.126

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.
Files changed (29) hide show
  1. package/package.json +6 -6
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/sidecar/dist/src/capture/api.d.ts +5 -1
  4. package/sidecar/dist/src/capture/check-classify.js +11 -5
  5. package/sidecar/dist/src/capture/check.js +8 -1
  6. package/sidecar/dist/src/capture/deep-walk.d.ts +31 -0
  7. package/sidecar/dist/src/capture/deep-walk.js +43 -1
  8. package/sidecar/dist/src/capture/deno-project.d.ts +7 -0
  9. package/sidecar/dist/src/capture/deno-project.js +7 -0
  10. package/sidecar/dist/src/capture/index.d.ts +1 -1
  11. package/sidecar/dist/src/capture/index.js +1 -1
  12. package/sidecar/dist/src/capture/service-config.d.ts +11 -0
  13. package/sidecar/dist/src/capture/service-config.js +40 -0
  14. package/sidecar/dist/src/declaration-attribution.d.ts +11 -0
  15. package/sidecar/dist/src/declaration-attribution.js +16 -0
  16. package/sidecar/dist/src/implementations.d.ts +43 -0
  17. package/sidecar/dist/src/implementations.js +224 -0
  18. package/sidecar/dist/src/index.js +35 -1
  19. package/sidecar/dist/src/library-claims.d.ts +17 -2
  20. package/sidecar/dist/src/library-claims.js +242 -47
  21. package/sidecar/dist/src/position-rows.d.ts +119 -1
  22. package/sidecar/dist/src/position-rows.js +163 -9
  23. package/sidecar/dist/src/project-loader.d.ts +31 -0
  24. package/sidecar/dist/src/project-loader.js +67 -4
  25. package/sidecar/dist/src/type-inferrer.d.ts +150 -16
  26. package/sidecar/dist/src/type-inferrer.js +651 -94
  27. package/sidecar/dist/src/types.d.ts +46 -2
  28. package/sidecar/dist/src/validators.d.ts +73 -0
  29. package/sidecar/dist/src/validators.js +10 -0
@@ -30,6 +30,7 @@ import { PROGRESS_INTERVAL_MS, atMostEvery } from './progress.js';
30
30
  import { mergeInferTimings } from './infer-timing.js';
31
31
  import { addProgramFiles, listProgramFiles } from './program-files.js';
32
32
  import { writePositionRows } from './position-rows.js';
33
+ import { findImplementations } from './implementations.js';
33
34
  // ===========================================================================
34
35
  // Module-level state
35
36
  // ===========================================================================
@@ -90,7 +91,10 @@ function projectComponents(key = '') {
90
91
  // (`npm/carrick/package.json`) resolves `typescript ^5.8.0` unpinned,
91
92
  // so an install can pair ts-morph's copy with a newer 5.x.
92
93
  retyper: new Retyper(project, typeInferrer, jsonWireDeclarations, findDisqualifyingTopTypes),
93
- claimsVerifier: new LibraryClaimsVerifier(project),
94
+ claimsVerifier: new LibraryClaimsVerifier(project, {
95
+ packageDirectoryOf: (file) => loader.packageDirectoryOf(file),
96
+ resolveModule: (name, from, options) => loader.resolveThroughGraph(name, from, options),
97
+ }),
94
98
  };
95
99
  components.set(key, built);
96
100
  }
@@ -556,6 +560,34 @@ function handleAddProgramFiles(request) {
556
560
  return { request_id: request.request_id, status: 'error', errors: [error] };
557
561
  }
558
562
  }
563
+ /**
564
+ * Handle the 'implementations' action - the implementations of the bodyless
565
+ * declaration each call goes through (carrick#2432). Asked of the analysis
566
+ * process: it reads the programs the service's other actions read, and
567
+ * releases none of them.
568
+ */
569
+ function handleImplementations(request) {
570
+ try {
571
+ const loader = initializedLoader();
572
+ let done = 0;
573
+ const report = atMostEvery(PROGRESS_INTERVAL_MS, () => writeProgress(request.request_id, 'implementations', `${done} of ${request.queries.length}`));
574
+ const answers = findImplementations({
575
+ loader,
576
+ scanRoot: request.scan_root,
577
+ queries: request.queries,
578
+ onAnswer: () => {
579
+ done += 1;
580
+ report();
581
+ },
582
+ });
583
+ return { request_id: request.request_id, status: 'success', result: { answers } };
584
+ }
585
+ catch (err) {
586
+ const error = err instanceof Error ? err.message : String(err);
587
+ logError(`Implementations failed: ${error}`);
588
+ return { request_id: request.request_id, status: 'error', errors: [error] };
589
+ }
590
+ }
559
591
  /**
560
592
  * Handle the 'position_rows' action - write the compiler's rows for each
561
593
  * call, member access, comparison and identifier of the given files
@@ -648,6 +680,8 @@ function handleRequest(request) {
648
680
  return handleAddProgramFiles(request);
649
681
  case 'position_rows':
650
682
  return handlePositionRows(request);
683
+ case 'implementations':
684
+ return handleImplementations(request);
651
685
  case 'health':
652
686
  return handleHealth(request);
653
687
  case 'shutdown':
@@ -35,7 +35,8 @@
35
35
  * package's: a `paths` alias or a `declare module` block the service writes
36
36
  * for itself is not evidence about the library.
37
37
  */
38
- import { type Project } from 'ts-morph';
38
+ import { ts, type Project } from 'ts-morph';
39
+ import { type PackageDirectoryOf } from './position-rows.js';
39
40
  import type { LibraryCheck, LibrarySurface, SemanticsCheck, SemanticsModule, SemanticsResult } from './types.js';
40
41
  export interface VerifyResult {
41
42
  semantics: SemanticsResult[];
@@ -54,9 +55,23 @@ export interface VerifyResult {
54
55
  * options at argument 1; `request_body` adds the body key there.
55
56
  */
56
57
  export declare function httpCheck(check: SemanticsCheck): LibraryCheck;
58
+ /**
59
+ * What a project's module graph says, where it resolved through one rather
60
+ * than through `node_modules` (carrick#2599). Such a resolver serves a package
61
+ * from a cache whose path has no `node_modules` segment, so neither the path
62
+ * nor a Node resolution of the package name finds the files the program
63
+ * loaded.
64
+ */
65
+ export interface ModuleGraph {
66
+ /** The installed package directory a file lies in, and its name. */
67
+ packageDirectoryOf: PackageDirectoryOf;
68
+ /** How the program resolves `name` from `from`; `undefined` where there is no graph, and Node resolution answers. */
69
+ resolveModule(name: string, from: string, options: ts.CompilerOptions): ts.ResolvedModuleFull | undefined;
70
+ }
57
71
  export declare class LibraryClaimsVerifier {
58
72
  private readonly project;
59
- constructor(project: Project);
73
+ private readonly graph;
74
+ constructor(project: Project, graph?: ModuleGraph);
60
75
  /**
61
76
  * Judge every check, in request order, spending at most `budgetMs`. Checks
62
77
  * the budget does not reach come back `unchecked` with reason `budget`.
@@ -40,6 +40,7 @@ import * as fs from 'node:fs';
40
40
  import * as path from 'node:path';
41
41
  import { ts } from 'ts-morph';
42
42
  import { PathNames, declaringFileName } from './position-rows.js';
43
+ import { memberDeclaration } from './declaration-attribution.js';
43
44
  /** The methods a `verb` claim may name, and a method key may accept. */
44
45
  const HTTP_METHODS = new Set([
45
46
  'GET',
@@ -248,10 +249,13 @@ function sortedDeclarations(declarations) {
248
249
  return [...seen.values()].sort((a, b) => (a.decl_file < b.decl_file ? -1 : a.decl_file > b.decl_file ? 1 : a.decl_line - b.decl_line));
249
250
  }
250
251
  let probeSequence = 0;
252
+ const NO_MODULE_GRAPH = { packageDirectoryOf: () => undefined, resolveModule: () => undefined };
251
253
  export class LibraryClaimsVerifier {
252
254
  project;
253
- constructor(project) {
255
+ graph;
256
+ constructor(project, graph = NO_MODULE_GRAPH) {
254
257
  this.project = project;
258
+ this.graph = graph;
255
259
  }
256
260
  /**
257
261
  * Judge every check, in request order, spending at most `budgetMs`. Checks
@@ -335,7 +339,19 @@ export class LibraryClaimsVerifier {
335
339
  builtIndex.set(key, importLines.length + builtLines.length);
336
340
  builtLines.push(`${expression};`);
337
341
  }
338
- const probeText = [...importLines, ...builtLines].join('\n');
342
+ // A `node:` key may name a file of the runtime's types, whose exports are
343
+ // globals (`runtimeGlobalFile`): the probe reads each as `globalThis` holds
344
+ // it, beside its import, and the reader takes the one the key resolves to.
345
+ const globalIndex = new Map();
346
+ const globalLines = [];
347
+ importKeys.forEach((key, i) => {
348
+ const [pkg, name] = JSON.parse(key);
349
+ if (!RUNTIME_MODULE.test(pkg))
350
+ return;
351
+ globalIndex.set(key, importLines.length + builtLines.length + globalLines.length);
352
+ globalLines.push(globalRead(name, `__carrick_g${i}`));
353
+ });
354
+ const probeText = [...importLines, ...builtLines, ...globalLines].join('\n');
339
355
  const probePath = path.join(fromDir, `__carrick_claims_probe_${process.pid}_${probeSequence++}.ts`);
340
356
  const probe = this.project.createSourceFile(probePath, `${probeText}\n`, { overwrite: true });
341
357
  try {
@@ -344,7 +360,7 @@ export class LibraryClaimsVerifier {
344
360
  const file = program.getSourceFile(probe.getFilePath());
345
361
  if (!file)
346
362
  throw new Error(`probe file ${probePath} is not in the program`);
347
- const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir, scanRoot);
363
+ const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir, scanRoot, this.graph);
348
364
  const localRanges = readLocalRanges(fromDir);
349
365
  const moduleReads = new Map();
350
366
  const exportReads = new Map();
@@ -352,6 +368,7 @@ export class LibraryClaimsVerifier {
352
368
  const judged = new Map();
353
369
  /** The probe's import of the check's export, or its read of a global (`probeLine`). */
354
370
  const statementOf = (check) => file.statements[importIndex.get(exportKey(check))];
371
+ const globalStatementOf = (check) => file.statements[globalIndex.get(exportKey(check)) ?? -1];
355
372
  /** The probe's no-argument build of `receiver` on the check's export, resolved. */
356
373
  const atDefaults = (check, receiver) => {
357
374
  const index = builtIndex.get(builtKey(check, receiver));
@@ -373,9 +390,12 @@ export class LibraryClaimsVerifier {
373
390
  const key = exportKey(check);
374
391
  let exportRead = exportReads.get(key);
375
392
  if (!exportRead) {
393
+ const globalFile = RUNTIME_MODULE.test(check.package) ? readModule(check, rulesFor(check)?.message ?? false).globalFile : undefined;
376
394
  exportRead = DEFAULT_LIBRARY.test(check.package)
377
- ? reader.readGlobal(check.package, statementOf(check))
378
- : reader.readExport(statementOf(check));
395
+ ? reader.readGlobal(reader.defaultLibraryFile(check.package), statementOf(check))
396
+ : globalFile
397
+ ? reader.readGlobal(globalFile, globalStatementOf(check))
398
+ : reader.readExport(statementOf(check));
379
399
  exportReads.set(key, exportRead);
380
400
  }
381
401
  return exportRead;
@@ -621,7 +641,7 @@ export class LibraryClaimsVerifier {
621
641
  const file = program.getSourceFile(probe.getFilePath());
622
642
  if (!file)
623
643
  throw new Error(`probe file ${probePath} is not in the program`);
624
- const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir);
644
+ const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir, undefined, this.graph);
625
645
  return read(program.getTypeChecker(), reader, file);
626
646
  }
627
647
  finally {
@@ -809,8 +829,10 @@ function typeSymbols(type) {
809
829
  * declares one.
810
830
  */
811
831
  function probeLine(pkg, name, local) {
812
- if (!DEFAULT_LIBRARY.test(pkg))
813
- return importLine(pkg, name, local);
832
+ return DEFAULT_LIBRARY.test(pkg) ? globalRead(name, local) : importLine(pkg, name, local);
833
+ }
834
+ /** The probe's read of the global `name`, bound to `local`. */
835
+ function globalRead(name, local) {
814
836
  const access = IDENTIFIER.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
815
837
  return `const ${local} = globalThis${access};`;
816
838
  }
@@ -869,6 +891,7 @@ class DeclarationReader {
869
891
  program;
870
892
  probe;
871
893
  host;
894
+ graph;
872
895
  checker;
873
896
  /** Names declaring files as position rows do: from the scan root, else the service root. */
874
897
  pathNames;
@@ -884,12 +907,13 @@ class DeclarationReader {
884
907
  declared = new Map();
885
908
  /** The service root as given and as a realpath, longest first: what a listing prints as `<root>`. */
886
909
  printedRoots;
887
- constructor(program, probe, host, serviceRoot, scanRoot) {
910
+ constructor(program, probe, host, serviceRoot, scanRoot, graph) {
888
911
  this.program = program;
889
912
  this.probe = probe;
890
913
  this.host = host;
914
+ this.graph = graph;
891
915
  this.checker = program.getTypeChecker();
892
- this.pathNames = new PathNames(scanRoot ?? serviceRoot);
916
+ this.pathNames = new PathNames(scanRoot ?? serviceRoot, graph.packageDirectoryOf);
893
917
  this.root = this.realpath(serviceRoot);
894
918
  this.printedRoots = [...new Set([this.root, serviceRoot])]
895
919
  .filter(root => root.length > 1)
@@ -931,11 +955,20 @@ class DeclarationReader {
931
955
  */
932
956
  readModule(pkg, declaration, runtimeModules = false) {
933
957
  const specifier = declaration.moduleSpecifier;
934
- const resolved = ts.resolveModuleName(pkg, this.probe.fileName, this.program.getCompilerOptions(), this.host, undefined, undefined, this.program.getModeForUsageLocation(this.probe, specifier)).resolvedModule;
958
+ const resolved = this.graph.resolveModule(pkg, this.probe.fileName, this.program.getCompilerOptions()) ??
959
+ ts.resolveModuleName(pkg, this.probe.fileName, this.program.getCompilerOptions(), this.host, undefined, undefined, this.program.getModeForUsageLocation(this.probe, specifier)).resolvedModule;
935
960
  const entry = { package: pkg };
936
961
  const done = (reason) => reason ? { entry: { ...entry, reason }, reason } : { entry };
937
962
  const moduleSymbol = this.checker.getSymbolAtLocation(specifier);
938
963
  const declarations = moduleSymbol?.declarations ?? [];
964
+ const globalFile = declarations.length === 0 ? this.runtimeGlobalFile(pkg) : undefined;
965
+ if (globalFile) {
966
+ entry.resolved_file = globalFile.fileName;
967
+ const version = this.installedPackage(globalFile.fileName)?.version;
968
+ if (version)
969
+ entry.installed_version = version;
970
+ return { entry, globalFile };
971
+ }
939
972
  if (runtimeModules && RUNTIME_MODULE.test(pkg))
940
973
  return this.readRuntimeModule(declarations, entry, done);
941
974
  // The module itself, not a service-side augmentation of it.
@@ -1029,6 +1062,27 @@ class DeclarationReader {
1029
1062
  entry.resolved_file = file.fileName;
1030
1063
  return { entry };
1031
1064
  }
1065
+ /**
1066
+ * The runtime types' file a `node:<path>` key names, when no module has
1067
+ * that name (carrick#2445): `node:web-globals/fetch` is the file
1068
+ * `web-globals/fetch.d.ts` (or `.../index.d.ts`) of the installed runtime
1069
+ * types package, which declares globals. Undefined when the program loads
1070
+ * no such file; the key is then a module name, read as one.
1071
+ */
1072
+ runtimeGlobalFile(pkg) {
1073
+ if (!RUNTIME_MODULE.test(pkg))
1074
+ return undefined;
1075
+ const relative = pkg.slice('node:'.length);
1076
+ return this.program.getSourceFiles().find(file => {
1077
+ const owner = this.packageOf(file);
1078
+ if (owner === undefined || !RUNTIME_TYPE_PACKAGES.has(owner))
1079
+ return false;
1080
+ const name = this.realpath(file.fileName).split(path.sep).join('/');
1081
+ const marker = `/node_modules/${owner}/`;
1082
+ const inside = name.slice(name.lastIndexOf(marker) + marker.length);
1083
+ return inside === `${relative}.d.ts` || inside === `${relative}/index.d.ts`;
1084
+ });
1085
+ }
1032
1086
  /** The default-library file a `lib:` key names, when the program loads it. */
1033
1087
  defaultLibraryFile(pkg) {
1034
1088
  const name = pkg.slice('lib:'.length);
@@ -1042,7 +1096,7 @@ class DeclarationReader {
1042
1096
  * key's lib file declares; one only another file declares (the runtime's
1043
1097
  * types, the service) is missing from it.
1044
1098
  */
1045
- readGlobal(pkg, statement) {
1099
+ readGlobal(file, statement) {
1046
1100
  const declaration = statement && ts.isVariableStatement(statement) ? statement.declarationList.declarations[0] : undefined;
1047
1101
  const access = declaration?.initializer;
1048
1102
  const name = access && ts.isPropertyAccessExpression(access)
@@ -1051,7 +1105,6 @@ class DeclarationReader {
1051
1105
  ? access.argumentExpression
1052
1106
  : undefined;
1053
1107
  const target = name ? this.checker.getSymbolAtLocation(name) : undefined;
1054
- const file = this.defaultLibraryFile(pkg);
1055
1108
  if (!declaration ||
1056
1109
  !target ||
1057
1110
  !file ||
@@ -1552,8 +1605,12 @@ class DeclarationReader {
1552
1605
  else
1553
1606
  best = furthest(best, result.rank, result.outcome);
1554
1607
  }
1555
- if (holding.length > 0)
1556
- return binds ? { outcome: VERIFIED } : { outcome: VERIFIED, holding };
1608
+ if (holding.length > 0) {
1609
+ // The member's own declarations, as a verified op carries them (#2636).
1610
+ const declarations = this.declarationsOf({ signatures: holding, named: callee.named });
1611
+ const outcome = declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
1612
+ return binds ? { outcome } : { outcome, holding };
1613
+ }
1557
1614
  return { outcome: best.outcome };
1558
1615
  }
1559
1616
  /**
@@ -1583,8 +1640,107 @@ class DeclarationReader {
1583
1640
  if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
1584
1641
  return unchecked('mounts_untyped');
1585
1642
  }
1643
+ const erased = this.fitsErased(receiver.type, slot, signature.typeParameters ?? []);
1644
+ if (erased !== undefined)
1645
+ return erased ? undefined : failed('mounts_not_instance');
1586
1646
  return this.checker.isTypeAssignableTo(receiver.type, slot) ? undefined : failed('mounts_not_instance');
1587
1647
  }
1648
+ /**
1649
+ * `receiver` fits `slot` with the signature's own type parameters
1650
+ * (`own`) erased to their constraints (carrick#2652), or undefined when
1651
+ * that reading does not apply. The receiver is built at its defaults, so a
1652
+ * parameter typed `Tree<SubEnv, SubBase>` has free arguments that no default
1653
+ * instance is assignable to. It applies when `slot` and `receiver` are
1654
+ * references to the same generic type and no argument mixes an own type
1655
+ * parameter into a larger type: an argument that is an own type parameter
1656
+ * takes any receiver argument its constraint takes (none: any), every other
1657
+ * argument must be the receiver's.
1658
+ */
1659
+ fitsErased(receiver, slot, own) {
1660
+ if (own.length === 0)
1661
+ return undefined;
1662
+ const from = this.referenceOf(receiver);
1663
+ const to = this.referenceOf(slot);
1664
+ if (from === undefined || to === undefined)
1665
+ return undefined;
1666
+ const count = (to.target.typeParameters ?? []).length;
1667
+ const given = this.argumentsAs(from.target, this.checker.getTypeArguments(from), to.target);
1668
+ const wanted = this.checker.getTypeArguments(to);
1669
+ if (count === 0 || given === undefined || given.length < count || wanted.length < count)
1670
+ return undefined;
1671
+ for (let i = 0; i < count; i++) {
1672
+ if (own.includes(wanted[i])) {
1673
+ const constraint = this.checker.getBaseConstraintOfType(wanted[i]);
1674
+ if (constraint === undefined || this.isOpenTop(constraint))
1675
+ continue;
1676
+ if (own.some(parameter => this.mentions(constraint, parameter)))
1677
+ return undefined;
1678
+ if (!this.checker.isTypeAssignableTo(given[i], constraint))
1679
+ return false;
1680
+ }
1681
+ else if (own.some(parameter => this.mentions(wanted[i], parameter))) {
1682
+ return undefined;
1683
+ }
1684
+ else if (given[i] !== wanted[i] &&
1685
+ !(this.checker.isTypeAssignableTo(given[i], wanted[i]) && this.checker.isTypeAssignableTo(wanted[i], given[i]))) {
1686
+ return false;
1687
+ }
1688
+ }
1689
+ return true;
1690
+ }
1691
+ /**
1692
+ * The type arguments the instance `current<args>` has as an instance of
1693
+ * `target`: its own when it is one, else through the base types its class
1694
+ * extends, where each base argument is a type parameter of the class
1695
+ * (replaced by the matching argument) or names none. Undefined when
1696
+ * `target` is not reached that way.
1697
+ */
1698
+ argumentsAs(current, args, target, depth = 0) {
1699
+ if (current === target)
1700
+ return args;
1701
+ if (depth > 6)
1702
+ return undefined;
1703
+ const parameters = current.typeParameters ?? [];
1704
+ for (const base of this.checker.getBaseTypes(current)) {
1705
+ const reference = this.referenceOf(base);
1706
+ if (reference === undefined)
1707
+ continue;
1708
+ const mapped = [];
1709
+ let usable = true;
1710
+ for (const argument of this.checker.getTypeArguments(reference)) {
1711
+ const index = parameters.indexOf(argument);
1712
+ if (index >= 0)
1713
+ mapped.push(args[index]);
1714
+ else if (parameters.some(parameter => this.mentions(argument, parameter)))
1715
+ usable = false;
1716
+ else
1717
+ mapped.push(argument);
1718
+ }
1719
+ const found = usable ? this.argumentsAs(reference.target, mapped, target, depth + 1) : undefined;
1720
+ if (found !== undefined)
1721
+ return found;
1722
+ }
1723
+ return undefined;
1724
+ }
1725
+ referenceOf(type) {
1726
+ if (!(type.flags & ts.TypeFlags.Object))
1727
+ return undefined;
1728
+ return type.objectFlags & ts.ObjectFlags.Reference ? type : undefined;
1729
+ }
1730
+ /** `type` names `parameter` anywhere in its arguments or members of a union or intersection. */
1731
+ mentions(type, parameter, depth = 0) {
1732
+ if (type === parameter)
1733
+ return true;
1734
+ if (depth > 6)
1735
+ return true;
1736
+ if (type.isUnionOrIntersection())
1737
+ return type.types.some(part => this.mentions(part, parameter, depth + 1));
1738
+ const reference = this.referenceOf(type);
1739
+ if (reference !== undefined) {
1740
+ return this.checker.getTypeArguments(reference).some(argument => this.mentions(argument, parameter, depth + 1));
1741
+ }
1742
+ return false;
1743
+ }
1588
1744
  /**
1589
1745
  * A send, receive or request op: the home-declared callable member (after
1590
1746
  * `path`; or the receiver itself, when null) with some overload that takes
@@ -1668,19 +1824,25 @@ class DeclarationReader {
1668
1824
  return best.outcome;
1669
1825
  if (boundRuns)
1670
1826
  return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
1671
- const declarations = this.declarationsOf(callee.signatures);
1827
+ const declarations = this.declarationsOf(callee);
1672
1828
  return declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
1673
1829
  }
1674
1830
  /**
1675
- * Where the signatures a verified op resolved to are declared, as position
1676
- * rows spell it (`decl_file`, `decl_line`): the member on this receiver, so
1677
- * two members of one name on two types give two answers. A declaration no
1678
- * row could name is left out.
1831
+ * Where a verified op or scope's member is declared, as position rows spell
1832
+ * it (`decl_file`, `decl_line`): the declaration the call row names for the
1833
+ * member symbol on this receiver ({@link memberDeclaration}), so two members
1834
+ * of one name on two types give two answers, and a property typed by an
1835
+ * interface with call signatures names the property, not the signatures
1836
+ * (carrick#2654). A callee that is the receiver itself has no member symbol
1837
+ * and names its signatures' declarations. A declaration no row could name is
1838
+ * left out.
1679
1839
  */
1680
- declarationsOf(signatures) {
1840
+ declarationsOf(callee) {
1681
1841
  const found = [];
1682
- for (const signature of signatures) {
1683
- const declaration = signature.getDeclaration();
1842
+ const named = callee.named
1843
+ ? [memberDeclaration(() => this.program, callee.named.property, callee.named.holder)]
1844
+ : callee.signatures.map(signature => signature.getDeclaration());
1845
+ for (const declaration of named) {
1684
1846
  if (!declaration)
1685
1847
  continue;
1686
1848
  const file = declaration.getSourceFile();
@@ -2394,6 +2556,9 @@ class DeclarationReader {
2394
2556
  */
2395
2557
  ownCallee(receiver, hops, member, form) {
2396
2558
  let holder = receiver;
2559
+ // The last member the path reaches, with the type it is read on: the
2560
+ // symbol the call row names (carrick#2654).
2561
+ let named;
2397
2562
  for (const hop of hops) {
2398
2563
  const step = this.ownMember(holder, hop);
2399
2564
  if ('failure' in step)
@@ -2401,6 +2566,7 @@ class DeclarationReader {
2401
2566
  const type = step.type;
2402
2567
  if (this.saysNothing(type))
2403
2568
  return { failure: unchecked('member_untyped') };
2569
+ named = { property: step.property, holder: holder.type };
2404
2570
  holder = { type, home: this.homeOf(receiver.pkg, typeSymbols(type), holder.home) };
2405
2571
  }
2406
2572
  let type = holder.type;
@@ -2412,6 +2578,7 @@ class DeclarationReader {
2412
2578
  return step;
2413
2579
  type = step.type;
2414
2580
  boundBase = step.bound;
2581
+ named = { property: step.property, holder: holder.type };
2415
2582
  }
2416
2583
  const all = form === 'new'
2417
2584
  ? this.checker.getNonNullableType(type).getConstructSignatures()
@@ -2425,7 +2592,7 @@ class DeclarationReader {
2425
2592
  ? library
2426
2593
  : library.filter(signature => this.isOwnFile(this.signatureFile(signature, type, form), home));
2427
2594
  if (own.length > 0)
2428
- return { signatures: own, holder: holder.type };
2595
+ return { signatures: own, holder: holder.type, named };
2429
2596
  if (library.length > 0)
2430
2597
  return { failure: failed('member_inherited') };
2431
2598
  return { failure: this.slotFailure(type, form === 'new' ? 'member_not_constructible' : 'member_not_callable') };
@@ -2442,9 +2609,9 @@ class DeclarationReader {
2442
2609
  return { failure: failed('member_missing') };
2443
2610
  const type = this.checker.getTypeOfSymbol(property);
2444
2611
  if (this.isOwnMember(property, apparent, holder.home))
2445
- return { type, bound: false };
2612
+ return { type, bound: false, property };
2446
2613
  if (this.isBoundByOwnType(property, holder))
2447
- return { type, bound: true };
2614
+ return { type, bound: true, property };
2448
2615
  return { failure: failed('member_inherited') };
2449
2616
  }
2450
2617
  /**
@@ -2539,7 +2706,7 @@ class DeclarationReader {
2539
2706
  surface.installed_version = moduleRead.entry.installed_version;
2540
2707
  if (moduleRead.reason)
2541
2708
  return { ...surface, reason: moduleRead.reason };
2542
- const exported = this.listedExports(probe, only);
2709
+ const exported = this.listedExports(probe, only, moduleRead.globalFile);
2543
2710
  if (!exported)
2544
2711
  return { ...surface, reason: 'module_unresolved' };
2545
2712
  let budget = maxEntries;
@@ -2555,6 +2722,9 @@ class DeclarationReader {
2555
2722
  // Three passes: every export and receiver, then every member name, then signatures.
2556
2723
  const names = [];
2557
2724
  const fills = [];
2725
+ // What the package's default export names: a maker also exported by name
2726
+ // under this declaration is imported the same way, so it is read the same.
2727
+ const defaultTarget = exported.find(each => each.name === 'default')?.target;
2558
2728
  for (const { name, target, type } of exported) {
2559
2729
  if (!take())
2560
2730
  continue;
@@ -2570,7 +2740,7 @@ class DeclarationReader {
2570
2740
  };
2571
2741
  receiverOf('export', type);
2572
2742
  for (const maker of this.makers(pkg, target, type)) {
2573
- const made = this.madeBy(maker.signatures, built(name, maker.receiver));
2743
+ const made = this.madeBy(maker.signatures, built(name, maker.receiver), target === defaultTarget);
2574
2744
  if (!made)
2575
2745
  continue;
2576
2746
  // One level below the export, what the member builds must declare a callable member.
@@ -2594,9 +2764,10 @@ class DeclarationReader {
2594
2764
  */
2595
2765
  makersOf(pkg, probe, only) {
2596
2766
  this.setPackage(pkg);
2597
- if ('namespace' in probe && this.readModule(pkg, probe.namespace, true).reason)
2767
+ const moduleRead = 'namespace' in probe ? this.readModule(pkg, probe.namespace, true) : undefined;
2768
+ if (moduleRead?.reason)
2598
2769
  return [];
2599
- return (this.listedExports(probe, only) ?? [])
2770
+ return (this.listedExports(probe, only, moduleRead?.globalFile) ?? [])
2600
2771
  .filter(({ type }) => !this.isOpenTop(type))
2601
2772
  .map(({ name, target, type }) => ({
2602
2773
  export: name,
@@ -2613,9 +2784,11 @@ class DeclarationReader {
2613
2784
  * `prototype`, which no service imports. Undefined when the specifier
2614
2785
  * resolves to no module symbol.
2615
2786
  */
2616
- listedExports(probe, only) {
2787
+ listedExports(probe, only, globalFile) {
2617
2788
  if ('defaultLibrary' in probe)
2618
- return this.listedGlobals(probe.defaultLibrary, only);
2789
+ return this.listedGlobals(this.defaultLibraryFile(probe.defaultLibrary), only);
2790
+ if (globalFile)
2791
+ return this.listedGlobals(globalFile, only);
2619
2792
  const moduleSymbol = this.checker.getSymbolAtLocation(probe.namespace.moduleSpecifier);
2620
2793
  if (!moduleSymbol)
2621
2794
  return undefined;
@@ -2646,8 +2819,7 @@ class DeclarationReader {
2646
2819
  * `only` names), sorted by name, each with its declared type: none when the
2647
2820
  * program's `lib` setting does not load the file.
2648
2821
  */
2649
- listedGlobals(pkg, only) {
2650
- const file = this.defaultLibraryFile(pkg);
2822
+ listedGlobals(file, only) {
2651
2823
  if (!file)
2652
2824
  return [];
2653
2825
  return this.checker
@@ -2694,13 +2866,25 @@ class DeclarationReader {
2694
2866
  * type-parameter defaults where `built` (the probe's no-argument build of
2695
2867
  * the receiver) instantiates it (`returnOf`), less the returns that say
2696
2868
  * nothing, which no maker claim holds on. Listed when what is left is one
2697
- * object type.
2698
- */
2699
- madeBy(signatures, built) {
2869
+ * object type. A `default` export, which a service imports under any local
2870
+ * name (usually the one maker a framework has), or the same declaration
2871
+ * exported by name (carrick#2640), that is overloaded and so
2872
+ * returns several types is listed for the return of the overload the
2873
+ * probe's no-argument build resolves (`built`), when that is an object type
2874
+ * with members of its own.
2875
+ */
2876
+ madeBy(signatures, built, isDefault) {
2700
2877
  const returns = [
2701
2878
  ...new Set(signatures.map(sig => this.returnOf(sig, built)).filter(type => !this.returnSaysNothing(type))),
2702
2879
  ];
2703
- return returns.length === 1 && this.isObjectType(returns[0]) ? returns[0] : undefined;
2880
+ if (returns.length === 1)
2881
+ return this.isObjectType(returns[0]) ? returns[0] : undefined;
2882
+ if (!isDefault || returns.length === 0 || built === undefined)
2883
+ return undefined;
2884
+ const resolved = this.checker.getReturnTypeOfSignature(built);
2885
+ return !this.returnSaysNothing(resolved) && this.isObjectType(resolved) && this.namedProperties(resolved).length > 0
2886
+ ? resolved
2887
+ : undefined;
2704
2888
  }
2705
2889
  /** The call (or construct) signatures of `type` an installed package or the default library declares. */
2706
2890
  librarySignatures(type, form) {
@@ -3108,23 +3292,34 @@ class DeclarationReader {
3108
3292
  * source file in a directory that happens to be named `node_modules`, or a
3109
3293
  * repository checked out under a `node_modules` ancestor, is not installed.
3110
3294
  * An install hoisted above the service root (`../../node_modules/pkg`) and
3111
- * a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are.
3295
+ * a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are. A path
3296
+ * with no `node_modules` segment is placed by the module graph, where there
3297
+ * is one (carrick#2599).
3112
3298
  * Mirrored by `installedPackageDirectory` in capture/, which the capture
3113
3299
  * seam keeps from being shared.
3114
3300
  */
3115
3301
  installedPackage(fileName) {
3116
- const segments = path.relative(this.root, this.realpath(fileName)).split(path.sep);
3302
+ const real = this.realpath(fileName);
3303
+ const segments = path.relative(this.root, real).split(path.sep);
3117
3304
  const last = segments.lastIndexOf('node_modules');
3118
- if (last < 0)
3305
+ let placed;
3306
+ if (last >= 0) {
3307
+ const width = segments[last + 1]?.startsWith('@') ? 2 : 1;
3308
+ placed = {
3309
+ directory: path.resolve(this.root, ...segments.slice(0, last + 1 + width)),
3310
+ name: segments.slice(last + 1, last + 1 + width).join('/'),
3311
+ };
3312
+ }
3313
+ else {
3314
+ placed = this.graph.packageDirectoryOf(real);
3315
+ }
3316
+ if (placed === undefined)
3119
3317
  return undefined;
3120
- const width = segments[last + 1]?.startsWith('@') ? 2 : 1;
3121
- const nameSegments = segments.slice(last + 1, last + 1 + width);
3122
3318
  // No package.json there (a file directly under node_modules included) means no package.
3123
- const directory = path.resolve(this.root, ...segments.slice(0, last + 1 + width));
3124
- let known = this.packageDirectories.get(directory);
3319
+ let known = this.packageDirectories.get(placed.directory);
3125
3320
  if (known === undefined) {
3126
- known = readInstalledPackage(this.host, directory, nameSegments.join('/'));
3127
- this.packageDirectories.set(directory, known);
3321
+ known = readInstalledPackage(this.host, placed.directory, placed.name);
3322
+ this.packageDirectories.set(placed.directory, known);
3128
3323
  }
3129
3324
  return known ?? undefined;
3130
3325
  }