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.
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/api.d.ts +5 -1
- package/sidecar/dist/src/capture/deno-project.d.ts +7 -0
- package/sidecar/dist/src/capture/deno-project.js +7 -0
- package/sidecar/dist/src/declaration-attribution.d.ts +11 -0
- package/sidecar/dist/src/declaration-attribution.js +16 -0
- package/sidecar/dist/src/implementations.d.ts +43 -0
- package/sidecar/dist/src/implementations.js +224 -0
- package/sidecar/dist/src/index.js +48 -1
- package/sidecar/dist/src/library-claims.d.ts +17 -2
- package/sidecar/dist/src/library-claims.js +297 -52
- package/sidecar/dist/src/position-rows.d.ts +119 -1
- package/sidecar/dist/src/position-rows.js +163 -9
- package/sidecar/dist/src/project-loader.d.ts +15 -0
- package/sidecar/dist/src/project-loader.js +16 -0
- package/sidecar/dist/src/retype.js +88 -8
- package/sidecar/dist/src/type-inferrer.d.ts +149 -8
- package/sidecar/dist/src/type-inferrer.js +569 -64
- package/sidecar/dist/src/types.d.ts +54 -2
- package/sidecar/dist/src/validators.d.ts +73 -0
- package/sidecar/dist/src/validators.js +10 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
813
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
1837
|
+
const declarations = this.declarationsOf(callee);
|
|
1672
1838
|
return declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
|
|
1673
1839
|
}
|
|
1674
1840
|
/**
|
|
1675
|
-
* Where
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
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(
|
|
1850
|
+
declarationsOf(callee) {
|
|
1681
1851
|
const found = [];
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
|
1769
|
-
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
}
|