carrick 0.3.106 → 0.3.108
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 +15 -2
- package/sidecar/dist/src/capture/anchors.js +28 -11
- package/sidecar/dist/src/capture/api.d.ts +15 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +9 -0
- package/sidecar/dist/src/capture/check-classify.js +20 -0
- package/sidecar/dist/src/capture/check-fields.d.ts +24 -0
- package/sidecar/dist/src/capture/check-fields.js +54 -36
- package/sidecar/dist/src/capture/check-poison.js +4 -14
- package/sidecar/dist/src/capture/check-union.d.ts +40 -0
- package/sidecar/dist/src/capture/check-union.js +92 -0
- package/sidecar/dist/src/capture/check.js +5 -0
- package/sidecar/dist/src/capture/index.js +138 -80
- 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 +3 -13
- package/sidecar/dist/src/capture/specifiers.d.ts +14 -0
- package/sidecar/dist/src/capture/specifiers.js +25 -0
- package/sidecar/dist/src/definition-resolver.d.ts +5 -8
- package/sidecar/dist/src/definition-resolver.js +5 -7
- 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 +62 -9
- package/sidecar/dist/src/infer-timing.d.ts +54 -0
- package/sidecar/dist/src/infer-timing.js +124 -0
- package/sidecar/dist/src/line-index.d.ts +9 -0
- package/sidecar/dist/src/line-index.js +26 -0
- package/sidecar/dist/src/progress.d.ts +22 -0
- package/sidecar/dist/src/progress.js +31 -0
- package/sidecar/dist/src/retype.d.ts +4 -2
- package/sidecar/dist/src/retype.js +10 -23
- package/sidecar/dist/src/type-inferrer.d.ts +238 -12
- package/sidecar/dist/src/type-inferrer.js +772 -168
- package/sidecar/dist/src/types.d.ts +83 -6
- package/sidecar/dist/src/validators.d.ts +54 -16
- package/sidecar/dist/src/validators.js +4 -0
- package/templates/skills/carrick-reuse.md +6 -5
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.108",
|
|
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.108",
|
|
62
|
+
"@carrick-tools/cli-darwin-x64": "0.3.108",
|
|
63
|
+
"@carrick-tools/cli-linux-arm64": "0.3.108",
|
|
64
|
+
"@carrick-tools/cli-linux-x64": "0.3.108",
|
|
65
|
+
"@carrick-tools/cli-win32-x64": "0.3.108"
|
|
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.108",
|
|
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
|
|
@@ -11,15 +11,32 @@ import { typeIsOrContainsMachinery } from './machinery.js';
|
|
|
11
11
|
import { installedPackageSpecifier } from './installed-package.js';
|
|
12
12
|
import { realPath } from './service-config.js';
|
|
13
13
|
import { unresolvedAtAnchor, unresolvedSpecifiersReachableFrom, } from './unresolved.js';
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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;
|
|
23
40
|
}
|
|
24
41
|
function moduleExport(checker, sourceFile, name) {
|
|
25
42
|
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
@@ -100,7 +117,7 @@ export function resolveAnchor(program, request, args) {
|
|
|
100
117
|
if (!sourceFile) {
|
|
101
118
|
return demote(`source file not in program: ${request.source_file}`);
|
|
102
119
|
}
|
|
103
|
-
const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file);
|
|
120
|
+
const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file, args.resolveFromEntry);
|
|
104
121
|
if (request.kind === 'symbol') {
|
|
105
122
|
const exported = moduleExport(checker, sourceFile, request.symbol_name);
|
|
106
123
|
if (!exported) {
|
|
@@ -436,7 +453,7 @@ function qualifyNamesFromSource(text, program, sourceFileRel, printed, args) {
|
|
|
436
453
|
return undefined;
|
|
437
454
|
if (rel.split(path.sep).includes('node_modules'))
|
|
438
455
|
return undefined;
|
|
439
|
-
return entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/'));
|
|
456
|
+
return entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/'), args.resolveFromEntry);
|
|
440
457
|
};
|
|
441
458
|
const imports = new Map();
|
|
442
459
|
const importFor = (name) => {
|
|
@@ -346,6 +346,14 @@ export interface CaptureStubResult {
|
|
|
346
346
|
ts_version: string;
|
|
347
347
|
errors: string[];
|
|
348
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* The stages of a capture that it reports as it reaches them (carrick#1916),
|
|
351
|
+
* in order: the program the anchors are read in is being built; the anchors
|
|
352
|
+
* are being resolved in it; the declarations are being emitted; the stub is
|
|
353
|
+
* being checked. A capture whose anchors belong to several projects goes
|
|
354
|
+
* through the first two once per project.
|
|
355
|
+
*/
|
|
356
|
+
export type CapturePhase = 'program' | 'anchors' | 'emit' | 'self-check';
|
|
349
357
|
export interface CaptureStubOptions {
|
|
350
358
|
repoRoot: string;
|
|
351
359
|
serviceName: string;
|
|
@@ -360,6 +368,13 @@ export interface CaptureStubOptions {
|
|
|
360
368
|
* lie inside it only beneath a `.carrick` directory (carrick#1768).
|
|
361
369
|
*/
|
|
362
370
|
scanRoot?: string;
|
|
371
|
+
/**
|
|
372
|
+
* Told each stage as the capture reaches it, and each anchor as it is
|
|
373
|
+
* resolved (`message` is then `<done> of <total>`). Called by the work
|
|
374
|
+
* itself, between units, so a capture that stops finishing them stops
|
|
375
|
+
* reporting. It changes nothing the capture writes.
|
|
376
|
+
*/
|
|
377
|
+
onProgress?: (phase: CapturePhase, message: string) => void;
|
|
363
378
|
}
|
|
364
379
|
/** Wire protocol of a matched pair (drives the direction table). */
|
|
365
380
|
export type ProbeProtocol = 'http' | 'graphql' | 'socket' | 'pubsub';
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
|
|
15
15
|
* IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
|
|
16
16
|
* raw-text read marked on a side -> unverifiable (text, even agreeing)
|
|
17
|
+
* assignment error, sent union taken in part by a weak expected type
|
|
18
|
+
* -> unverifiable (carrick#1995)
|
|
17
19
|
* assignment-class error -> incompatible
|
|
18
20
|
* no diagnostics -> compatible [lowest precedence]
|
|
19
21
|
*
|
|
@@ -27,6 +29,7 @@ import type { CheckVerdict } from './api.js';
|
|
|
27
29
|
import { type ProbePlan, type Side } from './check-probe.js';
|
|
28
30
|
import { type ScrubContext } from './check-scrub.js';
|
|
29
31
|
import type { PairDeepFindings } from './check-deep.js';
|
|
32
|
+
import type { DispatchUnion } from './check-union.js';
|
|
30
33
|
import { type PairFieldReport } from './check-fields.js';
|
|
31
34
|
export interface RawDiagnostic {
|
|
32
35
|
/** Workspace-relative, forward-slash file path (empty for global errors). */
|
|
@@ -66,6 +69,12 @@ export interface ClassifyInput {
|
|
|
66
69
|
* run, in which case the tsc text stands alone.
|
|
67
70
|
*/
|
|
68
71
|
fieldReport?: PairFieldReport;
|
|
72
|
+
/**
|
|
73
|
+
* Set when the sent side is a union the expected side takes only in part,
|
|
74
|
+
* every other member failing by nothing but the compiler's weak-type check
|
|
75
|
+
* (carrick#1995). Read only where the decisive assignment failed.
|
|
76
|
+
*/
|
|
77
|
+
dispatchUnion?: DispatchUnion;
|
|
69
78
|
/**
|
|
70
79
|
* The side whose capture record says it reads the body as raw text
|
|
71
80
|
* (carrick#1842), the sent side when both do. Read from the record, not the
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
|
|
15
15
|
* IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
|
|
16
16
|
* raw-text read marked on a side -> unverifiable (text, even agreeing)
|
|
17
|
+
* assignment error, sent union taken in part by a weak expected type
|
|
18
|
+
* -> unverifiable (carrick#1995)
|
|
17
19
|
* assignment-class error -> incompatible
|
|
18
20
|
* no diagnostics -> compatible [lowest precedence]
|
|
19
21
|
*
|
|
@@ -220,6 +222,24 @@ export function classifyPair(input) {
|
|
|
220
222
|
// the `string` it becomes) has no error on this line and is not a drift.
|
|
221
223
|
const decisiveLine = decisiveAssignmentLine(plan);
|
|
222
224
|
const assignDiag = probeDiags.find((d) => d.line === decisiveLine && ASSIGNMENT_CODES.has(d.code));
|
|
225
|
+
// 5a. Unless the mismatch is a sent union the expected type takes in part,
|
|
226
|
+
// the members it rejects sharing no field with a type whose every field
|
|
227
|
+
// is optional (carrick#1995). That is a route answering a body per
|
|
228
|
+
// branch read by a caller of one branch: no member breaks a reader whose
|
|
229
|
+
// every read allows absence, and which member the call receives is not
|
|
230
|
+
// in the types. Not a fact either way, and no side to retype.
|
|
231
|
+
if (assignDiag && input.dispatchUnion) {
|
|
232
|
+
const { members, agreeing } = input.dispatchUnion;
|
|
233
|
+
const sent = plan.direction.sent;
|
|
234
|
+
const expected = plan.direction.expected;
|
|
235
|
+
return {
|
|
236
|
+
...base,
|
|
237
|
+
bucket: 'unverifiable',
|
|
238
|
+
gate: `${sent}:union`,
|
|
239
|
+
diagnostic: `the ${sent} type is a union of ${members} bodies; the ${expected} type, every field optional, accepts ${agreeing} of them and shares no field with the other ${members - agreeing}, so the call reads one of the bodies the route answers and the types do not say which.`,
|
|
240
|
+
...notAFact(`the ${sent} type is a union of bodies and the ${expected} type, every field optional, accepts only some of them`),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
223
243
|
if (assignDiag) {
|
|
224
244
|
const text = scrubDiagnostic(assignDiag.message, scrubCtx, plan.sentEndpoint.alias, plan.expectedEndpoint.alias);
|
|
225
245
|
return {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* Seam: node builtins + `typescript` + this bundle only.
|
|
30
30
|
*/
|
|
31
|
+
import ts from 'typescript';
|
|
31
32
|
import type { ProbePlan, Side } from './check-probe.js';
|
|
32
33
|
import type { ProbeProgram } from './check-deep.js';
|
|
33
34
|
/** What kind of difference one field path carries. */
|
|
@@ -69,6 +70,29 @@ export interface PairFieldReport {
|
|
|
69
70
|
* how many more there are rather than pretending the list is complete.
|
|
70
71
|
*/
|
|
71
72
|
export declare const MAX_NAMED_FIELDS = 8;
|
|
73
|
+
/**
|
|
74
|
+
* The two types the judge's decisive assignment compared for one plan, read in
|
|
75
|
+
* the probe program, with the compiler's own assignability relation.
|
|
76
|
+
* `undefined` when the probe or either alias cannot be read, or the compiler
|
|
77
|
+
* build exposes no relation: every statement made without them would be a
|
|
78
|
+
* guess.
|
|
79
|
+
*
|
|
80
|
+
* The sent side is whatever that assignment actually sent: the GraphQL
|
|
81
|
+
* comparand where one exists, then the JSON wire form where serialising
|
|
82
|
+
* changes the type. Reading `sent` there instead would describe a type the
|
|
83
|
+
* judge did not compare, which is the one way a reader of this pair could
|
|
84
|
+
* contradict it.
|
|
85
|
+
*/
|
|
86
|
+
export declare function comparedTypes(opened: ProbeProgram, plan: ProbePlan): {
|
|
87
|
+
compared: ts.Type;
|
|
88
|
+
expected: {
|
|
89
|
+
type: ts.Type;
|
|
90
|
+
node: ts.Node;
|
|
91
|
+
};
|
|
92
|
+
wireApplied: boolean;
|
|
93
|
+
checker: ts.TypeChecker;
|
|
94
|
+
isAssignableTo: (source: ts.Type, target: ts.Type) => boolean;
|
|
95
|
+
} | undefined;
|
|
72
96
|
/**
|
|
73
97
|
* Field reports for every plan whose probe the program could read, keyed by
|
|
74
98
|
* pair id. A plan with no entry has no report, which is not a claim that its
|
|
@@ -54,6 +54,56 @@ function assignabilityOf(checker) {
|
|
|
54
54
|
const fn = checker.isTypeAssignableTo;
|
|
55
55
|
return typeof fn === 'function' ? fn.bind(checker) : undefined;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The two types the judge's decisive assignment compared for one plan, read in
|
|
59
|
+
* the probe program, with the compiler's own assignability relation.
|
|
60
|
+
* `undefined` when the probe or either alias cannot be read, or the compiler
|
|
61
|
+
* build exposes no relation: every statement made without them would be a
|
|
62
|
+
* guess.
|
|
63
|
+
*
|
|
64
|
+
* The sent side is whatever that assignment actually sent: the GraphQL
|
|
65
|
+
* comparand where one exists, then the JSON wire form where serialising
|
|
66
|
+
* changes the type. Reading `sent` there instead would describe a type the
|
|
67
|
+
* judge did not compare, which is the one way a reader of this pair could
|
|
68
|
+
* contradict it.
|
|
69
|
+
*/
|
|
70
|
+
export function comparedTypes(opened, plan) {
|
|
71
|
+
const { program, checker, probesDir } = opened;
|
|
72
|
+
const isAssignableTo = assignabilityOf(checker);
|
|
73
|
+
if (!isAssignableTo)
|
|
74
|
+
return undefined;
|
|
75
|
+
const file = program.getSourceFile(`${probesDir}/probes/${plan.fileName}`.split('\\').join('/'));
|
|
76
|
+
const source = file ??
|
|
77
|
+
program.getSourceFiles().find((sf) => sf.fileName.endsWith(`/probes/${plan.fileName}`));
|
|
78
|
+
if (!source)
|
|
79
|
+
return undefined;
|
|
80
|
+
const declared = declaredConstType(source, checker, 'sentComparand') ??
|
|
81
|
+
declaredConstType(source, checker, 'sent');
|
|
82
|
+
const expected = declaredConstType(source, checker, 'expected');
|
|
83
|
+
if (!declared || !expected)
|
|
84
|
+
return undefined;
|
|
85
|
+
const wire = declaredConstType(source, checker, 'sentWire');
|
|
86
|
+
// Whether serialising changes anything observable about the sent type.
|
|
87
|
+
const wireApplied = wire !== undefined &&
|
|
88
|
+
!(isAssignableTo(wire.type, declared.type) && isAssignableTo(declared.type, wire.type));
|
|
89
|
+
// The DECLARED type unless serialising really changed it.
|
|
90
|
+
//
|
|
91
|
+
// The probe declares its wire comparand through a conditional alias that
|
|
92
|
+
// short-circuits back to the declared type whenever that already assigns,
|
|
93
|
+
// and a conditional the checker has not had to resolve carries no members
|
|
94
|
+
// to walk. Reading it unconditionally therefore emptied the field report on
|
|
95
|
+
// exactly the pairs that AGREE — the ones whose only statement is an
|
|
96
|
+
// optionality gap or the wire note (carrick#1341). Where serialisation did
|
|
97
|
+
// change the type the wire form is a mapped type with real members, and it
|
|
98
|
+
// stays the thing compared, because that is what the judge judged.
|
|
99
|
+
return {
|
|
100
|
+
compared: wireApplied ? wire.type : declared.type,
|
|
101
|
+
expected,
|
|
102
|
+
wireApplied,
|
|
103
|
+
checker,
|
|
104
|
+
isAssignableTo,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
57
107
|
/**
|
|
58
108
|
* Field reports for every plan whose probe the program could read, keyed by
|
|
59
109
|
* pair id. A plan with no entry has no report, which is not a claim that its
|
|
@@ -63,44 +113,12 @@ export function pairFieldReports(opened, plans) {
|
|
|
63
113
|
const results = new Map();
|
|
64
114
|
if (!opened)
|
|
65
115
|
return results;
|
|
66
|
-
const { program, checker, probesDir } = opened;
|
|
67
|
-
const isAssignableTo = assignabilityOf(checker);
|
|
68
|
-
if (!isAssignableTo)
|
|
69
|
-
return results;
|
|
70
116
|
for (const plan of plans) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
program
|
|
74
|
-
.getSourceFiles()
|
|
75
|
-
.find((sf) => sf.fileName.endsWith(`/probes/${plan.fileName}`));
|
|
76
|
-
if (!source)
|
|
77
|
-
continue;
|
|
78
|
-
// Whatever the judge's decisive assignment actually sent: the GraphQL
|
|
79
|
-
// comparand where one exists, then the JSON wire form where one exists.
|
|
80
|
-
// Reading `sent` there instead would describe a type the judge did not
|
|
81
|
-
// compare, which is the one way this walk could contradict it.
|
|
82
|
-
const declared = declaredConstType(source, checker, 'sentComparand') ??
|
|
83
|
-
declaredConstType(source, checker, 'sent');
|
|
84
|
-
const expected = declaredConstType(source, checker, 'expected');
|
|
85
|
-
if (!declared || !expected)
|
|
117
|
+
const pair = comparedTypes(opened, plan);
|
|
118
|
+
if (!pair)
|
|
86
119
|
continue;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const wireChanges = wire !== undefined &&
|
|
90
|
-
!(isAssignableTo(wire.type, declared.type) && isAssignableTo(declared.type, wire.type));
|
|
91
|
-
// Walk the DECLARED type unless serialising really changed it.
|
|
92
|
-
//
|
|
93
|
-
// The probe declares its wire comparand through a conditional alias that
|
|
94
|
-
// short-circuits back to the declared type whenever that already assigns,
|
|
95
|
-
// and a conditional the checker has not had to resolve carries no members
|
|
96
|
-
// to walk. Reading it unconditionally therefore emptied the report on
|
|
97
|
-
// exactly the pairs that AGREE — the ones whose only statement is an
|
|
98
|
-
// optionality gap or the wire note (carrick#1341). Where serialisation
|
|
99
|
-
// did change the type the wire form is a mapped type with real members,
|
|
100
|
-
// and it stays the thing compared, because that is what the judge judged.
|
|
101
|
-
const compared = wireChanges ? wire.type : declared.type;
|
|
102
|
-
const report = diffReport(compared, expected.type, checker, isAssignableTo, expected.node, plan.spec.protocol === 'graphql' ? GRAPHQL_SERVER_SUPPLIED : NONE_SERVER_SUPPLIED);
|
|
103
|
-
report.wireApplied = wireChanges;
|
|
120
|
+
const report = diffReport(pair.compared, pair.expected.type, pair.checker, pair.isAssignableTo, pair.expected.node, plan.spec.protocol === 'graphql' ? GRAPHQL_SERVER_SUPPLIED : NONE_SERVER_SUPPLIED);
|
|
121
|
+
report.wireApplied = pair.wireApplied;
|
|
104
122
|
results.set(plan.pairId, report);
|
|
105
123
|
}
|
|
106
124
|
return results;
|
|
@@ -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();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sent union whose members the expected type takes only in part, where the
|
|
3
|
+
* rest fail by nothing but the compiler's weak-type check (carrick#1995).
|
|
4
|
+
*
|
|
5
|
+
* A handler that answers a different body per branch (an action that switches
|
|
6
|
+
* on a field of the request) publishes the union of those bodies. A caller of
|
|
7
|
+
* one branch reads the response with a type whose every field is optional. The
|
|
8
|
+
* compiler then rejects each member that shares no field with that type
|
|
9
|
+
* (TS2559, "has no properties in common"): a type whose every property is
|
|
10
|
+
* optional is a "weak type", and the check exists to catch a misspelt property
|
|
11
|
+
* bag. It is not a soundness rule. At runtime every member satisfies a type
|
|
12
|
+
* whose reads all allow absence, and the call only ever receives the body of
|
|
13
|
+
* the branch it asked for, which the types do not say.
|
|
14
|
+
*
|
|
15
|
+
* So the pair is neither compatible nor incompatible: the judge cannot say
|
|
16
|
+
* which member the call receives. Kept narrow on purpose:
|
|
17
|
+
* - the sent type must be a union with at least one member that assigns;
|
|
18
|
+
* - the expected type must be one object type whose every property is
|
|
19
|
+
* optional, with no index or call signatures;
|
|
20
|
+
* - every member that does not assign must be an object with properties, none
|
|
21
|
+
* of which the expected type names.
|
|
22
|
+
* A sent type that is not a union and shares no field with a weak expected
|
|
23
|
+
* type is the misspelling the check exists for, and stays a mismatch.
|
|
24
|
+
*
|
|
25
|
+
* Read in the probe program, over the two types the decisive assignment
|
|
26
|
+
* compared, with the compiler's own relation. Seam: typescript + this bundle.
|
|
27
|
+
*/
|
|
28
|
+
import ts from 'typescript';
|
|
29
|
+
import type { ProbePlan } from './check-probe.js';
|
|
30
|
+
import type { ProbeProgram } from './check-deep.js';
|
|
31
|
+
export interface DispatchUnion {
|
|
32
|
+
/** Members of the sent union. */
|
|
33
|
+
members: number;
|
|
34
|
+
/** Members the expected type takes. */
|
|
35
|
+
agreeing: number;
|
|
36
|
+
}
|
|
37
|
+
/** The finding for every plan whose compared types have this shape, by pair id. */
|
|
38
|
+
export declare function pairDispatchUnions(opened: ProbeProgram | undefined, plans: ProbePlan[]): Map<string, DispatchUnion>;
|
|
39
|
+
/** The shape above for one compared pair, or `undefined` when it does not hold. */
|
|
40
|
+
export declare function dispatchUnion(sent: ts.Type, expected: ts.Type, checker: ts.TypeChecker, isAssignableTo: (source: ts.Type, target: ts.Type) => boolean): DispatchUnion | undefined;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sent union whose members the expected type takes only in part, where the
|
|
3
|
+
* rest fail by nothing but the compiler's weak-type check (carrick#1995).
|
|
4
|
+
*
|
|
5
|
+
* A handler that answers a different body per branch (an action that switches
|
|
6
|
+
* on a field of the request) publishes the union of those bodies. A caller of
|
|
7
|
+
* one branch reads the response with a type whose every field is optional. The
|
|
8
|
+
* compiler then rejects each member that shares no field with that type
|
|
9
|
+
* (TS2559, "has no properties in common"): a type whose every property is
|
|
10
|
+
* optional is a "weak type", and the check exists to catch a misspelt property
|
|
11
|
+
* bag. It is not a soundness rule. At runtime every member satisfies a type
|
|
12
|
+
* whose reads all allow absence, and the call only ever receives the body of
|
|
13
|
+
* the branch it asked for, which the types do not say.
|
|
14
|
+
*
|
|
15
|
+
* So the pair is neither compatible nor incompatible: the judge cannot say
|
|
16
|
+
* which member the call receives. Kept narrow on purpose:
|
|
17
|
+
* - the sent type must be a union with at least one member that assigns;
|
|
18
|
+
* - the expected type must be one object type whose every property is
|
|
19
|
+
* optional, with no index or call signatures;
|
|
20
|
+
* - every member that does not assign must be an object with properties, none
|
|
21
|
+
* of which the expected type names.
|
|
22
|
+
* A sent type that is not a union and shares no field with a weak expected
|
|
23
|
+
* type is the misspelling the check exists for, and stays a mismatch.
|
|
24
|
+
*
|
|
25
|
+
* Read in the probe program, over the two types the decisive assignment
|
|
26
|
+
* compared, with the compiler's own relation. Seam: typescript + this bundle.
|
|
27
|
+
*/
|
|
28
|
+
import ts from 'typescript';
|
|
29
|
+
import { comparedTypes } from './check-fields.js';
|
|
30
|
+
/** The finding for every plan whose compared types have this shape, by pair id. */
|
|
31
|
+
export function pairDispatchUnions(opened, plans) {
|
|
32
|
+
const results = new Map();
|
|
33
|
+
if (!opened)
|
|
34
|
+
return results;
|
|
35
|
+
for (const plan of plans) {
|
|
36
|
+
const pair = comparedTypes(opened, plan);
|
|
37
|
+
if (!pair)
|
|
38
|
+
continue;
|
|
39
|
+
const finding = dispatchUnion(pair.compared, pair.expected.type, pair.checker, pair.isAssignableTo);
|
|
40
|
+
if (finding)
|
|
41
|
+
results.set(plan.pairId, finding);
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
/** The shape above for one compared pair, or `undefined` when it does not hold. */
|
|
46
|
+
export function dispatchUnion(sent, expected, checker, isAssignableTo) {
|
|
47
|
+
if (!sent.isUnion())
|
|
48
|
+
return undefined;
|
|
49
|
+
const names = weakPropertyNames(expected, checker);
|
|
50
|
+
if (!names)
|
|
51
|
+
return undefined;
|
|
52
|
+
let agreeing = 0;
|
|
53
|
+
for (const member of sent.types) {
|
|
54
|
+
if (isAssignableTo(member, expected)) {
|
|
55
|
+
agreeing += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!sharesNoProperty(member, names, checker))
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
if (agreeing === 0 || agreeing === sent.types.length)
|
|
62
|
+
return undefined;
|
|
63
|
+
return { members: sent.types.length, agreeing };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The property names of a weak object type: one object type, at least one
|
|
67
|
+
* property, every property optional, no index signature and no call or
|
|
68
|
+
* construct signature. `undefined` for any other type.
|
|
69
|
+
*/
|
|
70
|
+
function weakPropertyNames(type, checker) {
|
|
71
|
+
if (!(type.flags & ts.TypeFlags.Object))
|
|
72
|
+
return undefined;
|
|
73
|
+
if (checker.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0 ||
|
|
74
|
+
checker.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0 ||
|
|
75
|
+
checker.getIndexInfosOfType(type).length > 0) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const properties = checker.getPropertiesOfType(type);
|
|
79
|
+
if (properties.length === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (!properties.every((property) => (property.flags & ts.SymbolFlags.Optional) !== 0)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return new Set(properties.map((property) => property.name));
|
|
85
|
+
}
|
|
86
|
+
/** Whether `member` is an object with properties, none of them in `names`. */
|
|
87
|
+
function sharesNoProperty(member, names, checker) {
|
|
88
|
+
if (!(member.flags & ts.TypeFlags.Object))
|
|
89
|
+
return false;
|
|
90
|
+
const properties = checker.getPropertiesOfType(member);
|
|
91
|
+
return properties.length > 0 && properties.every((property) => !names.has(property.name));
|
|
92
|
+
}
|
|
@@ -26,6 +26,7 @@ import { assembleWorkspace, writeProbes, } from './check-workspace.js';
|
|
|
26
26
|
import { buildPoisonIndexes } from './check-poison.js';
|
|
27
27
|
import { openProbeProgram, probeDeepFindings } from './check-deep.js';
|
|
28
28
|
import { pairFieldReports } from './check-fields.js';
|
|
29
|
+
import { pairDispatchUnions } from './check-union.js';
|
|
29
30
|
function runProcess(command, args, cwd) {
|
|
30
31
|
return new Promise((resolve, reject) => {
|
|
31
32
|
const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
@@ -262,6 +263,9 @@ export async function runCheck(opts, onProgress) {
|
|
|
262
263
|
// incompatible (carrick-tools/carrick-cloud#1118). It names what the verdict
|
|
263
264
|
// is about; it never decides one.
|
|
264
265
|
const fieldsByPair = pairFieldReports(probeProgram, probing);
|
|
266
|
+
// And which pairs compared a union the expected side takes only in part,
|
|
267
|
+
// the rest failing by nothing but the weak-type check (carrick#1995).
|
|
268
|
+
const unionsByPair = pairDispatchUnions(probeProgram, probing);
|
|
265
269
|
const verdicts = sortVerdicts([
|
|
266
270
|
...probing.map((plan) => classifyPair({
|
|
267
271
|
plan,
|
|
@@ -270,6 +274,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
270
274
|
scrubCtx,
|
|
271
275
|
deepFindings: deepByPair.get(plan.pairId),
|
|
272
276
|
fieldReport: fieldsByPair.get(plan.pairId),
|
|
277
|
+
dispatchUnion: unionsByPair.get(plan.pairId),
|
|
273
278
|
rawTextSide: rawTextSideOf(plan, aliasRecords),
|
|
274
279
|
})),
|
|
275
280
|
...preGated,
|