carrick 0.3.105 → 0.3.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hook/refresh.js +8 -1
- package/dist/hook/refresh.js.map +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.d.ts +22 -2
- package/sidecar/dist/src/capture/anchors.js +178 -34
- package/sidecar/dist/src/capture/api.d.ts +42 -1
- package/sidecar/dist/src/capture/check-classify.d.ts +9 -1
- package/sidecar/dist/src/capture/check-classify.js +15 -0
- package/sidecar/dist/src/capture/check-poison.js +4 -14
- package/sidecar/dist/src/capture/check.js +13 -1
- package/sidecar/dist/src/capture/index.js +55 -29
- package/sidecar/dist/src/capture/installed-package.d.ts +2 -0
- package/sidecar/dist/src/capture/installed-package.js +2 -1
- package/sidecar/dist/src/capture/outside-root.d.ts +19 -0
- package/sidecar/dist/src/capture/outside-root.js +39 -2
- package/sidecar/dist/src/capture/self-check.js +12 -13
- package/sidecar/dist/src/capture/service-config.d.ts +2 -0
- package/sidecar/dist/src/capture/service-config.js +1 -1
- package/sidecar/dist/src/capture/specifiers.d.ts +14 -0
- package/sidecar/dist/src/capture/specifiers.js +25 -0
- package/sidecar/dist/src/failure-path.d.ts +34 -3
- package/sidecar/dist/src/failure-path.js +59 -35
- package/sidecar/dist/src/function-line-index.d.ts +30 -0
- package/sidecar/dist/src/function-line-index.js +162 -0
- package/sidecar/dist/src/index.d.ts +5 -0
- package/sidecar/dist/src/index.js +29 -5
- package/sidecar/dist/src/line-index.d.ts +9 -0
- package/sidecar/dist/src/line-index.js +26 -0
- package/sidecar/dist/src/printed-names.d.ts +43 -0
- package/sidecar/dist/src/printed-names.js +186 -0
- package/sidecar/dist/src/progress.d.ts +22 -0
- package/sidecar/dist/src/progress.js +31 -0
- package/sidecar/dist/src/retype.js +58 -128
- package/sidecar/dist/src/type-inferrer.d.ts +124 -13
- package/sidecar/dist/src/type-inferrer.js +531 -146
- package/sidecar/dist/src/type-structural-expander.js +10 -1
- package/sidecar/dist/src/types.d.ts +37 -6
- package/sidecar/dist/src/validators.d.ts +76 -0
- package/sidecar/dist/src/validators.js +8 -0
package/dist/hook/refresh.js
CHANGED
|
@@ -132,7 +132,14 @@ export function refreshInBackground(root, status, options = {}) {
|
|
|
132
132
|
return null;
|
|
133
133
|
if (!claimRefresh(marker, now, cooldown))
|
|
134
134
|
return null;
|
|
135
|
-
|
|
135
|
+
// Nobody started this refresh and nobody is reading it, so it does not keep
|
|
136
|
+
// the machine awake: that hold is for a scan a person is waiting on, and it
|
|
137
|
+
// comes with a line saying so that this one has nowhere to print
|
|
138
|
+
// (carrick#1889).
|
|
139
|
+
(options.start ?? startDetached)(binary(env), ["refresh", "--workspace", root], {
|
|
140
|
+
...env,
|
|
141
|
+
CARRICK_NO_KEEP_AWAKE: "1",
|
|
142
|
+
});
|
|
136
143
|
return "Carrick: a repo here is connected with no hosted index yet, so a refresh is running in the background. Its rows arrive in the next session.";
|
|
137
144
|
}
|
|
138
145
|
//# sourceMappingURL=refresh.js.map
|
package/dist/hook/refresh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/hook/refresh.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/hook/refresh.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.107",
|
|
4
4
|
"description": "Maps your entire TypeScript codebase across services and repositories, giving AI agents full context on existing types, routes, and function behaviours over MCP before they write duplicate or breaking code.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"zod": "^3.23.0"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@carrick-tools/cli-darwin-arm64": "0.3.
|
|
62
|
-
"@carrick-tools/cli-darwin-x64": "0.3.
|
|
63
|
-
"@carrick-tools/cli-linux-arm64": "0.3.
|
|
64
|
-
"@carrick-tools/cli-linux-x64": "0.3.
|
|
65
|
-
"@carrick-tools/cli-win32-x64": "0.3.
|
|
61
|
+
"@carrick-tools/cli-darwin-arm64": "0.3.107",
|
|
62
|
+
"@carrick-tools/cli-darwin-x64": "0.3.107",
|
|
63
|
+
"@carrick-tools/cli-linux-arm64": "0.3.107",
|
|
64
|
+
"@carrick-tools/cli-linux-x64": "0.3.107",
|
|
65
|
+
"@carrick-tools/cli-win32-x64": "0.3.107"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^24.13.3",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
3
|
"description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.107",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Carrick",
|
|
7
7
|
"email": "hello@carrick.tools"
|
|
@@ -56,8 +56,21 @@ export interface ResolvedAnchor {
|
|
|
56
56
|
*/
|
|
57
57
|
namesUnemittedModule?: true;
|
|
58
58
|
}
|
|
59
|
-
/**
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Repo-root-relative source file -> the specifier the surface entry imports
|
|
61
|
+
* it by: its path from `entryDir`, in the form the entry resolves.
|
|
62
|
+
*
|
|
63
|
+
* With the extension removed (`./a`), where `resolveFromEntry` says the entry
|
|
64
|
+
* resolves that to the module. It does not always (carrick#1911): under
|
|
65
|
+
* `module` `node16`..`nodenext` an entry in a `"type": "module"` package is
|
|
66
|
+
* an ES module, which must name the file the module compiles to, and a `.mts`
|
|
67
|
+
* or `.cts` module has no extensionless form under any setting. There, and
|
|
68
|
+
* with no resolver to ask, the specifier is that output name: `./a.js` for
|
|
69
|
+
* `.ts` and `.tsx`, `./a.mjs` for `.mts`, `./a.cjs` for `.cts`, which every
|
|
70
|
+
* module setting maps back to the source and, in the stub, to its
|
|
71
|
+
* declaration.
|
|
72
|
+
*/
|
|
73
|
+
export declare function entryRelativeSpecifier(entryDir: string, repoRoot: string, sourceFile: string, resolveFromEntry?: (specifier: string) => string | undefined): string;
|
|
61
74
|
/**
|
|
62
75
|
* Resolve one anchor against the analysis program. `placeholder` is the
|
|
63
76
|
* anchor's own placeholder alias declaration in the entry file -- the
|
|
@@ -73,6 +86,13 @@ export declare function resolveAnchor(program: ts.Program, request: CaptureAncho
|
|
|
73
86
|
* these resolves through that module instead of dangling in the entry.
|
|
74
87
|
*/
|
|
75
88
|
siblingSymbolSpecs?: Map<string, string>;
|
|
89
|
+
/**
|
|
90
|
+
* The file a module specifier resolves to from the surface entry, under
|
|
91
|
+
* the analysis program's own resolution (carrick#1789). A literal's name
|
|
92
|
+
* is imported through a package specifier only when the entry reaches the
|
|
93
|
+
* same module by it.
|
|
94
|
+
*/
|
|
95
|
+
resolveFromEntry?: (specifier: string) => string | undefined;
|
|
76
96
|
}): ResolvedAnchor;
|
|
77
97
|
/**
|
|
78
98
|
* Locate the target node: tightest expression covering the byte span when
|
|
@@ -8,16 +8,35 @@ import ts from 'typescript';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { printTypeForDestination, substituteUndeclaredNames, undeclaredNamesIn, } from './node-builder.js';
|
|
10
10
|
import { typeIsOrContainsMachinery } from './machinery.js';
|
|
11
|
+
import { installedPackageSpecifier } from './installed-package.js';
|
|
12
|
+
import { realPath } from './service-config.js';
|
|
11
13
|
import { unresolvedAtAnchor, unresolvedSpecifiersReachableFrom, } from './unresolved.js';
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Repo-root-relative source file -> the specifier the surface entry imports
|
|
16
|
+
* it by: its path from `entryDir`, in the form the entry resolves.
|
|
17
|
+
*
|
|
18
|
+
* With the extension removed (`./a`), where `resolveFromEntry` says the entry
|
|
19
|
+
* resolves that to the module. It does not always (carrick#1911): under
|
|
20
|
+
* `module` `node16`..`nodenext` an entry in a `"type": "module"` package is
|
|
21
|
+
* an ES module, which must name the file the module compiles to, and a `.mts`
|
|
22
|
+
* or `.cts` module has no extensionless form under any setting. There, and
|
|
23
|
+
* with no resolver to ask, the specifier is that output name: `./a.js` for
|
|
24
|
+
* `.ts` and `.tsx`, `./a.mjs` for `.mts`, `./a.cjs` for `.cts`, which every
|
|
25
|
+
* module setting maps back to the source and, in the stub, to its
|
|
26
|
+
* declaration.
|
|
27
|
+
*/
|
|
28
|
+
export function entryRelativeSpecifier(entryDir, repoRoot, sourceFile, resolveFromEntry) {
|
|
29
|
+
const target = path.join(repoRoot, sourceFile);
|
|
30
|
+
const from = (file) => {
|
|
31
|
+
const rel = path.relative(entryDir, file).split(path.sep).join('/');
|
|
32
|
+
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
33
|
+
};
|
|
34
|
+
const bare = from(target.replace(/\.(ts|tsx|mts|cts)$/, ''));
|
|
35
|
+
const resolved = resolveFromEntry?.(bare);
|
|
36
|
+
if (resolved !== undefined && realPath(resolved) === realPath(target))
|
|
37
|
+
return bare;
|
|
38
|
+
const kind = /(?:\.d)?\.([cm]?)[jt]sx?$/.exec(target);
|
|
39
|
+
return kind ? from(`${target.slice(0, kind.index)}.${kind[1]}js`) : bare;
|
|
21
40
|
}
|
|
22
41
|
function moduleExport(checker, sourceFile, name) {
|
|
23
42
|
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
@@ -56,10 +75,13 @@ export function resolveAnchor(program, request, args) {
|
|
|
56
75
|
// sibling symbol anchor imports is resolved by that import.
|
|
57
76
|
// carrick#1774: the text names types as they read where it was printed,
|
|
58
77
|
// so a name its source file means by a repo module's export is imported
|
|
59
|
-
// from that module (a string-union alias printed bare read `any`)
|
|
78
|
+
// from that module (a string-union alias printed bare read `any`), and
|
|
79
|
+
// (carrick#1789) a name it imports from a package is imported from that
|
|
80
|
+
// package. carrick#1836: a name the source file cannot see is imported
|
|
81
|
+
// from the module the inference recorded it was printed for.
|
|
60
82
|
const scoped = siblingSpec
|
|
61
83
|
? undefined
|
|
62
|
-
: qualifyNamesFromSource(text, program, request.source_file, args);
|
|
84
|
+
: qualifyNamesFromSource(text, program, request.source_file, request.printed_names, args);
|
|
63
85
|
const scopedText = scoped ?? text;
|
|
64
86
|
// carrick#1377: rewrite what nothing declares to `unknown` in place, so
|
|
65
87
|
// one member typed by a module the checkout does not have stops taking
|
|
@@ -95,7 +117,7 @@ export function resolveAnchor(program, request, args) {
|
|
|
95
117
|
if (!sourceFile) {
|
|
96
118
|
return demote(`source file not in program: ${request.source_file}`);
|
|
97
119
|
}
|
|
98
|
-
const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file);
|
|
120
|
+
const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file, args.resolveFromEntry);
|
|
99
121
|
if (request.kind === 'symbol') {
|
|
100
122
|
const exported = moduleExport(checker, sourceFile, request.symbol_name);
|
|
101
123
|
if (!exported) {
|
|
@@ -390,15 +412,25 @@ function substituteUndeclaredNamesInText(text, program, destination) {
|
|
|
390
412
|
* member that reads `any`) or a global of the same name (`Notification`).
|
|
391
413
|
* Each reference whose name the source resolves to a type that a module
|
|
392
414
|
* inside the repo exports becomes `import('<module>').<export>`, with its
|
|
393
|
-
* qualifier and type arguments kept.
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
415
|
+
* qualifier and type arguments kept. A name the source imports from an
|
|
416
|
+
* installed package becomes `import('<package specifier>').<export>`
|
|
417
|
+
* (carrick#1789): the stub ships no `node_modules`, but it pins the packages
|
|
418
|
+
* its surface imports, and the check phase installs them. Anything else is
|
|
419
|
+
* left as written: a global, a name only a function body declares, an
|
|
420
|
+
* unexported local, and a type declared outside the repo that no package
|
|
421
|
+
* import reaches.
|
|
422
|
+
*
|
|
423
|
+
* carrick#1836: a name the source file cannot see at all was printed without
|
|
424
|
+
* its scope (the inferrer's structural printer writes names bare). When the
|
|
425
|
+
* inference recorded one declaration for it (`printed`), the name is imported
|
|
426
|
+
* from that declaration's module, under the same repo rule. A name recorded
|
|
427
|
+
* for two declarations is left as written: the text no longer says which one
|
|
428
|
+
* a position meant.
|
|
397
429
|
*
|
|
398
430
|
* Returns the rewritten text, or undefined when nothing was rewritten, so a
|
|
399
431
|
* text with no such name stays byte-identical.
|
|
400
432
|
*/
|
|
401
|
-
function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
433
|
+
function qualifyNamesFromSource(text, program, sourceFileRel, printed, args) {
|
|
402
434
|
if (!sourceFileRel)
|
|
403
435
|
return undefined;
|
|
404
436
|
const source = program.getSourceFile(path.join(args.repoRoot, sourceFileRel));
|
|
@@ -415,6 +447,14 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
|
415
447
|
ts.forEachChild(node, collect);
|
|
416
448
|
};
|
|
417
449
|
collect(parsed);
|
|
450
|
+
const specOf = (file) => {
|
|
451
|
+
const rel = path.relative(args.repoRoot, file.fileName);
|
|
452
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
|
|
453
|
+
return undefined;
|
|
454
|
+
if (rel.split(path.sep).includes('node_modules'))
|
|
455
|
+
return undefined;
|
|
456
|
+
return entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/'), args.resolveFromEntry);
|
|
457
|
+
};
|
|
418
458
|
const imports = new Map();
|
|
419
459
|
const importFor = (name) => {
|
|
420
460
|
if (imports.has(name))
|
|
@@ -423,12 +463,8 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
|
423
463
|
const atSource = checker.resolveName(name, source, meaning, false);
|
|
424
464
|
const target = atSource && resolveSymbolAliases(checker, atSource);
|
|
425
465
|
const declaringFile = target?.declarations?.[0]?.getSourceFile();
|
|
426
|
-
const
|
|
427
|
-
if (target &&
|
|
428
|
-
declaringFile &&
|
|
429
|
-
rel &&
|
|
430
|
-
!rel.startsWith('..') &&
|
|
431
|
-
!rel.split(path.sep).includes('node_modules')) {
|
|
466
|
+
const spec = declaringFile && specOf(declaringFile);
|
|
467
|
+
if (target && declaringFile && spec) {
|
|
432
468
|
const moduleSymbol = checker.getSymbolAtLocation(declaringFile);
|
|
433
469
|
const exported = moduleSymbol
|
|
434
470
|
? checker
|
|
@@ -436,28 +472,46 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
|
436
472
|
.filter((candidate) => resolveSymbolAliases(checker, candidate) === target)
|
|
437
473
|
: [];
|
|
438
474
|
const chosen = exported.find((candidate) => candidate.getName() === name) ?? exported[0];
|
|
439
|
-
if (chosen)
|
|
440
|
-
found = {
|
|
441
|
-
spec: entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/')),
|
|
442
|
-
exportName: chosen.getName(),
|
|
443
|
-
};
|
|
444
|
-
}
|
|
475
|
+
if (chosen)
|
|
476
|
+
found = { spec, exportPath: [chosen.getName()] };
|
|
445
477
|
}
|
|
478
|
+
if (!found && atSource)
|
|
479
|
+
found = packageImportOf(program, atSource, args.resolveFromEntry);
|
|
480
|
+
if (!found && !atSource)
|
|
481
|
+
found = printedImportOf(program, name, printed, specOf);
|
|
446
482
|
imports.set(name, found);
|
|
447
483
|
return found;
|
|
448
484
|
};
|
|
449
485
|
const leftmost = (name) => ts.isIdentifier(name) ? name : leftmost(name.left);
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
486
|
+
const entityName = (parts) => parts
|
|
487
|
+
.slice(1)
|
|
488
|
+
.reduce((left, part) => ts.factory.createQualifiedName(left, part), ts.factory.createIdentifier(parts[0]));
|
|
489
|
+
const replaceLeftmost = (name, to) => ts.isIdentifier(name)
|
|
490
|
+
? entityName(to)
|
|
491
|
+
: ts.factory.createQualifiedName(replaceLeftmost(name.left, to), name.right);
|
|
492
|
+
const dropLeftmost = (name) => ts.isIdentifier(name.left)
|
|
493
|
+
? name.right
|
|
494
|
+
: ts.factory.createQualifiedName(dropLeftmost(name.left), name.right);
|
|
495
|
+
// What follows `import('<spec>')`: the export path, then the reference's
|
|
496
|
+
// own qualifier. Through a namespace import the namespace's name goes, and
|
|
497
|
+
// the next name must be something the package exports.
|
|
498
|
+
const qualifierFor = (typeName, target) => {
|
|
499
|
+
if ('exportPath' in target)
|
|
500
|
+
return replaceLeftmost(typeName, target.exportPath);
|
|
501
|
+
if (ts.isIdentifier(typeName))
|
|
502
|
+
return undefined;
|
|
503
|
+
const qualifier = dropLeftmost(typeName);
|
|
504
|
+
return target.namespaceExports.has(leftmost(qualifier).text) ? qualifier : undefined;
|
|
505
|
+
};
|
|
453
506
|
let rewrites = 0;
|
|
454
507
|
const rewrite = (node) => {
|
|
455
508
|
if (ts.isTypeReferenceNode(node)) {
|
|
456
509
|
const name = leftmost(node.typeName).text;
|
|
457
510
|
const target = typeParameters.has(name) ? undefined : importFor(name);
|
|
458
|
-
|
|
511
|
+
const qualifier = target && qualifierFor(node.typeName, target);
|
|
512
|
+
if (target && qualifier) {
|
|
459
513
|
rewrites += 1;
|
|
460
|
-
return ts.factory.createImportTypeNode(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(target.spec)), undefined,
|
|
514
|
+
return ts.factory.createImportTypeNode(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(target.spec)), undefined, qualifier, node.typeArguments?.map((argument) => rewrite(argument)), false);
|
|
461
515
|
}
|
|
462
516
|
}
|
|
463
517
|
return ts.visitEachChild(node, rewrite, /* context */ undefined);
|
|
@@ -469,6 +523,96 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
|
469
523
|
.createPrinter({ removeComments: true })
|
|
470
524
|
.printNode(ts.EmitHint.Unspecified, rewritten, parsed.getSourceFile());
|
|
471
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* carrick#1789: how the surface entry imports `local`, a name the source file
|
|
528
|
+
* binds with an import from an installed package, or undefined.
|
|
529
|
+
*
|
|
530
|
+
* The entry names the module the source's import resolves to by its file
|
|
531
|
+
* path, as the node builder names a library type it cannot reach by a bare
|
|
532
|
+
* specifier; the post-emit rewrite turns that path into the package's bare
|
|
533
|
+
* specifier and pins the installed version (`installedPackageSpecifier`). A
|
|
534
|
+
* bare specifier in the entry itself would be resolved from the entry: on a
|
|
535
|
+
* Deno service that goes through the graph's virtual `node_modules`, and the
|
|
536
|
+
* emitter then reads the package there, cannot name its types from the
|
|
537
|
+
* service's own modules (TS2742), and skips their declarations.
|
|
538
|
+
*
|
|
539
|
+
* Kept only when the module lies inside an installed package and the bare
|
|
540
|
+
* specifier that package gives resolves from the entry to the same file. So
|
|
541
|
+
* a tsconfig path alias to the repo's own module, a workspace package linked
|
|
542
|
+
* from the repo (no installed copy to pin), and an import of another
|
|
543
|
+
* installed copy than the entry reaches all stay as written.
|
|
544
|
+
*/
|
|
545
|
+
function packageImportOf(program, local, resolveFromEntry) {
|
|
546
|
+
if (!resolveFromEntry || !(local.flags & ts.SymbolFlags.Alias))
|
|
547
|
+
return undefined;
|
|
548
|
+
const declaration = local.declarations?.[0];
|
|
549
|
+
let statement;
|
|
550
|
+
let exportName;
|
|
551
|
+
if (declaration && ts.isImportSpecifier(declaration)) {
|
|
552
|
+
// `import { "a-b" as name }` has no qualifier an import type can write.
|
|
553
|
+
const exported = declaration.propertyName ?? declaration.name;
|
|
554
|
+
if (!ts.isIdentifier(exported))
|
|
555
|
+
return undefined;
|
|
556
|
+
statement = declaration.parent.parent.parent;
|
|
557
|
+
exportName = exported.text;
|
|
558
|
+
}
|
|
559
|
+
else if (declaration && ts.isImportClause(declaration)) {
|
|
560
|
+
statement = declaration.parent;
|
|
561
|
+
exportName = 'default';
|
|
562
|
+
}
|
|
563
|
+
else if (declaration && ts.isNamespaceImport(declaration)) {
|
|
564
|
+
statement = declaration.parent.parent;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
if (!ts.isImportDeclaration(statement))
|
|
570
|
+
return undefined;
|
|
571
|
+
const checker = program.getTypeChecker();
|
|
572
|
+
const moduleSymbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
|
|
573
|
+
const moduleFile = moduleSymbol?.declarations?.find(ts.isSourceFile);
|
|
574
|
+
if (!moduleSymbol || !moduleFile)
|
|
575
|
+
return undefined;
|
|
576
|
+
const installed = installedPackageSpecifier(moduleFile.fileName, exportName);
|
|
577
|
+
const fromEntry = installed && resolveFromEntry(installed.specifier);
|
|
578
|
+
if (!fromEntry || realPath(fromEntry) !== realPath(moduleFile.fileName))
|
|
579
|
+
return undefined;
|
|
580
|
+
const spec = moduleFile.fileName;
|
|
581
|
+
const exported = new Set(checker.getExportsOfModule(moduleSymbol).map((symbol) => symbol.getName()));
|
|
582
|
+
if (exportName === undefined)
|
|
583
|
+
return { spec, namespaceExports: exported };
|
|
584
|
+
return exported.has(exportName) ? { spec, exportPath: [exportName] } : undefined;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* carrick#1836: the import for `name` from the one declaration the inference
|
|
588
|
+
* recorded printing it for, or undefined.
|
|
589
|
+
*
|
|
590
|
+
* Undefined when the name was recorded for no declaration or for several,
|
|
591
|
+
* when the recorded module is not in this program or not inside the repo
|
|
592
|
+
* (`specOf`), and when its export path does not lead to a type there: a record
|
|
593
|
+
* that does not check out names nothing, and the text stays as written.
|
|
594
|
+
*/
|
|
595
|
+
function printedImportOf(program, name, printed, specOf) {
|
|
596
|
+
const recorded = (printed ?? []).filter((entry) => entry.name === name);
|
|
597
|
+
const identities = new Set(recorded.map((entry) => `${entry.file}\0${entry.export_path.join('.')}`));
|
|
598
|
+
if (identities.size !== 1)
|
|
599
|
+
return undefined;
|
|
600
|
+
const entry = recorded[0];
|
|
601
|
+
const declaring = program.getSourceFile(entry.file);
|
|
602
|
+
const spec = declaring && specOf(declaring);
|
|
603
|
+
if (!declaring || !spec)
|
|
604
|
+
return undefined;
|
|
605
|
+
const checker = program.getTypeChecker();
|
|
606
|
+
let current = checker.getSymbolAtLocation(declaring);
|
|
607
|
+
for (const part of entry.export_path) {
|
|
608
|
+
const next = current && checker.getExportsOfModule(current).find((s) => s.getName() === part);
|
|
609
|
+
current = next && resolveSymbolAliases(checker, next);
|
|
610
|
+
}
|
|
611
|
+
if (!current || !(current.flags & (ts.SymbolFlags.Type | ts.SymbolFlags.Namespace))) {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
return { spec, exportPath: entry.export_path };
|
|
615
|
+
}
|
|
472
616
|
/** The type node of `type __LiteralAnchor = <text>;`, or undefined. */
|
|
473
617
|
function parseLiteralAnchor(text) {
|
|
474
618
|
const parsed = ts.createSourceFile('literal-anchor.ts', `type __LiteralAnchor = ${text};`, ts.ScriptTarget.Latest, true);
|
|
@@ -71,6 +71,36 @@ export interface LiteralAnchorRequest {
|
|
|
71
71
|
* record's `source_file` stays `<inline>`: the answer is still the text.
|
|
72
72
|
*/
|
|
73
73
|
source_file?: string;
|
|
74
|
+
/**
|
|
75
|
+
* carrick#1836: what the text's bare names meant where they were printed,
|
|
76
|
+
* for the names `source_file` cannot resolve. A name listed once is
|
|
77
|
+
* imported from the module that declares it; a name listed for two
|
|
78
|
+
* declarations is left as written.
|
|
79
|
+
*/
|
|
80
|
+
printed_names?: PrintedName[];
|
|
81
|
+
/**
|
|
82
|
+
* carrick#1842: the text is a body the call site reads as raw text. Copied
|
|
83
|
+
* onto the alias's record, where the check phase reads it.
|
|
84
|
+
*/
|
|
85
|
+
raw_text_read?: true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The declaration a bare name in printed type text meant (carrick#1836).
|
|
89
|
+
*
|
|
90
|
+
* The v1 inferrer prints some types with no enclosing declaration, and that
|
|
91
|
+
* print writes every named type by its bare name, whether or not the file the
|
|
92
|
+
* request names can see it. The compiler knew the symbol when it printed the
|
|
93
|
+
* name; this is that symbol, recorded as the module that declares it and the
|
|
94
|
+
* export path that reaches it there (`['Status']`, or `['Billing', 'Kind']`
|
|
95
|
+
* for a namespace member printed as `Kind`).
|
|
96
|
+
*/
|
|
97
|
+
export interface PrintedName {
|
|
98
|
+
/** The name as the text prints it: the leftmost part of a reference. */
|
|
99
|
+
name: string;
|
|
100
|
+
/** Absolute path of the module that declares it. */
|
|
101
|
+
file: string;
|
|
102
|
+
/** Export names from that module down to the declaration. */
|
|
103
|
+
export_path: string[];
|
|
74
104
|
}
|
|
75
105
|
/**
|
|
76
106
|
* Addressable handler: `export type A = Awaited<ReturnType<typeof
|
|
@@ -152,7 +182,10 @@ export type SelfCheckOutcome = 'ok' | 'allowlisted_external' | 'decayed_internal
|
|
|
152
182
|
* contract.
|
|
153
183
|
* - `machinery_envelope`: the return resolved to transport (a
|
|
154
184
|
* Response/Request-shaped envelope) and no payload was recoverable inside
|
|
155
|
-
* it or from the handler's returned arguments.
|
|
185
|
+
* it or from the handler's returned arguments. On a consumer call result it
|
|
186
|
+
* is a decided abstain: what the call's result carrier holds is transport
|
|
187
|
+
* the service's wrapper rules verify and read no payload out of, such as a
|
|
188
|
+
* request library's own response object (carrick#1841).
|
|
156
189
|
* - `coerced_input`: a request schema's INPUT is `any`/`unknown` at this
|
|
157
190
|
* position while its parsed output is concrete, which is what a coercion
|
|
158
191
|
* declares (carrick#1101). The published type carries the output there, so
|
|
@@ -271,6 +304,14 @@ export interface CaptureAliasRecord {
|
|
|
271
304
|
* entry's other provenance. Absent when every position resolves.
|
|
272
305
|
*/
|
|
273
306
|
unresolved_in_tree?: TypeProvenance[];
|
|
307
|
+
/**
|
|
308
|
+
* carrick#1842: the alias is a body its call site reads as raw text, from
|
|
309
|
+
* the literal anchor that published it. The check phase reads a pair with
|
|
310
|
+
* this on either side unverifiable: raw text states no structural contract,
|
|
311
|
+
* whether or not the other side's type assigns to `string`. Absent
|
|
312
|
+
* otherwise, and absent on every record a release before this one stored.
|
|
313
|
+
*/
|
|
314
|
+
raw_text_read?: true;
|
|
274
315
|
}
|
|
275
316
|
/** Aggregate fidelity metric, emitted per capture (one service). */
|
|
276
317
|
export interface CaptureFidelity {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* IsVoid gate fired (TS2344) -> unverifiable (no body read)
|
|
14
14
|
* IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
|
|
15
15
|
* IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
|
|
16
|
+
* raw-text read marked on a side -> unverifiable (text, even agreeing)
|
|
16
17
|
* assignment-class error -> incompatible
|
|
17
18
|
* no diagnostics -> compatible [lowest precedence]
|
|
18
19
|
*
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
* Seam: node builtins + this bundle only.
|
|
24
25
|
*/
|
|
25
26
|
import type { CheckVerdict } from './api.js';
|
|
26
|
-
import { type ProbePlan } from './check-probe.js';
|
|
27
|
+
import { type ProbePlan, type Side } from './check-probe.js';
|
|
27
28
|
import { type ScrubContext } from './check-scrub.js';
|
|
28
29
|
import type { PairDeepFindings } from './check-deep.js';
|
|
29
30
|
import { type PairFieldReport } from './check-fields.js';
|
|
@@ -65,6 +66,13 @@ export interface ClassifyInput {
|
|
|
65
66
|
* run, in which case the tsc text stands alone.
|
|
66
67
|
*/
|
|
67
68
|
fieldReport?: PairFieldReport;
|
|
69
|
+
/**
|
|
70
|
+
* The side whose capture record says it reads the body as raw text
|
|
71
|
+
* (carrick#1842), the sent side when both do. Read from the record, not the
|
|
72
|
+
* type: a text read publishes `string`, which no type gate can tell from a
|
|
73
|
+
* JSON body that is a string.
|
|
74
|
+
*/
|
|
75
|
+
rawTextSide?: Side;
|
|
68
76
|
}
|
|
69
77
|
/** Classify one pair into exactly one bucket, honouring the precedence order. */
|
|
70
78
|
export declare function classifyPair(input: ClassifyInput): CheckVerdict;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* IsVoid gate fired (TS2344) -> unverifiable (no body read)
|
|
14
14
|
* IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
|
|
15
15
|
* IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
|
|
16
|
+
* raw-text read marked on a side -> unverifiable (text, even agreeing)
|
|
16
17
|
* assignment-class error -> incompatible
|
|
17
18
|
* no diagnostics -> compatible [lowest precedence]
|
|
18
19
|
*
|
|
@@ -194,6 +195,20 @@ export function classifyPair(input) {
|
|
|
194
195
|
...notAFact(`the ${side} body is bytes`, side),
|
|
195
196
|
};
|
|
196
197
|
}
|
|
198
|
+
// 4d. A body read as raw text (carrick#1842): a `.text()` read, or a request
|
|
199
|
+
// library's text format, takes the body unparsed, so `string` there
|
|
200
|
+
// states no structural contract. Like bytes, the pair is not compared,
|
|
201
|
+
// whether or not the other side's type assigns to `string`.
|
|
202
|
+
if (input.rawTextSide && plan.spec.protocol === 'http') {
|
|
203
|
+
const side = input.rawTextSide;
|
|
204
|
+
return {
|
|
205
|
+
...base,
|
|
206
|
+
bucket: 'unverifiable',
|
|
207
|
+
gate: `${side}:text`,
|
|
208
|
+
diagnostic: `the ${side} reads the body as raw text, which has no JSON shape to compare with the other side.`,
|
|
209
|
+
...notAFact(`the ${side} reads the body as raw text`, side),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
197
212
|
// 5. Assignment-class error on the DECISIVE assignment line -> incompatible.
|
|
198
213
|
//
|
|
199
214
|
// On an `http` pair that line is the JSON wire assignment, not the declared
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import ts from 'typescript';
|
|
28
28
|
import * as fs from 'node:fs';
|
|
29
29
|
import * as path from 'node:path';
|
|
30
|
-
import { collectSpecifiers, isRelative } from './specifiers.js';
|
|
30
|
+
import { collectSpecifiers, declarationCandidates, isDeclarationFileName, isRelative, } from './specifiers.js';
|
|
31
31
|
/** One index per assembled stub, keyed by service name. */
|
|
32
32
|
export function buildPoisonIndexes(ws) {
|
|
33
33
|
const indexes = new Map();
|
|
@@ -45,14 +45,14 @@ function buildOne(ws, serviceName, packageDir) {
|
|
|
45
45
|
return undefined;
|
|
46
46
|
const keyPrefix = `packages/${packageDir}/types`;
|
|
47
47
|
const surfaceFile = `${keyPrefix}/surface.d.ts`;
|
|
48
|
-
// Collect the whole
|
|
48
|
+
// Collect the whole declaration tree, keyed as `packages/<dir>/types/<rel>`.
|
|
49
49
|
const treeAbs = [];
|
|
50
50
|
const walk = (dir) => {
|
|
51
51
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
52
52
|
const p = path.join(dir, entry.name);
|
|
53
53
|
if (entry.isDirectory())
|
|
54
54
|
walk(p);
|
|
55
|
-
else if (entry.name
|
|
55
|
+
else if (isDeclarationFileName(entry.name))
|
|
56
56
|
treeAbs.push(p);
|
|
57
57
|
}
|
|
58
58
|
};
|
|
@@ -63,17 +63,7 @@ function buildOne(ws, serviceName, packageDir) {
|
|
|
63
63
|
if (!isRelative(spec))
|
|
64
64
|
return undefined;
|
|
65
65
|
const base = path.resolve(path.dirname(fromAbs), spec);
|
|
66
|
-
|
|
67
|
-
`${base}.d.ts`,
|
|
68
|
-
path.join(base, 'index.d.ts'),
|
|
69
|
-
base.endsWith('.js') ? `${base.slice(0, -3)}.d.ts` : undefined,
|
|
70
|
-
base, // already `.d.ts`
|
|
71
|
-
].filter((c) => c !== undefined);
|
|
72
|
-
for (const c of candidates) {
|
|
73
|
-
if (treeAbsSet.has(path.resolve(c)))
|
|
74
|
-
return c;
|
|
75
|
-
}
|
|
76
|
-
return undefined;
|
|
66
|
+
return declarationCandidates(base).find((candidate) => treeAbsSet.has(candidate));
|
|
77
67
|
};
|
|
78
68
|
// Per-file relative-import adjacency, over the fileKey namespace.
|
|
79
69
|
const adjacency = new Map();
|
|
@@ -158,7 +158,8 @@ export async function runCheck(opts, onProgress) {
|
|
|
158
158
|
const spec = opts.pairs.find((p) => p.pair_key === v.pair_key);
|
|
159
159
|
v.pair_id = fnvOfSpec(spec);
|
|
160
160
|
}
|
|
161
|
-
const
|
|
161
|
+
const aliasRecords = readStubAliasRecords(opts.stubs);
|
|
162
|
+
const { preGated, probing } = preGate(plans, aliasRecords);
|
|
162
163
|
writeProbes(ws, probing);
|
|
163
164
|
const errors = [];
|
|
164
165
|
const degraded = [];
|
|
@@ -269,6 +270,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
269
270
|
scrubCtx,
|
|
270
271
|
deepFindings: deepByPair.get(plan.pairId),
|
|
271
272
|
fieldReport: fieldsByPair.get(plan.pairId),
|
|
273
|
+
rawTextSide: rawTextSideOf(plan, aliasRecords),
|
|
272
274
|
})),
|
|
273
275
|
...preGated,
|
|
274
276
|
...unresolved,
|
|
@@ -355,6 +357,16 @@ function preGate(plans, aliasRecords) {
|
|
|
355
357
|
}
|
|
356
358
|
return { preGated, probing };
|
|
357
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* The side whose capture record marks its body as read raw (carrick#1842):
|
|
362
|
+
* the sent side when both are, as the bytes gate names it.
|
|
363
|
+
*/
|
|
364
|
+
function rawTextSideOf(plan, aliasRecords) {
|
|
365
|
+
return [plan.direction.sent, plan.direction.expected].find((side) => {
|
|
366
|
+
const endpoint = plan.spec[side];
|
|
367
|
+
return aliasRecords.get(endpoint.service_name)?.get(endpoint.alias)?.raw_text_read === true;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
358
370
|
/** First side (producer, then consumer) whose capture recorded a deep decay. */
|
|
359
371
|
function deepDecayOf(plan, aliasRecords) {
|
|
360
372
|
for (const side of ['producer', 'consumer']) {
|