carrick 0.3.53
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/LICENSE.md +99 -0
- package/README.md +79 -0
- package/bin/carrick.mjs +168 -0
- package/dist/channel.d.ts +16 -0
- package/dist/channel.js +37 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +154 -0
- package/dist/contract.js +141 -0
- package/dist/contract.js.map +1 -0
- package/dist/diagnostics.d.ts +54 -0
- package/dist/diagnostics.js +199 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hook/post-edit.d.ts +13 -0
- package/dist/hook/post-edit.js +76 -0
- package/dist/hook/post-edit.js.map +1 -0
- package/dist/hook/session-start.d.ts +2 -0
- package/dist/hook/session-start.js +44 -0
- package/dist/hook/session-start.js.map +1 -0
- package/dist/init/identity.d.ts +20 -0
- package/dist/init/identity.js +60 -0
- package/dist/init/identity.js.map +1 -0
- package/dist/init/repos.d.ts +19 -0
- package/dist/init/repos.js +46 -0
- package/dist/init/repos.js.map +1 -0
- package/dist/init/run.d.ts +11 -0
- package/dist/init/run.js +228 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/settings.d.ts +50 -0
- package/dist/init/settings.js +138 -0
- package/dist/init/settings.js.map +1 -0
- package/dist/log.d.ts +2 -0
- package/dist/log.js +35 -0
- package/dist/log.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.js +127 -0
- package/dist/native.js.map +1 -0
- package/dist/render.d.ts +65 -0
- package/dist/render.js +334 -0
- package/dist/render.js.map +1 -0
- package/dist/root.d.ts +30 -0
- package/dist/root.js +82 -0
- package/dist/root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +255 -0
- package/dist/server.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -0
- package/package.json +72 -0
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/.lsp.json +14 -0
- package/plugin/hooks/hooks.json +27 -0
- package/sidecar/dist/src/bundler.d.ts +141 -0
- package/sidecar/dist/src/bundler.js +680 -0
- package/sidecar/dist/src/capture/anchors.d.ts +61 -0
- package/sidecar/dist/src/capture/anchors.js +1132 -0
- package/sidecar/dist/src/capture/api.d.ts +378 -0
- package/sidecar/dist/src/capture/api.js +10 -0
- package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
- package/sidecar/dist/src/capture/augmentations.js +60 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
- package/sidecar/dist/src/capture/check-classify.js +189 -0
- package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
- package/sidecar/dist/src/capture/check-deep.js +91 -0
- package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
- package/sidecar/dist/src/capture/check-poison.js +155 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
- package/sidecar/dist/src/capture/check-probe.js +147 -0
- package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
- package/sidecar/dist/src/capture/check-scrub.js +71 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
- package/sidecar/dist/src/capture/check-workspace.js +218 -0
- package/sidecar/dist/src/capture/check.d.ts +39 -0
- package/sidecar/dist/src/capture/check.js +450 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
- package/sidecar/dist/src/capture/deep-walk.js +243 -0
- package/sidecar/dist/src/capture/index.d.ts +36 -0
- package/sidecar/dist/src/capture/index.js +477 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
- package/sidecar/dist/src/capture/lockfile.js +490 -0
- package/sidecar/dist/src/capture/machinery.d.ts +59 -0
- package/sidecar/dist/src/capture/machinery.js +160 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
- package/sidecar/dist/src/capture/node-builder.js +123 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
- package/sidecar/dist/src/capture/self-check.d.ts +38 -0
- package/sidecar/dist/src/capture/self-check.js +317 -0
- package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
- package/sidecar/dist/src/capture/specifiers.js +56 -0
- package/sidecar/dist/src/definition-resolver.d.ts +57 -0
- package/sidecar/dist/src/definition-resolver.js +153 -0
- package/sidecar/dist/src/index.d.ts +14 -0
- package/sidecar/dist/src/index.js +564 -0
- package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
- package/sidecar/dist/src/monorepo-builder.js +584 -0
- package/sidecar/dist/src/project-loader.d.ts +130 -0
- package/sidecar/dist/src/project-loader.js +399 -0
- package/sidecar/dist/src/type-inferrer.d.ts +940 -0
- package/sidecar/dist/src/type-inferrer.js +3540 -0
- package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
- package/sidecar/dist/src/type-structural-expander.js +283 -0
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
- package/sidecar/dist/src/types.d.ts +669 -0
- package/sidecar/dist/src/types.js +5 -0
- package/sidecar/dist/src/validators.d.ts +2214 -0
- package/sidecar/dist/src/validators.js +336 -0
- package/sidecar/package.json +6 -0
- package/templates/carrick.json +7 -0
- package/templates/carrick.yml +45 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-machinery detection for the v2 capture path (carrick#371).
|
|
3
|
+
*
|
|
4
|
+
* A producer response anchor whose resolved type IS or CONTAINS HTTP transport
|
|
5
|
+
* machinery — a fetch/DOM `Response`/`Request`, a Node `http.ServerResponse`, a
|
|
6
|
+
* wrapper envelope `{ response: Response; error?: undefined } | { ...; error }`,
|
|
7
|
+
* or a wrapper function `(req: Request) => Promise<Response>` — must never be
|
|
8
|
+
* emitted as a comparable contract. Comparing it against the consumer's real
|
|
9
|
+
* payload manufactures a false compat mismatch (the bug this module fixes).
|
|
10
|
+
*
|
|
11
|
+
* This is the raw-`ts` seam mirror of `type-inferrer.ts`'s
|
|
12
|
+
* `typeIsOrContainsResponseMachinery` (ts-morph): the capture/ seam forbids
|
|
13
|
+
* importing a module from outside it, so the canonical indicator set and the
|
|
14
|
+
* detection shape are duplicated here in lockstep — the same pattern by which
|
|
15
|
+
* `BUILTIN_ANCHOR_SYMBOLS` mirrors `socket_io.rs`. Detection is STRUCTURAL and
|
|
16
|
+
* framework-agnostic: no framework NAME appears, only the shared HTTP-message
|
|
17
|
+
* member surface, gated by a lib / `node_modules` declaration origin so a user
|
|
18
|
+
* payload that merely shares a member name can never trip it.
|
|
19
|
+
*/
|
|
20
|
+
import ts from 'typescript';
|
|
21
|
+
/**
|
|
22
|
+
* Strongly-discriminating member names of HTTP transport machinery. Kept
|
|
23
|
+
* identical to `MACHINERY_MEMBER_INDICATORS` in `type-inferrer.ts`. These are
|
|
24
|
+
* the names no JSON payload carries (`ok`, `redirected`, `bodyUsed`,
|
|
25
|
+
* `writeHead`, ...), so the origin gate + threshold never fire on real data.
|
|
26
|
+
* Exported so a drift-guard test (`machinery-indicator-mirror.test.ts`) asserts
|
|
27
|
+
* it stays equal to the `type-inferrer.ts` copy — nothing else enforces the
|
|
28
|
+
* lockstep, and if the two drift one path silently stops abstaining.
|
|
29
|
+
*/
|
|
30
|
+
export const MACHINERY_MEMBER_INDICATORS = new Set([
|
|
31
|
+
// fetch / DOM Response & Request body-consumer surface
|
|
32
|
+
'ok',
|
|
33
|
+
'redirected',
|
|
34
|
+
'bodyUsed',
|
|
35
|
+
'arrayBuffer',
|
|
36
|
+
'blob',
|
|
37
|
+
'formData',
|
|
38
|
+
'clone',
|
|
39
|
+
'json',
|
|
40
|
+
'statusText',
|
|
41
|
+
// Node http ServerResponse / reply-object surface
|
|
42
|
+
'statusCode',
|
|
43
|
+
'statusMessage',
|
|
44
|
+
'setHeader',
|
|
45
|
+
'getHeader',
|
|
46
|
+
'removeHeader',
|
|
47
|
+
'writeHead',
|
|
48
|
+
'flushHeaders',
|
|
49
|
+
]);
|
|
50
|
+
/** Machinery needs at least this many indicator members to be recognized. */
|
|
51
|
+
const MACHINERY_INDICATOR_THRESHOLD = 3;
|
|
52
|
+
/**
|
|
53
|
+
* True when `type`, resolved against `node`, IS or CONTAINS framework
|
|
54
|
+
* machinery.
|
|
55
|
+
*
|
|
56
|
+
* DETECTS, exactly:
|
|
57
|
+
* 1. the type itself is machinery (`isFrameworkMachinery`);
|
|
58
|
+
* 2. a union/intersection member is machinery (the envelope union);
|
|
59
|
+
* 3. a DIRECT property whose type is machinery (`{ response: Response }`),
|
|
60
|
+
* ONE level of descent only;
|
|
61
|
+
* 4. a CALL SIGNATURE whose AWAITED return type is machinery — the wrapper
|
|
62
|
+
* function `(req) => Promise<Response>` the Infer fallback resolves to.
|
|
63
|
+
* (Only the call return is awaited; property types below are not.)
|
|
64
|
+
*
|
|
65
|
+
* DELIBERATELY NOT DETECTED — stated so this comment never overstates the
|
|
66
|
+
* guarantee (mirrors the same list in `type-inferrer.ts`). Each is a
|
|
67
|
+
* non-regression (pre-existing verdict unchanged, never a new wrong one), a
|
|
68
|
+
* tracked follow-up:
|
|
69
|
+
* - machinery nested deeper than one property level;
|
|
70
|
+
* - a PROPERTY typed `Promise<Response>` (property types are not awaited /
|
|
71
|
+
* Promise-unwrapped before the check — only call-signature returns are);
|
|
72
|
+
* - an array element type: `Response[]` is not descended to its element;
|
|
73
|
+
* - `interface X extends Response` declared in USER source — the origin gate
|
|
74
|
+
* is lib/`node_modules` only, so a user-declared subtype reads as a real
|
|
75
|
+
* contract.
|
|
76
|
+
*
|
|
77
|
+
* The depth cap + origin gate keep a legitimate payload that merely references a
|
|
78
|
+
* machinery type far inside from over-abstaining.
|
|
79
|
+
*/
|
|
80
|
+
export function typeIsOrContainsMachinery(checker, type, node) {
|
|
81
|
+
return isOrContains(checker, type, node, 0);
|
|
82
|
+
}
|
|
83
|
+
function isOrContains(checker, type, node, depth) {
|
|
84
|
+
if (isFrameworkMachinery(checker, type)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (type.isUnion() || type.isIntersection()) {
|
|
88
|
+
return type.types.some((part) => isOrContains(checker, part, node, depth));
|
|
89
|
+
}
|
|
90
|
+
if (depth >= 1) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// A wrapper function value: descend into its (awaited) return type.
|
|
94
|
+
for (const sig of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
|
|
95
|
+
const returnType = sig.getReturnType();
|
|
96
|
+
const awaited = checker.getAwaitedType?.(returnType) ?? returnType;
|
|
97
|
+
if (isOrContains(checker, awaited, node, depth + 1)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Direct properties: the `{ response: Response; error }` envelope shape.
|
|
102
|
+
for (const prop of checker.getPropertiesOfType(type)) {
|
|
103
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, node);
|
|
104
|
+
if (isOrContains(checker, propType, node, depth + 1)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* True when `type` itself is an HTTP-machinery type: it structurally carries at
|
|
112
|
+
* least `MACHINERY_INDICATOR_THRESHOLD` of the indicator members AND its symbol
|
|
113
|
+
* is declared in a lib (`lib.*.d.ts`) or `node_modules` origin.
|
|
114
|
+
*/
|
|
115
|
+
function isFrameworkMachinery(checker, type) {
|
|
116
|
+
let hits = 0;
|
|
117
|
+
for (const prop of checker.getPropertiesOfType(type)) {
|
|
118
|
+
if (MACHINERY_MEMBER_INDICATORS.has(prop.getName())) {
|
|
119
|
+
hits += 1;
|
|
120
|
+
if (hits >= MACHINERY_INDICATOR_THRESHOLD)
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (hits < MACHINERY_INDICATOR_THRESHOLD) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return symbolIsLibOrExternalOrigin(checker, type.getSymbol() ?? type.aliasSymbol);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* True when the symbol is declared in a TS lib file (`lib.*.d.ts`) or under
|
|
131
|
+
* `node_modules`. Works on a bare checkout: the DOM `Response`/`Request`
|
|
132
|
+
* resolve from the bundled `lib.dom.d.ts` even with no installed dependencies.
|
|
133
|
+
*/
|
|
134
|
+
function symbolIsLibOrExternalOrigin(checker, symbol) {
|
|
135
|
+
if (!symbol) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const isExternalPath = (filePath) => {
|
|
139
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
140
|
+
return (normalized.includes('/node_modules/') ||
|
|
141
|
+
/\/lib\.[^/]*\.d\.ts$/.test(normalized));
|
|
142
|
+
};
|
|
143
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
144
|
+
if (isExternalPath(decl.getSourceFile().fileName)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
149
|
+
try {
|
|
150
|
+
const aliased = checker.getAliasedSymbol(symbol);
|
|
151
|
+
if (aliased && aliased !== symbol) {
|
|
152
|
+
return symbolIsLibOrExternalOrigin(checker, aliased);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Ignore errors when resolving the aliased symbol.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SymbolTracker-backed node-builder printing for anonymous inferred types
|
|
3
|
+
* (design doc, Capture step 5). This is WP1's risk concentration, built
|
|
4
|
+
* failure-visibility-first: any tracked symbol that is not plainly
|
|
5
|
+
* accessible from the destination file demotes the alias to the
|
|
6
|
+
* structural_fallback tier with a recorded reason -- the bundle never ships
|
|
7
|
+
* a silently wrong .d.ts.
|
|
8
|
+
*
|
|
9
|
+
* The four banked corrections from the 2026-07-02 derisk sweep, applied
|
|
10
|
+
* verbatim:
|
|
11
|
+
* 1. enclosingDeclaration anchors in the DESTINATION surface file (the
|
|
12
|
+
* entry's placeholder alias for this anchor), never undefined.
|
|
13
|
+
* 2. Demotion triggers on accessibility !== Accessible -- that includes
|
|
14
|
+
* CannotBeNamed (2), not just NotAccessible (1).
|
|
15
|
+
* 3. Detection drives off the trackSymbol callback; the
|
|
16
|
+
* reportInaccessibleUniqueSymbolError / reportInaccessibleThisError
|
|
17
|
+
* callbacks never fire for these shapes and are implemented only as
|
|
18
|
+
* belt-and-braces recorders.
|
|
19
|
+
* 4. The tracker is passed as the 5th argument of the (internal)
|
|
20
|
+
* typeToTypeNode signature: (type, enclosingDeclaration, flags,
|
|
21
|
+
* internalFlags, tracker).
|
|
22
|
+
*/
|
|
23
|
+
import ts from 'typescript';
|
|
24
|
+
export interface NodeBuilderPrintResult {
|
|
25
|
+
/** Printed type text, present only when the print is trusted. */
|
|
26
|
+
text?: string;
|
|
27
|
+
/** Symbols the tracker flagged as not accessible from the destination. */
|
|
28
|
+
inaccessible: string[];
|
|
29
|
+
/** Failure description when text is absent. */
|
|
30
|
+
failure?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Print `type` as a type node anchored at `destination` (a declaration inside
|
|
34
|
+
* the surface entry file). Returns untrusted-failure instead of text whenever
|
|
35
|
+
* any referenced symbol is not plainly accessible from the destination.
|
|
36
|
+
*/
|
|
37
|
+
export declare function printTypeForDestination(program: ts.Program, type: ts.Type, destination: ts.Node): NodeBuilderPrintResult;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SymbolTracker-backed node-builder printing for anonymous inferred types
|
|
3
|
+
* (design doc, Capture step 5). This is WP1's risk concentration, built
|
|
4
|
+
* failure-visibility-first: any tracked symbol that is not plainly
|
|
5
|
+
* accessible from the destination file demotes the alias to the
|
|
6
|
+
* structural_fallback tier with a recorded reason -- the bundle never ships
|
|
7
|
+
* a silently wrong .d.ts.
|
|
8
|
+
*
|
|
9
|
+
* The four banked corrections from the 2026-07-02 derisk sweep, applied
|
|
10
|
+
* verbatim:
|
|
11
|
+
* 1. enclosingDeclaration anchors in the DESTINATION surface file (the
|
|
12
|
+
* entry's placeholder alias for this anchor), never undefined.
|
|
13
|
+
* 2. Demotion triggers on accessibility !== Accessible -- that includes
|
|
14
|
+
* CannotBeNamed (2), not just NotAccessible (1).
|
|
15
|
+
* 3. Detection drives off the trackSymbol callback; the
|
|
16
|
+
* reportInaccessibleUniqueSymbolError / reportInaccessibleThisError
|
|
17
|
+
* callbacks never fire for these shapes and are implemented only as
|
|
18
|
+
* belt-and-braces recorders.
|
|
19
|
+
* 4. The tracker is passed as the 5th argument of the (internal)
|
|
20
|
+
* typeToTypeNode signature: (type, enclosingDeclaration, flags,
|
|
21
|
+
* internalFlags, tracker).
|
|
22
|
+
*/
|
|
23
|
+
import ts from 'typescript';
|
|
24
|
+
/**
|
|
25
|
+
* SymbolAccessibility is internal to the compiler (stable since TS 1.x):
|
|
26
|
+
* Accessible = 0, NotAccessible = 1, CannotBeNamed = 2. Mirrored here
|
|
27
|
+
* because the public .d.ts does not export it; correction 2 depends on the
|
|
28
|
+
* distinction between the two failure members.
|
|
29
|
+
*/
|
|
30
|
+
const ACCESSIBLE = 0;
|
|
31
|
+
/**
|
|
32
|
+
* The node builder can only print out-of-scope symbols as `import("...")`
|
|
33
|
+
* type references when the tracker carries a moduleResolverHost -- without
|
|
34
|
+
* one it returns no node at all for external-package symbols
|
|
35
|
+
* (probe-verified on TS 5.8). Hand-rolling the host is a losing game (it
|
|
36
|
+
* needs a dozen emit-host internals); TS's own factory builds it from the
|
|
37
|
+
* program, which is exactly the "route through the emitter's machinery, not
|
|
38
|
+
* a bare typeToTypeNode" guidance from the derisk sweep.
|
|
39
|
+
*/
|
|
40
|
+
function moduleResolverHostFor(program) {
|
|
41
|
+
const factory = ts.createModuleSpecifierResolutionHost;
|
|
42
|
+
if (!factory)
|
|
43
|
+
return undefined;
|
|
44
|
+
return factory(program, {
|
|
45
|
+
fileExists: ts.sys.fileExists,
|
|
46
|
+
readFile: ts.sys.readFile,
|
|
47
|
+
directoryExists: ts.sys.directoryExists,
|
|
48
|
+
getCurrentDirectory: () => program.getCurrentDirectory(),
|
|
49
|
+
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Print `type` as a type node anchored at `destination` (a declaration inside
|
|
54
|
+
* the surface entry file). Returns untrusted-failure instead of text whenever
|
|
55
|
+
* any referenced symbol is not plainly accessible from the destination.
|
|
56
|
+
*/
|
|
57
|
+
export function printTypeForDestination(program, type, destination) {
|
|
58
|
+
const checker = program.getTypeChecker();
|
|
59
|
+
const inaccessible = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const record = (symbol, meaning) => {
|
|
62
|
+
if (seen.has(symbol))
|
|
63
|
+
return;
|
|
64
|
+
seen.add(symbol);
|
|
65
|
+
const accessibility = checker.isSymbolAccessible(symbol, destination, // correction 1: destination file, never undefined
|
|
66
|
+
meaning ?? ts.SymbolFlags.Type,
|
|
67
|
+
/* shouldComputeAliasesToMakeVisible */ false).accessibility;
|
|
68
|
+
// Correction 2: anything other than Accessible demotes -- CannotBeNamed
|
|
69
|
+
// (2) is a distinct enum member that a `=== NotAccessible` check misses.
|
|
70
|
+
if (accessibility !== ACCESSIBLE) {
|
|
71
|
+
inaccessible.push(symbol.getName());
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
// Correction 3: trackSymbol is the callback that actually fires. The
|
|
75
|
+
// report* callbacks are dead for the verified failure shapes (unexported
|
|
76
|
+
// local interfaces, unique-symbol keys, local recursive aliases) but are
|
|
77
|
+
// kept as recorders in case other shapes reach them.
|
|
78
|
+
const tracker = {
|
|
79
|
+
moduleResolverHost: moduleResolverHostFor(program),
|
|
80
|
+
trackSymbol: (symbol, enclosing, meaning) => {
|
|
81
|
+
void enclosing;
|
|
82
|
+
record(symbol, meaning);
|
|
83
|
+
// Returning false tells the builder no error was reported here; the
|
|
84
|
+
// demotion decision is ours, made after the walk completes.
|
|
85
|
+
return false;
|
|
86
|
+
},
|
|
87
|
+
reportInaccessibleThisError: () => {
|
|
88
|
+
inaccessible.push('this');
|
|
89
|
+
},
|
|
90
|
+
reportInaccessibleUniqueSymbolError: () => {
|
|
91
|
+
inaccessible.push('(unique symbol)');
|
|
92
|
+
},
|
|
93
|
+
reportPrivateInBaseOfClassExpression: (propertyName) => {
|
|
94
|
+
inaccessible.push(propertyName);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
let node;
|
|
98
|
+
try {
|
|
99
|
+
node = checker.typeToTypeNode(type, destination, ts.NodeBuilderFlags.NoTruncation |
|
|
100
|
+
ts.NodeBuilderFlags.UseStructuralFallback |
|
|
101
|
+
ts.NodeBuilderFlags.InTypeAlias,
|
|
102
|
+
/* internalFlags */ undefined, tracker // correction 4: 5th argument
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return {
|
|
107
|
+
inaccessible,
|
|
108
|
+
failure: `node builder threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!node) {
|
|
112
|
+
return { inaccessible, failure: 'node builder returned no type node' };
|
|
113
|
+
}
|
|
114
|
+
if (inaccessible.length > 0) {
|
|
115
|
+
return {
|
|
116
|
+
inaccessible,
|
|
117
|
+
failure: `symbols not accessible from the surface entry: ${[...new Set(inaccessible)].join(', ')}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const printer = ts.createPrinter({ removeComments: true });
|
|
121
|
+
const text = printer.printNode(ts.EmitHint.Unspecified, node, destination.getSourceFile());
|
|
122
|
+
return { text, inaccessible };
|
|
123
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-emit specifier rewrite pass (design doc, Capture step 3 -- REQUIRED
|
|
3
|
+
* component). Declaration emit ships tsconfig-`paths`-mapped specifiers
|
|
4
|
+
* verbatim (`@app/models/item` dangles after relocation into the stub), and
|
|
5
|
+
* node-builder-printed import types can carry absolute source paths. Both
|
|
6
|
+
* are mapped onto tree-relative specifiers here, at capture time, in the
|
|
7
|
+
* producer's own resolution context -- `paths` cannot be fixed at check time
|
|
8
|
+
* (program-global, and namespaces collide across stubs).
|
|
9
|
+
*
|
|
10
|
+
* Specifiers that map outside the emitted tree are left untouched: the
|
|
11
|
+
* per-alias self-check classifies them as dangling internals with a recorded
|
|
12
|
+
* reason, which is the honest outcome.
|
|
13
|
+
*/
|
|
14
|
+
import ts from 'typescript';
|
|
15
|
+
import { type PathsPattern } from './specifiers.js';
|
|
16
|
+
export interface RewriteArgs {
|
|
17
|
+
/** Absolute path of the stub's types/ directory. */
|
|
18
|
+
typesDir: string;
|
|
19
|
+
/** Tree-relative emitted file paths (as recorded in emitted_files, without
|
|
20
|
+
* the leading "types/"). */
|
|
21
|
+
files: string[];
|
|
22
|
+
/** The producer repo's parsed compiler options. */
|
|
23
|
+
options: ts.CompilerOptions;
|
|
24
|
+
/** Absolute path of the tsconfig the options came from. */
|
|
25
|
+
configPath: string;
|
|
26
|
+
/** Effective rootDir the emit ran with (tree layout mirrors it). */
|
|
27
|
+
entryDir: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function parsePathsPatterns(options: ts.CompilerOptions, configPath: string): PathsPattern[];
|
|
30
|
+
/**
|
|
31
|
+
* Rewrite paths-mapped and absolute-internal specifiers in every emitted
|
|
32
|
+
* file. Returns the number of specifiers rewritten.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rewriteEmittedSpecifiers(args: RewriteArgs): number;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-emit specifier rewrite pass (design doc, Capture step 3 -- REQUIRED
|
|
3
|
+
* component). Declaration emit ships tsconfig-`paths`-mapped specifiers
|
|
4
|
+
* verbatim (`@app/models/item` dangles after relocation into the stub), and
|
|
5
|
+
* node-builder-printed import types can carry absolute source paths. Both
|
|
6
|
+
* are mapped onto tree-relative specifiers here, at capture time, in the
|
|
7
|
+
* producer's own resolution context -- `paths` cannot be fixed at check time
|
|
8
|
+
* (program-global, and namespaces collide across stubs).
|
|
9
|
+
*
|
|
10
|
+
* Specifiers that map outside the emitted tree are left untouched: the
|
|
11
|
+
* per-alias self-check classifies them as dangling internals with a recorded
|
|
12
|
+
* reason, which is the honest outcome.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import { isRelative, matchPathsPattern, rewriteSpecifiers, } from './specifiers.js';
|
|
17
|
+
export function parsePathsPatterns(options, configPath) {
|
|
18
|
+
const paths = options.paths;
|
|
19
|
+
if (!paths)
|
|
20
|
+
return [];
|
|
21
|
+
// Since TS 4.1 `paths` without `baseUrl` resolves relative to the config
|
|
22
|
+
// file; the parser records that base internally as pathsBasePath.
|
|
23
|
+
const base = path.resolve(options.baseUrl ??
|
|
24
|
+
(options.pathsBasePath || path.dirname(configPath)));
|
|
25
|
+
const patterns = [];
|
|
26
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
27
|
+
const starIdx = pattern.indexOf('*');
|
|
28
|
+
patterns.push({
|
|
29
|
+
pattern,
|
|
30
|
+
prefix: starIdx === -1 ? pattern : pattern.slice(0, starIdx),
|
|
31
|
+
suffix: starIdx === -1 ? undefined : pattern.slice(starIdx + 1),
|
|
32
|
+
targets: targets.map((t) => path.resolve(base, t)),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return patterns;
|
|
36
|
+
}
|
|
37
|
+
/** Map an absolute source path to its tree-relative emitted file, if any. */
|
|
38
|
+
function treeFileFor(absSource, entryDir, emitted) {
|
|
39
|
+
const noExt = absSource.replace(/\.(d\.ts|ts|tsx|mts|cts)$/, '');
|
|
40
|
+
const rel = path.relative(entryDir, noExt).split(path.sep).join('/');
|
|
41
|
+
if (rel.startsWith('..'))
|
|
42
|
+
return undefined;
|
|
43
|
+
for (const candidate of [`${rel}.d.ts`, `${rel}/index.d.ts`]) {
|
|
44
|
+
if (emitted.has(candidate))
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/** Tree-relative .d.ts path -> extensionless specifier from `fromFile`. */
|
|
50
|
+
function relativeSpecifier(fromFile, toFile) {
|
|
51
|
+
const fromDir = path.posix.dirname(fromFile);
|
|
52
|
+
let rel = path.posix.relative(fromDir, toFile.replace(/\.d\.ts$/, ''));
|
|
53
|
+
if (!rel.startsWith('.'))
|
|
54
|
+
rel = `./${rel}`;
|
|
55
|
+
return rel;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rewrite paths-mapped and absolute-internal specifiers in every emitted
|
|
59
|
+
* file. Returns the number of specifiers rewritten.
|
|
60
|
+
*/
|
|
61
|
+
export function rewriteEmittedSpecifiers(args) {
|
|
62
|
+
const patterns = parsePathsPatterns(args.options, args.configPath);
|
|
63
|
+
const emitted = new Set(args.files);
|
|
64
|
+
let total = 0;
|
|
65
|
+
for (const file of args.files) {
|
|
66
|
+
const absFile = path.join(args.typesDir, file);
|
|
67
|
+
const text = fs.readFileSync(absFile, 'utf8');
|
|
68
|
+
const { text: rewritten, rewrites } = rewriteSpecifiers(text, (spec) => {
|
|
69
|
+
// Absolute internal paths (node-builder import types).
|
|
70
|
+
if (spec.startsWith('/')) {
|
|
71
|
+
const target = treeFileFor(spec, args.entryDir, emitted);
|
|
72
|
+
return target ? relativeSpecifier(file, target) : undefined;
|
|
73
|
+
}
|
|
74
|
+
if (isRelative(spec))
|
|
75
|
+
return undefined;
|
|
76
|
+
// tsconfig-paths patterns, first matching target that exists in-tree
|
|
77
|
+
// (mirrors the resolver's declaration-order semantics).
|
|
78
|
+
for (const pattern of patterns) {
|
|
79
|
+
const star = matchPathsPattern(spec, pattern);
|
|
80
|
+
if (star === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
for (const targetTemplate of pattern.targets) {
|
|
83
|
+
const absTarget = targetTemplate.replace('*', star);
|
|
84
|
+
const target = treeFileFor(absTarget, args.entryDir, emitted);
|
|
85
|
+
if (target)
|
|
86
|
+
return relativeSpecifier(file, target);
|
|
87
|
+
}
|
|
88
|
+
// Matched a pattern but no in-tree target: leave it for the
|
|
89
|
+
// self-check to classify (never guess).
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
});
|
|
94
|
+
if (rewrites > 0) {
|
|
95
|
+
fs.writeFileSync(absFile, rewritten);
|
|
96
|
+
total += rewrites;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return total;
|
|
100
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-time self-check (design doc, Capture step 8, amendment 1), with
|
|
3
|
+
* per-alias closure attribution.
|
|
4
|
+
*
|
|
5
|
+
* The stub tree is typechecked standalone with `skipLibCheck: false` --
|
|
6
|
+
* spike-verified as load-bearing: the tree is entirely .d.ts and skipLibCheck
|
|
7
|
+
* skips declaration files wholesale, making the gate vacuous. When the
|
|
8
|
+
* source repo has node_modules, resolution is pointed at it via a temporary
|
|
9
|
+
* node_modules symlink inside the stub, so externals resolve exactly as they
|
|
10
|
+
* will at check time against installed pins.
|
|
11
|
+
*
|
|
12
|
+
* Classification per alias (three-way, keyed on diagnostics, never on
|
|
13
|
+
* printed type text):
|
|
14
|
+
* - ok: resolves to a concrete type.
|
|
15
|
+
* - allowlisted_external: resolution failed only through external
|
|
16
|
+
* specifiers pinned in the stub's dependencies, on a bare checkout. The
|
|
17
|
+
* alias KEEPS its serialization tier; the check-phase probe gates
|
|
18
|
+
* (any/unknown/never, both sides) are the backstop.
|
|
19
|
+
* - decayed_internal: a dangling internal specifier, an unpinned external,
|
|
20
|
+
* or a top-type resolution with no allowlisted explanation.
|
|
21
|
+
*
|
|
22
|
+
* Attribution is per-alias closure: failed specifiers are blamed on an alias
|
|
23
|
+
* only if they occur in a file reachable from that alias's surface statement
|
|
24
|
+
* (import-type seeds, then BFS over relative imports). The spike's
|
|
25
|
+
* file-granularity shortcut is gone.
|
|
26
|
+
*/
|
|
27
|
+
import type { CaptureAliasRecord } from './api.js';
|
|
28
|
+
import type { ResolvedAnchor } from './anchors.js';
|
|
29
|
+
export interface SelfCheckArgs {
|
|
30
|
+
stubDir: string;
|
|
31
|
+
surfaceAbsPath: string;
|
|
32
|
+
resolved: ResolvedAnchor[];
|
|
33
|
+
pinned: Record<string, string>;
|
|
34
|
+
bareCheckout: boolean;
|
|
35
|
+
/** Producer repo root; its node_modules (if any) backs resolution. */
|
|
36
|
+
repoRoot: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function selfCheckStub(args: SelfCheckArgs): CaptureAliasRecord[];
|