carrick 0.3.72 → 0.3.75
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 +11 -2
- package/dist/contract.js +5 -0
- package/dist/contract.js.map +1 -1
- package/dist/init/run.js +1 -1
- package/dist/init/run.js.map +1 -1
- package/dist/render.js +11 -3
- package/dist/render.js.map +1 -1
- package/package.json +6 -6
- package/sidecar/dist/src/capture/anchors.d.ts +15 -0
- package/sidecar/dist/src/capture/anchors.js +70 -5
- package/sidecar/dist/src/capture/api.d.ts +39 -4
- package/sidecar/dist/src/capture/check-classify.d.ts +2 -0
- package/sidecar/dist/src/capture/check-classify.js +28 -0
- package/sidecar/dist/src/capture/check-deep.js +1 -1
- package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
- package/sidecar/dist/src/capture/check-probe.js +16 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +33 -6
- package/sidecar/dist/src/capture/deep-walk.js +81 -28
- package/sidecar/dist/src/capture/index.js +16 -6
- package/sidecar/dist/src/capture/installed-package.d.ts +58 -0
- package/sidecar/dist/src/capture/installed-package.js +311 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +8 -0
- package/sidecar/dist/src/capture/lockfile.js +1 -1
- package/sidecar/dist/src/capture/node-builder.d.ts +27 -0
- package/sidecar/dist/src/capture/node-builder.js +107 -1
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +23 -6
- package/sidecar/dist/src/capture/paths-rewrite.js +43 -11
- package/sidecar/dist/src/capture/self-check.js +12 -3
- package/sidecar/dist/src/capture/unresolved.d.ts +28 -0
- package/sidecar/dist/src/capture/unresolved.js +107 -0
- package/sidecar/dist/src/type-inferrer.d.ts +108 -11
- package/sidecar/dist/src/type-inferrer.js +513 -90
- package/sidecar/dist/src/type-structural-expander.d.ts +23 -2
- package/sidecar/dist/src/type-structural-expander.js +55 -17
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +14 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +23 -2
- package/sidecar/dist/src/validators.d.ts +10 -0
- package/sidecar/dist/src/validators.js +1 -0
|
@@ -43,7 +43,7 @@ export declare function fnv1a(input: string): string;
|
|
|
43
43
|
* path, so it is byte-stable across runs. */
|
|
44
44
|
export declare function pairId(spec: CheckPairSpec): string;
|
|
45
45
|
/** Names each gate line so a TS2344 can be attributed to a specific side+kind. */
|
|
46
|
-
export type GateName = 'sent:any' | 'sent:unknown' | 'sent:never' | 'expected:any' | 'expected:unknown' | 'expected:never';
|
|
46
|
+
export type GateName = 'sent:any' | 'sent:unknown' | 'sent:never' | 'sent:void' | 'sent:form' | 'expected:any' | 'expected:unknown' | 'expected:never' | 'expected:void';
|
|
47
47
|
export interface ProbePlan {
|
|
48
48
|
pairId: string;
|
|
49
49
|
spec: CheckPairSpec;
|
|
@@ -95,6 +95,22 @@ export function buildProbe(spec, packageOf) {
|
|
|
95
95
|
gateLines.set(push(`type _G_expected_any = Assert<Not<IsAny<Expected>>>;`), 'expected:any');
|
|
96
96
|
gateLines.set(push(`type _G_expected_unknown = Assert<Not<IsUnknown<Expected>>>;`), 'expected:unknown');
|
|
97
97
|
gateLines.set(push(`type _G_expected_never = Assert<Not<IsNever<Expected>>>;`), 'expected:never');
|
|
98
|
+
// carrick#1162: `void`/`undefined` is what a call site that reads no body
|
|
99
|
+
// types its response as (a wrapper returning `Promise<void>`). It states no
|
|
100
|
+
// contract, so no counterparty shape can be judged against it.
|
|
101
|
+
// `any` is excluded first: `[any] extends [void]` holds, and an `any` side
|
|
102
|
+
// must keep its own top-type reason, never read as "reads no body".
|
|
103
|
+
push(`type IsVoid<T> = 0 extends 1 & T ? false : [T] extends [never] ? false : [T] extends [void] ? true : false;`);
|
|
104
|
+
gateLines.set(push(`type _G_sent_void = Assert<Not<IsVoid<Sent>>>;`), 'sent:void');
|
|
105
|
+
gateLines.set(push(`type _G_expected_void = Assert<Not<IsVoid<Expected>>>;`), 'expected:void');
|
|
106
|
+
// carrick#1162: a form-encoded request body (the platform `FormData` or
|
|
107
|
+
// `URLSearchParams`) carries its fields as runtime appends, which no type
|
|
108
|
+
// records, so a field-by-field comparison against a declared shape reads
|
|
109
|
+
// every such body as missing all of its fields.
|
|
110
|
+
if (spec.protocol === 'http' && spec.type_kind === 'request') {
|
|
111
|
+
push(`type IsFormBody<T> = 0 extends 1 & T ? false : [T] extends [never] ? false : [T] extends [FormData | URLSearchParams] ? true : false;`);
|
|
112
|
+
gateLines.set(push(`type _G_sent_form = Assert<Not<IsFormBody<Sent>>>;`), 'sent:form');
|
|
113
|
+
}
|
|
98
114
|
push(`declare const sent: Sent;`);
|
|
99
115
|
let assignmentLine;
|
|
100
116
|
if (spec.protocol === 'graphql') {
|
|
@@ -44,19 +44,46 @@ export type DeepTopType = Pick<TypeProvenance, 'kind' | 'path'>;
|
|
|
44
44
|
* void` safely accepts a stricter counterparty), so it is not a masked
|
|
45
45
|
* mismatch and demoting it would over-demote a sound shape;
|
|
46
46
|
* - TypeScript's unresolved-reference `error` placeholder (`intrinsicName ===
|
|
47
|
-
* 'error'`) is excluded (see `
|
|
47
|
+
* 'error'`) is excluded (see `disqualifyingFlag`): it heals when the check installs the
|
|
48
48
|
* pinned external, so it is a healable decay, not an author-baked `any`.
|
|
49
49
|
* A type the walk cannot cheaply finish is NOT flagged — over-demoting a
|
|
50
50
|
* legitimately fully-resolved type is the failure mode this guard must not have.
|
|
51
51
|
*/
|
|
52
52
|
export declare function findDisqualifyingTopTypes(root: ts.Type, program: ts.Program, checker: ts.TypeChecker, location: ts.Node): DeepTopType[];
|
|
53
|
+
/**
|
|
54
|
+
* Member paths at which `root` holds TypeScript's unresolved-reference
|
|
55
|
+
* placeholder (the `error` intrinsic) rather than a type (carrick#1164).
|
|
56
|
+
*
|
|
57
|
+
* Run on the SOURCE program, where a member typed through an import that did
|
|
58
|
+
* not resolve still carries the placeholder. Once printed into the stub the
|
|
59
|
+
* placeholder is the keyword `any`, indistinguishable from an author's `any`,
|
|
60
|
+
* so this is the only point at which the two causes can be told apart. Same
|
|
61
|
+
* walk, same budget and the same path notation as the self-check's walk over
|
|
62
|
+
* the emitted text, so a path found here names the member a self-check finding
|
|
63
|
+
* names. A walk that runs out of budget reports what it found before it did.
|
|
64
|
+
*/
|
|
65
|
+
export declare function findUnresolvedPlaceholders(root: ts.Type, program: ts.Program, checker: ts.TypeChecker, location: ts.Node): string[];
|
|
66
|
+
/** What an anchor's SOURCE program could not resolve (carrick#1164). */
|
|
67
|
+
export interface UnresolvedAtAnchor {
|
|
68
|
+
/** Member paths holding the unresolved-reference placeholder. */
|
|
69
|
+
paths: readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Module specifiers, as the source wrote them, that did not resolve from the
|
|
72
|
+
* anchor's file or a module it reaches. Internal specifiers first. May be
|
|
73
|
+
* empty: a name the program never declared leaves the same placeholder.
|
|
74
|
+
*/
|
|
75
|
+
specifiers: readonly string[];
|
|
76
|
+
}
|
|
53
77
|
/**
|
|
54
78
|
* Turn a deep finding into the published provenance entry (carrick#376).
|
|
55
79
|
*
|
|
56
80
|
* The self-check reads EMITTED declaration text, so a top type it finds is
|
|
57
|
-
* text:
|
|
58
|
-
*
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
81
|
+
* text: an author annotation and an emitter that printed an unresolved value
|
|
82
|
+
* both read `any` there. The anchor's source program can still tell them apart
|
|
83
|
+
* (`findUnresolvedPlaceholders`), and when it recorded the finding's path as a
|
|
84
|
+
* placeholder the cause is `unresolved_import`: a dependency or a generated
|
|
85
|
+
* module was missing on the scanned checkout, which installing or generating
|
|
86
|
+
* it fixes (carrick#1164). Anything else at that position is `declared`. The
|
|
87
|
+
* one other cause the walk itself can distinguish is its own budget.
|
|
61
88
|
*/
|
|
62
|
-
export declare function provenanceOf(finding: DeepTopType): TypeProvenance;
|
|
89
|
+
export declare function provenanceOf(finding: DeepTopType, unresolved?: UnresolvedAtAnchor): TypeProvenance;
|
|
@@ -43,12 +43,38 @@ const MAX_DEEP_FINDINGS = 32;
|
|
|
43
43
|
* void` safely accepts a stricter counterparty), so it is not a masked
|
|
44
44
|
* mismatch and demoting it would over-demote a sound shape;
|
|
45
45
|
* - TypeScript's unresolved-reference `error` placeholder (`intrinsicName ===
|
|
46
|
-
* 'error'`) is excluded (see `
|
|
46
|
+
* 'error'`) is excluded (see `disqualifyingFlag`): it heals when the check installs the
|
|
47
47
|
* pinned external, so it is a healable decay, not an author-baked `any`.
|
|
48
48
|
* A type the walk cannot cheaply finish is NOT flagged — over-demoting a
|
|
49
49
|
* legitimately fully-resolved type is the failure mode this guard must not have.
|
|
50
50
|
*/
|
|
51
51
|
export function findDisqualifyingTopTypes(root, program, checker, location) {
|
|
52
|
+
return walkTopTypes(root, program, checker, location, disqualifyingFlag);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Member paths at which `root` holds TypeScript's unresolved-reference
|
|
56
|
+
* placeholder (the `error` intrinsic) rather than a type (carrick#1164).
|
|
57
|
+
*
|
|
58
|
+
* Run on the SOURCE program, where a member typed through an import that did
|
|
59
|
+
* not resolve still carries the placeholder. Once printed into the stub the
|
|
60
|
+
* placeholder is the keyword `any`, indistinguishable from an author's `any`,
|
|
61
|
+
* so this is the only point at which the two causes can be told apart. Same
|
|
62
|
+
* walk, same budget and the same path notation as the self-check's walk over
|
|
63
|
+
* the emitted text, so a path found here names the member a self-check finding
|
|
64
|
+
* names. A walk that runs out of budget reports what it found before it did.
|
|
65
|
+
*/
|
|
66
|
+
export function findUnresolvedPlaceholders(root, program, checker, location) {
|
|
67
|
+
return walkTopTypes(root, program, checker, location, (t) => isErrorPlaceholder(t) ? 'any' : undefined)
|
|
68
|
+
.filter((finding) => finding.kind !== 'budget_exhausted')
|
|
69
|
+
.map((finding) => finding.path);
|
|
70
|
+
}
|
|
71
|
+
/** TypeScript's unresolved-reference placeholder: `TypeFlags.Any` with the
|
|
72
|
+
* internal `intrinsicName === 'error'` (stable since TS 1.x; see `anchors.ts`). */
|
|
73
|
+
function isErrorPlaceholder(t) {
|
|
74
|
+
return ((t.flags & ts.TypeFlags.Any) !== 0 &&
|
|
75
|
+
t.intrinsicName === 'error');
|
|
76
|
+
}
|
|
77
|
+
function walkTopTypes(root, program, checker, location, flagOf) {
|
|
52
78
|
// Cover v1's inline-expander reach with margin so this structural walk is a
|
|
53
79
|
// genuine superset of v1's text-scan disqualifier AT DEPTH: anything v1 could
|
|
54
80
|
// expand-and-flag as `any`/`unknown`, this walk reaches too. v1's expander
|
|
@@ -63,28 +89,6 @@ export function findDisqualifyingTopTypes(root, program, checker, location) {
|
|
|
63
89
|
const MAX_VISITED = 4096;
|
|
64
90
|
const seen = new Set();
|
|
65
91
|
let visited = 0;
|
|
66
|
-
const flagOf = (t) => {
|
|
67
|
-
if (t.flags & ts.TypeFlags.Any) {
|
|
68
|
-
// TypeScript's unresolved-reference placeholder (e.g. `import('ext').Foo`
|
|
69
|
-
// on a bare checkout) carries `TypeFlags.Any` but `intrinsicName ===
|
|
70
|
-
// 'error'` — NOT an author-baked `any`. It resolves to the real type once
|
|
71
|
-
// the check phase installs the pinned external, so it must not count as a
|
|
72
|
-
// disqualifier: treating it as `any` would demote a healable external
|
|
73
|
-
// reference. Genuine author `any` carries `intrinsicName === 'any'`.
|
|
74
|
-
// (`intrinsicName` is internal but stable since TS 1.x — same standing as
|
|
75
|
-
// its use in anchors.ts.)
|
|
76
|
-
//
|
|
77
|
-
// The `error` placeholder also stands in for NON-healable causes (TS2304
|
|
78
|
-
// undefined name, TS2315 wrong-arity generic, a dangling internal
|
|
79
|
-
// specifier). Excluding those here is not a hole: each emits a diagnostic
|
|
80
|
-
// in the alias's own closure, so the closure-failure classification
|
|
81
|
-
// (`internalFailure` -> decayed_internal) or the check-phase POISON rule
|
|
82
|
-
// — NOT this deep walk — is their backstop, and both fail closed.
|
|
83
|
-
const name = t.intrinsicName;
|
|
84
|
-
return name === 'error' ? undefined : 'any';
|
|
85
|
-
}
|
|
86
|
-
return t.flags & ts.TypeFlags.Unknown ? 'unknown' : undefined;
|
|
87
|
-
};
|
|
88
92
|
// Findings accumulate rather than short-circuiting: the FIRST is what the
|
|
89
93
|
// check phase pre-gates on (so the verdict is identical to the
|
|
90
94
|
// stop-at-first walk this replaces), and the rest answer "which fields are
|
|
@@ -216,16 +220,44 @@ export function findDisqualifyingTopTypes(root, program, checker, location) {
|
|
|
216
220
|
rest.sort((a, b) => a.path.localeCompare(b.path));
|
|
217
221
|
return [head, ...rest];
|
|
218
222
|
}
|
|
223
|
+
/** The disqualifier the self-check and the check phase gate on (see
|
|
224
|
+
* `findDisqualifyingTopTypes`). */
|
|
225
|
+
function disqualifyingFlag(t) {
|
|
226
|
+
if (t.flags & ts.TypeFlags.Any) {
|
|
227
|
+
// TypeScript's unresolved-reference placeholder (e.g. `import('ext').Foo`
|
|
228
|
+
// on a bare checkout) carries `TypeFlags.Any` but `intrinsicName ===
|
|
229
|
+
// 'error'` — NOT an author-baked `any`. It resolves to the real type once
|
|
230
|
+
// the check phase installs the pinned external, so it must not count as a
|
|
231
|
+
// disqualifier: treating it as `any` would demote a healable external
|
|
232
|
+
// reference. Genuine author `any` carries `intrinsicName === 'any'`.
|
|
233
|
+
// (`intrinsicName` is internal but stable since TS 1.x — same standing as
|
|
234
|
+
// its use in anchors.ts.)
|
|
235
|
+
//
|
|
236
|
+
// The `error` placeholder also stands in for NON-healable causes (TS2304
|
|
237
|
+
// undefined name, TS2315 wrong-arity generic, a dangling internal
|
|
238
|
+
// specifier). Excluding those here is not a hole: each emits a diagnostic
|
|
239
|
+
// in the alias's own closure, so the closure-failure classification
|
|
240
|
+
// (`internalFailure` -> decayed_internal) or the check-phase POISON rule
|
|
241
|
+
// — NOT this deep walk — is their backstop, and both fail closed.
|
|
242
|
+
return isErrorPlaceholder(t) ? undefined : 'any';
|
|
243
|
+
}
|
|
244
|
+
return t.flags & ts.TypeFlags.Unknown ? 'unknown' : undefined;
|
|
245
|
+
}
|
|
246
|
+
/** How many unresolved specifiers a detail names before it counts the rest. */
|
|
247
|
+
const MAX_NAMED_SPECIFIERS = 3;
|
|
219
248
|
/**
|
|
220
249
|
* Turn a deep finding into the published provenance entry (carrick#376).
|
|
221
250
|
*
|
|
222
251
|
* The self-check reads EMITTED declaration text, so a top type it finds is
|
|
223
|
-
* text:
|
|
224
|
-
*
|
|
225
|
-
* `
|
|
226
|
-
*
|
|
252
|
+
* text: an author annotation and an emitter that printed an unresolved value
|
|
253
|
+
* both read `any` there. The anchor's source program can still tell them apart
|
|
254
|
+
* (`findUnresolvedPlaceholders`), and when it recorded the finding's path as a
|
|
255
|
+
* placeholder the cause is `unresolved_import`: a dependency or a generated
|
|
256
|
+
* module was missing on the scanned checkout, which installing or generating
|
|
257
|
+
* it fixes (carrick#1164). Anything else at that position is `declared`. The
|
|
258
|
+
* one other cause the walk itself can distinguish is its own budget.
|
|
227
259
|
*/
|
|
228
|
-
export function provenanceOf(finding) {
|
|
260
|
+
export function provenanceOf(finding, unresolved) {
|
|
229
261
|
if (finding.kind === 'budget_exhausted') {
|
|
230
262
|
return {
|
|
231
263
|
path: finding.path,
|
|
@@ -234,6 +266,14 @@ export function provenanceOf(finding) {
|
|
|
234
266
|
detail: 'the type is too deep or wide to verify within the capture budget here, so it is reported unverified rather than assumed clean',
|
|
235
267
|
};
|
|
236
268
|
}
|
|
269
|
+
if (finding.kind === 'any' && unresolved?.paths.includes(finding.path)) {
|
|
270
|
+
return {
|
|
271
|
+
path: finding.path,
|
|
272
|
+
kind: finding.kind,
|
|
273
|
+
reason: 'unresolved_import',
|
|
274
|
+
detail: unresolvedDetail(unresolved.specifiers),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
237
277
|
return {
|
|
238
278
|
path: finding.path,
|
|
239
279
|
kind: finding.kind,
|
|
@@ -241,3 +281,16 @@ export function provenanceOf(finding) {
|
|
|
241
281
|
detail: `the captured declaration states '${finding.kind}' at this position, so no counterparty shape can disagree with it`,
|
|
242
282
|
};
|
|
243
283
|
}
|
|
284
|
+
function unresolvedDetail(specifiers) {
|
|
285
|
+
const lead = "the type at this position did not resolve on the scanned checkout, so the compiler printed a placeholder 'any' rather than a declared type";
|
|
286
|
+
if (specifiers.length === 0)
|
|
287
|
+
return lead;
|
|
288
|
+
const named = specifiers
|
|
289
|
+
.slice(0, MAX_NAMED_SPECIFIERS)
|
|
290
|
+
.map((specifier) => `'${specifier}'`)
|
|
291
|
+
.join(', ');
|
|
292
|
+
const more = specifiers.length > MAX_NAMED_SPECIFIERS
|
|
293
|
+
? ` and ${specifiers.length - MAX_NAMED_SPECIFIERS} more`
|
|
294
|
+
: '';
|
|
295
|
+
return `${lead}; unresolved imports reachable from the anchor: ${named}${more}`;
|
|
296
|
+
}
|
|
@@ -35,6 +35,7 @@ import { entryRelativeSpecifier, resolveAnchor } from './anchors.js';
|
|
|
35
35
|
import { findAugmentationFiles } from './augmentations.js';
|
|
36
36
|
import { installedVersions, lockfileVersions } from './lockfile.js';
|
|
37
37
|
import { rewriteEmittedSpecifiers } from './paths-rewrite.js';
|
|
38
|
+
import { typesPackageOf, withInstalledPackages } from './installed-package.js';
|
|
38
39
|
import { selfCheckStub } from './self-check.js';
|
|
39
40
|
import { collectSpecifiers, isRelative, packageNameOf } from './specifiers.js';
|
|
40
41
|
import { DenoProject, findDenoConfig } from './deno-project.js';
|
|
@@ -304,13 +305,14 @@ export function captureStub(opts) {
|
|
|
304
305
|
catch (err) {
|
|
305
306
|
return fail(stubDir, packageName, [err instanceof Error ? err.message : String(err)]);
|
|
306
307
|
}
|
|
307
|
-
const
|
|
308
|
+
const rewritten = rewriteEmittedSpecifiers({
|
|
308
309
|
typesDir,
|
|
309
310
|
files: emittedFiles,
|
|
310
311
|
options: parsed.options,
|
|
311
312
|
configPath,
|
|
312
313
|
entryDir,
|
|
313
314
|
});
|
|
315
|
+
const specifierRewrites = denoRewrites + rewritten.rewrites;
|
|
314
316
|
// ---- Pin external deps: installed node_modules first, lockfile fallback ----
|
|
315
317
|
// Externals are collected AFTER the rewrite pass: a rewritten paths
|
|
316
318
|
// specifier is internal, not a dependency.
|
|
@@ -335,13 +337,20 @@ export function captureStub(opts) {
|
|
|
335
337
|
const lockVersions = lockfileVersions(repoRoot);
|
|
336
338
|
for (const name of Object.keys(deno?.pinned ?? {}))
|
|
337
339
|
externalSpecs.add(name);
|
|
340
|
+
// A package an absolute specifier was rewritten into carries the version
|
|
341
|
+
// installed at that path (#1174); it may be a transitive the repo root
|
|
342
|
+
// neither installs by name nor locks.
|
|
343
|
+
for (const name of Object.keys(rewritten.pins))
|
|
344
|
+
externalSpecs.add(name);
|
|
338
345
|
const pinned = {};
|
|
339
346
|
const unpinned = [];
|
|
340
347
|
for (const name of [...externalSpecs].sort()) {
|
|
341
|
-
const version = deno?.pinned[name] ?? installed.get(name) ?? lockVersions.get(name);
|
|
348
|
+
const version = deno?.pinned[name] ?? rewritten.pins[name] ?? installed.get(name) ?? lockVersions.get(name);
|
|
342
349
|
if (version)
|
|
343
350
|
pinned[name] = version;
|
|
344
|
-
|
|
351
|
+
// A runtime name whose declarations come from a rewritten `@types/*`
|
|
352
|
+
// package resolves through that pin.
|
|
353
|
+
else if (!rewritten.pins[typesPackageOf(name)])
|
|
345
354
|
unpinned.push(name);
|
|
346
355
|
}
|
|
347
356
|
const dependencyRoot = deno?.config.workspaceRoot ?? repoRoot;
|
|
@@ -369,7 +378,9 @@ export function captureStub(opts) {
|
|
|
369
378
|
pinned,
|
|
370
379
|
bareCheckout,
|
|
371
380
|
repoRoot: dependencyRoot,
|
|
372
|
-
compilerHost: deno
|
|
381
|
+
compilerHost: deno || Object.keys(rewritten.installs).length > 0
|
|
382
|
+
? (options) => withInstalledPackages(options, rewritten.installs, deno ? deno.host(options) : undefined)
|
|
383
|
+
: undefined,
|
|
373
384
|
});
|
|
374
385
|
const fidelity = computeFidelity(aliases);
|
|
375
386
|
fs.writeFileSync(path.join(stubDir, 'carrick-manifest.json'), JSON.stringify({
|
|
@@ -478,8 +489,7 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
478
489
|
try {
|
|
479
490
|
const anchorSources = [
|
|
480
491
|
...new Set(opts.anchors
|
|
481
|
-
.
|
|
482
|
-
.map((a) => path.join(ctx.repoRoot, a.source_file))),
|
|
492
|
+
.flatMap((a) => (a.source_file ? [path.join(ctx.repoRoot, a.source_file)] : []))),
|
|
483
493
|
].filter((f) => fs.existsSync(f));
|
|
484
494
|
const options = {
|
|
485
495
|
...parsed.options,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map an absolute path into an installed package onto the specifier a
|
|
3
|
+
* consumer of that package would write (carrick#1174).
|
|
4
|
+
*
|
|
5
|
+
* The v1 printer and the node builder name an out-of-scope type by the
|
|
6
|
+
* absolute path of its declaration file when no bare specifier reaches it
|
|
7
|
+
* from where they print. Inside a capture stub that path is both private (the
|
|
8
|
+
* checkout root, or the home directory of a runtime's npm cache) and dead:
|
|
9
|
+
* the check workspace resolves packages from the stub's pinned dependencies,
|
|
10
|
+
* never from the scanning machine's disk.
|
|
11
|
+
*
|
|
12
|
+
* The replacement is decided against the package itself, installed alone at
|
|
13
|
+
* `node_modules/<name>`, which is exactly what the check workspace sees:
|
|
14
|
+
* 1. an `exports` entry (or, without `exports`, the types entry or the file's
|
|
15
|
+
* subpath) that resolves to the same file under bundler resolution;
|
|
16
|
+
* 2. failing that, an exported entry whose module exports the imported name
|
|
17
|
+
* from that same file (a type in an unexported file, re-exported by the
|
|
18
|
+
* package root);
|
|
19
|
+
* 3. failing both, the subpath as written, so the self-check reports the
|
|
20
|
+
* specifier as unresolvable rather than the stub leaking the path.
|
|
21
|
+
*
|
|
22
|
+
* Seam: node builtins, `typescript`, and this bundle only.
|
|
23
|
+
*/
|
|
24
|
+
import ts from 'typescript';
|
|
25
|
+
export interface InstalledPackageSpecifier {
|
|
26
|
+
/** Bare specifier: public package name plus subpath, e.g. `pkg/sub`. */
|
|
27
|
+
specifier: string;
|
|
28
|
+
/** The package's own name and real install directory (never written out). */
|
|
29
|
+
install: {
|
|
30
|
+
name: string;
|
|
31
|
+
root: string;
|
|
32
|
+
};
|
|
33
|
+
/** The package the path landed in and its installed version, when published. */
|
|
34
|
+
pin?: {
|
|
35
|
+
name: string;
|
|
36
|
+
version: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The `@types/*` package that serves a bare name's declarations, for callers
|
|
41
|
+
* deciding whether a runtime name is covered by a pinned types package.
|
|
42
|
+
*/
|
|
43
|
+
export declare function typesPackageOf(name: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* A compiler host that also resolves the packages an absolute specifier was
|
|
46
|
+
* rewritten into, each from its own install directory. The capture's
|
|
47
|
+
* self-check compiles the stub against the producer's `node_modules`, which
|
|
48
|
+
* does not name a transitive package at its root; the check workspace does,
|
|
49
|
+
* because the stub pins it. Without this the self-check would fail a
|
|
50
|
+
* specifier the check resolves.
|
|
51
|
+
*/
|
|
52
|
+
export declare function withInstalledPackages(options: ts.CompilerOptions, installs: Record<string, string>, base?: ts.CompilerHost): ts.CompilerHost;
|
|
53
|
+
/**
|
|
54
|
+
* The bare specifier for an absolute path into an installed package, or
|
|
55
|
+
* undefined when the path does not land in one. `importedName` is the first
|
|
56
|
+
* name read off the import (`import("...").Name`), when the text has one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installedPackageSpecifier(absoluteSpecifier: string, importedName?: string): InstalledPackageSpecifier | undefined;
|