carrick 0.3.133 → 0.3.135
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/contract.d.ts +6 -0
- package/dist/contract.js.map +1 -1
- package/dist/hook/post-edit.d.ts +10 -5
- package/dist/hook/post-edit.js +80 -23
- package/dist/hook/post-edit.js.map +1 -1
- package/dist/hook/refresh.d.ts +23 -3
- package/dist/hook/refresh.js +48 -6
- package/dist/hook/refresh.js.map +1 -1
- package/dist/hook/reuse.d.ts +7 -21
- package/dist/hook/reuse.js +23 -36
- package/dist/hook/reuse.js.map +1 -1
- package/dist/hook/session-start.js +15 -2
- package/dist/hook/session-start.js.map +1 -1
- package/dist/hook/stop.js.map +1 -1
- package/dist/init/agents-section.js +1 -1
- package/dist/init/agents-section.js.map +1 -1
- package/dist/init/doctor.js +4 -0
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/hosted.d.ts +1 -0
- package/dist/init/hosted.js +4 -1
- package/dist/init/hosted.js.map +1 -1
- package/dist/init/remove.js +7 -1
- package/dist/init/remove.js.map +1 -1
- package/dist/init/repo-copies.js +3 -1
- package/dist/init/repo-copies.js.map +1 -1
- package/dist/init/run.d.ts +1 -1
- package/dist/init/run.js +10 -2
- package/dist/init/run.js.map +1 -1
- package/dist/init/task-skills.d.ts +22 -1
- package/dist/init/task-skills.js +65 -1
- package/dist/init/task-skills.js.map +1 -1
- package/dist/render.d.ts +41 -16
- package/dist/render.js +91 -102
- package/dist/render.js.map +1 -1
- package/package.json +10 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.js +18 -1
- package/sidecar/dist/src/capture/api.d.ts +17 -4
- package/sidecar/dist/src/capture/check-workspace.js +1 -1
- package/sidecar/dist/src/capture/check.d.ts +44 -0
- package/sidecar/dist/src/capture/check.js +112 -10
- package/sidecar/dist/src/capture/index.js +18 -0
- package/sidecar/dist/src/capture/installed-package.d.ts +5 -0
- package/sidecar/dist/src/capture/installed-package.js +64 -6
- package/sidecar/dist/src/capture/machine-paths.d.ts +44 -0
- package/sidecar/dist/src/capture/machine-paths.js +97 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +2 -0
- package/sidecar/dist/src/capture/node-builder.js +1 -1
- package/sidecar/dist/src/capture/paths-rewrite.js +3 -3
- package/sidecar/dist/src/capture/result-label.d.ts +42 -0
- package/sidecar/dist/src/capture/result-label.js +91 -0
- package/sidecar/dist/src/capture/self-check.js +13 -2
- package/sidecar/dist/src/index.js +117 -5
- package/sidecar/dist/src/infer-cache.d.ts +125 -0
- package/sidecar/dist/src/infer-cache.js +244 -0
- package/sidecar/dist/src/library-claims.d.ts +35 -3
- package/sidecar/dist/src/library-claims.js +417 -61
- package/sidecar/dist/src/node-index.d.ts +53 -0
- package/sidecar/dist/src/node-index.js +100 -0
- package/sidecar/dist/src/project-loader.d.ts +11 -0
- package/sidecar/dist/src/project-loader.js +15 -0
- package/sidecar/dist/src/result-candidates.d.ts +28 -0
- package/sidecar/dist/src/result-candidates.js +209 -0
- package/sidecar/dist/src/type-inferrer.d.ts +27 -0
- package/sidecar/dist/src/type-inferrer.js +175 -43
- package/sidecar/dist/src/types.d.ts +77 -1
- package/sidecar/dist/src/unwidened.js +10 -1
- package/sidecar/dist/src/validators.d.ts +931 -197
- package/sidecar/dist/src/validators.js +34 -0
- package/templates/skills/carrick-reuse.md +11 -0
|
@@ -61,6 +61,31 @@ export function carriesHeapLimitLine(stderr) {
|
|
|
61
61
|
.some((line) => line.includes(HEAP_OUT_OF_MEMORY) &&
|
|
62
62
|
!HARD_SIZE_LIMITS.some((limit) => line.includes(limit)));
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether the native compiler (TypeScript 7, a Go program) died out of
|
|
66
|
+
* memory. The Go runtime prints `fatal error: runtime: out of memory` and
|
|
67
|
+
* exits 2; V8's line above never appears, and a heap cap passed through
|
|
68
|
+
* `NODE_OPTIONS` reaches only the launcher, not the compiler.
|
|
69
|
+
*/
|
|
70
|
+
export function carriesNativeOutOfMemoryLine(output) {
|
|
71
|
+
return output.split('\n').some((line) => line.includes('fatal error: runtime: out of memory'));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether a diagnostic is about the compiler's configuration rather than a
|
|
75
|
+
* file it checked: one placed in the tsconfig the judge wrote, or "no inputs"
|
|
76
|
+
* (TS18003). Keyed on the file, not on the TS5xxx option range: some of those
|
|
77
|
+
* codes sit in a source file (TS5097, an import ending in `.ts`) and poison
|
|
78
|
+
* only the stub that holds it.
|
|
79
|
+
*/
|
|
80
|
+
export function isConfigDiagnostic(d) {
|
|
81
|
+
return path.posix.basename(d.file) === 'tsconfig.json' || d.code === 18003;
|
|
82
|
+
}
|
|
83
|
+
/** Why the compiler ended abnormally, when its output names a known cause. */
|
|
84
|
+
export function abnormalCause(output) {
|
|
85
|
+
if (carriesNativeOutOfMemoryLine(output))
|
|
86
|
+
return 'the type checker ran out of memory';
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
64
89
|
/** V8's default old-space ceiling on a 64-bit host, in MB. */
|
|
65
90
|
const V8_DEFAULT_HEAP_CEILING_MB = 4096;
|
|
66
91
|
/**
|
|
@@ -119,9 +144,16 @@ function sidecarRoot() {
|
|
|
119
144
|
* which is the caller's "not found" signal and the most useful thing to name.
|
|
120
145
|
*/
|
|
121
146
|
export function resolveVendoredBin(name, from = sidecarRoot()) {
|
|
122
|
-
|
|
147
|
+
return resolveVendored(path.join('.bin', name), from);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The first `node_modules/<rel>` at or above `from`, the same walk as
|
|
151
|
+
* {@link resolveVendoredBin}; the sidecar-local path when none exists.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveVendored(rel, from = sidecarRoot()) {
|
|
154
|
+
const local = path.join(from, 'node_modules', rel);
|
|
123
155
|
for (let dir = from;; dir = path.dirname(dir)) {
|
|
124
|
-
const candidate = path.join(dir, 'node_modules',
|
|
156
|
+
const candidate = path.join(dir, 'node_modules', rel);
|
|
125
157
|
if (fs.existsSync(candidate))
|
|
126
158
|
return candidate;
|
|
127
159
|
if (path.dirname(dir) === dir)
|
|
@@ -131,6 +163,58 @@ export function resolveVendoredBin(name, from = sidecarRoot()) {
|
|
|
131
163
|
function binPath(name) {
|
|
132
164
|
return resolveVendoredBin(name);
|
|
133
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The package that brings the judge's compiler: TypeScript 7 under an alias
|
|
168
|
+
* beside the TypeScript 6 that capture and inference read through ts-morph.
|
|
169
|
+
* Its platform package holds the native executable the judge runs. Both
|
|
170
|
+
* packages declare a `tsc` bin, so `node_modules/.bin/tsc` may be either; the
|
|
171
|
+
* judge never reads it.
|
|
172
|
+
*/
|
|
173
|
+
export const JUDGE_COMPILER_PACKAGE = 'typescript-native';
|
|
174
|
+
/**
|
|
175
|
+
* The judge's tsc: the host's native executable, from the platform package
|
|
176
|
+
* the compiler installs as an optional dependency (`@typescript/
|
|
177
|
+
* typescript-<platform>-<arch>`), found by the same walk as the bins. Where it
|
|
178
|
+
* is not installed (an install that omits optional dependencies, such as the
|
|
179
|
+
* cloud runner's, or a pnpm store the walk does not reach), TypeScript 6's own
|
|
180
|
+
* tsc, which ships for ts-morph anyway. The two read every probe the same
|
|
181
|
+
* (measured on 1,018 real probes; the one wording difference moves only a
|
|
182
|
+
* verdict's `codes`), so the fallback changes no verdict, only the time.
|
|
183
|
+
*/
|
|
184
|
+
export function judgeTscPath(from = sidecarRoot(), platform = process.platform, arch = process.arch) {
|
|
185
|
+
const exe = platform === 'win32' ? 'tsc.exe' : 'tsc';
|
|
186
|
+
const native = path.join('@typescript', `typescript-${platform}-${arch}`, 'lib', exe);
|
|
187
|
+
const found = resolveVendored(native, from);
|
|
188
|
+
if (fs.existsSync(found))
|
|
189
|
+
return found;
|
|
190
|
+
return resolveVendored(path.join('typescript', 'bin', 'tsc'), from);
|
|
191
|
+
}
|
|
192
|
+
/** Which compiler a tsc path is, in words, for the run log and the result. */
|
|
193
|
+
export function judgeLabel(tscPath) {
|
|
194
|
+
const version = compilerVersion(tscPath);
|
|
195
|
+
const parts = tscPath.split(/[\\/]/);
|
|
196
|
+
if (parts.some((p) => p.startsWith('typescript-') && p !== JUDGE_COMPILER_PACKAGE)) {
|
|
197
|
+
return `TypeScript ${version} (native)`;
|
|
198
|
+
}
|
|
199
|
+
if (parts.includes('typescript') && parts[parts.length - 2] === 'bin') {
|
|
200
|
+
return `TypeScript ${version} (native compiler not installed)`;
|
|
201
|
+
}
|
|
202
|
+
return `a stand-in compiler at ${tscPath}`;
|
|
203
|
+
}
|
|
204
|
+
/** The version of the compiler a tsc path belongs to, read from the package
|
|
205
|
+
* that holds it; the compiler-API version for a stand-in with no package. */
|
|
206
|
+
function compilerVersion(tscPath) {
|
|
207
|
+
try {
|
|
208
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(path.dirname(tscPath), '..', 'package.json'), 'utf8'));
|
|
209
|
+
const compiler = manifest.name === 'typescript' || manifest.name?.startsWith('@typescript/typescript-');
|
|
210
|
+
if (compiler && manifest.version)
|
|
211
|
+
return manifest.version;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// A stand-in compiler (tests) sits in no package.
|
|
215
|
+
}
|
|
216
|
+
return ts.version;
|
|
217
|
+
}
|
|
134
218
|
function unverifiableAll(plans, gate, diagnostic) {
|
|
135
219
|
return plans.map((plan) => ({
|
|
136
220
|
pair_id: plan.pairId,
|
|
@@ -155,8 +239,9 @@ export async function runCheck(opts, onProgress) {
|
|
|
155
239
|
const progress = onProgress ?? (() => { });
|
|
156
240
|
const cleanup = opts.cleanup !== false;
|
|
157
241
|
const pnpmPath = opts.pnpmPath ?? binPath('pnpm');
|
|
158
|
-
const tscPath = opts.tscPath ??
|
|
159
|
-
const tsVersion =
|
|
242
|
+
const tscPath = opts.tscPath ?? judgeTscPath();
|
|
243
|
+
const tsVersion = compilerVersion(tscPath);
|
|
244
|
+
const judge = judgeLabel(tscPath);
|
|
160
245
|
// Isolation is non-negotiable: without the vendored pnpm we would fall back
|
|
161
246
|
// to a flat npm install that manufactures nominal false-incompatibles. Fail
|
|
162
247
|
// explicitly instead (design Check step 2).
|
|
@@ -169,6 +254,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
169
254
|
isolation: 'unavailable',
|
|
170
255
|
install_ok: false,
|
|
171
256
|
ts_version: tsVersion,
|
|
257
|
+
judge,
|
|
172
258
|
verdicts: sortVerdicts([
|
|
173
259
|
...unverifiableAll(probing, 'isolation:unavailable', 'type isolation unavailable (pnpm missing); compatibility cannot be verified.'),
|
|
174
260
|
...preGated,
|
|
@@ -248,13 +334,14 @@ export async function runCheck(opts, onProgress) {
|
|
|
248
334
|
isolation: 'pnpm',
|
|
249
335
|
install_ok: false,
|
|
250
336
|
ts_version: tsVersion,
|
|
337
|
+
judge,
|
|
251
338
|
verdicts,
|
|
252
339
|
degraded_services: degraded,
|
|
253
340
|
errors,
|
|
254
341
|
};
|
|
255
342
|
}
|
|
256
343
|
// ---- Judge (async, off the event loop) ----------------------------------
|
|
257
|
-
progress('checking',
|
|
344
|
+
progress('checking', `type-checking probes (judge: ${judge})`);
|
|
258
345
|
const heap = childHeap(process.execArgv, process.env.NODE_OPTIONS, process.memoryUsage().rss / (1024 * 1024));
|
|
259
346
|
const tsc = await runProcess(tscPath, ['--noEmit', '--pretty', 'false', '-p', path.join(ws.probesRel, 'tsconfig.json')], ws.workspaceDir, { ...process.env, NODE_OPTIONS: heap.nodeOptions });
|
|
260
347
|
const diagnostics = parseTscOutput(tsc.stdout + '\n' + tsc.stderr);
|
|
@@ -267,8 +354,20 @@ export async function runCheck(opts, onProgress) {
|
|
|
267
354
|
// `code === 0` guard.
|
|
268
355
|
// A compiler a signal ended never finished, so a pair with no diagnostic
|
|
269
356
|
// was never cleared, whatever it printed first (carrick#2209).
|
|
270
|
-
|
|
271
|
-
|
|
357
|
+
//
|
|
358
|
+
// A diagnostic about the configuration (a tsconfig the compiler refuses,
|
|
359
|
+
// an option it does not know) is printed WITH a position, in the tsconfig,
|
|
360
|
+
// and belongs to no pair: TypeScript 7 refuses an option and checks
|
|
361
|
+
// nothing, so without this every pair read compatible. Such a run checked
|
|
362
|
+
// no pair, whatever else it printed.
|
|
363
|
+
const configError = diagnostics.find(isConfigDiagnostic);
|
|
364
|
+
if (tsc.signal !== null ||
|
|
365
|
+
(tsc.code !== 0 && diagnostics.length === 0) ||
|
|
366
|
+
configError !== undefined) {
|
|
367
|
+
const excerpt = scrubPaths((configError
|
|
368
|
+
? `${configError.file}(${configError.line},${configError.col}): error TS${configError.code}: ${configError.message}`
|
|
369
|
+
: (tsc.stderr || tsc.stdout).trim()).slice(0, 2000), scrubCtx);
|
|
370
|
+
const cause = abnormalCause(tsc.stderr + '\n' + tsc.stdout);
|
|
272
371
|
errors.push(`tsc terminated abnormally (exit code ${tsc.code ?? 'null'}) ` +
|
|
273
372
|
`with no parseable diagnostics${excerpt ? `: ${excerpt}` : ''}`);
|
|
274
373
|
for (const s of opts.stubs) {
|
|
@@ -278,8 +377,9 @@ export async function runCheck(opts, onProgress) {
|
|
|
278
377
|
});
|
|
279
378
|
}
|
|
280
379
|
const verdicts = sortVerdicts([
|
|
281
|
-
...unverifiableAll(probing, 'tsc:abnormal-termination', `the type checker terminated abnormally (exit code ${tsc.code ?? 'null'})
|
|
282
|
-
|
|
380
|
+
...unverifiableAll(probing, 'tsc:abnormal-termination', `the type checker terminated abnormally (exit code ${tsc.code ?? 'null'})` +
|
|
381
|
+
(cause ? ` because ${cause}` : '') +
|
|
382
|
+
'; compatibility cannot be verified.' +
|
|
283
383
|
(excerpt ? ` Compiler output: ${excerpt}` : '')),
|
|
284
384
|
...preGated,
|
|
285
385
|
...unresolved,
|
|
@@ -290,7 +390,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
290
390
|
const compilerEnd = {
|
|
291
391
|
exit_code: tsc.code,
|
|
292
392
|
signal: tsc.signal,
|
|
293
|
-
heap_limit: carriesHeapLimitLine(tsc.stderr),
|
|
393
|
+
heap_limit: carriesHeapLimitLine(tsc.stderr) || carriesNativeOutOfMemoryLine(tsc.stderr + '\n' + tsc.stdout),
|
|
294
394
|
heap_cap_mb: heap.heapCapMb,
|
|
295
395
|
};
|
|
296
396
|
if (cleanup)
|
|
@@ -301,6 +401,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
301
401
|
isolation: 'pnpm',
|
|
302
402
|
install_ok: true,
|
|
303
403
|
ts_version: tsVersion,
|
|
404
|
+
judge,
|
|
304
405
|
verdicts,
|
|
305
406
|
degraded_services: dedupeDegraded(degraded),
|
|
306
407
|
errors,
|
|
@@ -359,6 +460,7 @@ export async function runCheck(opts, onProgress) {
|
|
|
359
460
|
isolation: 'pnpm',
|
|
360
461
|
install_ok: true,
|
|
361
462
|
ts_version: tsVersion,
|
|
463
|
+
judge,
|
|
362
464
|
verdicts,
|
|
363
465
|
degraded_services: dedupeDegraded(degraded),
|
|
364
466
|
errors,
|
|
@@ -35,6 +35,7 @@ import { findAugmentationFiles } from './augmentations.js';
|
|
|
35
35
|
import { installedVersions, lockfileVersions } from './lockfile.js';
|
|
36
36
|
import { installedResolutionEdges, RESOLUTION_FILE } from './resolution-edges.js';
|
|
37
37
|
import { rewriteEmittedSpecifiers } from './paths-rewrite.js';
|
|
38
|
+
import { substituteMachinePathImports } from './machine-paths.js';
|
|
38
39
|
import { installedPackageSpecifier, typesPackageOf, withInstalledPackages } from './installed-package.js';
|
|
39
40
|
import { selfCheckStub } from './self-check.js';
|
|
40
41
|
import { collectSpecifiers, isRelative, packageNameOf } from './specifiers.js';
|
|
@@ -448,6 +449,23 @@ export function captureStub(opts) {
|
|
|
448
449
|
entryDir,
|
|
449
450
|
});
|
|
450
451
|
const specifierRewrites = denoRewrites + placed.rewrites + rewritten.rewrites;
|
|
452
|
+
// What the rewrite could not map still names a machine path (carrick#2943).
|
|
453
|
+
const machinePaths = substituteMachinePathImports({
|
|
454
|
+
files: emittedFiles.map((rel) => path.join(typesDir, rel)),
|
|
455
|
+
surfaceAbsPath,
|
|
456
|
+
tree: stubGuard.narrow(typesDir),
|
|
457
|
+
labelRoot: opts.scanRoot ?? repoRoot,
|
|
458
|
+
});
|
|
459
|
+
if (machinePaths.size > 0) {
|
|
460
|
+
resolved = resolved.map((anchor) => {
|
|
461
|
+
const found = machinePaths.get(anchor.request.alias);
|
|
462
|
+
if (!found)
|
|
463
|
+
return anchor;
|
|
464
|
+
const paths = [...new Set([...(anchor.unresolved?.paths ?? []), ...found.paths])];
|
|
465
|
+
const specifiers = [...new Set([...found.modules, ...(anchor.unresolved?.specifiers ?? [])])];
|
|
466
|
+
return { ...anchor, unresolved: { paths, specifiers } };
|
|
467
|
+
});
|
|
468
|
+
}
|
|
451
469
|
// ---- Pin external deps: installed node_modules first, lockfile fallback ----
|
|
452
470
|
// Externals are collected AFTER the rewrite pass: a rewritten paths
|
|
453
471
|
// specifier is internal, not a dependency.
|
|
@@ -35,6 +35,11 @@ export interface InstalledPackageSpecifier {
|
|
|
35
35
|
name: string;
|
|
36
36
|
version: string;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The name the entry exports the declaration under, set only when it differs
|
|
40
|
+
* from the name the path's declaration carries (`export { O as OmitKeys }`).
|
|
41
|
+
*/
|
|
42
|
+
exportName?: string;
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
45
|
* The `@types/*` package that serves a bare name's declarations, for callers
|
|
@@ -245,29 +245,78 @@ function entrySubpaths(manifest) {
|
|
|
245
245
|
}
|
|
246
246
|
return [''];
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Declaration keys (`file\0declaredName`) of what `file` exports as `name`.
|
|
250
|
+
* A chunk can declare a type under its public name and export it under a
|
|
251
|
+
* short one (`export { type OmitKeyof as O }`), so the name read off the
|
|
252
|
+
* import is not the name the declaration carries.
|
|
253
|
+
*/
|
|
254
|
+
function declarationKeysOfExport(file, name) {
|
|
255
|
+
const program = ts.createProgram([file], RESOLUTION_OPTIONS);
|
|
256
|
+
const checker = program.getTypeChecker();
|
|
257
|
+
const source = program.getSourceFile(file);
|
|
258
|
+
const moduleSymbol = source && checker.getSymbolAtLocation(source);
|
|
259
|
+
const symbol = moduleSymbol && checker.getExportsOfModule(moduleSymbol).find((s) => s.getName() === name);
|
|
260
|
+
if (!symbol)
|
|
261
|
+
return [];
|
|
262
|
+
let target = symbol;
|
|
263
|
+
for (let hops = 0; target.flags & ts.SymbolFlags.Alias && hops < 16; hops++) {
|
|
264
|
+
const next = checker.getAliasedSymbol(target);
|
|
265
|
+
if (next === target)
|
|
266
|
+
break;
|
|
267
|
+
target = next;
|
|
268
|
+
}
|
|
269
|
+
const keys = [];
|
|
270
|
+
for (const declaration of target.declarations ?? []) {
|
|
271
|
+
try {
|
|
272
|
+
keys.push(`${fs.realpathSync(declaration.getSourceFile().fileName)}\0${target.getName()}`);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// declaration outside the real file system: ignore
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return keys;
|
|
279
|
+
}
|
|
248
280
|
const moduleExportsCache = new Map();
|
|
249
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* Exports of `entryFile`: by exported name (with the real files declaring it)
|
|
283
|
+
* and by declaration identity (the file and the name the declaration carries
|
|
284
|
+
* there, whatever name the entry exports it under).
|
|
285
|
+
*/
|
|
250
286
|
function exportsOf(entryFile) {
|
|
251
287
|
const cached = moduleExportsCache.get(entryFile);
|
|
252
288
|
if (cached)
|
|
253
289
|
return cached;
|
|
254
|
-
const exported = new Map();
|
|
290
|
+
const exported = { byName: new Map(), byDeclaration: new Map() };
|
|
255
291
|
const program = ts.createProgram([entryFile], RESOLUTION_OPTIONS);
|
|
256
292
|
const checker = program.getTypeChecker();
|
|
257
293
|
const source = program.getSourceFile(entryFile);
|
|
258
294
|
const moduleSymbol = source && checker.getSymbolAtLocation(source);
|
|
259
295
|
for (const symbol of moduleSymbol ? checker.getExportsOfModule(moduleSymbol) : []) {
|
|
260
|
-
|
|
296
|
+
let target = symbol;
|
|
297
|
+
// Follow the alias chain until it stops (an entry can re-export a re-export).
|
|
298
|
+
for (let hops = 0; target.flags & ts.SymbolFlags.Alias && hops < 16; hops++) {
|
|
299
|
+
const next = checker.getAliasedSymbol(target);
|
|
300
|
+
if (next === target)
|
|
301
|
+
break;
|
|
302
|
+
target = next;
|
|
303
|
+
}
|
|
261
304
|
const files = new Set();
|
|
262
305
|
for (const declaration of target.declarations ?? []) {
|
|
263
306
|
try {
|
|
264
|
-
|
|
307
|
+
const real = fs.realpathSync(declaration.getSourceFile().fileName);
|
|
308
|
+
files.add(real);
|
|
309
|
+
const key = `${real}\0${target.getName()}`;
|
|
310
|
+
// The first export of a declaration wins; its own name wins over a rename.
|
|
311
|
+
if (!exported.byDeclaration.has(key) || symbol.getName() === target.getName()) {
|
|
312
|
+
exported.byDeclaration.set(key, symbol.getName());
|
|
313
|
+
}
|
|
265
314
|
}
|
|
266
315
|
catch {
|
|
267
316
|
// declaration outside the real file system: ignore
|
|
268
317
|
}
|
|
269
318
|
}
|
|
270
|
-
exported.set(symbol.getName(), files);
|
|
319
|
+
exported.byName.set(symbol.getName(), files);
|
|
271
320
|
}
|
|
272
321
|
moduleExportsCache.set(entryFile, exported);
|
|
273
322
|
return exported;
|
|
@@ -304,9 +353,18 @@ export function installedPackageSpecifier(absoluteSpecifier, importedName) {
|
|
|
304
353
|
const entryFile = resolveAgainstInstall(publicName + entry, root, manifest.name)?.resolvedFileName;
|
|
305
354
|
if (!entryFile)
|
|
306
355
|
continue;
|
|
307
|
-
|
|
356
|
+
const entryExports = exportsOf(entryFile);
|
|
357
|
+
if (entryExports.byName.get(importedName)?.has(file)) {
|
|
308
358
|
return withPin(publicName + entry);
|
|
309
359
|
}
|
|
360
|
+
// The entry may export the declaration under another name.
|
|
361
|
+
const renamed = entryExports.byDeclaration.get(`${file}\0${importedName}`) ??
|
|
362
|
+
declarationKeysOfExport(file, importedName)
|
|
363
|
+
.map((key) => entryExports.byDeclaration.get(key))
|
|
364
|
+
.find((name) => name !== undefined);
|
|
365
|
+
if (renamed !== undefined) {
|
|
366
|
+
return { ...withPin(publicName + entry), exportName: renamed };
|
|
367
|
+
}
|
|
310
368
|
}
|
|
311
369
|
}
|
|
312
370
|
return withPin(publicName + (candidates[0] ?? `/${withoutExtension(subpath)}`));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No stub names a module by a machine path (carrick#2943).
|
|
3
|
+
*
|
|
4
|
+
* The specifier rewrite maps an absolute specifier onto a file of the stub's
|
|
5
|
+
* tree or onto an installed package. What it cannot map stays as printed: a
|
|
6
|
+
* type that a printer could name only by where its module sits on this
|
|
7
|
+
* machine, such as a workspace sibling's source reached through a
|
|
8
|
+
* `node_modules` link, which the declaration emit does not carry. That path
|
|
9
|
+
* resolves on the machine that ran the capture, to a source file outside the
|
|
10
|
+
* stub, and nowhere else: the upload rewrites it to `<checkout>/...`, so the
|
|
11
|
+
* stored stub and a fresh one disagree about the same alias.
|
|
12
|
+
*
|
|
13
|
+
* Each such reference becomes `unknown` at its own position. In the surface
|
|
14
|
+
* the position is reported per alias, so the self-check labels the finding
|
|
15
|
+
* there as a reference that did not resolve rather than as declared; the rest
|
|
16
|
+
* of the alias's type survives. Any other tree file gets the dangling-import
|
|
17
|
+
* repair (carrick#1397), which writes `unknown` where the import's names were
|
|
18
|
+
* used. The stub then reads the same on every machine.
|
|
19
|
+
*
|
|
20
|
+
* Seam: node builtins + `typescript`, like the rest of this directory.
|
|
21
|
+
*/
|
|
22
|
+
import type { WriteGuard } from './guarded-fs.js';
|
|
23
|
+
/** A specifier that names a module by where it sits on one machine. */
|
|
24
|
+
export declare function isMachinePath(specifier: string): boolean;
|
|
25
|
+
/** What the surface substitution replaced in one alias. */
|
|
26
|
+
export interface MachinePathPositions {
|
|
27
|
+
/** Member paths now `unknown`, in the deep walk's notation (`''` = root). */
|
|
28
|
+
paths: string[];
|
|
29
|
+
/** The modules they named, relative to `labelRoot` (never absolute). */
|
|
30
|
+
modules: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Replace every machine-path import in the stub's tree with `unknown`.
|
|
34
|
+
* `files` are absolute tree paths; `surfaceAbsPath` is one of them. Returns
|
|
35
|
+
* the positions replaced in the surface, by alias.
|
|
36
|
+
*/
|
|
37
|
+
export declare function substituteMachinePathImports(args: {
|
|
38
|
+
files: readonly string[];
|
|
39
|
+
surfaceAbsPath: string;
|
|
40
|
+
/** A guard over the stub's types tree. */
|
|
41
|
+
tree: WriteGuard;
|
|
42
|
+
/** The root a module is named from in a label (the scanned repo's). */
|
|
43
|
+
labelRoot: string;
|
|
44
|
+
}): Map<string, MachinePathPositions>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No stub names a module by a machine path (carrick#2943).
|
|
3
|
+
*
|
|
4
|
+
* The specifier rewrite maps an absolute specifier onto a file of the stub's
|
|
5
|
+
* tree or onto an installed package. What it cannot map stays as printed: a
|
|
6
|
+
* type that a printer could name only by where its module sits on this
|
|
7
|
+
* machine, such as a workspace sibling's source reached through a
|
|
8
|
+
* `node_modules` link, which the declaration emit does not carry. That path
|
|
9
|
+
* resolves on the machine that ran the capture, to a source file outside the
|
|
10
|
+
* stub, and nowhere else: the upload rewrites it to `<checkout>/...`, so the
|
|
11
|
+
* stored stub and a fresh one disagree about the same alias.
|
|
12
|
+
*
|
|
13
|
+
* Each such reference becomes `unknown` at its own position. In the surface
|
|
14
|
+
* the position is reported per alias, so the self-check labels the finding
|
|
15
|
+
* there as a reference that did not resolve rather than as declared; the rest
|
|
16
|
+
* of the alias's type survives. Any other tree file gets the dangling-import
|
|
17
|
+
* repair (carrick#1397), which writes `unknown` where the import's names were
|
|
18
|
+
* used. The stub then reads the same on every machine.
|
|
19
|
+
*
|
|
20
|
+
* Seam: node builtins + `typescript`, like the rest of this directory.
|
|
21
|
+
*/
|
|
22
|
+
import ts from 'typescript';
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
import { childPath } from './node-builder.js';
|
|
26
|
+
import { repairDanglingImports } from './repair-dangling.js';
|
|
27
|
+
import { collectSpecifiers } from './specifiers.js';
|
|
28
|
+
/** A specifier that names a module by where it sits on one machine. */
|
|
29
|
+
export function isMachinePath(specifier) {
|
|
30
|
+
return path.posix.isAbsolute(specifier) || /^[A-Za-z]:[\\/]/.test(specifier) || specifier.startsWith('\\\\');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Replace every machine-path import in the stub's tree with `unknown`.
|
|
34
|
+
* `files` are absolute tree paths; `surfaceAbsPath` is one of them. Returns
|
|
35
|
+
* the positions replaced in the surface, by alias.
|
|
36
|
+
*/
|
|
37
|
+
export function substituteMachinePathImports(args) {
|
|
38
|
+
const failing = new Map();
|
|
39
|
+
for (const file of args.files) {
|
|
40
|
+
if (path.resolve(file) === path.resolve(args.surfaceAbsPath))
|
|
41
|
+
continue;
|
|
42
|
+
const machine = [...collectSpecifiers(fs.readFileSync(file, 'utf8'))].filter(isMachinePath);
|
|
43
|
+
if (machine.length > 0)
|
|
44
|
+
failing.set(file, new Set(machine));
|
|
45
|
+
}
|
|
46
|
+
if (failing.size > 0)
|
|
47
|
+
repairDanglingImports(failing, args.tree);
|
|
48
|
+
return substituteInSurface(args.surfaceAbsPath, args.tree, args.labelRoot);
|
|
49
|
+
}
|
|
50
|
+
function substituteInSurface(surfaceAbsPath, tree, labelRoot) {
|
|
51
|
+
const byAlias = new Map();
|
|
52
|
+
const text = fs.readFileSync(surfaceAbsPath, 'utf8');
|
|
53
|
+
if (!/import\s*\(/.test(text))
|
|
54
|
+
return byAlias;
|
|
55
|
+
const source = ts.createSourceFile(surfaceAbsPath, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
56
|
+
const edits = [];
|
|
57
|
+
for (const statement of source.statements) {
|
|
58
|
+
if (!ts.isTypeAliasDeclaration(statement))
|
|
59
|
+
continue;
|
|
60
|
+
const found = { paths: [], modules: [] };
|
|
61
|
+
const visit = (node, at) => {
|
|
62
|
+
const specifier = importTypeSpecifier(node);
|
|
63
|
+
if (specifier !== undefined && isMachinePath(specifier)) {
|
|
64
|
+
edits.push({ start: node.getStart(source), end: node.getEnd() });
|
|
65
|
+
if (!found.paths.includes(at))
|
|
66
|
+
found.paths.push(at);
|
|
67
|
+
const module = moduleLabel(specifier, labelRoot);
|
|
68
|
+
if (!found.modules.includes(module))
|
|
69
|
+
found.modules.push(module);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
node.forEachChild((child) => visit(child, childPath(node, child, at)));
|
|
73
|
+
};
|
|
74
|
+
visit(statement.type, '');
|
|
75
|
+
if (found.paths.length > 0)
|
|
76
|
+
byAlias.set(statement.name.text, found);
|
|
77
|
+
}
|
|
78
|
+
if (edits.length === 0)
|
|
79
|
+
return byAlias;
|
|
80
|
+
edits.sort((a, b) => b.start - a.start);
|
|
81
|
+
let out = text;
|
|
82
|
+
for (const edit of edits)
|
|
83
|
+
out = `${out.slice(0, edit.start)}unknown${out.slice(edit.end)}`;
|
|
84
|
+
tree.writeFile(surfaceAbsPath, out);
|
|
85
|
+
return byAlias;
|
|
86
|
+
}
|
|
87
|
+
/** The string an `import("...")` type names, when `node` is one. */
|
|
88
|
+
function importTypeSpecifier(node) {
|
|
89
|
+
if (!ts.isImportTypeNode(node) || !ts.isLiteralTypeNode(node.argument))
|
|
90
|
+
return undefined;
|
|
91
|
+
const literal = node.argument.literal;
|
|
92
|
+
return ts.isStringLiteral(literal) ? literal.text : undefined;
|
|
93
|
+
}
|
|
94
|
+
/** `specifier` relative to `root`, with forward slashes. */
|
|
95
|
+
function moduleLabel(specifier, root) {
|
|
96
|
+
return path.relative(path.resolve(root), path.resolve(specifier)).split(path.sep).join('/');
|
|
97
|
+
}
|
|
@@ -67,6 +67,8 @@ export declare function substituteUndeclaredNames(node: ts.TypeNode, program: ts
|
|
|
67
67
|
node: ts.TypeNode;
|
|
68
68
|
substitutions: UndeclaredSubstitution[];
|
|
69
69
|
};
|
|
70
|
+
/** The deep walk's path for `child` inside `parent`, extending `path`. */
|
|
71
|
+
export declare function childPath(parent: ts.Node, child: ts.Node, path: string): string;
|
|
70
72
|
/**
|
|
71
73
|
* Bare names in a printed type node that nothing in the producer's program
|
|
72
74
|
* declares (carrick#1165).
|
|
@@ -174,7 +174,7 @@ export function substituteUndeclaredNames(node, program, destination) {
|
|
|
174
174
|
return { node: rewritten, substitutions };
|
|
175
175
|
}
|
|
176
176
|
/** The deep walk's path for `child` inside `parent`, extending `path`. */
|
|
177
|
-
function childPath(parent, child, path) {
|
|
177
|
+
export function childPath(parent, child, path) {
|
|
178
178
|
if (ts.isPropertySignature(parent) && parent.type === child) {
|
|
179
179
|
const name = ts.isIdentifier(parent.name) || ts.isStringLiteral(parent.name)
|
|
180
180
|
? parent.name.text
|
|
@@ -82,7 +82,7 @@ export function rewriteEmittedSpecifiers(args) {
|
|
|
82
82
|
installs[mapped.install.name] = mapped.install.root;
|
|
83
83
|
if (mapped.pin)
|
|
84
84
|
pins[mapped.pin.name] = mapped.pin.version;
|
|
85
|
-
return mapped.specifier;
|
|
85
|
+
return { specifier: mapped.specifier, exportName: mapped.exportName };
|
|
86
86
|
};
|
|
87
87
|
for (const file of args.files) {
|
|
88
88
|
const absFile = path.join(args.typesDir, file);
|
|
@@ -97,13 +97,13 @@ export function rewriteEmittedSpecifiers(args) {
|
|
|
97
97
|
if (replacement === undefined)
|
|
98
98
|
return whole;
|
|
99
99
|
importTypeRewrites++;
|
|
100
|
-
return `${open}${quote}${replacement}${quote}${close}${dot}${name}`;
|
|
100
|
+
return `${open}${quote}${replacement.specifier}${quote}${close}${dot}${replacement.exportName ?? name}`;
|
|
101
101
|
});
|
|
102
102
|
const { text: rewritten, rewrites } = rewriteSpecifiers(text, (spec) => {
|
|
103
103
|
// Absolute paths: an emitted tree file, else an installed package.
|
|
104
104
|
if (spec.startsWith('/')) {
|
|
105
105
|
const target = treeFileFor(spec, args.entryDir, emitted, args.outside);
|
|
106
|
-
return target ? relativeSpecifier(file, target) : installed(spec);
|
|
106
|
+
return target ? relativeSpecifier(file, target) : installed(spec)?.specifier;
|
|
107
107
|
}
|
|
108
108
|
if (isRelative(spec))
|
|
109
109
|
return undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a labelled call's result (carrick#2818).
|
|
3
|
+
*
|
|
4
|
+
* A request library's call rarely returns the response body itself: the body
|
|
5
|
+
* sits in a member of what comes back (`data`), behind a parse method
|
|
6
|
+
* (`json()`), or is the whole value. The library store labels which, per
|
|
7
|
+
* operation, and the scanner sends the label on the consumer's infer anchor.
|
|
8
|
+
* This reads the labelled part of the located call's type, so the contract the
|
|
9
|
+
* consumer states is the body and never the library's envelope.
|
|
10
|
+
*
|
|
11
|
+
* The label is one of:
|
|
12
|
+
* - `{ whole: true }`: the awaited type;
|
|
13
|
+
* - `{ member: [m] }`: `m` on every non-nullish constituent of the awaited
|
|
14
|
+
* type, `undefined` and `null` removed from what is read;
|
|
15
|
+
* - `{ parse: p }`: the awaited return of calling `p()`, read off the type as
|
|
16
|
+
* the call returns it (a promise-like with a `json()` method is a thenable,
|
|
17
|
+
* and awaiting it first would drop the method).
|
|
18
|
+
*
|
|
19
|
+
* A label the type cannot satisfy abstains with `result_member_missing`.
|
|
20
|
+
*/
|
|
21
|
+
import ts from 'typescript';
|
|
22
|
+
/** The wire form of a result label. Exactly one key is set. */
|
|
23
|
+
export type ResultLabel = {
|
|
24
|
+
whole: true;
|
|
25
|
+
} | {
|
|
26
|
+
member: [string];
|
|
27
|
+
} | {
|
|
28
|
+
parse: string;
|
|
29
|
+
};
|
|
30
|
+
export type ResultRead = {
|
|
31
|
+
type: ts.Type;
|
|
32
|
+
} | {
|
|
33
|
+
abstain: string;
|
|
34
|
+
};
|
|
35
|
+
/** The constituents of a union, or the type alone. */
|
|
36
|
+
export declare function constituentsOf(type: ts.Type): readonly ts.Type[];
|
|
37
|
+
/**
|
|
38
|
+
* Read `label` off a call's type. `raw` is the type as the call returns it and
|
|
39
|
+
* `awaited` the type after the transport unwrap; `at` is the node properties
|
|
40
|
+
* are instantiated at.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readResultLabel(checker: ts.TypeChecker, raw: ts.Type, awaited: ts.Type, label: ResultLabel, at: ts.Node): ResultRead;
|