carrick 0.3.117 → 0.3.119

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carrick",
3
- "version": "0.3.117",
3
+ "version": "0.3.119",
4
4
  "carrickCache": {
5
5
  "cache_version": 22,
6
6
  "intent_cache_version": 2
@@ -113,11 +113,11 @@
113
113
  "zod": "^3.23.0"
114
114
  },
115
115
  "optionalDependencies": {
116
- "@carrick-tools/cli-darwin-arm64": "0.3.117",
117
- "@carrick-tools/cli-darwin-x64": "0.3.117",
118
- "@carrick-tools/cli-linux-arm64": "0.3.117",
119
- "@carrick-tools/cli-linux-x64": "0.3.117",
120
- "@carrick-tools/cli-win32-x64": "0.3.117"
116
+ "@carrick-tools/cli-darwin-arm64": "0.3.119",
117
+ "@carrick-tools/cli-darwin-x64": "0.3.119",
118
+ "@carrick-tools/cli-linux-arm64": "0.3.119",
119
+ "@carrick-tools/cli-linux-x64": "0.3.119",
120
+ "@carrick-tools/cli-win32-x64": "0.3.119"
121
121
  },
122
122
  "devDependencies": {
123
123
  "@types/node": "^24.13.3"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "carrick",
3
3
  "description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
4
- "version": "0.3.117",
4
+ "version": "0.3.119",
5
5
  "author": {
6
6
  "name": "Carrick",
7
7
  "email": "hello@carrick.tools"
@@ -89,6 +89,20 @@ const RUNTIME_TYPE_PACKAGES = new Set([
89
89
  * package's, never the runtime module.
90
90
  */
91
91
  const RUNTIME_MODULE = /^node:/;
92
+ /**
93
+ * A key for the language's own library (carrick#2445): `lib:<lib file>`
94
+ * (`lib:lib.dom.d.ts`) names the globals that default-library file declares.
95
+ * Its installed version is the compiler's, so the store keys it
96
+ * `lib:lib.dom.d.ts@<typescript version>`. A global has no specifier: the
97
+ * probe reads it as `globalThis.<name>`, and its home is the default-library
98
+ * file set (`DEFAULT_LIBRARY_HOME`).
99
+ */
100
+ const DEFAULT_LIBRARY = /^lib:/;
101
+ /**
102
+ * The owner a home names for every default-library file. No package name
103
+ * holds a `:`, so only a `lib:` key's home ever holds it.
104
+ */
105
+ const DEFAULT_LIBRARY_HOME = 'lib:';
92
106
  /** A dependency range that names the service's own source, not a registry release. */
93
107
  const LOCAL_RANGE = /^(workspace|file|link|portal):/;
94
108
  /** A resolved module lands on TypeScript: a declaration file or source. */
@@ -283,7 +297,7 @@ export class LibraryClaimsVerifier {
283
297
  }
284
298
  const importLines = importKeys.map((key, i) => {
285
299
  const [pkg, name] = JSON.parse(key);
286
- return importLine(pkg, name, `__carrick_e${i}`);
300
+ return probeLine(pkg, name, `__carrick_e${i}`);
287
301
  });
288
302
  // One statement per receiver a message check's maker or scope claim
289
303
  // builds, after the imports: that receiver built with no argument and no
@@ -322,7 +336,8 @@ export class LibraryClaimsVerifier {
322
336
  const exportReads = new Map();
323
337
  const httpReceivers = new Map();
324
338
  const judged = new Map();
325
- const declarationOf = (check) => file.statements[importIndex.get(exportKey(check))];
339
+ /** The probe's import of the check's export, or its read of a global (`probeLine`). */
340
+ const statementOf = (check) => file.statements[importIndex.get(exportKey(check))];
326
341
  /** The probe's no-argument build of `receiver` on the check's export, resolved. */
327
342
  const atDefaults = (check, receiver) => {
328
343
  const index = builtIndex.get(builtKey(check, receiver));
@@ -333,7 +348,9 @@ export class LibraryClaimsVerifier {
333
348
  const key = `${runtimeModules}\u0000${check.package}`;
334
349
  let moduleRead = moduleReads.get(key);
335
350
  if (!moduleRead) {
336
- moduleRead = reader.readModule(check.package, declarationOf(check), runtimeModules);
351
+ moduleRead = DEFAULT_LIBRARY.test(check.package)
352
+ ? reader.readDefaultLibrary(check.package)
353
+ : reader.readModule(check.package, statementOf(check), runtimeModules);
337
354
  moduleReads.set(key, moduleRead);
338
355
  }
339
356
  return moduleRead;
@@ -342,7 +359,9 @@ export class LibraryClaimsVerifier {
342
359
  const key = exportKey(check);
343
360
  let exportRead = exportReads.get(key);
344
361
  if (!exportRead) {
345
- exportRead = reader.readExport(declarationOf(check));
362
+ exportRead = DEFAULT_LIBRARY.test(check.package)
363
+ ? reader.readGlobal(check.package, statementOf(check))
364
+ : reader.readExport(statementOf(check));
346
365
  exportReads.set(key, exportRead);
347
366
  }
348
367
  return exportRead;
@@ -534,15 +553,25 @@ export class LibraryClaimsVerifier {
534
553
  return { surfaces: [], surface_sha256: fullSurfaceSha256([]) };
535
554
  // Each specifier twice: as a namespace, which holds its named exports, and
536
555
  // as a default import, which is how a module that exports a value whole
537
- // (`export =`) is imported as `default`.
538
- const imports = packages.flatMap((pkg, i) => [
539
- `import * as __carrick_ns${i} from ${JSON.stringify(pkg)};`,
540
- importLine(pkg, 'default', `__carrick_d${i}`),
541
- ]);
542
- const probeOf = (file, i) => ({
543
- namespace: file.statements[2 * i],
544
- defaultImport: file.statements[2 * i + 1],
556
+ // (`export =`) is imported as `default`. A `lib:` key imports nothing: its
557
+ // globals are read from the file it names.
558
+ const imports = [];
559
+ const importAt = packages.map((pkg, i) => {
560
+ if (DEFAULT_LIBRARY.test(pkg))
561
+ return undefined;
562
+ const at = imports.length;
563
+ imports.push(`import * as __carrick_ns${i} from ${JSON.stringify(pkg)};`, importLine(pkg, 'default', `__carrick_d${i}`));
564
+ return at;
545
565
  });
566
+ const probeOf = (file, i) => {
567
+ const at = importAt[i];
568
+ return at === undefined
569
+ ? { defaultLibrary: packages[i] }
570
+ : {
571
+ namespace: file.statements[at],
572
+ defaultImport: file.statements[at + 1],
573
+ };
574
+ };
546
575
  const makers = this.readProbe(fromDir, imports, (_checker, reader, file) => packages.map((pkg, i) => reader.makersOf(pkg, probeOf(file, i), only[pkg])));
547
576
  const importLines = [];
548
577
  const buildLines = [];
@@ -550,7 +579,7 @@ export class LibraryClaimsVerifier {
550
579
  packages.forEach((pkg, i) => {
551
580
  for (const { export: name, receivers } of makers[i]) {
552
581
  const local = `__carrick_e${importLines.length}`;
553
- importLines.push(importLine(pkg, name, local));
582
+ importLines.push(probeLine(pkg, name, local));
554
583
  for (const receiver of receivers) {
555
584
  const expression = receiverExpression(local, receiver);
556
585
  if (expression === undefined)
@@ -744,6 +773,18 @@ function typeSymbols(type) {
744
773
  }
745
774
  return symbols;
746
775
  }
776
+ /**
777
+ * The probe's read of one export, bound to `local`: its import, or for a
778
+ * `lib:` key the global itself, as `globalThis` holds it. A block-scoped
779
+ * global (`declare const`) is not on `globalThis`; no default-library file
780
+ * declares one.
781
+ */
782
+ function probeLine(pkg, name, local) {
783
+ if (!DEFAULT_LIBRARY.test(pkg))
784
+ return importLine(pkg, name, local);
785
+ const access = IDENTIFIER.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
786
+ return `const ${local} = globalThis${access};`;
787
+ }
747
788
  /** The probe's import of one export, bound to `local`. */
748
789
  function importLine(pkg, name, local) {
749
790
  const specifier = JSON.stringify(pkg);
@@ -943,6 +984,54 @@ class DeclarationReader {
943
984
  ?.redirectInfo?.redirectTarget;
944
985
  return target === file && this.isInstalledFile(file.fileName);
945
986
  }
987
+ /**
988
+ * A `lib:` key: the default-library file it names, as the program's own
989
+ * `lib` setting loads it, read by the compiler that built the program. A
990
+ * file the program does not load is no module reason: each global answers
991
+ * `export_missing` (`readGlobal`).
992
+ */
993
+ readDefaultLibrary(pkg) {
994
+ const entry = { package: pkg, installed_version: ts.version };
995
+ const file = this.defaultLibraryFile(pkg);
996
+ if (file)
997
+ entry.resolved_file = file.fileName;
998
+ return { entry };
999
+ }
1000
+ /** The default-library file a `lib:` key names, when the program loads it. */
1001
+ defaultLibraryFile(pkg) {
1002
+ const name = pkg.slice('lib:'.length);
1003
+ return this.program
1004
+ .getSourceFiles()
1005
+ .find(file => this.program.isSourceFileDefaultLibrary(file) && path.basename(file.fileName) === name);
1006
+ }
1007
+ /**
1008
+ * `T` for a global (`probeLine`'s `globalThis` read): the type the probe's
1009
+ * local gets, and the global it names. The global must be a value the
1010
+ * key's lib file declares; one only another file declares (the runtime's
1011
+ * types, the service) is missing from it.
1012
+ */
1013
+ readGlobal(pkg, statement) {
1014
+ const declaration = statement && ts.isVariableStatement(statement) ? statement.declarationList.declarations[0] : undefined;
1015
+ const access = declaration?.initializer;
1016
+ const name = access && ts.isPropertyAccessExpression(access)
1017
+ ? access.name
1018
+ : access && ts.isElementAccessExpression(access)
1019
+ ? access.argumentExpression
1020
+ : undefined;
1021
+ const target = name ? this.checker.getSymbolAtLocation(name) : undefined;
1022
+ const file = this.defaultLibraryFile(pkg);
1023
+ if (!declaration ||
1024
+ !target ||
1025
+ !file ||
1026
+ !(target.flags & ts.SymbolFlags.Value) ||
1027
+ !(target.declarations ?? []).some(each => each.getSourceFile() === file)) {
1028
+ return { reason: 'export_missing' };
1029
+ }
1030
+ const type = this.checker.getTypeAtLocation(declaration.name);
1031
+ if (this.isOpenTop(type))
1032
+ return { reason: 'export_untyped' };
1033
+ return { value: { type, target } };
1034
+ }
946
1035
  /** `T`: the type the probe's import gets, and the declaration it names. */
947
1036
  readExport(declaration) {
948
1037
  const clause = declaration.importClause;
@@ -1038,6 +1127,9 @@ class DeclarationReader {
1038
1127
  * sub-object's, along a claim's `path`).
1039
1128
  */
1040
1129
  homeOf(pkg, symbols, base) {
1130
+ // A global's home is the default-library file set, and only that.
1131
+ if (DEFAULT_LIBRARY.test(pkg))
1132
+ return base ?? new Set([DEFAULT_LIBRARY_HOME]);
1041
1133
  const named = packageNameOf(pkg);
1042
1134
  const home = new Set(base ?? [named, typesPackageOf(named)]);
1043
1135
  const runtime = RUNTIME_MODULE.test(pkg) || RUNTIME_TYPE_PACKAGES.has(named) || RUNTIME_TYPE_PACKAGES.has(typesPackageOf(named));
@@ -1061,6 +1153,8 @@ class DeclarationReader {
1061
1153
  return installed === undefined ? undefined : installed.packageName ?? installed.directoryName;
1062
1154
  }
1063
1155
  isOwnFile(file, home) {
1156
+ if (this.program.isSourceFileDefaultLibrary(file))
1157
+ return home.has(DEFAULT_LIBRARY_HOME);
1064
1158
  const owner = this.packageOf(file);
1065
1159
  return owner !== undefined && home.has(owner);
1066
1160
  }
@@ -2231,7 +2325,7 @@ class DeclarationReader {
2231
2325
  this.setPackage(pkg);
2232
2326
  // Runtime modules on: a `node:` specifier is listed from the runtime's
2233
2327
  // types package, as the message roles read it.
2234
- const moduleRead = this.readModule(pkg, probe.namespace, true);
2328
+ const moduleRead = 'defaultLibrary' in probe ? this.readDefaultLibrary(pkg) : this.readModule(pkg, probe.namespace, true);
2235
2329
  const surface = { package: pkg, truncated: 0, exports: [] };
2236
2330
  if (moduleRead.entry.resolved_file)
2237
2331
  surface.resolved_file = moduleRead.entry.resolved_file;
@@ -2294,7 +2388,7 @@ class DeclarationReader {
2294
2388
  */
2295
2389
  makersOf(pkg, probe, only) {
2296
2390
  this.setPackage(pkg);
2297
- if (this.readModule(pkg, probe.namespace, true).reason)
2391
+ if ('namespace' in probe && this.readModule(pkg, probe.namespace, true).reason)
2298
2392
  return [];
2299
2393
  return (this.listedExports(probe, only) ?? [])
2300
2394
  .filter(({ type }) => !this.isOpenTop(type))
@@ -2314,6 +2408,8 @@ class DeclarationReader {
2314
2408
  * resolves to no module symbol.
2315
2409
  */
2316
2410
  listedExports(probe, only) {
2411
+ if ('defaultLibrary' in probe)
2412
+ return this.listedGlobals(probe.defaultLibrary, only);
2317
2413
  const moduleSymbol = this.checker.getSymbolAtLocation(probe.namespace.moduleSpecifier);
2318
2414
  if (!moduleSymbol)
2319
2415
  return undefined;
@@ -2339,6 +2435,23 @@ class DeclarationReader {
2339
2435
  }
2340
2436
  return listed.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
2341
2437
  }
2438
+ /**
2439
+ * The value globals a `lib:` key's file declares (every one, or only those
2440
+ * `only` names), sorted by name, each with its declared type: none when the
2441
+ * program's `lib` setting does not load the file.
2442
+ */
2443
+ listedGlobals(pkg, only) {
2444
+ const file = this.defaultLibraryFile(pkg);
2445
+ if (!file)
2446
+ return [];
2447
+ return this.checker
2448
+ .getSymbolsInScope(this.probe, ts.SymbolFlags.Value)
2449
+ .filter(symbol => (symbol.flags & ts.SymbolFlags.Prototype) === 0 &&
2450
+ (only === undefined || only.includes(symbol.getName())) &&
2451
+ (symbol.declarations ?? []).some(declaration => declaration.getSourceFile() === file))
2452
+ .map(symbol => ({ name: symbol.getName(), target: symbol, type: this.checker.getTypeOfSymbol(symbol) }))
2453
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
2454
+ }
2342
2455
  /**
2343
2456
  * The ways an export makes a receiver, in listing order: calling it
2344
2457
  * (`instance:()`), constructing it (`instance:new`), then calling or
@@ -2896,16 +3009,21 @@ class DeclarationReader {
2896
3009
  }
2897
3010
  /**
2898
3011
  * What a factory builds says nothing: the type itself, or any branch of a
2899
- * conditional return type, does.
3012
+ * conditional return type, does. A conditional whose branches lead back to
3013
+ * itself (a recursive alias) is read once: `seen` holds the conditionals
3014
+ * on the way down, and one met again says nothing new.
2900
3015
  */
2901
- returnSaysNothing(type) {
3016
+ returnSaysNothing(type, seen = new Set()) {
2902
3017
  if (this.saysNothing(type))
2903
3018
  return true;
2904
3019
  return this.parts(type).some(part => {
2905
3020
  if (!(part.flags & ts.TypeFlags.Conditional))
2906
3021
  return false;
2907
3022
  const node = part.root.node;
2908
- return [node.trueType, node.falseType].some(branch => this.returnSaysNothing(this.checker.getTypeFromTypeNode(branch)));
3023
+ if (seen.has(node))
3024
+ return false;
3025
+ seen.add(node);
3026
+ return [node.trueType, node.falseType].some(branch => this.returnSaysNothing(this.checker.getTypeFromTypeNode(branch), seen));
2909
3027
  });
2910
3028
  }
2911
3029
  /** `any` (an unresolved type included) or `unknown`. */
@@ -2,7 +2,7 @@
2
2
  * Per-position compiler rows for a service (carrick#2405, move 2 of the fact
3
3
  * layer). Record-only: nothing in a scan reads the file yet.
4
4
  *
5
- * For every source file the scanner sends, one row for each position of four
5
+ * For every source file the scanner sends, one row for each position of six
6
6
  * kinds, each holding what the compiler knows there: the symbol, the
7
7
  * declaration it is attributed to (file and line), the package that declares
8
8
  * it and the type text.
@@ -13,11 +13,22 @@
13
13
  * | `member` | property or element access | the name or the argument | the access |
14
14
  * | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
15
15
  * | `ident` | an identifier in expression position | itself | itself |
16
+ * | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
17
+ * | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
16
18
  *
17
- * A row's key is `(start, end, kind)`: TypeScript `getStart()`/`getEnd()` of
18
- * the node, in UTF-16 code units from the start of the file, which is what
19
- * the scanner's `sidecar_position` gives for an SWC span. Every path written
20
- * is relative to the scanned repo's root, with `/` separators.
19
+ * An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
20
+ * it belongs to and has its span: one per declaration that go-to-implementation
21
+ * names for that row's declaration, when that declaration is an interface's
22
+ * or type literal's method, a function-typed property signature, an abstract
23
+ * member, or a function-typed class property or parameter with no
24
+ * initializer. Its `decl_*` name the implementing declaration. Past
25
+ * {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
26
+ *
27
+ * A row's key is `(start, end, kind)`, except that several `impl` rows share
28
+ * one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
29
+ * from the start of the file, which is what the scanner's `sidecar_position`
30
+ * gives for an SWC span. Every path written is relative to the scanned repo's
31
+ * root, with `/` separators.
21
32
  *
22
33
  * Each file is read in the program of the project that owns it
23
34
  * (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
@@ -33,7 +44,7 @@ import { ts } from 'ts-morph';
33
44
  import type { ProjectLoader } from './project-loader.js';
34
45
  import type { PositionRowsCounts } from './types.js';
35
46
  /** The format's version, written in the header. */
36
- export declare const POSITION_ROWS_VERSION = 1;
47
+ export declare const POSITION_ROWS_VERSION = 2;
37
48
  /** The type is `any`, declared or inferred. Never set with the error type. */
38
49
  export declare const FLAG_ANY = 1;
39
50
  /** The type is the compiler's error type: something did not resolve. */
@@ -42,7 +53,22 @@ export declare const FLAG_ERROR_TYPE = 2;
42
53
  export declare const FLAG_CHECKER_THREW = 4;
43
54
  /** The declaration is outside the checkout, not the default library and in no installed package. */
44
55
  export declare const FLAG_DECLARATION_UNNAMED = 8;
45
- export type PositionKind = 'call' | 'member' | 'compare' | 'ident';
56
+ /** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
57
+ export declare const FLAG_TOO_MANY = 16;
58
+ /**
59
+ * The declaration is published under the row's symbol name: its module
60
+ * exports it by that name, or it is a global (a script's top-level
61
+ * declaration, or one in `declare global`). A member of a class, interface,
62
+ * type literal or object literal never is (carrick#2508).
63
+ */
64
+ export declare const FLAG_PUBLISHED = 32;
65
+ /** The declaration is what its module exports as `export =` or `default` (carrick#2508). */
66
+ export declare const FLAG_MODULE_DEFAULT = 64;
67
+ /** The most implementations one declaration's `impl` rows list. */
68
+ export declare const IMPL_CAP = 16;
69
+ /** The kinds {@link kindOf} gives a node; `impl` and `jsx` rows are written beside them. */
70
+ export type NodeKind = 'call' | 'member' | 'compare' | 'ident';
71
+ export type PositionKind = NodeKind | 'impl' | 'jsx';
46
72
  /** `[kind, start, end, symbol, decl_file, decl_line, decl_package, type_text, flags]` */
47
73
  export type PositionRow = [
48
74
  PositionKind,
@@ -75,4 +101,4 @@ export interface PositionRowsOptions {
75
101
  */
76
102
  export declare function writePositionRows(options: PositionRowsOptions): PositionRowsCounts;
77
103
  /** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
78
- export declare function kindOf(node: ts.Node): PositionKind | undefined;
104
+ export declare function kindOf(node: ts.Node): NodeKind | undefined;
@@ -2,7 +2,7 @@
2
2
  * Per-position compiler rows for a service (carrick#2405, move 2 of the fact
3
3
  * layer). Record-only: nothing in a scan reads the file yet.
4
4
  *
5
- * For every source file the scanner sends, one row for each position of four
5
+ * For every source file the scanner sends, one row for each position of six
6
6
  * kinds, each holding what the compiler knows there: the symbol, the
7
7
  * declaration it is attributed to (file and line), the package that declares
8
8
  * it and the type text.
@@ -13,11 +13,22 @@
13
13
  * | `member` | property or element access | the name or the argument | the access |
14
14
  * | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
15
15
  * | `ident` | an identifier in expression position | itself | itself |
16
+ * | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
17
+ * | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
16
18
  *
17
- * A row's key is `(start, end, kind)`: TypeScript `getStart()`/`getEnd()` of
18
- * the node, in UTF-16 code units from the start of the file, which is what
19
- * the scanner's `sidecar_position` gives for an SWC span. Every path written
20
- * is relative to the scanned repo's root, with `/` separators.
19
+ * An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
20
+ * it belongs to and has its span: one per declaration that go-to-implementation
21
+ * names for that row's declaration, when that declaration is an interface's
22
+ * or type literal's method, a function-typed property signature, an abstract
23
+ * member, or a function-typed class property or parameter with no
24
+ * initializer. Its `decl_*` name the implementing declaration. Past
25
+ * {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
26
+ *
27
+ * A row's key is `(start, end, kind)`, except that several `impl` rows share
28
+ * one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
29
+ * from the start of the file, which is what the scanner's `sidecar_position`
30
+ * gives for an SWC span. Every path written is relative to the scanned repo's
31
+ * root, with `/` separators.
21
32
  *
22
33
  * Each file is read in the program of the project that owns it
23
34
  * (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
@@ -38,7 +49,7 @@ import { WriteGuard, installedPackageDirectory, isDeclarationFileName, moduleExp
38
49
  import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
39
50
  import { typeToStringAsDeclared } from './print-type.js';
40
51
  /** The format's version, written in the header. */
41
- export const POSITION_ROWS_VERSION = 1;
52
+ export const POSITION_ROWS_VERSION = 2;
42
53
  /** The type is `any`, declared or inferred. Never set with the error type. */
43
54
  export const FLAG_ANY = 1;
44
55
  /** The type is the compiler's error type: something did not resolve. */
@@ -47,6 +58,19 @@ export const FLAG_ERROR_TYPE = 2;
47
58
  export const FLAG_CHECKER_THREW = 4;
48
59
  /** The declaration is outside the checkout, not the default library and in no installed package. */
49
60
  export const FLAG_DECLARATION_UNNAMED = 8;
61
+ /** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
62
+ export const FLAG_TOO_MANY = 16;
63
+ /**
64
+ * The declaration is published under the row's symbol name: its module
65
+ * exports it by that name, or it is a global (a script's top-level
66
+ * declaration, or one in `declare global`). A member of a class, interface,
67
+ * type literal or object literal never is (carrick#2508).
68
+ */
69
+ export const FLAG_PUBLISHED = 32;
70
+ /** The declaration is what its module exports as `export =` or `default` (carrick#2508). */
71
+ export const FLAG_MODULE_DEFAULT = 64;
72
+ /** The most implementations one declaration's `impl` rows list. */
73
+ export const IMPL_CAP = 16;
50
74
  const COMPARE_OPERATORS = new Set([
51
75
  ts.SyntaxKind.EqualsEqualsEqualsToken,
52
76
  ts.SyntaxKind.ExclamationEqualsEqualsToken,
@@ -79,6 +103,8 @@ export function writePositionRows(options) {
79
103
  resolved: 0,
80
104
  any: 0,
81
105
  error_type: 0,
106
+ impl: 0,
107
+ jsx: 0,
82
108
  bytes: 0,
83
109
  load_ms: 0,
84
110
  walk_ms: 0,
@@ -127,11 +153,16 @@ export function writePositionRows(options) {
127
153
  }
128
154
  read.set(index, sourceFile?.getFilePath());
129
155
  }
130
- // Built once, after every file the group adds.
131
- const program = project.getProgram().compilerObject;
156
+ // Built once, after every file the group adds. The language service's
157
+ // program is the one walked, so go-to-implementation answers over it
158
+ // and no second program is built.
159
+ const service = project.getLanguageService().compilerObject;
160
+ const program = service.getProgram();
161
+ if (!program)
162
+ throw new Error('the language service built no program');
132
163
  counts.load_ms += performance.now() - loadStarted;
133
164
  const walkStarted = performance.now();
134
- const walker = new PositionWalker(program, paths, loader);
165
+ const walker = new PositionWalker(program, service, paths, loader);
135
166
  const missed = [];
136
167
  for (const index of indexes) {
137
168
  if (!read.has(index))
@@ -151,22 +182,23 @@ export function writePositionRows(options) {
151
182
  counts.any += 1;
152
183
  if (row[8] & FLAG_ERROR_TYPE)
153
184
  counts.error_type += 1;
185
+ if (row[0] === 'impl')
186
+ counts.impl += 1;
187
+ if (row[0] === 'jsx')
188
+ counts.jsx += 1;
154
189
  }
155
190
  lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), r: rows });
156
191
  options.onFile?.();
157
192
  }
158
193
  counts.walk_ms += performance.now() - walkStarted;
159
- return missed;
194
+ return { missed, program };
160
195
  };
161
196
  const hashes = new Map();
162
197
  for (const [key, indexes] of groups) {
163
198
  const project = loader.getProjectFor(key);
164
- const missed = walkGroup(project, indexes, hashes);
199
+ const { missed, program } = walkGroup(project, indexes, hashes);
165
200
  if (missed.length > 0) {
166
- const declarations = project
167
- .getProgram()
168
- .compilerObject.getRootFileNames()
169
- .filter(isDeclarationFileName);
201
+ const declarations = program.getRootFileNames().filter(isDeclarationFileName);
170
202
  untaken.set(key, { indexes: missed, declarations });
171
203
  }
172
204
  // One owner's program at a time.
@@ -184,7 +216,7 @@ export function writePositionRows(options) {
184
216
  }
185
217
  }
186
218
  counts.load_ms += performance.now() - loadStarted;
187
- for (const index of walkGroup(project, indexes, hashes)) {
219
+ for (const index of walkGroup(project, indexes, hashes).missed) {
188
220
  lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), skip: 'not_in_program', r: [] });
189
221
  options.onFile?.();
190
222
  }
@@ -344,11 +376,23 @@ function toSlashes(p) {
344
376
  /** The rows of one program's files. */
345
377
  class PositionWalker {
346
378
  program;
379
+ service;
347
380
  paths;
348
381
  loader;
349
382
  checker;
350
- constructor(program, paths, loader) {
383
+ /**
384
+ * Each bodyless declaration's implementations, as `impl` rows with no span
385
+ * yet (`start` and `end` are 0): go-to-implementation searches the whole
386
+ * program, so it is asked once per declaration, never per call.
387
+ */
388
+ implementations = new Map();
389
+ /** The same for a member whose symbol has no declaration, keyed by symbol. */
390
+ undeclaredImplementations = new Map();
391
+ /** Each module's exports by name, and its `export =` and `default` targets, aliases followed. */
392
+ moduleExports = new Map();
393
+ constructor(program, service, paths, loader) {
351
394
  this.program = program;
395
+ this.service = service;
352
396
  this.paths = paths;
353
397
  this.loader = loader;
354
398
  this.checker = program.getTypeChecker();
@@ -357,8 +401,17 @@ class PositionWalker {
357
401
  const rows = [];
358
402
  const visit = (node) => {
359
403
  const kind = kindOf(node);
360
- if (kind)
361
- rows.push(this.row(kind, node, sourceFile));
404
+ if (kind) {
405
+ const row = this.row(kind, node, sourceFile);
406
+ rows.push(row);
407
+ if ((kind === 'call' || kind === 'member') && !(row[8] & FLAG_CHECKER_THREW)) {
408
+ for (const impl of this.implRows(kind, node))
409
+ rows.push(['impl', row[1], row[2], ...tail(impl)]);
410
+ }
411
+ }
412
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
413
+ rows.push(this.row('jsx', node.tagName, sourceFile));
414
+ }
362
415
  ts.forEachChild(node, visit);
363
416
  };
364
417
  ts.forEachChild(sourceFile, visit);
@@ -370,7 +423,7 @@ class PositionWalker {
370
423
  try {
371
424
  const symbol = this.symbolOf(kind, node);
372
425
  const typed = kind === 'compare' ? node.left : node;
373
- const type = this.checker.getTypeAtLocation(typed);
426
+ const type = (kind === 'jsx' && this.intrinsicProps(node)) || this.checker.getTypeAtLocation(typed);
374
427
  let flags = 0;
375
428
  if (type.flags & ts.TypeFlags.Any) {
376
429
  flags |= type.intrinsicName === 'error' ? FLAG_ERROR_TYPE : FLAG_ANY;
@@ -382,6 +435,9 @@ class PositionWalker {
382
435
  this.ownerDeclaration(kind, node, symbol);
383
436
  if (!declaration)
384
437
  return [kind, start, end, ts.symbolName(symbol), null, null, null, typeText, flags];
438
+ // A declaration named through its owner's type is a member.
439
+ if ((symbol.declarations ?? []).includes(declaration))
440
+ flags |= this.publication(symbol, declaration);
385
441
  // A file's own module symbol is named by the compiler as the quoted
386
442
  // absolute file name (`"/<checkout>/pkg/index"`). Name it as `decl_file`
387
443
  // names the file. A symbol that merely lives in a file (CommonJS
@@ -389,13 +445,9 @@ class PositionWalker {
389
445
  const symbolName = ts.symbolName(symbol);
390
446
  const isModule = declaration.kind === ts.SyntaxKind.SourceFile && symbolName.startsWith('"');
391
447
  const declaringFile = declaration.getSourceFile();
392
- const declFile = this.program.isSourceFileDefaultLibrary(declaringFile)
393
- ? `lib:${path.basename(declaringFile.fileName)}`
394
- : this.paths.declaringFile(declaringFile.fileName);
448
+ const { declFile, declLine, declPackage } = this.located(declaringFile, declaration.getStart(declaringFile));
395
449
  if (declFile === null)
396
450
  flags |= FLAG_DECLARATION_UNNAMED;
397
- const declLine = declaringFile.getLineAndCharacterOfPosition(declaration.getStart(declaringFile)).line + 1;
398
- const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
399
451
  const name = isModule ? declFile : symbolName;
400
452
  return [kind, start, end, name, declFile, declLine, declPackage, typeText, flags];
401
453
  }
@@ -403,6 +455,121 @@ class PositionWalker {
403
455
  return [kind, start, end, null, null, null, null, null, FLAG_CHECKER_THREW];
404
456
  }
405
457
  }
458
+ /**
459
+ * An intrinsic element's type: the attributes `JSX.IntrinsicElements`
460
+ * gives its tag. The compiler types an intrinsic tag name itself as the
461
+ * error type, though the name resolves. A tag is intrinsic when it is an
462
+ * identifier that starts with a lowercase letter or holds a `-`, as the
463
+ * language reads it.
464
+ */
465
+ intrinsicProps(tag) {
466
+ if (!ts.isIdentifier(tag) || !/^[a-z]|-/.test(tag.text))
467
+ return undefined;
468
+ const element = tag.parent;
469
+ if (!ts.isJsxOpeningElement(element) && !ts.isJsxSelfClosingElement(element))
470
+ return undefined;
471
+ return this.checker.getContextualType(element.attributes);
472
+ }
473
+ /** How rows name a position in a program file: its file, 1-based line and package. */
474
+ located(declaringFile, position) {
475
+ const declFile = this.program.isSourceFileDefaultLibrary(declaringFile)
476
+ ? `lib:${path.basename(declaringFile.fileName)}`
477
+ : this.paths.declaringFile(declaringFile.fileName);
478
+ const declLine = declaringFile.getLineAndCharacterOfPosition(position).line + 1;
479
+ const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
480
+ return { declFile, declLine, declPackage };
481
+ }
482
+ /**
483
+ * The `impl` rows of a `call` or `member` row (spans unset): none unless the
484
+ * row's declaration has no body of its own ({@link isBodyless}). Its own
485
+ * search, apart from the row's: a throw here writes one `impl` row flagged
486
+ * {@link FLAG_CHECKER_THREW} and leaves the row as it was.
487
+ */
488
+ implRows(kind, node) {
489
+ let declaration;
490
+ try {
491
+ const symbol = this.symbolOf(kind, node);
492
+ if (symbol && (symbol.declarations ?? []).length === 0)
493
+ return this.undeclaredImplRows(kind, node, symbol);
494
+ declaration = symbol ? attributedDeclaration(() => this.program, symbol.declarations ?? []) : undefined;
495
+ if (!declaration || !isBodyless(declaration, this.checker))
496
+ return [];
497
+ }
498
+ catch {
499
+ return [];
500
+ }
501
+ let rows = this.implementations.get(declaration);
502
+ if (!rows) {
503
+ try {
504
+ rows = this.implementationsOf(declaration);
505
+ }
506
+ catch {
507
+ rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
508
+ }
509
+ this.implementations.set(declaration, rows);
510
+ }
511
+ return rows;
512
+ }
513
+ /**
514
+ * The `impl` rows of a member whose symbol has no declaration (a mapped
515
+ * type's member): there is no declaration name to ask at, so the answer is
516
+ * asked at the use site's name, once per symbol. Same shape as
517
+ * {@link implementationsOf}; nothing of the symbol's own is filtered out.
518
+ */
519
+ undeclaredImplRows(kind, node, symbol) {
520
+ const cached = this.undeclaredImplementations.get(symbol);
521
+ if (cached)
522
+ return cached;
523
+ let rows;
524
+ try {
525
+ const named = nameNodeOf(kind, node);
526
+ rows = named ? this.implementationRows(named, []) : [];
527
+ }
528
+ catch {
529
+ rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
530
+ }
531
+ this.undeclaredImplementations.set(symbol, rows);
532
+ return rows;
533
+ }
534
+ /**
535
+ * What go-to-implementation names for `declaration`, asked at its name:
536
+ * every declaration the language service answers, except the symbol's own
537
+ * (an overloaded method answers with its other signatures), in file and
538
+ * position order.
539
+ */
540
+ implementationsOf(declaration) {
541
+ const name = ts.getNameOfDeclaration(declaration) ?? declaration;
542
+ const own = (this.checker.getSymbolAtLocation(name) ?? declaration.symbol)
543
+ ?.declarations ?? [declaration];
544
+ return this.implementationRows(name, own);
545
+ }
546
+ /** Go-to-implementation at `name`, minus results inside `own`, as sorted `impl` rows. */
547
+ implementationRows(name, own) {
548
+ const declaringFile = name.getSourceFile();
549
+ const found = new Map();
550
+ for (const location of this.service.getImplementationAtPosition(declaringFile.fileName, name.getStart(declaringFile)) ?? []) {
551
+ const file = this.program.getSourceFile(location.fileName);
552
+ if (!file)
553
+ continue;
554
+ const start = location.textSpan.start;
555
+ if (own.some((d) => d.getSourceFile() === file && d.pos <= start && start < d.end))
556
+ continue;
557
+ found.set(`${location.fileName}\u0000${start}`, { file, start });
558
+ }
559
+ if (found.size === 0)
560
+ return [];
561
+ if (found.size > IMPL_CAP)
562
+ return [['impl', 0, 0, null, null, null, null, null, FLAG_TOO_MANY]];
563
+ const rows = [...found.values()].map(({ file, start }) => {
564
+ const { declFile, declLine, declPackage } = this.located(file, start);
565
+ const named = nodeStartingAt(file, start);
566
+ const symbol = named ? this.checker.getSymbolAtLocation(named) : undefined;
567
+ const flags = declFile === null ? FLAG_DECLARATION_UNNAMED : 0;
568
+ return { start, row: ['impl', 0, 0, symbol ? ts.symbolName(symbol) : null, declFile, declLine, declPackage, null, flags] };
569
+ });
570
+ rows.sort((a, b) => compareNames(a.row[4], b.row[4]) || a.start - b.start);
571
+ return rows.map(({ row }) => row);
572
+ }
406
573
  /**
407
574
  * The declaration a member with none is attributed to (carrick#2448): the
408
575
  * mapped type node, or the alias naming it, of the receiver's type. A
@@ -427,6 +594,68 @@ class PositionWalker {
427
594
  const owner = holders[0].getSymbol() ?? holders[0].aliasSymbol;
428
595
  return attributedDeclaration(() => this.program, owner?.declarations ?? []);
429
596
  }
597
+ /**
598
+ * How `declaration`'s module publishes `symbol`: {@link FLAG_PUBLISHED}
599
+ * when under the symbol's own name (or as a global), and
600
+ * {@link FLAG_MODULE_DEFAULT} when as the module's `export =` or `default`.
601
+ * The module is the nearest ambient `declare module "…"` or the file.
602
+ */
603
+ publication(symbol, declaration) {
604
+ let container = declaration.parent;
605
+ while (container && !ts.isSourceFile(container) && !ts.isModuleBlock(container)) {
606
+ // Anything but a statement list between the declaration and its module
607
+ // (a class, interface, type or object literal, a function body) makes
608
+ // it a member or a local.
609
+ if (!ts.isVariableDeclarationList(container) && !ts.isVariableStatement(container))
610
+ return 0;
611
+ container = container.parent;
612
+ }
613
+ if (!container)
614
+ return 0;
615
+ const block = ts.isModuleBlock(container) ? container.parent : undefined;
616
+ if (block && block.flags & ts.NodeFlags.GlobalAugmentation)
617
+ return FLAG_PUBLISHED;
618
+ // A namespace's member is published when the module its file is publishes it.
619
+ if (block && !ts.isStringLiteral(block.name))
620
+ return this.publishedByExports(symbol, declaration);
621
+ const moduleSymbol = this.checker.getSymbolAtLocation(block ? block.name : container);
622
+ if (moduleSymbol)
623
+ return this.publishedIn(moduleSymbol, symbol);
624
+ // A script's top-level declaration is a global.
625
+ return ts.isSourceFile(container) && !ts.isExternalModule(container) ? FLAG_PUBLISHED : 0;
626
+ }
627
+ /** A namespace member, found among the exports of the module it sits in. */
628
+ publishedByExports(symbol, declaration) {
629
+ for (let node = declaration.parent; node; node = node.parent) {
630
+ if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
631
+ const moduleSymbol = this.checker.getSymbolAtLocation(node.name);
632
+ return moduleSymbol ? this.publishedIn(moduleSymbol, symbol) : 0;
633
+ }
634
+ if (ts.isSourceFile(node)) {
635
+ const moduleSymbol = this.checker.getSymbolAtLocation(node);
636
+ return moduleSymbol ? this.publishedIn(moduleSymbol, symbol) & FLAG_PUBLISHED : 0;
637
+ }
638
+ }
639
+ return 0;
640
+ }
641
+ publishedIn(moduleSymbol, symbol) {
642
+ let exports = this.moduleExports.get(moduleSymbol);
643
+ if (!exports) {
644
+ const resolve = (s) => s.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(s) : s;
645
+ const byName = new Map();
646
+ for (const exported of this.checker.getExportsOfModule(moduleSymbol)) {
647
+ byName.set(exported.getName(), resolve(exported));
648
+ }
649
+ const wholes = [ts.InternalSymbolName.ExportEquals, ts.InternalSymbolName.Default]
650
+ .map((key) => moduleSymbol.exports?.get(key))
651
+ .filter((own) => own !== undefined)
652
+ .map(resolve);
653
+ exports = { byName, wholes };
654
+ this.moduleExports.set(moduleSymbol, exports);
655
+ }
656
+ return ((exports.byName.get(symbol.getName()) === symbol ? FLAG_PUBLISHED : 0) |
657
+ (exports.wholes.includes(symbol) ? FLAG_MODULE_DEFAULT : 0));
658
+ }
430
659
  /** The row's symbol, following aliases to a target that has a declaration. */
431
660
  symbolOf(kind, node) {
432
661
  let symbol;
@@ -550,6 +779,54 @@ class PositionWalker {
550
779
  return this.host;
551
780
  }
552
781
  }
782
+ /** A row's fields after its kind and span. */
783
+ function tail(row) {
784
+ return [row[3], row[4], row[5], row[6], row[7], row[8]];
785
+ }
786
+ /** Order two `decl_file` names, `null` last. */
787
+ function compareNames(a, b) {
788
+ if (a === b)
789
+ return 0;
790
+ if (a === null)
791
+ return 1;
792
+ if (b === null)
793
+ return -1;
794
+ return a < b ? -1 : 1;
795
+ }
796
+ /**
797
+ * Whether a declaration has no body for a call to run: an interface's or type
798
+ * literal's method, a function-typed property signature, an abstract method or
799
+ * property, or a function-typed class property or parameter with no
800
+ * initializer. Its implementations are what go-to-implementation names.
801
+ */
802
+ function isBodyless(declaration, checker) {
803
+ const functionTyped = () => checker.getTypeAtLocation(declaration).getNonNullableType().getCallSignatures().length > 0;
804
+ if (ts.isMethodSignature(declaration))
805
+ return true;
806
+ if (ts.isPropertySignature(declaration))
807
+ return functionTyped();
808
+ if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
809
+ if (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Abstract)
810
+ return true;
811
+ return ts.isPropertyDeclaration(declaration) && !declaration.initializer && functionTyped();
812
+ }
813
+ if (ts.isParameter(declaration))
814
+ return !declaration.initializer && functionTyped();
815
+ return false;
816
+ }
817
+ /** The innermost node of `file` that starts at `position` (an implementation's name). */
818
+ function nodeStartingAt(file, position) {
819
+ let found;
820
+ const visit = (node) => {
821
+ if (position < node.getStart(file) || position >= node.getEnd())
822
+ return;
823
+ if (node.getStart(file) === position)
824
+ found = node;
825
+ ts.forEachChild(node, visit);
826
+ };
827
+ ts.forEachChild(file, visit);
828
+ return found;
829
+ }
553
830
  /** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
554
831
  export function kindOf(node) {
555
832
  if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isTaggedTemplateExpression(node)) {
@@ -576,6 +853,12 @@ function nameNodeOf(kind, node) {
576
853
  return referenceName(node.left);
577
854
  case 'ident':
578
855
  return node;
856
+ case 'impl':
857
+ return undefined;
858
+ case 'jsx':
859
+ // The tag: `<Badge>`, `<ui.Button>`, `<this.Panel>`; a namespaced
860
+ // name (`<svg:path>`) names nothing.
861
+ return ts.isJsxNamespacedName(node) ? undefined : (referenceName(node) ?? node);
579
862
  }
580
863
  }
581
864
  /**
@@ -765,6 +765,10 @@ export interface PositionRowsCounts {
765
765
  any: number;
766
766
  /** Rows whose type is the compiler's error type (flag 2). */
767
767
  error_type: number;
768
+ /** `impl` rows: implementations of a bodyless declaration, or one past the cap (carrick#2430). */
769
+ impl: number;
770
+ /** `jsx` rows: one per JSX opening or self-closing element. */
771
+ jsx: number;
768
772
  /** The rows file's size on disk, compressed. */
769
773
  bytes: number;
770
774
  /** Time spent building programs and reading files into them. */