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.
- 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/check-classify.js +11 -5
- package/sidecar/dist/src/capture/check.js +8 -1
- package/sidecar/dist/src/capture/deep-walk.d.ts +31 -0
- package/sidecar/dist/src/capture/deep-walk.js +43 -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/capture/index.d.ts +1 -1
- package/sidecar/dist/src/capture/index.js +1 -1
- package/sidecar/dist/src/capture/service-config.d.ts +11 -0
- package/sidecar/dist/src/capture/service-config.js +40 -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 +35 -1
- package/sidecar/dist/src/library-claims.d.ts +17 -2
- package/sidecar/dist/src/library-claims.js +242 -47
- 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 +31 -0
- package/sidecar/dist/src/project-loader.js +67 -4
- package/sidecar/dist/src/type-inferrer.d.ts +150 -16
- package/sidecar/dist/src/type-inferrer.js +651 -94
- package/sidecar/dist/src/types.d.ts +46 -2
- package/sidecar/dist/src/validators.d.ts +73 -0
- 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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
813
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
1827
|
+
const declarations = this.declarationsOf(callee);
|
|
1672
1828
|
return declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
|
|
1673
1829
|
}
|
|
1674
1830
|
/**
|
|
1675
|
-
* Where
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
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(
|
|
1840
|
+
declarationsOf(callee) {
|
|
1681
1841
|
const found = [];
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
}
|