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
|
@@ -34,13 +34,13 @@ import { entryRelativeSpecifier, resolveAnchor } from './anchors.js';
|
|
|
34
34
|
import { findAugmentationFiles } from './augmentations.js';
|
|
35
35
|
import { installedVersions, lockfileVersions } from './lockfile.js';
|
|
36
36
|
import { rewriteEmittedSpecifiers } from './paths-rewrite.js';
|
|
37
|
-
import { typesPackageOf, withInstalledPackages } from './installed-package.js';
|
|
37
|
+
import { installedPackageSpecifier, typesPackageOf, withInstalledPackages } from './installed-package.js';
|
|
38
38
|
import { selfCheckStub } from './self-check.js';
|
|
39
39
|
import { collectSpecifiers, isRelative, packageNameOf } from './specifiers.js';
|
|
40
40
|
import { DenoProject, findDenoConfig } from './deno-project.js';
|
|
41
41
|
import { emitsAlike, ProjectGraph } from './project-references.js';
|
|
42
42
|
import { findServiceTsconfig } from './service-config.js';
|
|
43
|
-
import { placeEmittedTree } from './outside-root.js';
|
|
43
|
+
import { placeEmittedTree, surfaceModuleInTree } from './outside-root.js';
|
|
44
44
|
import { WriteGuard } from './guarded-fs.js';
|
|
45
45
|
export { DenoProject, findDenoConfig } from './deno-project.js';
|
|
46
46
|
export { serviceConfigPath } from './project-references.js';
|
|
@@ -256,7 +256,8 @@ export function captureStub(opts) {
|
|
|
256
256
|
}
|
|
257
257
|
// ---- Phase A: analysis program over placeholder entry + anchor sources ----
|
|
258
258
|
let resolved;
|
|
259
|
-
const
|
|
259
|
+
const progress = opts.onProgress ?? (() => { });
|
|
260
|
+
const analysisCtx = { repoRoot, entryDir: path.dirname(entryPath), entryPath, guard, progress };
|
|
260
261
|
try {
|
|
261
262
|
resolved = ownerGroups && emitProject
|
|
262
263
|
? resolveAnchorsByOwner(opts, ownerGroups, emitProject, analysisCtx, errors)
|
|
@@ -277,14 +278,9 @@ export function captureStub(opts) {
|
|
|
277
278
|
}
|
|
278
279
|
const scratch = WriteGuard.scratch('carrick-capture-v2-');
|
|
279
280
|
const staging = scratch.dir;
|
|
280
|
-
|
|
281
|
-
// Input .d.ts files (ambient stubs, augmentation declarations, local
|
|
282
|
-
// hand-written declarations in the import closure) are never re-emitted by
|
|
283
|
-
// tsc; they must ship verbatim or the tree's references to them dangle.
|
|
284
|
-
const declarationSources = new Map();
|
|
285
|
-
const sourceByEmitted = new Map();
|
|
286
|
-
let emitPartial = false;
|
|
281
|
+
let written;
|
|
287
282
|
try {
|
|
283
|
+
progress('emit', 'emitting declarations');
|
|
288
284
|
guard.writeFile(entryPath, entryLines.join('\n') + '\n');
|
|
289
285
|
const emitOptions = {
|
|
290
286
|
...parsed.options,
|
|
@@ -300,35 +296,13 @@ export function captureStub(opts) {
|
|
|
300
296
|
outDir: staging,
|
|
301
297
|
rootDir: entryDir,
|
|
302
298
|
};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// emitSkipped is PER-PROGRAM even when only one file's declaration emit
|
|
311
|
-
// failed (e.g. TS4023 from a hand-rolled ambient stub shadowing a real
|
|
312
|
-
// package): every other file's .d.ts was still written to the callback.
|
|
313
|
-
// Fail wholesale only when nothing at all emitted; otherwise keep the
|
|
314
|
-
// emitted subset and demote exactly the aliases it cannot support.
|
|
315
|
-
if (emitResult.emitSkipped && emitted.size === 0) {
|
|
316
|
-
return fail(stubDir, packageName, ['declaration emit was skipped']);
|
|
317
|
-
}
|
|
318
|
-
emitPartial = emitResult.emitSkipped;
|
|
319
|
-
for (const d of emitResult.diagnostics) {
|
|
320
|
-
errors.push(ts.flattenDiagnosticMessageText(d.messageText, '\n'));
|
|
321
|
-
}
|
|
322
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
323
|
-
if (!sourceFile.isDeclarationFile)
|
|
324
|
-
continue;
|
|
325
|
-
const abs = path.resolve(sourceFile.fileName);
|
|
326
|
-
const rel = path.relative(entryDir, abs).split(path.sep).join('/');
|
|
327
|
-
if (rel.startsWith('..') || rel.includes('node_modules/'))
|
|
328
|
-
continue;
|
|
329
|
-
declarationSources.set(rel, sourceFile.getFullText());
|
|
330
|
-
sourceByEmitted.set(rel, abs);
|
|
331
|
-
}
|
|
299
|
+
written = emitDeclarations({
|
|
300
|
+
rootNames: [entryPath, ...augmentationSources],
|
|
301
|
+
options: emitOptions,
|
|
302
|
+
host: deno?.host(emitOptions),
|
|
303
|
+
staging,
|
|
304
|
+
entryDir,
|
|
305
|
+
});
|
|
332
306
|
}
|
|
333
307
|
catch (err) {
|
|
334
308
|
return fail(stubDir, packageName, [err instanceof Error ? err.message : String(err)]);
|
|
@@ -338,6 +312,9 @@ export function captureStub(opts) {
|
|
|
338
312
|
guard.unlink(entryPath);
|
|
339
313
|
scratch.guard.remove(staging);
|
|
340
314
|
}
|
|
315
|
+
const { emitted, declarationSources, sourceByEmitted } = written;
|
|
316
|
+
const emitPartial = written.partial;
|
|
317
|
+
errors.push(...written.diagnostics);
|
|
341
318
|
// ---- Partial-emit recovery ----
|
|
342
319
|
// The corpus-2 notifications-svc shape: one file's declaration emit was
|
|
343
320
|
// skipped but the rest of the tree emitted fine. Keep the tree; demote any
|
|
@@ -351,7 +328,14 @@ export function captureStub(opts) {
|
|
|
351
328
|
if (emitPartial) {
|
|
352
329
|
errors.push(`declaration emit was partial: kept ${emitted.size} emitted file(s); ` +
|
|
353
330
|
'aliases referencing unemitted modules are demoted to structural_fallback');
|
|
354
|
-
resolved = demoteDanglingAliases({
|
|
331
|
+
resolved = demoteDanglingAliases({
|
|
332
|
+
resolved,
|
|
333
|
+
emitted,
|
|
334
|
+
declarationSources,
|
|
335
|
+
staging,
|
|
336
|
+
entryDir,
|
|
337
|
+
surfaceDeclaration,
|
|
338
|
+
});
|
|
355
339
|
}
|
|
356
340
|
// ---- Relocate the emitted tree into the stub package ----
|
|
357
341
|
const typesDir = path.join(stubDir, 'types');
|
|
@@ -483,6 +467,7 @@ export function captureStub(opts) {
|
|
|
483
467
|
target: parsed.options.target !== undefined ? ts.ScriptTarget[parsed.options.target] : undefined,
|
|
484
468
|
}, null, 2) + '\n');
|
|
485
469
|
// ---- Capture-time self-check (per-alias closure attribution) ----
|
|
470
|
+
progress('self-check', 'checking the stub');
|
|
486
471
|
const aliases = selfCheckStub({
|
|
487
472
|
guard: stubGuard,
|
|
488
473
|
stubDir,
|
|
@@ -520,43 +505,99 @@ export function captureStub(opts) {
|
|
|
520
505
|
};
|
|
521
506
|
}
|
|
522
507
|
/**
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
508
|
+
* Phase B: build the program of the final entry and emit its declarations.
|
|
509
|
+
*
|
|
510
|
+
* A function of its own so that its program dies with it (carrick#1916). The
|
|
511
|
+
* program is as large as the service, and nothing after the emit reads it:
|
|
512
|
+
* what the capture needs is the text returned here. While the emit ran inside
|
|
513
|
+
* `captureStub`, that one long function's frame went on holding the program
|
|
514
|
+
* for as long as the capture ran, so the self-check loaded its own program
|
|
515
|
+
* beside it; on a 2,345-file service that was 1,290 MB of a 2,741 MB peak.
|
|
516
|
+
*
|
|
517
|
+
* The caller builds the options and the host. A host is held by its program
|
|
518
|
+
* and holds none itself, so the caller keeping one keeps no program alive.
|
|
519
|
+
*
|
|
520
|
+
* Throws when the emit was skipped and wrote nothing.
|
|
521
|
+
*/
|
|
522
|
+
function emitDeclarations(args) {
|
|
523
|
+
const emitted = new Map();
|
|
524
|
+
const declarationSources = new Map();
|
|
525
|
+
const sourceByEmitted = new Map();
|
|
526
|
+
const program = ts.createProgram(args.rootNames, args.options, args.host);
|
|
527
|
+
const emitResult = program.emit(undefined, (fileName, text, _bom, _error, sources) => {
|
|
528
|
+
emitted.set(fileName, text);
|
|
529
|
+
if (sources?.[0])
|
|
530
|
+
sourceByEmitted.set(path.relative(args.staging, fileName).split(path.sep).join('/'), sources[0].fileName);
|
|
531
|
+
}, undefined,
|
|
532
|
+
/* emitOnlyDtsFiles */ true);
|
|
533
|
+
// emitSkipped is PER-PROGRAM even when only one file's declaration emit
|
|
534
|
+
// failed (e.g. TS4023 from a hand-rolled ambient stub shadowing a real
|
|
535
|
+
// package): every other file's .d.ts was still written to the callback.
|
|
536
|
+
// Fail wholesale only when nothing at all emitted; otherwise keep the
|
|
537
|
+
// emitted subset and demote exactly the aliases it cannot support.
|
|
538
|
+
if (emitResult.emitSkipped && emitted.size === 0) {
|
|
539
|
+
throw new Error('declaration emit was skipped');
|
|
540
|
+
}
|
|
541
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
542
|
+
if (!sourceFile.isDeclarationFile)
|
|
543
|
+
continue;
|
|
544
|
+
const abs = path.resolve(sourceFile.fileName);
|
|
545
|
+
const rel = path.relative(args.entryDir, abs).split(path.sep).join('/');
|
|
546
|
+
if (rel.startsWith('..') || rel.includes('node_modules/'))
|
|
547
|
+
continue;
|
|
548
|
+
declarationSources.set(rel, sourceFile.getFullText());
|
|
549
|
+
sourceByEmitted.set(rel, abs);
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
emitted,
|
|
553
|
+
declarationSources,
|
|
554
|
+
sourceByEmitted,
|
|
555
|
+
partial: emitResult.emitSkipped,
|
|
556
|
+
diagnostics: emitResult.diagnostics.map((d) => ts.flattenDiagnosticMessageText(d.messageText, '\n')),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Partial-emit demotion: demote every anchor whose alias text names, by a
|
|
561
|
+
* relative or an absolute path, a module the stub will not resolve, and
|
|
526
562
|
* rewrite the demoted aliases' lines in the emitted surface to `unknown`.
|
|
563
|
+
*
|
|
564
|
+
* A path resolves when the tree holds its module (`surfaceModuleInTree`: an
|
|
565
|
+
* emitted declaration inside rootDir or outside it, or a verbatim declaration
|
|
566
|
+
* source), read from where the entry was written, not from where the surface
|
|
567
|
+
* sits in the tree. An absolute path into an installed package resolves too
|
|
568
|
+
* (carrick#1773): no emit writes that module, and the specifier rewrite turns
|
|
569
|
+
* the path into the package's bare specifier and a pin, as it does when the
|
|
570
|
+
* emit is whole. A relative path into a package is not rewritten, so it stays
|
|
571
|
+
* demoted (carrick#1857).
|
|
572
|
+
*
|
|
527
573
|
* Anchors already demoted stay as they are; anchors whose text is
|
|
528
574
|
* self-contained (node-builder structural prints, literal object text) are
|
|
529
575
|
* untouched even when their source file failed to emit — their surface line
|
|
530
576
|
* references nothing that can dangle.
|
|
531
577
|
*/
|
|
532
578
|
function demoteDanglingAliases(args) {
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const moduleInTree = (spec) => {
|
|
550
|
-
const id = path.posix.normalize(path.posix.join(surfaceDir, spec));
|
|
551
|
-
if (id.startsWith('..'))
|
|
552
|
-
return false;
|
|
553
|
-
return treeModules.has(id) || treeModules.has(`${id}/index`);
|
|
579
|
+
const surfaceKey = [...args.emitted.keys()].find((fileName) => path.basename(fileName) === args.surfaceDeclaration);
|
|
580
|
+
const inTree = surfaceModuleInTree({
|
|
581
|
+
emitted: args.emitted.keys(),
|
|
582
|
+
declarationSources: args.declarationSources.keys(),
|
|
583
|
+
staging: args.staging,
|
|
584
|
+
entryDir: args.entryDir,
|
|
585
|
+
surfaceDeclaration: args.surfaceDeclaration,
|
|
586
|
+
});
|
|
587
|
+
const resolves = new Map();
|
|
588
|
+
const moduleResolves = (spec) => {
|
|
589
|
+
let answer = resolves.get(spec);
|
|
590
|
+
if (answer === undefined) {
|
|
591
|
+
answer = inTree(spec) || (spec.startsWith('/') && installedPackageSpecifier(spec) !== undefined);
|
|
592
|
+
resolves.set(spec, answer);
|
|
593
|
+
}
|
|
594
|
+
return answer;
|
|
554
595
|
};
|
|
555
596
|
const demoted = new Set();
|
|
556
597
|
const next = args.resolved.map((anchor) => {
|
|
557
598
|
if (anchor.failureReason !== undefined)
|
|
558
599
|
return anchor;
|
|
559
|
-
const dangling = [...collectSpecifiers(anchor.aliasText)].find((spec) => isRelative(spec) && !
|
|
600
|
+
const dangling = [...collectSpecifiers(anchor.aliasText)].find((spec) => isRelative(spec) && !moduleResolves(spec));
|
|
560
601
|
if (dangling === undefined)
|
|
561
602
|
return anchor;
|
|
562
603
|
demoted.add(anchor.request.alias);
|
|
@@ -644,6 +685,7 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
644
685
|
}
|
|
645
686
|
ctx.guard.writeFile(ctx.entryPath, placeholderLines.join('\n') + '\n');
|
|
646
687
|
try {
|
|
688
|
+
ctx.progress('program', 'building the program the anchors are read in');
|
|
647
689
|
const anchorSources = [
|
|
648
690
|
...new Set(opts.anchors
|
|
649
691
|
.flatMap((a) => (a.source_file ? [path.join(ctx.repoRoot, a.source_file)] : []))),
|
|
@@ -653,6 +695,11 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
653
695
|
noEmit: true,
|
|
654
696
|
};
|
|
655
697
|
const program = ts.createProgram([ctx.entryPath, ...anchorSources, ...(deno?.globals ?? [])], options, deno?.host(options));
|
|
698
|
+
// The checker binds every file of the program when it is first asked for,
|
|
699
|
+
// which the first anchor would otherwise do: asked for here, so that the
|
|
700
|
+
// report below says the program is whole before any anchor is read.
|
|
701
|
+
program.getTypeChecker();
|
|
702
|
+
ctx.progress('anchors', `0 of ${opts.anchors.length}`);
|
|
656
703
|
const entrySource = program.getSourceFile(ctx.entryPath);
|
|
657
704
|
const placeholders = new Map();
|
|
658
705
|
if (entrySource) {
|
|
@@ -661,6 +708,20 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
661
708
|
placeholders.set(stmt.name.text, stmt);
|
|
662
709
|
}
|
|
663
710
|
}
|
|
711
|
+
// A module specifier as the entry resolves it, under the program's own
|
|
712
|
+
// resolution (the Deno graph's, for a Deno service). Asked once per
|
|
713
|
+
// specifier: every anchor of a module asks for the same one.
|
|
714
|
+
const entryMode = entrySource?.impliedNodeFormat;
|
|
715
|
+
const fromEntry = new Map();
|
|
716
|
+
const resolveFromEntry = (specifier) => {
|
|
717
|
+
if (!fromEntry.has(specifier)) {
|
|
718
|
+
fromEntry.set(specifier, (deno
|
|
719
|
+
? deno.resolve(specifier, ctx.entryPath, options)
|
|
720
|
+
: ts.resolveModuleName(specifier, ctx.entryPath, options, ts.sys, undefined, undefined, entryMode)
|
|
721
|
+
.resolvedModule)?.resolvedFileName);
|
|
722
|
+
}
|
|
723
|
+
return fromEntry.get(specifier);
|
|
724
|
+
};
|
|
664
725
|
// A literal anchor whose text is a bare identifier resolves through a
|
|
665
726
|
// sibling symbol anchor's module when one names the same symbol.
|
|
666
727
|
const siblingSymbolSpecs = new Map();
|
|
@@ -668,23 +729,20 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
668
729
|
if (anchor.kind !== 'symbol')
|
|
669
730
|
continue;
|
|
670
731
|
if (!siblingSymbolSpecs.has(anchor.symbol_name)) {
|
|
671
|
-
siblingSymbolSpecs.set(anchor.symbol_name, entryRelativeSpecifier(ctx.entryDir, ctx.repoRoot, anchor.source_file));
|
|
732
|
+
siblingSymbolSpecs.set(anchor.symbol_name, entryRelativeSpecifier(ctx.entryDir, ctx.repoRoot, anchor.source_file, resolveFromEntry));
|
|
672
733
|
}
|
|
673
734
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
siblingSymbolSpecs,
|
|
686
|
-
resolveFromEntry,
|
|
687
|
-
}));
|
|
735
|
+
return opts.anchors.map((request, index) => {
|
|
736
|
+
const anchor = resolveAnchor(program, request, {
|
|
737
|
+
repoRoot: ctx.repoRoot,
|
|
738
|
+
entryDir: ctx.entryDir,
|
|
739
|
+
placeholder: placeholders.get(request.alias),
|
|
740
|
+
siblingSymbolSpecs,
|
|
741
|
+
resolveFromEntry,
|
|
742
|
+
});
|
|
743
|
+
ctx.progress('anchors', `${index + 1} of ${opts.anchors.length}`);
|
|
744
|
+
return anchor;
|
|
745
|
+
});
|
|
688
746
|
}
|
|
689
747
|
finally {
|
|
690
748
|
if (fs.existsSync(ctx.entryPath))
|
|
@@ -41,6 +41,8 @@ export interface InstalledPackageSpecifier {
|
|
|
41
41
|
* deciding whether a runtime name is covered by a pinned types package.
|
|
42
42
|
*/
|
|
43
43
|
export declare function typesPackageOf(name: string): string;
|
|
44
|
+
/** A path or specifier without its script, source or declaration extension. */
|
|
45
|
+
export declare function withoutExtension(subpath: string): string;
|
|
44
46
|
/**
|
|
45
47
|
* A compiler host that also resolves the packages an absolute specifier was
|
|
46
48
|
* rewritten into, each from its own install directory. The capture's
|
|
@@ -101,7 +101,8 @@ function existingFile(spec) {
|
|
|
101
101
|
}
|
|
102
102
|
return undefined;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
/** A path or specifier without its script, source or declaration extension. */
|
|
105
|
+
export function withoutExtension(subpath) {
|
|
105
106
|
return subpath.replace(/(?:\.d)?\.(?:ts|mts|cts|js|mjs|cjs|tsx|jsx)$/, '');
|
|
106
107
|
}
|
|
107
108
|
/** Every string target an exports value names, whatever its conditions. */
|
|
@@ -39,3 +39,22 @@ export declare function placeEmittedTree(args: {
|
|
|
39
39
|
entryDir: string;
|
|
40
40
|
surfaceDeclaration: string;
|
|
41
41
|
}): PlacedTree;
|
|
42
|
+
/**
|
|
43
|
+
* A test for whether the tree holds the module a specifier in the surface
|
|
44
|
+
* entry names (carrick#1773).
|
|
45
|
+
*
|
|
46
|
+
* The specifier is read the way the placement above reads one: resolved from
|
|
47
|
+
* the surface's SOURCE-side directory, so an absolute path stays what it is
|
|
48
|
+
* and `../` leaves rootDir, then looked up among the source-side paths of the
|
|
49
|
+
* declarations the tree holds. Those are every emitted declaration, inside
|
|
50
|
+
* rootDir or placed under `OUTSIDE_DIR`, and the declaration sources shipped
|
|
51
|
+
* verbatim (`declarationSources`, relative to rootDir).
|
|
52
|
+
*/
|
|
53
|
+
export declare function surfaceModuleInTree(args: {
|
|
54
|
+
/** tsc's file name for each emitted declaration. */
|
|
55
|
+
emitted: Iterable<string>;
|
|
56
|
+
declarationSources: Iterable<string>;
|
|
57
|
+
staging: string;
|
|
58
|
+
entryDir: string;
|
|
59
|
+
surfaceDeclaration: string;
|
|
60
|
+
}): (spec: string) => boolean;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* A capture whose program stays inside rootDir is placed exactly as before.
|
|
16
16
|
*/
|
|
17
17
|
import * as path from 'node:path';
|
|
18
|
+
import { withoutExtension } from './installed-package.js';
|
|
18
19
|
import { rewriteSpecifiers } from './specifiers.js';
|
|
19
20
|
/** Tree directory holding declarations of sources outside rootDir. */
|
|
20
21
|
export const OUTSIDE_DIR = '__outside__';
|
|
@@ -31,13 +32,12 @@ export function placeEmittedTree(args) {
|
|
|
31
32
|
const sourceSideOf = new Map();
|
|
32
33
|
const outsideFiles = [];
|
|
33
34
|
for (const fileName of args.emitted.keys()) {
|
|
35
|
+
sourceSideOf.set(fileName, sourceSidePath(fileName, args.staging, args.entryDir));
|
|
34
36
|
const rel = posix(path.relative(args.staging, fileName));
|
|
35
37
|
if (escapes(rel)) {
|
|
36
38
|
outsideFiles.push(fileName);
|
|
37
|
-
sourceSideOf.set(fileName, path.resolve(fileName));
|
|
38
39
|
continue;
|
|
39
40
|
}
|
|
40
|
-
sourceSideOf.set(fileName, path.join(args.entryDir, rel));
|
|
41
41
|
relOf.set(fileName, path.basename(rel) === args.surfaceDeclaration ? 'surface.d.ts' : rel);
|
|
42
42
|
}
|
|
43
43
|
const outside = new Map();
|
|
@@ -82,6 +82,43 @@ export function placeEmittedTree(args) {
|
|
|
82
82
|
}
|
|
83
83
|
return { relOf, textOf, outside, rewrites };
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Where tsc would have written an emitted declaration in the source tree. A
|
|
87
|
+
* file under the staging dir mirrors rootDir; one outside it arrived at its
|
|
88
|
+
* source's own path.
|
|
89
|
+
*/
|
|
90
|
+
function sourceSidePath(fileName, staging, entryDir) {
|
|
91
|
+
const rel = posix(path.relative(staging, fileName));
|
|
92
|
+
return escapes(rel) ? path.resolve(fileName) : path.join(entryDir, rel);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A test for whether the tree holds the module a specifier in the surface
|
|
96
|
+
* entry names (carrick#1773).
|
|
97
|
+
*
|
|
98
|
+
* The specifier is read the way the placement above reads one: resolved from
|
|
99
|
+
* the surface's SOURCE-side directory, so an absolute path stays what it is
|
|
100
|
+
* and `../` leaves rootDir, then looked up among the source-side paths of the
|
|
101
|
+
* declarations the tree holds. Those are every emitted declaration, inside
|
|
102
|
+
* rootDir or placed under `OUTSIDE_DIR`, and the declaration sources shipped
|
|
103
|
+
* verbatim (`declarationSources`, relative to rootDir).
|
|
104
|
+
*/
|
|
105
|
+
export function surfaceModuleInTree(args) {
|
|
106
|
+
const held = new Set();
|
|
107
|
+
let surfaceDir = args.entryDir;
|
|
108
|
+
for (const fileName of args.emitted) {
|
|
109
|
+
const sourceSide = sourceSidePath(fileName, args.staging, args.entryDir);
|
|
110
|
+
if (path.basename(sourceSide) === args.surfaceDeclaration)
|
|
111
|
+
surfaceDir = path.dirname(sourceSide);
|
|
112
|
+
held.add(sourceSide.replace(DECLARATION_EXT, ''));
|
|
113
|
+
}
|
|
114
|
+
for (const rel of args.declarationSources) {
|
|
115
|
+
held.add(path.join(args.entryDir, rel).replace(DECLARATION_EXT, ''));
|
|
116
|
+
}
|
|
117
|
+
return (spec) => {
|
|
118
|
+
const target = path.resolve(surfaceDir, withoutExtension(spec));
|
|
119
|
+
return held.has(target) || held.has(path.join(target, 'index'));
|
|
120
|
+
};
|
|
121
|
+
}
|
|
85
122
|
function posix(p) {
|
|
86
123
|
return p.split(path.sep).join('/');
|
|
87
124
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
import ts from 'typescript';
|
|
33
33
|
import * as fs from 'node:fs';
|
|
34
34
|
import * as path from 'node:path';
|
|
35
|
-
import { collectSpecifiers, isRelative, packageNameOf } from './specifiers.js';
|
|
35
|
+
import { collectSpecifiers, declarationCandidates, isDeclarationFileName, isRelative, packageNameOf, } from './specifiers.js';
|
|
36
36
|
import { repairDanglingImports } from './repair-dangling.js';
|
|
37
37
|
import { findDisqualifyingTopTypes, findUnresolvedPlaceholders, isErrorPlaceholder, provenanceOf, unemittedModuleProvenance, unresolvedInTreeProvenance, } from './deep-walk.js';
|
|
38
38
|
export function selfCheckStub(args) {
|
|
@@ -43,7 +43,7 @@ export function selfCheckStub(args) {
|
|
|
43
43
|
const p = path.join(dir, entry.name);
|
|
44
44
|
if (entry.isDirectory())
|
|
45
45
|
walk(p);
|
|
46
|
-
else if (entry.name
|
|
46
|
+
else if (isDeclarationFileName(entry.name))
|
|
47
47
|
treeFiles.push(p);
|
|
48
48
|
}
|
|
49
49
|
};
|
|
@@ -464,15 +464,5 @@ function resolveTreeSpecifier(fromAbs, spec, tree) {
|
|
|
464
464
|
if (!isRelative(spec))
|
|
465
465
|
return undefined;
|
|
466
466
|
const base = path.resolve(path.dirname(fromAbs), spec);
|
|
467
|
-
|
|
468
|
-
`${base}.d.ts`,
|
|
469
|
-
path.join(base, 'index.d.ts'),
|
|
470
|
-
base.endsWith('.js') ? `${base.slice(0, -3)}.d.ts` : undefined,
|
|
471
|
-
base, // already .d.ts
|
|
472
|
-
].filter((c) => c !== undefined);
|
|
473
|
-
for (const candidate of candidates) {
|
|
474
|
-
if (tree.has(candidate))
|
|
475
|
-
return candidate;
|
|
476
|
-
}
|
|
477
|
-
return undefined;
|
|
467
|
+
return declarationCandidates(base).find((candidate) => tree.has(candidate));
|
|
478
468
|
}
|
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
* pattern matching for the post-emit rewrite pass.
|
|
5
5
|
*/
|
|
6
6
|
export declare function isRelative(spec: string): boolean;
|
|
7
|
+
/** A declaration file of any module format: `.d.ts`, `.d.mts`, `.d.cts`. */
|
|
8
|
+
export declare function isDeclarationFileName(name: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The declaration files a relative specifier can name in a stub's tree, in
|
|
11
|
+
* the order tried, given the path it resolves to from the file that holds it.
|
|
12
|
+
*
|
|
13
|
+
* A specifier names its module with no extension (`./a`: `a.d.ts`, or the
|
|
14
|
+
* directory's `index.d.ts`), by the file the module compiles to (`./a.js`:
|
|
15
|
+
* `a.d.ts`, `./a.mjs`: `a.d.mts`, `./a.cjs`: `a.d.cts`), or by the
|
|
16
|
+
* declaration file itself. The surface names an anchor's module the second
|
|
17
|
+
* way where the first does not resolve (carrick#1911), and so does any source
|
|
18
|
+
* written for `node16`..`nodenext`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function declarationCandidates(resolved: string): string[];
|
|
7
21
|
/** zod -> zod, @scope/pkg/sub -> @scope/pkg, pkg/sub -> pkg */
|
|
8
22
|
export declare function packageNameOf(spec: string): string;
|
|
9
23
|
/** Extract every module specifier mentioned in a .d.ts text: `from "x"`,
|
|
@@ -3,9 +3,34 @@
|
|
|
3
3
|
* declaration text, external/internal classification, and tsconfig-`paths`
|
|
4
4
|
* pattern matching for the post-emit rewrite pass.
|
|
5
5
|
*/
|
|
6
|
+
import * as path from 'node:path';
|
|
6
7
|
export function isRelative(spec) {
|
|
7
8
|
return spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/');
|
|
8
9
|
}
|
|
10
|
+
/** A declaration file of any module format: `.d.ts`, `.d.mts`, `.d.cts`. */
|
|
11
|
+
export function isDeclarationFileName(name) {
|
|
12
|
+
return /\.d\.[cm]?ts$/.test(name);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The declaration files a relative specifier can name in a stub's tree, in
|
|
16
|
+
* the order tried, given the path it resolves to from the file that holds it.
|
|
17
|
+
*
|
|
18
|
+
* A specifier names its module with no extension (`./a`: `a.d.ts`, or the
|
|
19
|
+
* directory's `index.d.ts`), by the file the module compiles to (`./a.js`:
|
|
20
|
+
* `a.d.ts`, `./a.mjs`: `a.d.mts`, `./a.cjs`: `a.d.cts`), or by the
|
|
21
|
+
* declaration file itself. The surface names an anchor's module the second
|
|
22
|
+
* way where the first does not resolve (carrick#1911), and so does any source
|
|
23
|
+
* written for `node16`..`nodenext`.
|
|
24
|
+
*/
|
|
25
|
+
export function declarationCandidates(resolved) {
|
|
26
|
+
const output = /\.([cm]?)jsx?$/.exec(resolved);
|
|
27
|
+
return [
|
|
28
|
+
`${resolved}.d.ts`,
|
|
29
|
+
path.join(resolved, 'index.d.ts'),
|
|
30
|
+
...(output ? [`${resolved.slice(0, output.index)}.d.${output[1]}ts`] : []),
|
|
31
|
+
resolved,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
9
34
|
/** zod -> zod, @scope/pkg/sub -> @scope/pkg, pkg/sub -> pkg */
|
|
10
35
|
export function packageNameOf(spec) {
|
|
11
36
|
const parts = spec.split('/');
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
* inference path in `type-inferrer.ts`), which walks the resolved `Type` and
|
|
27
27
|
* rebuilds the inlined text.
|
|
28
28
|
*
|
|
29
|
-
* Each resolve call builds its own throwaway
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Each resolve call builds its own throwaway project over the stub tree and
|
|
30
|
+
* reads nothing else: not the service's project, and not whether the sidecar
|
|
31
|
+
* was ever initialised. So the request costs what the stub costs, in a process
|
|
32
|
+
* that has built nothing as in one that has (carrick#1927), and the sidecar's
|
|
33
|
+
* long-lived project never sees stub files.
|
|
32
34
|
*/
|
|
33
|
-
import { Project } from 'ts-morph';
|
|
34
35
|
export interface ResolvedDefinition {
|
|
35
36
|
type_alias: string;
|
|
36
37
|
/** Original declaration text as written (preserves named types) */
|
|
@@ -39,10 +40,6 @@ export interface ResolvedDefinition {
|
|
|
39
40
|
expanded: string;
|
|
40
41
|
}
|
|
41
42
|
export declare class DefinitionResolver {
|
|
42
|
-
private readonly project;
|
|
43
|
-
constructor(options: {
|
|
44
|
-
project: Project;
|
|
45
|
-
});
|
|
46
43
|
/**
|
|
47
44
|
* Resolve surface aliases from a capture stub package directory
|
|
48
45
|
* (`<stub_dir>/types/surface.d.ts` + its declaration tree).
|
|
@@ -26,19 +26,17 @@
|
|
|
26
26
|
* inference path in `type-inferrer.ts`), which walks the resolved `Type` and
|
|
27
27
|
* rebuilds the inlined text.
|
|
28
28
|
*
|
|
29
|
-
* Each resolve call builds its own throwaway
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Each resolve call builds its own throwaway project over the stub tree and
|
|
30
|
+
* reads nothing else: not the service's project, and not whether the sidecar
|
|
31
|
+
* was ever initialised. So the request costs what the stub costs, in a process
|
|
32
|
+
* that has built nothing as in one that has (carrick#1927), and the sidecar's
|
|
33
|
+
* long-lived project never sees stub files.
|
|
32
34
|
*/
|
|
33
35
|
import * as path from 'node:path';
|
|
34
36
|
import * as fs from 'node:fs';
|
|
35
37
|
import { Project, Node } from 'ts-morph';
|
|
36
38
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
37
39
|
export class DefinitionResolver {
|
|
38
|
-
project;
|
|
39
|
-
constructor(options) {
|
|
40
|
-
this.project = options.project;
|
|
41
|
-
}
|
|
42
40
|
/**
|
|
43
41
|
* Resolve surface aliases from a capture stub package directory
|
|
44
42
|
* (`<stub_dir>/types/surface.d.ts` + its declaration tree).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The function a line names, from an index built once per source file
|
|
3
|
+
* (carrick#1915).
|
|
4
|
+
*
|
|
5
|
+
* A signature request carries only the line its function starts on, and a
|
|
6
|
+
* signature pass sends one request per unannotated slot: thousands of
|
|
7
|
+
* lookups, several per function and many per file. Answering each by walking
|
|
8
|
+
* the file's every node cost the file's size per slot, and was 81% of the
|
|
9
|
+
* pass on a 2,345-file program. Here the file is walked once, over the
|
|
10
|
+
* compiler's own nodes, and each lookup reads the few entries near its line.
|
|
11
|
+
*
|
|
12
|
+
* The index belongs to the compiler's source file node, not to the file's
|
|
13
|
+
* name: replacing a file's text gives it a new node, so a file rewritten for
|
|
14
|
+
* one reading (the unwidened reading, the retype check) and then restored is
|
|
15
|
+
* indexed again each time, and never answered from positions it no longer has.
|
|
16
|
+
*/
|
|
17
|
+
import { type ArrowFunction, type FunctionDeclaration, type FunctionExpression, type MethodDeclaration, type SourceFile } from 'ts-morph';
|
|
18
|
+
/** The declarations a line can name. */
|
|
19
|
+
export type LineFunction = FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration;
|
|
20
|
+
/**
|
|
21
|
+
* The function whose declaration starts at, or within `LINE_TOLERANCE` lines
|
|
22
|
+
* of, the given line: the closest, then the smallest, then the first in the
|
|
23
|
+
* file. A function starting after the line is passed over when a statement
|
|
24
|
+
* that opens on the line or after it, on a line before the function's, does
|
|
25
|
+
* not contain the function. Undefined when no function is left.
|
|
26
|
+
*
|
|
27
|
+
* These are `TypeInferrer.findFunctionByLine`'s rules, which says why each is
|
|
28
|
+
* there; this is where they are computed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function functionAtLine(sourceFile: SourceFile, line: number): LineFunction | undefined;
|