carrick 0.3.125 → 0.3.127

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.
@@ -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;
@@ -436,6 +456,7 @@ export class LibraryClaimsVerifier {
436
456
  makerBindsName,
437
457
  scoped: receiverPath.scope !== undefined,
438
458
  makerHolding,
459
+ madeBy: receiverPath.maker?.member === null && receiverPath.scope === undefined ? check.export : undefined,
439
460
  })),
440
461
  };
441
462
  };
@@ -621,7 +642,7 @@ export class LibraryClaimsVerifier {
621
642
  const file = program.getSourceFile(probe.getFilePath());
622
643
  if (!file)
623
644
  throw new Error(`probe file ${probePath} is not in the program`);
624
- const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir);
645
+ const reader = new DeclarationReader(program, file, this.project.getModuleResolutionHost(), fromDir, undefined, this.graph);
625
646
  return read(program.getTypeChecker(), reader, file);
626
647
  }
627
648
  finally {
@@ -809,8 +830,10 @@ function typeSymbols(type) {
809
830
  * declares one.
810
831
  */
811
832
  function probeLine(pkg, name, local) {
812
- if (!DEFAULT_LIBRARY.test(pkg))
813
- return importLine(pkg, name, local);
833
+ return DEFAULT_LIBRARY.test(pkg) ? globalRead(name, local) : importLine(pkg, name, local);
834
+ }
835
+ /** The probe's read of the global `name`, bound to `local`. */
836
+ function globalRead(name, local) {
814
837
  const access = IDENTIFIER.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
815
838
  return `const ${local} = globalThis${access};`;
816
839
  }
@@ -869,6 +892,7 @@ class DeclarationReader {
869
892
  program;
870
893
  probe;
871
894
  host;
895
+ graph;
872
896
  checker;
873
897
  /** Names declaring files as position rows do: from the scan root, else the service root. */
874
898
  pathNames;
@@ -884,12 +908,13 @@ class DeclarationReader {
884
908
  declared = new Map();
885
909
  /** The service root as given and as a realpath, longest first: what a listing prints as `<root>`. */
886
910
  printedRoots;
887
- constructor(program, probe, host, serviceRoot, scanRoot) {
911
+ constructor(program, probe, host, serviceRoot, scanRoot, graph) {
888
912
  this.program = program;
889
913
  this.probe = probe;
890
914
  this.host = host;
915
+ this.graph = graph;
891
916
  this.checker = program.getTypeChecker();
892
- this.pathNames = new PathNames(scanRoot ?? serviceRoot);
917
+ this.pathNames = new PathNames(scanRoot ?? serviceRoot, graph.packageDirectoryOf);
893
918
  this.root = this.realpath(serviceRoot);
894
919
  this.printedRoots = [...new Set([this.root, serviceRoot])]
895
920
  .filter(root => root.length > 1)
@@ -931,11 +956,20 @@ class DeclarationReader {
931
956
  */
932
957
  readModule(pkg, declaration, runtimeModules = false) {
933
958
  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;
959
+ const resolved = this.graph.resolveModule(pkg, this.probe.fileName, this.program.getCompilerOptions()) ??
960
+ ts.resolveModuleName(pkg, this.probe.fileName, this.program.getCompilerOptions(), this.host, undefined, undefined, this.program.getModeForUsageLocation(this.probe, specifier)).resolvedModule;
935
961
  const entry = { package: pkg };
936
962
  const done = (reason) => reason ? { entry: { ...entry, reason }, reason } : { entry };
937
963
  const moduleSymbol = this.checker.getSymbolAtLocation(specifier);
938
964
  const declarations = moduleSymbol?.declarations ?? [];
965
+ const globalFile = declarations.length === 0 ? this.runtimeGlobalFile(pkg) : undefined;
966
+ if (globalFile) {
967
+ entry.resolved_file = globalFile.fileName;
968
+ const version = this.installedPackage(globalFile.fileName)?.version;
969
+ if (version)
970
+ entry.installed_version = version;
971
+ return { entry, globalFile };
972
+ }
939
973
  if (runtimeModules && RUNTIME_MODULE.test(pkg))
940
974
  return this.readRuntimeModule(declarations, entry, done);
941
975
  // The module itself, not a service-side augmentation of it.
@@ -1029,6 +1063,27 @@ class DeclarationReader {
1029
1063
  entry.resolved_file = file.fileName;
1030
1064
  return { entry };
1031
1065
  }
1066
+ /**
1067
+ * The runtime types' file a `node:<path>` key names, when no module has
1068
+ * that name (carrick#2445): `node:web-globals/fetch` is the file
1069
+ * `web-globals/fetch.d.ts` (or `.../index.d.ts`) of the installed runtime
1070
+ * types package, which declares globals. Undefined when the program loads
1071
+ * no such file; the key is then a module name, read as one.
1072
+ */
1073
+ runtimeGlobalFile(pkg) {
1074
+ if (!RUNTIME_MODULE.test(pkg))
1075
+ return undefined;
1076
+ const relative = pkg.slice('node:'.length);
1077
+ return this.program.getSourceFiles().find(file => {
1078
+ const owner = this.packageOf(file);
1079
+ if (owner === undefined || !RUNTIME_TYPE_PACKAGES.has(owner))
1080
+ return false;
1081
+ const name = this.realpath(file.fileName).split(path.sep).join('/');
1082
+ const marker = `/node_modules/${owner}/`;
1083
+ const inside = name.slice(name.lastIndexOf(marker) + marker.length);
1084
+ return inside === `${relative}.d.ts` || inside === `${relative}/index.d.ts`;
1085
+ });
1086
+ }
1032
1087
  /** The default-library file a `lib:` key names, when the program loads it. */
1033
1088
  defaultLibraryFile(pkg) {
1034
1089
  const name = pkg.slice('lib:'.length);
@@ -1042,7 +1097,7 @@ class DeclarationReader {
1042
1097
  * key's lib file declares; one only another file declares (the runtime's
1043
1098
  * types, the service) is missing from it.
1044
1099
  */
1045
- readGlobal(pkg, statement) {
1100
+ readGlobal(file, statement) {
1046
1101
  const declaration = statement && ts.isVariableStatement(statement) ? statement.declarationList.declarations[0] : undefined;
1047
1102
  const access = declaration?.initializer;
1048
1103
  const name = access && ts.isPropertyAccessExpression(access)
@@ -1051,7 +1106,6 @@ class DeclarationReader {
1051
1106
  ? access.argumentExpression
1052
1107
  : undefined;
1053
1108
  const target = name ? this.checker.getSymbolAtLocation(name) : undefined;
1054
- const file = this.defaultLibraryFile(pkg);
1055
1109
  if (!declaration ||
1056
1110
  !target ||
1057
1111
  !file ||
@@ -1552,8 +1606,12 @@ class DeclarationReader {
1552
1606
  else
1553
1607
  best = furthest(best, result.rank, result.outcome);
1554
1608
  }
1555
- if (holding.length > 0)
1556
- return binds ? { outcome: VERIFIED } : { outcome: VERIFIED, holding };
1609
+ if (holding.length > 0) {
1610
+ // The member's own declarations, as a verified op carries them (#2636).
1611
+ const declarations = this.declarationsOf({ signatures: holding, named: callee.named });
1612
+ const outcome = declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
1613
+ return binds ? { outcome } : { outcome, holding };
1614
+ }
1557
1615
  return { outcome: best.outcome };
1558
1616
  }
1559
1617
  /**
@@ -1583,8 +1641,116 @@ class DeclarationReader {
1583
1641
  if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
1584
1642
  return unchecked('mounts_untyped');
1585
1643
  }
1644
+ const erased = this.fitsErased(receiver.type, slot, signature.typeParameters ?? []);
1645
+ if (erased !== undefined)
1646
+ return erased ? undefined : failed('mounts_not_instance');
1586
1647
  return this.checker.isTypeAssignableTo(receiver.type, slot) ? undefined : failed('mounts_not_instance');
1587
1648
  }
1649
+ /**
1650
+ * `receiver` fits `slot` with the signature's own type parameters
1651
+ * (`own`) erased to their constraints (carrick#2652), or undefined when
1652
+ * that reading does not apply. The receiver is built at its defaults, so a
1653
+ * parameter typed `Tree<SubEnv, SubBase>` has free arguments that no default
1654
+ * instance is assignable to. It applies when `slot` and `receiver` are
1655
+ * references to the same generic type and no argument mixes an own type
1656
+ * parameter into a larger type: an argument that is an own type parameter
1657
+ * takes any receiver argument its constraint takes (none: any), every other
1658
+ * argument must be the receiver's.
1659
+ */
1660
+ fitsErased(receiver, slot, own) {
1661
+ if (own.length === 0)
1662
+ return undefined;
1663
+ const from = this.referenceOf(receiver);
1664
+ const to = this.referenceOf(slot);
1665
+ if (from === undefined || to === undefined)
1666
+ return undefined;
1667
+ const count = (to.target.typeParameters ?? []).length;
1668
+ const given = this.argumentsAs(from.target, this.checker.getTypeArguments(from), to.target);
1669
+ const wanted = this.checker.getTypeArguments(to);
1670
+ if (count === 0 || given === undefined || given.length < count || wanted.length < count)
1671
+ return undefined;
1672
+ for (let i = 0; i < count; i++) {
1673
+ if (own.includes(wanted[i])) {
1674
+ const constraint = this.checker.getBaseConstraintOfType(wanted[i]);
1675
+ if (constraint === undefined || this.isOpenTop(constraint))
1676
+ continue;
1677
+ if (own.some(parameter => this.mentions(constraint, parameter)))
1678
+ return undefined;
1679
+ if (!this.checker.isTypeAssignableTo(given[i], constraint))
1680
+ return false;
1681
+ }
1682
+ else if (own.some(parameter => this.mentions(wanted[i], parameter))) {
1683
+ return undefined;
1684
+ }
1685
+ else if (given[i] !== wanted[i] &&
1686
+ !(this.checker.isTypeAssignableTo(given[i], wanted[i]) && this.checker.isTypeAssignableTo(wanted[i], given[i]))) {
1687
+ return false;
1688
+ }
1689
+ }
1690
+ return true;
1691
+ }
1692
+ /**
1693
+ * The type arguments the instance `current<args>` has as an instance of
1694
+ * `target`: its own when it is one, else through the base types its class
1695
+ * extends, where each base argument is a type parameter of the class
1696
+ * (replaced by the matching argument) or names none. Undefined when
1697
+ * `target` is not reached that way.
1698
+ */
1699
+ argumentsAs(current, args, target, depth = 0) {
1700
+ if (current === target)
1701
+ return args;
1702
+ if (depth > 6)
1703
+ return undefined;
1704
+ const parameters = current.typeParameters ?? [];
1705
+ for (const base of this.checker.getBaseTypes(current)) {
1706
+ const reference = this.referenceOf(base);
1707
+ if (reference === undefined)
1708
+ continue;
1709
+ const mapped = [];
1710
+ let usable = true;
1711
+ for (const argument of this.checker.getTypeArguments(reference)) {
1712
+ const index = parameters.indexOf(argument);
1713
+ if (index >= 0)
1714
+ mapped.push(args[index]);
1715
+ else if (parameters.some(parameter => this.mentions(argument, parameter)))
1716
+ usable = false;
1717
+ else
1718
+ mapped.push(argument);
1719
+ }
1720
+ const found = usable ? this.argumentsAs(reference.target, mapped, target, depth + 1) : undefined;
1721
+ if (found !== undefined)
1722
+ return found;
1723
+ }
1724
+ return undefined;
1725
+ }
1726
+ referenceOf(type) {
1727
+ if (!(type.flags & ts.TypeFlags.Object))
1728
+ return undefined;
1729
+ return type.objectFlags & ts.ObjectFlags.Reference ? type : undefined;
1730
+ }
1731
+ /** `type` names `parameter` anywhere in its arguments or members of a union or intersection. */
1732
+ mentions(type, parameter, depth = 0) {
1733
+ if (type === parameter)
1734
+ return true;
1735
+ if (depth > 6)
1736
+ return true;
1737
+ if (type.isUnionOrIntersection())
1738
+ return type.types.some(part => this.mentions(part, parameter, depth + 1));
1739
+ // A deferred conditional (`Server extends X ? A : B`) or an alias
1740
+ // instance names its parameter in its check type or its alias arguments
1741
+ // (carrick#2663).
1742
+ if (type.aliasTypeArguments?.some(argument => this.mentions(argument, parameter, depth + 1)))
1743
+ return true;
1744
+ if (type.flags & ts.TypeFlags.Conditional) {
1745
+ const conditional = type;
1746
+ return [conditional.checkType, conditional.extendsType].some(part => this.mentions(part, parameter, depth + 1));
1747
+ }
1748
+ const reference = this.referenceOf(type);
1749
+ if (reference !== undefined) {
1750
+ return this.checker.getTypeArguments(reference).some(argument => this.mentions(argument, parameter, depth + 1));
1751
+ }
1752
+ return false;
1753
+ }
1588
1754
  /**
1589
1755
  * A send, receive or request op: the home-declared callable member (after
1590
1756
  * `path`; or the receiver itself, when null) with some overload that takes
@@ -1668,19 +1834,25 @@ class DeclarationReader {
1668
1834
  return best.outcome;
1669
1835
  if (boundRuns)
1670
1836
  return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
1671
- const declarations = this.declarationsOf(callee.signatures);
1837
+ const declarations = this.declarationsOf(callee);
1672
1838
  return declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
1673
1839
  }
1674
1840
  /**
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.
1841
+ * Where a verified op or scope's member is declared, as position rows spell
1842
+ * it (`decl_file`, `decl_line`): the declaration the call row names for the
1843
+ * member symbol on this receiver ({@link memberDeclaration}), so two members
1844
+ * of one name on two types give two answers, and a property typed by an
1845
+ * interface with call signatures names the property, not the signatures
1846
+ * (carrick#2654). A callee that is the receiver itself has no member symbol
1847
+ * and names its signatures' declarations. A declaration no row could name is
1848
+ * left out.
1679
1849
  */
1680
- declarationsOf(signatures) {
1850
+ declarationsOf(callee) {
1681
1851
  const found = [];
1682
- for (const signature of signatures) {
1683
- const declaration = signature.getDeclaration();
1852
+ const named = callee.named
1853
+ ? [memberDeclaration(() => this.program, callee.named.property, callee.named.holder)]
1854
+ : callee.signatures.map(signature => signature.getDeclaration());
1855
+ for (const declaration of named) {
1684
1856
  if (!declaration)
1685
1857
  continue;
1686
1858
  const file = declaration.getSourceFile();
@@ -1765,11 +1937,17 @@ class DeclarationReader {
1765
1937
  return failure.verdict === 'failed' ? failed(reason) : unchecked(reason);
1766
1938
  }
1767
1939
  /**
1768
- * `yields: { param, export, side }` on a receive op: some signature of the
1769
- * handler this overload takes declares a parameter at `param` whose type
1770
- * is the package's own export named `export` (a class, or the interface
1771
- * an instance has), declared in the receiver's home packages. `side` is
1772
- * `client` or `server` (`yields_side_invalid`). Undefined when it holds.
1940
+ * `yields: { param, export, side }` on a receive op or a scope: some
1941
+ * signature of the function this overload takes declares a parameter at
1942
+ * `param` whose type is either
1943
+ * - the package's own export named `export` (a class, or the interface an
1944
+ * instance has), declared in the receiver's home packages; or
1945
+ * - when `export` is the maker whose own call made the instance the claim
1946
+ * is read on (`madeBy`, carrick#2663): that instance, compared by
1947
+ * declaration (`handsInstance`), since a factory's result has no export
1948
+ * name of its own.
1949
+ * `side` is `client` or `server` (`yields_side_invalid`). Undefined when it
1950
+ * holds.
1773
1951
  */
1774
1952
  yieldsOutcome(signature, handler, yields, receiver) {
1775
1953
  if (yields.side !== 'client' && yields.side !== 'server')
@@ -1793,10 +1971,44 @@ class DeclarationReader {
1793
1971
  (symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home)));
1794
1972
  if (named)
1795
1973
  return undefined;
1974
+ if (yields.export === receiver.madeBy && this.handsInstance(given, receiver, signature.typeParameters ?? [])) {
1975
+ return undefined;
1976
+ }
1796
1977
  }
1797
1978
  }
1798
1979
  return failed('yields_not_export');
1799
1980
  }
1981
+ /**
1982
+ * `given` (each part, nullish dropped) is the instance `receiver` holds:
1983
+ * some part of its intersection is a class or interface the receiver's
1984
+ * home packages declare, and the same declaration (the same symbol, never
1985
+ * the same name) as a part of the instance's own intersection. A generic
1986
+ * instance is built at its defaults while `given` threads the overload's
1987
+ * own type parameters (`own`) through its arguments, so the arguments are
1988
+ * read as `fitsErased` reads a mounted instance (carrick#2652): a
1989
+ * definite mismatch refuses, an argument it cannot read leaves the
1990
+ * declaration to decide.
1991
+ */
1992
+ handsInstance(given, receiver, own) {
1993
+ const declared = (type) => {
1994
+ const symbol = type.getSymbol();
1995
+ if (!symbol || !(symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)))
1996
+ return undefined;
1997
+ const home = (symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home));
1998
+ return home ? symbol : undefined;
1999
+ };
2000
+ const intersected = (type) => (type.isIntersection() ? type.types : [type]);
2001
+ const instances = this.parts(receiver.type)
2002
+ .flatMap(intersected)
2003
+ .filter(part => declared(part) !== undefined);
2004
+ const parts = this.parts(given);
2005
+ return (parts.length > 0 &&
2006
+ parts.every(part => intersected(part).some(handed => {
2007
+ const symbol = declared(handed);
2008
+ return (symbol !== undefined &&
2009
+ instances.some(instance => declared(instance) === symbol && this.fitsErased(instance, handed, own) !== false));
2010
+ })));
2011
+ }
1800
2012
  /**
1801
2013
  * `request: { param, path?, url?, method? }` on a receive op (#2474 R0):
1802
2014
  * some signature of the handler this overload takes declares a parameter
@@ -2394,6 +2606,9 @@ class DeclarationReader {
2394
2606
  */
2395
2607
  ownCallee(receiver, hops, member, form) {
2396
2608
  let holder = receiver;
2609
+ // The last member the path reaches, with the type it is read on: the
2610
+ // symbol the call row names (carrick#2654).
2611
+ let named;
2397
2612
  for (const hop of hops) {
2398
2613
  const step = this.ownMember(holder, hop);
2399
2614
  if ('failure' in step)
@@ -2401,6 +2616,7 @@ class DeclarationReader {
2401
2616
  const type = step.type;
2402
2617
  if (this.saysNothing(type))
2403
2618
  return { failure: unchecked('member_untyped') };
2619
+ named = { property: step.property, holder: holder.type };
2404
2620
  holder = { type, home: this.homeOf(receiver.pkg, typeSymbols(type), holder.home) };
2405
2621
  }
2406
2622
  let type = holder.type;
@@ -2412,6 +2628,7 @@ class DeclarationReader {
2412
2628
  return step;
2413
2629
  type = step.type;
2414
2630
  boundBase = step.bound;
2631
+ named = { property: step.property, holder: holder.type };
2415
2632
  }
2416
2633
  const all = form === 'new'
2417
2634
  ? this.checker.getNonNullableType(type).getConstructSignatures()
@@ -2425,7 +2642,7 @@ class DeclarationReader {
2425
2642
  ? library
2426
2643
  : library.filter(signature => this.isOwnFile(this.signatureFile(signature, type, form), home));
2427
2644
  if (own.length > 0)
2428
- return { signatures: own, holder: holder.type };
2645
+ return { signatures: own, holder: holder.type, named };
2429
2646
  if (library.length > 0)
2430
2647
  return { failure: failed('member_inherited') };
2431
2648
  return { failure: this.slotFailure(type, form === 'new' ? 'member_not_constructible' : 'member_not_callable') };
@@ -2442,9 +2659,9 @@ class DeclarationReader {
2442
2659
  return { failure: failed('member_missing') };
2443
2660
  const type = this.checker.getTypeOfSymbol(property);
2444
2661
  if (this.isOwnMember(property, apparent, holder.home))
2445
- return { type, bound: false };
2662
+ return { type, bound: false, property };
2446
2663
  if (this.isBoundByOwnType(property, holder))
2447
- return { type, bound: true };
2664
+ return { type, bound: true, property };
2448
2665
  return { failure: failed('member_inherited') };
2449
2666
  }
2450
2667
  /**
@@ -2539,7 +2756,7 @@ class DeclarationReader {
2539
2756
  surface.installed_version = moduleRead.entry.installed_version;
2540
2757
  if (moduleRead.reason)
2541
2758
  return { ...surface, reason: moduleRead.reason };
2542
- const exported = this.listedExports(probe, only);
2759
+ const exported = this.listedExports(probe, only, moduleRead.globalFile);
2543
2760
  if (!exported)
2544
2761
  return { ...surface, reason: 'module_unresolved' };
2545
2762
  let budget = maxEntries;
@@ -2555,6 +2772,9 @@ class DeclarationReader {
2555
2772
  // Three passes: every export and receiver, then every member name, then signatures.
2556
2773
  const names = [];
2557
2774
  const fills = [];
2775
+ // What the package's default export names: a maker also exported by name
2776
+ // under this declaration is imported the same way, so it is read the same.
2777
+ const defaultTarget = exported.find(each => each.name === 'default')?.target;
2558
2778
  for (const { name, target, type } of exported) {
2559
2779
  if (!take())
2560
2780
  continue;
@@ -2570,7 +2790,7 @@ class DeclarationReader {
2570
2790
  };
2571
2791
  receiverOf('export', type);
2572
2792
  for (const maker of this.makers(pkg, target, type)) {
2573
- const made = this.madeBy(maker.signatures, built(name, maker.receiver));
2793
+ const made = this.madeBy(maker.signatures, built(name, maker.receiver), target === defaultTarget);
2574
2794
  if (!made)
2575
2795
  continue;
2576
2796
  // One level below the export, what the member builds must declare a callable member.
@@ -2594,9 +2814,10 @@ class DeclarationReader {
2594
2814
  */
2595
2815
  makersOf(pkg, probe, only) {
2596
2816
  this.setPackage(pkg);
2597
- if ('namespace' in probe && this.readModule(pkg, probe.namespace, true).reason)
2817
+ const moduleRead = 'namespace' in probe ? this.readModule(pkg, probe.namespace, true) : undefined;
2818
+ if (moduleRead?.reason)
2598
2819
  return [];
2599
- return (this.listedExports(probe, only) ?? [])
2820
+ return (this.listedExports(probe, only, moduleRead?.globalFile) ?? [])
2600
2821
  .filter(({ type }) => !this.isOpenTop(type))
2601
2822
  .map(({ name, target, type }) => ({
2602
2823
  export: name,
@@ -2613,9 +2834,11 @@ class DeclarationReader {
2613
2834
  * `prototype`, which no service imports. Undefined when the specifier
2614
2835
  * resolves to no module symbol.
2615
2836
  */
2616
- listedExports(probe, only) {
2837
+ listedExports(probe, only, globalFile) {
2617
2838
  if ('defaultLibrary' in probe)
2618
- return this.listedGlobals(probe.defaultLibrary, only);
2839
+ return this.listedGlobals(this.defaultLibraryFile(probe.defaultLibrary), only);
2840
+ if (globalFile)
2841
+ return this.listedGlobals(globalFile, only);
2619
2842
  const moduleSymbol = this.checker.getSymbolAtLocation(probe.namespace.moduleSpecifier);
2620
2843
  if (!moduleSymbol)
2621
2844
  return undefined;
@@ -2646,8 +2869,7 @@ class DeclarationReader {
2646
2869
  * `only` names), sorted by name, each with its declared type: none when the
2647
2870
  * program's `lib` setting does not load the file.
2648
2871
  */
2649
- listedGlobals(pkg, only) {
2650
- const file = this.defaultLibraryFile(pkg);
2872
+ listedGlobals(file, only) {
2651
2873
  if (!file)
2652
2874
  return [];
2653
2875
  return this.checker
@@ -2694,13 +2916,25 @@ class DeclarationReader {
2694
2916
  * type-parameter defaults where `built` (the probe's no-argument build of
2695
2917
  * the receiver) instantiates it (`returnOf`), less the returns that say
2696
2918
  * nothing, which no maker claim holds on. Listed when what is left is one
2697
- * object type.
2698
- */
2699
- madeBy(signatures, built) {
2919
+ * object type. A `default` export, which a service imports under any local
2920
+ * name (usually the one maker a framework has), or the same declaration
2921
+ * exported by name (carrick#2640), that is overloaded and so
2922
+ * returns several types is listed for the return of the overload the
2923
+ * probe's no-argument build resolves (`built`), when that is an object type
2924
+ * with members of its own.
2925
+ */
2926
+ madeBy(signatures, built, isDefault) {
2700
2927
  const returns = [
2701
2928
  ...new Set(signatures.map(sig => this.returnOf(sig, built)).filter(type => !this.returnSaysNothing(type))),
2702
2929
  ];
2703
- return returns.length === 1 && this.isObjectType(returns[0]) ? returns[0] : undefined;
2930
+ if (returns.length === 1)
2931
+ return this.isObjectType(returns[0]) ? returns[0] : undefined;
2932
+ if (!isDefault || returns.length === 0 || built === undefined)
2933
+ return undefined;
2934
+ const resolved = this.checker.getReturnTypeOfSignature(built);
2935
+ return !this.returnSaysNothing(resolved) && this.isObjectType(resolved) && this.namedProperties(resolved).length > 0
2936
+ ? resolved
2937
+ : undefined;
2704
2938
  }
2705
2939
  /** The call (or construct) signatures of `type` an installed package or the default library declares. */
2706
2940
  librarySignatures(type, form) {
@@ -3108,23 +3342,34 @@ class DeclarationReader {
3108
3342
  * source file in a directory that happens to be named `node_modules`, or a
3109
3343
  * repository checked out under a `node_modules` ancestor, is not installed.
3110
3344
  * An install hoisted above the service root (`../../node_modules/pkg`) and
3111
- * a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are.
3345
+ * a pnpm store (`node_modules/.pnpm/pkg@1/node_modules/pkg`) are. A path
3346
+ * with no `node_modules` segment is placed by the module graph, where there
3347
+ * is one (carrick#2599).
3112
3348
  * Mirrored by `installedPackageDirectory` in capture/, which the capture
3113
3349
  * seam keeps from being shared.
3114
3350
  */
3115
3351
  installedPackage(fileName) {
3116
- const segments = path.relative(this.root, this.realpath(fileName)).split(path.sep);
3352
+ const real = this.realpath(fileName);
3353
+ const segments = path.relative(this.root, real).split(path.sep);
3117
3354
  const last = segments.lastIndexOf('node_modules');
3118
- if (last < 0)
3355
+ let placed;
3356
+ if (last >= 0) {
3357
+ const width = segments[last + 1]?.startsWith('@') ? 2 : 1;
3358
+ placed = {
3359
+ directory: path.resolve(this.root, ...segments.slice(0, last + 1 + width)),
3360
+ name: segments.slice(last + 1, last + 1 + width).join('/'),
3361
+ };
3362
+ }
3363
+ else {
3364
+ placed = this.graph.packageDirectoryOf(real);
3365
+ }
3366
+ if (placed === undefined)
3119
3367
  return undefined;
3120
- const width = segments[last + 1]?.startsWith('@') ? 2 : 1;
3121
- const nameSegments = segments.slice(last + 1, last + 1 + width);
3122
3368
  // 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);
3369
+ let known = this.packageDirectories.get(placed.directory);
3125
3370
  if (known === undefined) {
3126
- known = readInstalledPackage(this.host, directory, nameSegments.join('/'));
3127
- this.packageDirectories.set(directory, known);
3371
+ known = readInstalledPackage(this.host, placed.directory, placed.name);
3372
+ this.packageDirectories.set(placed.directory, known);
3128
3373
  }
3129
3374
  return known ?? undefined;
3130
3375
  }