carrick 0.3.124 → 0.3.126

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.
Files changed (29) hide show
  1. package/package.json +6 -6
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/sidecar/dist/src/capture/api.d.ts +5 -1
  4. package/sidecar/dist/src/capture/check-classify.js +11 -5
  5. package/sidecar/dist/src/capture/check.js +8 -1
  6. package/sidecar/dist/src/capture/deep-walk.d.ts +31 -0
  7. package/sidecar/dist/src/capture/deep-walk.js +43 -1
  8. package/sidecar/dist/src/capture/deno-project.d.ts +7 -0
  9. package/sidecar/dist/src/capture/deno-project.js +7 -0
  10. package/sidecar/dist/src/capture/index.d.ts +1 -1
  11. package/sidecar/dist/src/capture/index.js +1 -1
  12. package/sidecar/dist/src/capture/service-config.d.ts +11 -0
  13. package/sidecar/dist/src/capture/service-config.js +40 -0
  14. package/sidecar/dist/src/declaration-attribution.d.ts +11 -0
  15. package/sidecar/dist/src/declaration-attribution.js +16 -0
  16. package/sidecar/dist/src/implementations.d.ts +43 -0
  17. package/sidecar/dist/src/implementations.js +224 -0
  18. package/sidecar/dist/src/index.js +35 -1
  19. package/sidecar/dist/src/library-claims.d.ts +17 -2
  20. package/sidecar/dist/src/library-claims.js +242 -47
  21. package/sidecar/dist/src/position-rows.d.ts +119 -1
  22. package/sidecar/dist/src/position-rows.js +163 -9
  23. package/sidecar/dist/src/project-loader.d.ts +31 -0
  24. package/sidecar/dist/src/project-loader.js +67 -4
  25. package/sidecar/dist/src/type-inferrer.d.ts +150 -16
  26. package/sidecar/dist/src/type-inferrer.js +651 -94
  27. package/sidecar/dist/src/types.d.ts +46 -2
  28. package/sidecar/dist/src/validators.d.ts +73 -0
  29. package/sidecar/dist/src/validators.js +10 -0
@@ -71,6 +71,17 @@ export declare const FLAG_TOO_MANY = 16;
71
71
  export declare const FLAG_PUBLISHED = 32;
72
72
  /** The declaration is what its module exports as `export =` or `default` (carrick#2508). */
73
73
  export declare const FLAG_MODULE_DEFAULT = 64;
74
+ /**
75
+ * A `call` row only: the call's declared return is the result of the
76
+ * function it is handed (`ReturnType<F>`, a bare type parameter the handed
77
+ * function returns, or an awaitable of one), so the library runs it and
78
+ * hands its result back (carrick#2634). Set when the call passes at least
79
+ * one argument at a function-typed parameter and every such parameter
80
+ * meets that return. The last free bit of the `u8`: the next flag widens
81
+ * the field and bumps the format. Pinned to `FLAG_RUNS_HANDED` in
82
+ * `src/position_rows.rs` by `position-rows-runs-handed.test.ts`.
83
+ */
84
+ export declare const FLAG_RUNS_HANDED = 128;
74
85
  /** The most `decl` rows one position lists. */
75
86
  export declare const DECL_CAP = 16;
76
87
  /** The kinds {@link kindOf} gives a node; `decl` and `jsx` rows are written beside them. */
@@ -109,14 +120,26 @@ export interface PositionRowsOptions {
109
120
  export declare function writePositionRows(options: PositionRowsOptions): PositionRowsCounts;
110
121
  /** What a declaration's file is written as in a row's `decl_file`: `lib:<name>` for the default library, else `PathNames`'. */
111
122
  export declare function declaringFileName(program: ts.Program, paths: PathNames, file: ts.SourceFile): string | null;
123
+ /** The installed package directory a resolved file lies in, and its name, as the module graph places it. */
124
+ export type PackageDirectoryOf = (fileName: string) => {
125
+ directory: string;
126
+ name: string;
127
+ } | undefined;
112
128
  /**
113
129
  * Names for the files rows point at, never an absolute path: relative to the
114
130
  * scanned repo's root when inside it (its real path too, so a root reached
115
131
  * through a link still contains its files), `lib:<name>` for the compiler's
116
132
  * default library, `pkg:<name>/<path>` for an installed package outside the
117
133
  * root, and nothing otherwise.
134
+ *
135
+ * An installed package outside the root is found by its path's last
136
+ * `node_modules` segment, else by `packageDirectoryOf`: the module graph's
137
+ * answer where the resolver serves a package from a cache whose path has no
138
+ * `node_modules` segment (carrick#2599). Either way the directory must hold a
139
+ * package.json.
118
140
  */
119
141
  export declare class PathNames {
142
+ private readonly packageDirectoryOf;
120
143
  private readonly roots;
121
144
  private readonly packageNames;
122
145
  /**
@@ -126,7 +149,7 @@ export declare class PathNames {
126
149
  */
127
150
  private readonly declaringFiles;
128
151
  private readonly declaringPackages;
129
- constructor(scanRoot: string);
152
+ constructor(scanRoot: string, packageDirectoryOf?: PackageDirectoryOf);
130
153
  /** The root `file` lies under, and its path from there; `undefined` outside every root. */
131
154
  private inside;
132
155
  /** A request file's name: relative to the root, which every file the scanner sends is inside. */
@@ -151,5 +174,100 @@ export declare class PathNames {
151
174
  */
152
175
  scrubType(text: string): string;
153
176
  }
177
+ /**
178
+ * The rows of one program's files. The `implementations` action
179
+ * (`implementations.ts`, carrick#2432) reads a call's symbol and names its
180
+ * answers through the same walker, so they join as the rows do.
181
+ */
182
+ export declare class PositionWalker {
183
+ private readonly program;
184
+ private readonly paths;
185
+ private readonly loader;
186
+ private readonly checker;
187
+ /** Each module's exports by name, and its `export =` and `default` targets, aliases followed. */
188
+ private readonly moduleExports;
189
+ constructor(program: ts.Program, paths: PathNames, loader: ProjectLoader);
190
+ rowsOf(sourceFile: ts.SourceFile): PositionRow[];
191
+ private row;
192
+ /**
193
+ * Whether a call's declared return is the result of the function it is
194
+ * handed (carrick#2634), read from the declared signature through public
195
+ * checker calls. See {@link FLAG_RUNS_HANDED}.
196
+ */
197
+ private runsHanded;
198
+ /** The call signatures of a parameter's type, a type parameter read through its constraint, `null` and `undefined` skipped. */
199
+ private callSignaturesOf;
200
+ /** The compiler's own awaited type differs from the type: `Promise<R>`, `PromiseLike<R>`. */
201
+ private isAwaitable;
202
+ /**
203
+ * An intrinsic element's type: the attributes `JSX.IntrinsicElements`
204
+ * gives its tag. The compiler types an intrinsic tag name itself as the
205
+ * error type, though the name resolves. A tag is intrinsic when it is an
206
+ * identifier that starts with a lowercase letter or holds a `-`, as the
207
+ * language reads it.
208
+ */
209
+ private intrinsicProps;
210
+ /** How rows name a position in a program file: its file, 1-based line and package. */
211
+ located(declaringFile: ts.SourceFile, position: number): {
212
+ declFile: string | null;
213
+ declLine: number;
214
+ declPackage: string | null;
215
+ };
216
+ /**
217
+ * The declaration a member with none is attributed to (carrick#2448): the
218
+ * mapped type node, or the alias naming it, of the receiver's type. A
219
+ * mapped type's members are made by the checker and have no declaration of
220
+ * their own. This is the owner `memberTypeWithoutDeclaration` reads such a
221
+ * member at (type-structural-expander.ts): the type's symbol, else its
222
+ * alias symbol. A union or intersection receiver contributes the one
223
+ * constituent that has the property; with several the row is left as it is.
224
+ */
225
+ private ownerDeclaration;
226
+ /** The declaration of a type's symbol, else of its alias symbol: a mapped type node, or the alias naming it. */
227
+ private typeDeclaration;
228
+ /**
229
+ * How `declaration`'s module publishes `symbol`: {@link FLAG_PUBLISHED}
230
+ * when under the symbol's own name (or as a global), and
231
+ * {@link FLAG_MODULE_DEFAULT} when as the module's `export =` or `default`.
232
+ * The module is the nearest ambient `declare module "…"` or the file.
233
+ */
234
+ private publication;
235
+ /** A namespace member, found among the exports of the module it sits in. */
236
+ private publishedByExports;
237
+ private publishedIn;
238
+ /** The type of the object a `member` or `call` row's name is read on, if the name is read on one. */
239
+ private receiverOf;
240
+ /**
241
+ * The `decl` rows of a `call` or `member` row (spans unset): one per
242
+ * declaration the receiver's union constituents give the member, when they
243
+ * give more than one (carrick#2448). A constituent's declaration is its
244
+ * member's ({@link attributedDeclaration}), or, for a member with none, the
245
+ * constituent type's own ({@link typeDeclaration}). Two instantiations of
246
+ * one generic class, or one class and `undefined`, give one, and no row.
247
+ * A throw writes one row flagged {@link FLAG_CHECKER_THREW}.
248
+ */
249
+ private declRows;
250
+ /** The row's symbol, following aliases to a target that has a declaration. */
251
+ symbolOf(kind: PositionKind, node: ts.Node): ts.Symbol | undefined;
252
+ /**
253
+ * In a TypeScript file `const { a } = require("m")` binds `a` as `any` with
254
+ * the binding as its only declaration (TypeScript resolves `require` itself
255
+ * only in JavaScript files), so a trace stops there. Name the export the
256
+ * call resolves to instead (carrick#2454): for the binding itself, and for
257
+ * `m.x` where `m` is a whole-module binding. `undefined` when the name is
258
+ * not such a binding, the specifier is not a literal or does not resolve,
259
+ * or the module has no such export.
260
+ */
261
+ private requiredExport;
262
+ /** The `require` binding a symbol is, when its only declaration is one. */
263
+ private requireBinding;
264
+ /** `"m"` of a call `require("m")` to the global `require`, else `undefined`. */
265
+ private requireSpecifier;
266
+ private exportOfRequired;
267
+ private host;
268
+ private resolutionHost;
269
+ }
270
+ /** Order two `decl_file` names, `null` last. */
271
+ export declare function compareNames(a: string | null, b: string | null): number;
154
272
  /** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
155
273
  export declare function kindOf(node: ts.Node): NodeKind | undefined;
@@ -53,7 +53,7 @@ import * as path from 'node:path';
53
53
  import * as zlib from 'node:zlib';
54
54
  import { ts } from 'ts-morph';
55
55
  import { WriteGuard, installedPackageDirectory, isDeclarationFileName, moduleExport } from './capture/index.js';
56
- import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
56
+ import { attributedDeclaration, installedPackageOf, typeDeclaration } from './declaration-attribution.js';
57
57
  import { typeToStringAsDeclared } from './print-type.js';
58
58
  /** The format's version, written in the header. */
59
59
  export const POSITION_ROWS_VERSION = 4;
@@ -76,6 +76,17 @@ export const FLAG_TOO_MANY = 16;
76
76
  export const FLAG_PUBLISHED = 32;
77
77
  /** The declaration is what its module exports as `export =` or `default` (carrick#2508). */
78
78
  export const FLAG_MODULE_DEFAULT = 64;
79
+ /**
80
+ * A `call` row only: the call's declared return is the result of the
81
+ * function it is handed (`ReturnType<F>`, a bare type parameter the handed
82
+ * function returns, or an awaitable of one), so the library runs it and
83
+ * hands its result back (carrick#2634). Set when the call passes at least
84
+ * one argument at a function-typed parameter and every such parameter
85
+ * meets that return. The last free bit of the `u8`: the next flag widens
86
+ * the field and bumps the format. Pinned to `FLAG_RUNS_HANDED` in
87
+ * `src/position_rows.rs` by `position-rows-runs-handed.test.ts`.
88
+ */
89
+ export const FLAG_RUNS_HANDED = 128;
79
90
  /** The most `decl` rows one position lists. */
80
91
  export const DECL_CAP = 16;
81
92
  const COMPARE_OPERATORS = new Set([
@@ -102,7 +113,7 @@ export function writePositionRows(options) {
102
113
  const { loader, files, scanRoot, outPath, service } = options;
103
114
  const outDir = path.dirname(outPath);
104
115
  const guard = WriteGuard.of({ dirs: [outDir], protect: [loader.getRepoRoot(), scanRoot] });
105
- const paths = new PathNames(scanRoot);
116
+ const paths = new PathNames(scanRoot, (file) => loader.packageDirectoryOf(file));
106
117
  const lines = new Array(files.length);
107
118
  const counts = {
108
119
  files: files.length,
@@ -256,8 +267,15 @@ function fileLine(line) {
256
267
  * through a link still contains its files), `lib:<name>` for the compiler's
257
268
  * default library, `pkg:<name>/<path>` for an installed package outside the
258
269
  * root, and nothing otherwise.
270
+ *
271
+ * An installed package outside the root is found by its path's last
272
+ * `node_modules` segment, else by `packageDirectoryOf`: the module graph's
273
+ * answer where the resolver serves a package from a cache whose path has no
274
+ * `node_modules` segment (carrick#2599). Either way the directory must hold a
275
+ * package.json.
259
276
  */
260
277
  export class PathNames {
278
+ packageDirectoryOf;
261
279
  roots;
262
280
  packageNames = new Map();
263
281
  /**
@@ -267,7 +285,8 @@ export class PathNames {
267
285
  */
268
286
  declaringFiles = new Map();
269
287
  declaringPackages = new Map();
270
- constructor(scanRoot) {
288
+ constructor(scanRoot, packageDirectoryOf = () => undefined) {
289
+ this.packageDirectoryOf = packageDirectoryOf;
271
290
  const resolved = path.resolve(scanRoot);
272
291
  let real = resolved;
273
292
  try {
@@ -306,7 +325,7 @@ export class PathNames {
306
325
  const inside = this.inside(fileName);
307
326
  if (inside)
308
327
  return toSlashes(inside.rel);
309
- const installed = installedPackageDirectory(this.roots[0], fileName);
328
+ const installed = installedPackageDirectory(this.roots[0], fileName) ?? this.packageDirectoryOf(fileName);
310
329
  if (installed && fs.existsSync(path.join(installed.directory, 'package.json'))) {
311
330
  return `pkg:${installed.name}/${toSlashes(path.relative(installed.directory, fileName))}`;
312
331
  }
@@ -381,8 +400,12 @@ export class PathNames {
381
400
  function toSlashes(p) {
382
401
  return p.split(path.sep).join('/');
383
402
  }
384
- /** The rows of one program's files. */
385
- class PositionWalker {
403
+ /**
404
+ * The rows of one program's files. The `implementations` action
405
+ * (`implementations.ts`, carrick#2432) reads a call's symbol and names its
406
+ * answers through the same walker, so they join as the rows do.
407
+ */
408
+ export class PositionWalker {
386
409
  program;
387
410
  paths;
388
411
  loader;
@@ -426,6 +449,8 @@ class PositionWalker {
426
449
  if (type.flags & ts.TypeFlags.Any) {
427
450
  flags |= type.intrinsicName === 'error' ? FLAG_ERROR_TYPE : FLAG_ANY;
428
451
  }
452
+ if (kind === 'call' && this.runsHanded(node))
453
+ flags |= FLAG_RUNS_HANDED;
429
454
  const typeText = this.paths.scrubType(typeToStringAsDeclared(this.checker, type, undefined));
430
455
  if (!symbol)
431
456
  return [kind, start, end, null, null, null, null, typeText, flags];
@@ -453,6 +478,114 @@ class PositionWalker {
453
478
  return [kind, start, end, null, null, null, null, null, FLAG_CHECKER_THREW];
454
479
  }
455
480
  }
481
+ /**
482
+ * Whether a call's declared return is the result of the function it is
483
+ * handed (carrick#2634), read from the declared signature through public
484
+ * checker calls. See {@link FLAG_RUNS_HANDED}.
485
+ */
486
+ runsHanded(node) {
487
+ if (!ts.isCallExpression(node) && !ts.isNewExpression(node))
488
+ return false;
489
+ const args = node.arguments ?? [];
490
+ if (args.length === 0)
491
+ return false;
492
+ try {
493
+ const declaration = this.checker.getResolvedSignature(node)?.declaration;
494
+ if (!declaration || ts.isJSDocSignature(declaration))
495
+ return false;
496
+ const declared = this.checker.getSignatureFromDeclaration(declaration);
497
+ if (!declared)
498
+ return false;
499
+ const own = new Set(declared.getTypeParameters() ?? []);
500
+ const results = new Map();
501
+ const resultOf = (f) => {
502
+ let atom = results.get(f);
503
+ if (!atom)
504
+ results.set(f, (atom = {}));
505
+ return atom;
506
+ };
507
+ const atoms = (t, out, depth = 0) => {
508
+ if (depth > 8)
509
+ return out;
510
+ if (t.flags & ts.TypeFlags.TypeParameter) {
511
+ if (own.has(t))
512
+ out.add(t);
513
+ }
514
+ else if (t.flags & ts.TypeFlags.UnionOrIntersection) {
515
+ for (const member of t.types)
516
+ atoms(member, out, depth + 1);
517
+ }
518
+ else if (t.flags & ts.TypeFlags.Conditional) {
519
+ const f = resultOfConditional(t, own);
520
+ if (f)
521
+ out.add(resultOf(f));
522
+ }
523
+ else if (isReference(t) && this.isAwaitable(t)) {
524
+ for (const arg of this.checker.getTypeArguments(t))
525
+ atoms(arg, out, depth + 1);
526
+ }
527
+ return out;
528
+ };
529
+ const returned = atoms(this.checker.getReturnTypeOfSignature(declared), new Set());
530
+ if (returned.size === 0)
531
+ return false;
532
+ const parameters = declared.getParameters();
533
+ let handed = 0;
534
+ for (let i = 0; i < args.length; i++) {
535
+ const parameter = parameters[Math.min(i, parameters.length - 1)];
536
+ if (!parameter)
537
+ continue;
538
+ const isRest = parameter.valueDeclaration !== undefined &&
539
+ ts.isParameter(parameter.valueDeclaration) &&
540
+ parameter.valueDeclaration.dotDotDotToken !== undefined;
541
+ if (!isRest && i >= parameters.length)
542
+ continue;
543
+ let type = this.checker.getTypeOfSymbolAtLocation(parameter, declaration);
544
+ if (isRest) {
545
+ if (!isReference(type))
546
+ continue;
547
+ const [element] = this.checker.getTypeArguments(type);
548
+ if (!element)
549
+ continue;
550
+ type = element;
551
+ }
552
+ const signatures = this.callSignaturesOf(type);
553
+ if (signatures.length === 0)
554
+ continue;
555
+ handed++;
556
+ const mine = new Set();
557
+ if (type.flags & ts.TypeFlags.TypeParameter)
558
+ mine.add(resultOf(type));
559
+ else
560
+ for (const signature of signatures)
561
+ atoms(this.checker.getReturnTypeOfSignature(signature), mine);
562
+ if (![...mine].some((atom) => returned.has(atom)))
563
+ return false;
564
+ }
565
+ return handed > 0;
566
+ }
567
+ catch {
568
+ return false;
569
+ }
570
+ }
571
+ /** The call signatures of a parameter's type, a type parameter read through its constraint, `null` and `undefined` skipped. */
572
+ callSignaturesOf(type) {
573
+ let t = type;
574
+ if (t.flags & ts.TypeFlags.TypeParameter)
575
+ t = this.checker.getBaseConstraintOfType(t) ?? t;
576
+ if (t.flags & ts.TypeFlags.Union) {
577
+ const kept = t.types.filter((m) => !(m.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)));
578
+ if (kept.length !== 1)
579
+ return [];
580
+ t = kept[0];
581
+ }
582
+ return this.checker.getSignaturesOfType(t, ts.SignatureKind.Call);
583
+ }
584
+ /** The compiler's own awaited type differs from the type: `Promise<R>`, `PromiseLike<R>`. */
585
+ isAwaitable(t) {
586
+ const awaited = this.checker.getAwaitedType(t);
587
+ return awaited !== undefined && awaited !== t;
588
+ }
456
589
  /**
457
590
  * An intrinsic element's type: the attributes `JSX.IntrinsicElements`
458
591
  * gives its tag. The compiler types an intrinsic tag name itself as the
@@ -495,8 +628,7 @@ class PositionWalker {
495
628
  }
496
629
  /** The declaration of a type's symbol, else of its alias symbol: a mapped type node, or the alias naming it. */
497
630
  typeDeclaration(type) {
498
- const owner = type.getSymbol() ?? type.aliasSymbol;
499
- return attributedDeclaration(() => this.program, owner?.declarations ?? []);
631
+ return typeDeclaration(() => this.program, type);
500
632
  }
501
633
  /**
502
634
  * How `declaration`'s module publishes `symbol`: {@link FLAG_PUBLISHED}
@@ -742,7 +874,7 @@ function tail(row) {
742
874
  return [row[3], row[4], row[5], row[6], row[7], row[8]];
743
875
  }
744
876
  /** Order two `decl_file` names, `null` last. */
745
- function compareNames(a, b) {
877
+ export function compareNames(a, b) {
746
878
  if (a === b)
747
879
  return 0;
748
880
  if (a === null)
@@ -751,6 +883,28 @@ function compareNames(a, b) {
751
883
  return -1;
752
884
  return a < b ? -1 : 1;
753
885
  }
886
+ function isReference(t) {
887
+ return !!(t.flags & ts.TypeFlags.Object) && !!(t.objectFlags & ts.ObjectFlags.Reference);
888
+ }
889
+ /**
890
+ * The type parameter `F` of a conditional written `F extends (...) => infer R ? R : _`
891
+ * (`ReturnType<F>`): the result of `F`. `Parameters<F>` and the like infer
892
+ * elsewhere and give nothing.
893
+ */
894
+ function resultOfConditional(t, own) {
895
+ if (!(t.checkType.flags & ts.TypeFlags.TypeParameter) || !own.has(t.checkType))
896
+ return undefined;
897
+ const node = t.root.node;
898
+ if (!ts.isFunctionTypeNode(node.extendsType))
899
+ return undefined;
900
+ const returns = node.extendsType.type;
901
+ if (!ts.isInferTypeNode(returns))
902
+ return undefined;
903
+ const yielded = node.trueType;
904
+ if (!ts.isTypeReferenceNode(yielded) || !ts.isIdentifier(yielded.typeName))
905
+ return undefined;
906
+ return yielded.typeName.text === returns.typeParameter.name.text ? t.checkType : undefined;
907
+ }
754
908
  /** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
755
909
  export function kindOf(node) {
756
910
  if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isTaggedTemplateExpression(node)) {
@@ -80,6 +80,16 @@ export declare class ProjectLoader {
80
80
  private denoProject;
81
81
  /** The tsconfig the service names, when the project is built from one. */
82
82
  private namedTsconfigPath;
83
+ /**
84
+ * Set when the service has no config at or above its root: each file is then
85
+ * typed under the nearest `tsconfig.json`/`jsconfig.json` on its way up to
86
+ * the service root (carrick#1604), and the default project holds the rest.
87
+ */
88
+ private inferredService;
89
+ /** The per-directory answers of `fileConfigPath` for this service. */
90
+ private readonly fileConfigs;
91
+ /** The reference owner rule of each config a file was found under. */
92
+ private readonly ownerRules;
83
93
  /** Each file's owning config (carrick#1604), resolved on first use. */
84
94
  private configPathFor;
85
95
  /** Programs built for owning projects other than the named tsconfig. */
@@ -128,6 +138,12 @@ export declare class ProjectLoader {
128
138
  * as before.
129
139
  */
130
140
  projectKeyFor(file?: string): string;
141
+ /**
142
+ * `projectKeyFor` for a service with no config at its root: the owner of the
143
+ * nearest config on the file's way up to the service root, or `''` (the
144
+ * default project, under the inferred options) for a file with none.
145
+ */
146
+ private inferredKeyFor;
131
147
  /**
132
148
  * The project behind a key from `projectKeyFor`, built on first use and
133
149
  * kept: a service whose files all belong to one project builds one.
@@ -166,6 +182,21 @@ export declare class ProjectLoader {
166
182
  * through Deno, and for a file the workspace itself owns.
167
183
  */
168
184
  packageOf(filePath: string): string | undefined;
185
+ /**
186
+ * The installed package directory a resolved file lies in, and its name,
187
+ * when the module graph places it (carrick#2599). `undefined` for a project
188
+ * that did not resolve through Deno: there the path says it.
189
+ */
190
+ packageDirectoryOf(filePath: string): {
191
+ directory: string;
192
+ name: string;
193
+ } | undefined;
194
+ /**
195
+ * How the module graph resolves `name` from `from`, as the program did
196
+ * (carrick#2599). `undefined` for a project that did not resolve through
197
+ * Deno: there Node resolution is the program's.
198
+ */
199
+ resolveThroughGraph(name: string, from: string, options: import('typescript').CompilerOptions): import('typescript').ResolvedModuleFull | undefined;
169
200
  /**
170
201
  * Check if the project has been successfully initialized
171
202
  */
@@ -11,8 +11,9 @@
11
11
  */
12
12
  import { Project, getCompilerOptionsFromTsConfig } from 'ts-morph';
13
13
  import * as path from 'node:path';
14
+ import ts from 'typescript';
14
15
  import * as fs from 'node:fs';
15
- import { DenoProject, findDenoConfig, findServiceTsconfig, referenceRedirectOf, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
16
+ import { DenoProject, findDenoConfig, fileConfigPath, findServiceTsconfig, referenceRedirectOf, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
16
17
  import { moduleFormatResolutionHost } from './module-format.js';
17
18
  import { ExternalImports, registerExternalImports } from './origin.js';
18
19
  /**
@@ -92,6 +93,11 @@ const TARGET_MAP = {
92
93
  'ES2023': 10,
93
94
  'ESNext': 99,
94
95
  };
96
+ /** Whether the config's own `compilerOptions` state `maxNodeModuleJsDepth`. */
97
+ function statesJsDepth(configPath) {
98
+ const raw = ts.readConfigFile(configPath, ts.sys.readFile).config;
99
+ return raw?.compilerOptions?.maxNodeModuleJsDepth !== undefined;
100
+ }
95
101
  /**
96
102
  * ProjectLoader - Manages ts-morph Project initialization and access
97
103
  *
@@ -126,6 +132,16 @@ export class ProjectLoader {
126
132
  denoProject;
127
133
  /** The tsconfig the service names, when the project is built from one. */
128
134
  namedTsconfigPath;
135
+ /**
136
+ * Set when the service has no config at or above its root: each file is then
137
+ * typed under the nearest `tsconfig.json`/`jsconfig.json` on its way up to
138
+ * the service root (carrick#1604), and the default project holds the rest.
139
+ */
140
+ inferredService = false;
141
+ /** The per-directory answers of `fileConfigPath` for this service. */
142
+ fileConfigs = new Map();
143
+ /** The reference owner rule of each config a file was found under. */
144
+ ownerRules = new Map();
129
145
  /** Each file's owning config (carrick#1604), resolved on first use. */
130
146
  configPathFor;
131
147
  /** Programs built for owning projects other than the named tsconfig. */
@@ -231,6 +247,7 @@ export class ProjectLoader {
231
247
  }
232
248
  else {
233
249
  this.log('No tsconfig.json found, using default compiler options');
250
+ this.inferredService = true;
234
251
  this.buildProject = (kind) => {
235
252
  const project = new Project({
236
253
  ...this.fileSystemOption(),
@@ -239,7 +256,7 @@ export class ProjectLoader {
239
256
  });
240
257
  // Add source files from common locations
241
258
  if (kind === 'listed')
242
- this.addDefaultSourceFiles(project);
259
+ this.addDefaultSourceFiles(project, (file) => this.projectKeyFor(file) === '');
243
260
  return project;
244
261
  };
245
262
  }
@@ -347,11 +364,18 @@ export class ProjectLoader {
347
364
  */
348
365
  projectFromConfig(configPath, kind = 'listed') {
349
366
  const imports = new ExternalImports();
367
+ const options = getCompilerOptionsFromTsConfig(configPath).options;
368
+ // TypeScript reads a `jsconfig.json` with an implicit depth of 2, which
369
+ // loads the JavaScript sources of installed packages; every other program
370
+ // here reads 0. A depth the config states is kept (carrick#1604).
371
+ if (this.inferredService && path.basename(configPath) === 'jsconfig.json' && !statesJsDepth(configPath)) {
372
+ options.maxNodeModuleJsDepth = 0;
373
+ }
350
374
  const project = new Project({
351
375
  ...this.fileSystemOption(),
352
376
  tsConfigFilePath: configPath,
353
377
  // Over the tsconfig's own options: ts-morph lays these on top.
354
- compilerOptions: sidecarCompilerOptions(optionsFor(kind, getCompilerOptionsFromTsConfig(configPath).options)),
378
+ compilerOptions: sidecarCompilerOptions(optionsFor(kind, options)),
355
379
  skipAddingFilesFromTsConfig: kind === 'scripts',
356
380
  // Each import resolves in its file's own module format (carrick#1619),
357
381
  // a referenced project's unbuilt output reads as its source, under its
@@ -373,12 +397,33 @@ export class ProjectLoader {
373
397
  * as before.
374
398
  */
375
399
  projectKeyFor(file) {
400
+ if (this.inferredService)
401
+ return this.inferredKeyFor(file);
376
402
  if (!this.namedTsconfigPath || file === undefined)
377
403
  return '';
378
404
  this.configPathFor ??= serviceConfigPath(this.namedTsconfigPath, (d) => this.logError(d));
379
405
  const config = this.configPathFor(path.resolve(this.repoRoot, file));
380
406
  return config === this.namedTsconfigPath ? '' : config;
381
407
  }
408
+ /**
409
+ * `projectKeyFor` for a service with no config at its root: the owner of the
410
+ * nearest config on the file's way up to the service root, or `''` (the
411
+ * default project, under the inferred options) for a file with none.
412
+ */
413
+ inferredKeyFor(file) {
414
+ if (file === undefined)
415
+ return '';
416
+ const absolute = path.resolve(this.repoRoot, file);
417
+ const config = fileConfigPath(absolute, this.repoRoot, this.fileConfigs);
418
+ if (config === undefined)
419
+ return '';
420
+ let owner = this.ownerRules.get(config);
421
+ if (!owner) {
422
+ owner = serviceConfigPath(config, (d) => this.logError(d));
423
+ this.ownerRules.set(config, owner);
424
+ }
425
+ return owner(absolute);
426
+ }
382
427
  /**
383
428
  * The project behind a key from `projectKeyFor`, built on first use and
384
429
  * kept: a service whose files all belong to one project builds one.
@@ -457,6 +502,22 @@ export class ProjectLoader {
457
502
  packageOf(filePath) {
458
503
  return this.denoProject?.packageOf(filePath);
459
504
  }
505
+ /**
506
+ * The installed package directory a resolved file lies in, and its name,
507
+ * when the module graph places it (carrick#2599). `undefined` for a project
508
+ * that did not resolve through Deno: there the path says it.
509
+ */
510
+ packageDirectoryOf(filePath) {
511
+ return this.denoProject?.packageDirectoryOf(filePath);
512
+ }
513
+ /**
514
+ * How the module graph resolves `name` from `from`, as the program did
515
+ * (carrick#2599). `undefined` for a project that did not resolve through
516
+ * Deno: there Node resolution is the program's.
517
+ */
518
+ resolveThroughGraph(name, from, options) {
519
+ return this.denoProject?.resolve(name, from, options);
520
+ }
460
521
  /**
461
522
  * Check if the project has been successfully initialized
462
523
  */
@@ -512,7 +573,7 @@ export class ProjectLoader {
512
573
  * dependencies installed it cost 40 s and added nothing the compiler needs
513
574
  * (carrick#749).
514
575
  */
515
- addDefaultSourceFiles(project) {
576
+ addDefaultSourceFiles(project, keep = () => true) {
516
577
  const globs = DEFAULT_SOURCE_PATTERNS.map((pattern) => path.join(this.repoRoot, pattern));
517
578
  // Negated last so it applies to every pattern above it.
518
579
  globs.push(`!${path.join(this.repoRoot, '**/node_modules/**')}`);
@@ -525,6 +586,8 @@ export class ProjectLoader {
525
586
  return;
526
587
  }
527
588
  for (const filePath of filePaths) {
589
+ if (!keep(filePath))
590
+ continue;
528
591
  try {
529
592
  project.addSourceFileAtPath(filePath);
530
593
  }