carrick 0.3.116 → 0.3.118

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 (42) hide show
  1. package/dist/contract.d.ts +2 -0
  2. package/dist/contract.js.map +1 -1
  3. package/dist/render.js +2 -1
  4. package/dist/render.js.map +1 -1
  5. package/package.json +6 -6
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/sidecar/dist/src/capture/anchors.d.ts +1 -0
  8. package/sidecar/dist/src/capture/anchors.js +1 -1
  9. package/sidecar/dist/src/capture/api.d.ts +6 -0
  10. package/sidecar/dist/src/capture/check-classify.js +25 -0
  11. package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
  12. package/sidecar/dist/src/capture/check-probe.js +20 -0
  13. package/sidecar/dist/src/capture/checkout.d.ts +3 -0
  14. package/sidecar/dist/src/capture/checkout.js +1 -1
  15. package/sidecar/dist/src/capture/deep-walk.d.ts +10 -0
  16. package/sidecar/dist/src/capture/deep-walk.js +15 -0
  17. package/sidecar/dist/src/capture/guarded-fs.d.ts +6 -0
  18. package/sidecar/dist/src/capture/guarded-fs.js +10 -0
  19. package/sidecar/dist/src/capture/index.d.ts +5 -0
  20. package/sidecar/dist/src/capture/index.js +62 -7
  21. package/sidecar/dist/src/capture/project-references.d.ts +24 -13
  22. package/sidecar/dist/src/capture/project-references.js +53 -16
  23. package/sidecar/dist/src/capture/reference-redirect.d.ts +84 -0
  24. package/sidecar/dist/src/capture/reference-redirect.js +209 -0
  25. package/sidecar/dist/src/capture/unresolved.d.ts +8 -0
  26. package/sidecar/dist/src/capture/unresolved.js +18 -2
  27. package/sidecar/dist/src/declaration-attribution.d.ts +40 -0
  28. package/sidecar/dist/src/declaration-attribution.js +66 -0
  29. package/sidecar/dist/src/index.js +37 -0
  30. package/sidecar/dist/src/library-claims.js +234 -23
  31. package/sidecar/dist/src/module-format.d.ts +19 -1
  32. package/sidecar/dist/src/module-format.js +9 -2
  33. package/sidecar/dist/src/position-rows.d.ts +95 -0
  34. package/sidecar/dist/src/position-rows.js +844 -0
  35. package/sidecar/dist/src/project-loader.d.ts +36 -7
  36. package/sidecar/dist/src/project-loader.js +70 -28
  37. package/sidecar/dist/src/retype.js +13 -0
  38. package/sidecar/dist/src/type-inferrer.d.ts +25 -29
  39. package/sidecar/dist/src/type-inferrer.js +119 -70
  40. package/sidecar/dist/src/types.d.ts +78 -2
  41. package/sidecar/dist/src/validators.d.ts +431 -30
  42. package/sidecar/dist/src/validators.js +45 -1
@@ -0,0 +1,844 @@
1
+ /**
2
+ * Per-position compiler rows for a service (carrick#2405, move 2 of the fact
3
+ * layer). Record-only: nothing in a scan reads the file yet.
4
+ *
5
+ * For every source file the scanner sends, one row for each position of six
6
+ * kinds, each holding what the compiler knows there: the symbol, the
7
+ * declaration it is attributed to (file and line), the package that declares
8
+ * it and the type text.
9
+ *
10
+ * | kind | node | symbol of | type of |
11
+ * |-----------|---------------------------------------------------|---------------------------|----------------|
12
+ * | `call` | call, `new`, tagged template | the callee's name | the call |
13
+ * | `member` | property or element access | the name or the argument | the access |
14
+ * | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
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 |
18
+ *
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.
32
+ *
33
+ * Each file is read in the program of the project that owns it
34
+ * (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
35
+ * time: a group's program is released before the next one is built. A file
36
+ * the owner's program does not take (JavaScript under a config without
37
+ * `allowJs`) is read after every owner, in that owner's `scripts` program
38
+ * (`ProjectLoader.scriptProjectFor`, carrick#2413), with the owner's root
39
+ * declaration files beside it so that the globals they declare are declared.
40
+ *
41
+ * The file format is pinned on the scanner side by `src/position_rows.rs`.
42
+ */
43
+ import * as crypto from 'node:crypto';
44
+ import * as fs from 'node:fs';
45
+ import * as path from 'node:path';
46
+ import * as zlib from 'node:zlib';
47
+ import { ts } from 'ts-morph';
48
+ import { WriteGuard, installedPackageDirectory, isDeclarationFileName, moduleExport } from './capture/index.js';
49
+ import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
50
+ import { typeToStringAsDeclared } from './print-type.js';
51
+ /** The format's version, written in the header. */
52
+ export const POSITION_ROWS_VERSION = 2;
53
+ /** The type is `any`, declared or inferred. Never set with the error type. */
54
+ export const FLAG_ANY = 1;
55
+ /** The type is the compiler's error type: something did not resolve. */
56
+ export const FLAG_ERROR_TYPE = 2;
57
+ /** The checker threw at this node; the row holds nothing else. */
58
+ export const FLAG_CHECKER_THREW = 4;
59
+ /** The declaration is outside the checkout, not the default library and in no installed package. */
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
+ /** The most implementations one declaration's `impl` rows list. */
64
+ export const IMPL_CAP = 16;
65
+ const COMPARE_OPERATORS = new Set([
66
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
67
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
68
+ ts.SyntaxKind.EqualsEqualsToken,
69
+ ts.SyntaxKind.ExclamationEqualsToken,
70
+ ts.SyntaxKind.LessThanToken,
71
+ ts.SyntaxKind.GreaterThanToken,
72
+ ts.SyntaxKind.LessThanEqualsToken,
73
+ ts.SyntaxKind.GreaterThanEqualsToken,
74
+ ts.SyntaxKind.InstanceOfKeyword,
75
+ ts.SyntaxKind.InKeyword,
76
+ ]);
77
+ /**
78
+ * Walk `files`, write their rows to `outPath` and say what was written.
79
+ *
80
+ * The file is written beside `outPath` as `<outPath>.partial` and renamed
81
+ * over it, so a walk that dies leaves no half-written rows file. Every write
82
+ * goes through a {@link WriteGuard} over the file's directory, which refuses
83
+ * a directory inside the scanned trees anywhere but beneath `.carrick`.
84
+ */
85
+ export function writePositionRows(options) {
86
+ const { loader, files, scanRoot, outPath, service } = options;
87
+ const outDir = path.dirname(outPath);
88
+ const guard = WriteGuard.of({ dirs: [outDir], protect: [loader.getRepoRoot(), scanRoot] });
89
+ const paths = new PathNames(scanRoot);
90
+ const lines = new Array(files.length);
91
+ const counts = {
92
+ files: files.length,
93
+ rows: 0,
94
+ resolved: 0,
95
+ any: 0,
96
+ error_type: 0,
97
+ impl: 0,
98
+ jsx: 0,
99
+ bytes: 0,
100
+ load_ms: 0,
101
+ walk_ms: 0,
102
+ };
103
+ // Files in request order within each owner's group; groups in the order
104
+ // their first file appears.
105
+ const groups = new Map();
106
+ files.forEach((file, index) => {
107
+ const key = loader.projectKeyFor(file);
108
+ groups.set(key, [...(groups.get(key) ?? []), index]);
109
+ });
110
+ // Files an owner's program did not take, by owner, with that program's
111
+ // root declaration files.
112
+ const untaken = new Map();
113
+ /**
114
+ * Add each file of `indexes` to `project`, build its program once, and walk
115
+ * every file the program takes. Returns the files it did not take; every
116
+ * other file is settled, and reported to `onFile`.
117
+ */
118
+ const walkGroup = (project, indexes, hashes) => {
119
+ const loadStarted = performance.now();
120
+ const read = new Map();
121
+ for (const index of indexes) {
122
+ const file = files[index];
123
+ if (!hashes.has(index)) {
124
+ let bytes;
125
+ try {
126
+ bytes = fs.readFileSync(file);
127
+ }
128
+ catch {
129
+ lines[index] = fileLine({ f: paths.relative(file), skip: 'unreadable', r: [] });
130
+ options.onFile?.();
131
+ continue;
132
+ }
133
+ hashes.set(index, crypto.createHash('sha256').update(bytes).digest('hex'));
134
+ }
135
+ // A file no program lists is added, as `infer` adds one.
136
+ let sourceFile = project.getSourceFile(file);
137
+ if (!sourceFile) {
138
+ try {
139
+ sourceFile = project.addSourceFileAtPath(file);
140
+ }
141
+ catch {
142
+ sourceFile = undefined;
143
+ }
144
+ }
145
+ read.set(index, sourceFile?.getFilePath());
146
+ }
147
+ // Built once, after every file the group adds. The language service's
148
+ // program is the one walked, so go-to-implementation answers over it
149
+ // and no second program is built.
150
+ const service = project.getLanguageService().compilerObject;
151
+ const program = service.getProgram();
152
+ if (!program)
153
+ throw new Error('the language service built no program');
154
+ counts.load_ms += performance.now() - loadStarted;
155
+ const walkStarted = performance.now();
156
+ const walker = new PositionWalker(program, service, paths, loader);
157
+ const missed = [];
158
+ for (const index of indexes) {
159
+ if (!read.has(index))
160
+ continue;
161
+ const fileName = read.get(index);
162
+ const sourceFile = fileName ? program.getSourceFile(fileName) : undefined;
163
+ if (!sourceFile) {
164
+ missed.push(index);
165
+ continue;
166
+ }
167
+ const rows = walker.rowsOf(sourceFile);
168
+ for (const row of rows) {
169
+ counts.rows += 1;
170
+ if (row[3] !== null)
171
+ counts.resolved += 1;
172
+ if (row[8] & FLAG_ANY)
173
+ counts.any += 1;
174
+ if (row[8] & FLAG_ERROR_TYPE)
175
+ counts.error_type += 1;
176
+ if (row[0] === 'impl')
177
+ counts.impl += 1;
178
+ if (row[0] === 'jsx')
179
+ counts.jsx += 1;
180
+ }
181
+ lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), r: rows });
182
+ options.onFile?.();
183
+ }
184
+ counts.walk_ms += performance.now() - walkStarted;
185
+ return { missed, program };
186
+ };
187
+ const hashes = new Map();
188
+ for (const [key, indexes] of groups) {
189
+ const project = loader.getProjectFor(key);
190
+ const { missed, program } = walkGroup(project, indexes, hashes);
191
+ if (missed.length > 0) {
192
+ const declarations = program.getRootFileNames().filter(isDeclarationFileName);
193
+ untaken.set(key, { indexes: missed, declarations });
194
+ }
195
+ // One owner's program at a time.
196
+ loader.releaseProjectFor(key);
197
+ }
198
+ for (const [key, { indexes, declarations }] of untaken) {
199
+ const loadStarted = performance.now();
200
+ const project = loader.scriptProjectFor(key);
201
+ for (const declaration of declarations) {
202
+ try {
203
+ project.addSourceFileAtPath(declaration);
204
+ }
205
+ catch {
206
+ // A declaration file that went away is not the walk's to report.
207
+ }
208
+ }
209
+ counts.load_ms += performance.now() - loadStarted;
210
+ for (const index of walkGroup(project, indexes, hashes).missed) {
211
+ lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), skip: 'not_in_program', r: [] });
212
+ options.onFile?.();
213
+ }
214
+ }
215
+ const writeStarted = performance.now();
216
+ const header = JSON.stringify({
217
+ v: POSITION_ROWS_VERSION,
218
+ typescript: ts.version,
219
+ service,
220
+ files: files.length,
221
+ });
222
+ const gzipped = zlib.gzipSync(Buffer.from([header, ...lines].join('\n') + '\n', 'utf8'));
223
+ const partial = `${outPath}.partial`;
224
+ guard.mkdir(outDir);
225
+ guard.writeFile(partial, gzipped);
226
+ guard.rename(partial, outPath);
227
+ counts.bytes = gzipped.length;
228
+ counts.walk_ms = Math.round(counts.walk_ms + performance.now() - writeStarted);
229
+ counts.load_ms = Math.round(counts.load_ms);
230
+ return counts;
231
+ }
232
+ /** A file's line, with its keys in the pinned order. */
233
+ function fileLine(line) {
234
+ return JSON.stringify(line);
235
+ }
236
+ /**
237
+ * Names for the files rows point at, never an absolute path: relative to the
238
+ * scanned repo's root when inside it (its real path too, so a root reached
239
+ * through a link still contains its files), `lib:<name>` for the compiler's
240
+ * default library, `pkg:<name>/<path>` for an installed package outside the
241
+ * root, and nothing otherwise.
242
+ */
243
+ class PathNames {
244
+ roots;
245
+ packageNames = new Map();
246
+ /**
247
+ * Answers by declaring file: a service's rows name a few thousand files
248
+ * hundreds of thousands of times, and the module graph's answer is a scan
249
+ * of its packages (on a Deno service it was 40% of the walk).
250
+ */
251
+ declaringFiles = new Map();
252
+ declaringPackages = new Map();
253
+ constructor(scanRoot) {
254
+ const resolved = path.resolve(scanRoot);
255
+ let real = resolved;
256
+ try {
257
+ real = fs.realpathSync.native(resolved);
258
+ }
259
+ catch {
260
+ // A root that cannot be resolved is named as given.
261
+ }
262
+ this.roots = real === resolved ? [resolved] : [resolved, real];
263
+ }
264
+ /** The root `file` lies under, and its path from there; `undefined` outside every root. */
265
+ inside(file) {
266
+ for (const root of this.roots) {
267
+ const rel = path.relative(root, file);
268
+ if (rel === '' || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel))
269
+ continue;
270
+ return { root, rel };
271
+ }
272
+ return undefined;
273
+ }
274
+ /** A request file's name: relative to the root, which every file the scanner sends is inside. */
275
+ relative(file) {
276
+ const inside = this.inside(path.resolve(file));
277
+ return toSlashes(inside ? inside.rel : path.basename(file));
278
+ }
279
+ /** What a declaring file is written as, or `null` when nothing names it. */
280
+ declaringFile(fileName) {
281
+ let named = this.declaringFiles.get(fileName);
282
+ if (named === undefined) {
283
+ named = this.nameDeclaringFile(fileName);
284
+ this.declaringFiles.set(fileName, named);
285
+ }
286
+ return named;
287
+ }
288
+ nameDeclaringFile(fileName) {
289
+ const inside = this.inside(fileName);
290
+ if (inside)
291
+ return toSlashes(inside.rel);
292
+ const installed = installedPackageDirectory(this.roots[0], fileName);
293
+ if (installed && fs.existsSync(path.join(installed.directory, 'package.json'))) {
294
+ return `pkg:${installed.name}/${toSlashes(path.relative(installed.directory, fileName))}`;
295
+ }
296
+ return null;
297
+ }
298
+ /**
299
+ * The package a declaring file belongs to: the module graph's or the path's
300
+ * answer (`installedPackageOf`), else, for a file inside the root, the
301
+ * `name` of the nearest package.json at or above it, up to the root.
302
+ */
303
+ declaringPackage(fileName, packageOf) {
304
+ let named = this.declaringPackages.get(fileName);
305
+ if (named === undefined) {
306
+ named = this.nameDeclaringPackage(fileName, packageOf);
307
+ this.declaringPackages.set(fileName, named);
308
+ }
309
+ return named;
310
+ }
311
+ nameDeclaringPackage(fileName, packageOf) {
312
+ const installed = installedPackageOf(fileName, packageOf);
313
+ if (installed)
314
+ return installed;
315
+ const inside = this.inside(fileName);
316
+ if (!inside)
317
+ return null;
318
+ let dir = path.dirname(fileName);
319
+ for (;;) {
320
+ const name = this.packageNameIn(dir);
321
+ if (name !== null)
322
+ return name;
323
+ if (dir === inside.root)
324
+ return null;
325
+ const parent = path.dirname(dir);
326
+ if (parent === dir)
327
+ return null;
328
+ dir = parent;
329
+ }
330
+ }
331
+ packageNameIn(dir) {
332
+ let name = this.packageNames.get(dir);
333
+ if (name === undefined) {
334
+ name = null;
335
+ try {
336
+ const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
337
+ if (typeof manifest.name === 'string' && manifest.name.length > 0)
338
+ name = manifest.name;
339
+ }
340
+ catch {
341
+ // No package.json here, or one with no name.
342
+ }
343
+ this.packageNames.set(dir, name);
344
+ }
345
+ return name;
346
+ }
347
+ /**
348
+ * `import("/abs/path")` in a printed type, written with the declaring
349
+ * file's name instead. The print has no enclosing node, so it names every
350
+ * module by the file that declares it and one type reads the same from
351
+ * every file; it also skips the accessibility search an enclosing node
352
+ * costs (40% of the walk on a 600-file service).
353
+ */
354
+ scrubType(text) {
355
+ if (!text.includes('import("'))
356
+ return text;
357
+ return text.replace(/import\("([^"]+)"\)/g, (whole, specifier) => {
358
+ if (!path.isAbsolute(specifier))
359
+ return whole;
360
+ return `import("${this.declaringFile(specifier) ?? '<outside the checkout>'}")`;
361
+ });
362
+ }
363
+ }
364
+ function toSlashes(p) {
365
+ return p.split(path.sep).join('/');
366
+ }
367
+ /** The rows of one program's files. */
368
+ class PositionWalker {
369
+ program;
370
+ service;
371
+ paths;
372
+ loader;
373
+ checker;
374
+ /**
375
+ * Each bodyless declaration's implementations, as `impl` rows with no span
376
+ * yet (`start` and `end` are 0): go-to-implementation searches the whole
377
+ * program, so it is asked once per declaration, never per call.
378
+ */
379
+ implementations = new Map();
380
+ /** The same for a member whose symbol has no declaration, keyed by symbol. */
381
+ undeclaredImplementations = new Map();
382
+ constructor(program, service, paths, loader) {
383
+ this.program = program;
384
+ this.service = service;
385
+ this.paths = paths;
386
+ this.loader = loader;
387
+ this.checker = program.getTypeChecker();
388
+ }
389
+ rowsOf(sourceFile) {
390
+ const rows = [];
391
+ const visit = (node) => {
392
+ const kind = kindOf(node);
393
+ if (kind) {
394
+ const row = this.row(kind, node, sourceFile);
395
+ rows.push(row);
396
+ if ((kind === 'call' || kind === 'member') && !(row[8] & FLAG_CHECKER_THREW)) {
397
+ for (const impl of this.implRows(kind, node))
398
+ rows.push(['impl', row[1], row[2], ...tail(impl)]);
399
+ }
400
+ }
401
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
402
+ rows.push(this.row('jsx', node.tagName, sourceFile));
403
+ }
404
+ ts.forEachChild(node, visit);
405
+ };
406
+ ts.forEachChild(sourceFile, visit);
407
+ return rows;
408
+ }
409
+ row(kind, node, sourceFile) {
410
+ const start = node.getStart(sourceFile);
411
+ const end = node.getEnd();
412
+ try {
413
+ const symbol = this.symbolOf(kind, node);
414
+ const typed = kind === 'compare' ? node.left : node;
415
+ const type = (kind === 'jsx' && this.intrinsicProps(node)) || this.checker.getTypeAtLocation(typed);
416
+ let flags = 0;
417
+ if (type.flags & ts.TypeFlags.Any) {
418
+ flags |= type.intrinsicName === 'error' ? FLAG_ERROR_TYPE : FLAG_ANY;
419
+ }
420
+ const typeText = this.paths.scrubType(typeToStringAsDeclared(this.checker, type, undefined));
421
+ if (!symbol)
422
+ return [kind, start, end, null, null, null, null, typeText, flags];
423
+ const declaration = attributedDeclaration(() => this.program, symbol.declarations ?? []) ??
424
+ this.ownerDeclaration(kind, node, symbol);
425
+ if (!declaration)
426
+ return [kind, start, end, ts.symbolName(symbol), null, null, null, typeText, flags];
427
+ // A file's own module symbol is named by the compiler as the quoted
428
+ // absolute file name (`"/<checkout>/pkg/index"`). Name it as `decl_file`
429
+ // names the file. A symbol that merely lives in a file (CommonJS
430
+ // `exports`) keeps its name.
431
+ const symbolName = ts.symbolName(symbol);
432
+ const isModule = declaration.kind === ts.SyntaxKind.SourceFile && symbolName.startsWith('"');
433
+ const declaringFile = declaration.getSourceFile();
434
+ const { declFile, declLine, declPackage } = this.located(declaringFile, declaration.getStart(declaringFile));
435
+ if (declFile === null)
436
+ flags |= FLAG_DECLARATION_UNNAMED;
437
+ const name = isModule ? declFile : symbolName;
438
+ return [kind, start, end, name, declFile, declLine, declPackage, typeText, flags];
439
+ }
440
+ catch {
441
+ return [kind, start, end, null, null, null, null, null, FLAG_CHECKER_THREW];
442
+ }
443
+ }
444
+ /**
445
+ * An intrinsic element's type: the attributes `JSX.IntrinsicElements`
446
+ * gives its tag. The compiler types an intrinsic tag name itself as the
447
+ * error type, though the name resolves. A tag is intrinsic when it is an
448
+ * identifier that starts with a lowercase letter or holds a `-`, as the
449
+ * language reads it.
450
+ */
451
+ intrinsicProps(tag) {
452
+ if (!ts.isIdentifier(tag) || !/^[a-z]|-/.test(tag.text))
453
+ return undefined;
454
+ const element = tag.parent;
455
+ if (!ts.isJsxOpeningElement(element) && !ts.isJsxSelfClosingElement(element))
456
+ return undefined;
457
+ return this.checker.getContextualType(element.attributes);
458
+ }
459
+ /** How rows name a position in a program file: its file, 1-based line and package. */
460
+ located(declaringFile, position) {
461
+ const declFile = this.program.isSourceFileDefaultLibrary(declaringFile)
462
+ ? `lib:${path.basename(declaringFile.fileName)}`
463
+ : this.paths.declaringFile(declaringFile.fileName);
464
+ const declLine = declaringFile.getLineAndCharacterOfPosition(position).line + 1;
465
+ const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
466
+ return { declFile, declLine, declPackage };
467
+ }
468
+ /**
469
+ * The `impl` rows of a `call` or `member` row (spans unset): none unless the
470
+ * row's declaration has no body of its own ({@link isBodyless}). Its own
471
+ * search, apart from the row's: a throw here writes one `impl` row flagged
472
+ * {@link FLAG_CHECKER_THREW} and leaves the row as it was.
473
+ */
474
+ implRows(kind, node) {
475
+ let declaration;
476
+ try {
477
+ const symbol = this.symbolOf(kind, node);
478
+ if (symbol && (symbol.declarations ?? []).length === 0)
479
+ return this.undeclaredImplRows(kind, node, symbol);
480
+ declaration = symbol ? attributedDeclaration(() => this.program, symbol.declarations ?? []) : undefined;
481
+ if (!declaration || !isBodyless(declaration, this.checker))
482
+ return [];
483
+ }
484
+ catch {
485
+ return [];
486
+ }
487
+ let rows = this.implementations.get(declaration);
488
+ if (!rows) {
489
+ try {
490
+ rows = this.implementationsOf(declaration);
491
+ }
492
+ catch {
493
+ rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
494
+ }
495
+ this.implementations.set(declaration, rows);
496
+ }
497
+ return rows;
498
+ }
499
+ /**
500
+ * The `impl` rows of a member whose symbol has no declaration (a mapped
501
+ * type's member): there is no declaration name to ask at, so the answer is
502
+ * asked at the use site's name, once per symbol. Same shape as
503
+ * {@link implementationsOf}; nothing of the symbol's own is filtered out.
504
+ */
505
+ undeclaredImplRows(kind, node, symbol) {
506
+ const cached = this.undeclaredImplementations.get(symbol);
507
+ if (cached)
508
+ return cached;
509
+ let rows;
510
+ try {
511
+ const named = nameNodeOf(kind, node);
512
+ rows = named ? this.implementationRows(named, []) : [];
513
+ }
514
+ catch {
515
+ rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
516
+ }
517
+ this.undeclaredImplementations.set(symbol, rows);
518
+ return rows;
519
+ }
520
+ /**
521
+ * What go-to-implementation names for `declaration`, asked at its name:
522
+ * every declaration the language service answers, except the symbol's own
523
+ * (an overloaded method answers with its other signatures), in file and
524
+ * position order.
525
+ */
526
+ implementationsOf(declaration) {
527
+ const name = ts.getNameOfDeclaration(declaration) ?? declaration;
528
+ const own = (this.checker.getSymbolAtLocation(name) ?? declaration.symbol)
529
+ ?.declarations ?? [declaration];
530
+ return this.implementationRows(name, own);
531
+ }
532
+ /** Go-to-implementation at `name`, minus results inside `own`, as sorted `impl` rows. */
533
+ implementationRows(name, own) {
534
+ const declaringFile = name.getSourceFile();
535
+ const found = new Map();
536
+ for (const location of this.service.getImplementationAtPosition(declaringFile.fileName, name.getStart(declaringFile)) ?? []) {
537
+ const file = this.program.getSourceFile(location.fileName);
538
+ if (!file)
539
+ continue;
540
+ const start = location.textSpan.start;
541
+ if (own.some((d) => d.getSourceFile() === file && d.pos <= start && start < d.end))
542
+ continue;
543
+ found.set(`${location.fileName}\u0000${start}`, { file, start });
544
+ }
545
+ if (found.size === 0)
546
+ return [];
547
+ if (found.size > IMPL_CAP)
548
+ return [['impl', 0, 0, null, null, null, null, null, FLAG_TOO_MANY]];
549
+ const rows = [...found.values()].map(({ file, start }) => {
550
+ const { declFile, declLine, declPackage } = this.located(file, start);
551
+ const named = nodeStartingAt(file, start);
552
+ const symbol = named ? this.checker.getSymbolAtLocation(named) : undefined;
553
+ const flags = declFile === null ? FLAG_DECLARATION_UNNAMED : 0;
554
+ return { start, row: ['impl', 0, 0, symbol ? ts.symbolName(symbol) : null, declFile, declLine, declPackage, null, flags] };
555
+ });
556
+ rows.sort((a, b) => compareNames(a.row[4], b.row[4]) || a.start - b.start);
557
+ return rows.map(({ row }) => row);
558
+ }
559
+ /**
560
+ * The declaration a member with none is attributed to (carrick#2448): the
561
+ * mapped type node, or the alias naming it, of the receiver's type. A
562
+ * mapped type's members are made by the checker and have no declaration of
563
+ * their own. This is the owner `memberTypeWithoutDeclaration` reads such a
564
+ * member at (type-structural-expander.ts): the type's symbol, else its
565
+ * alias symbol. A union or intersection receiver contributes the one
566
+ * constituent that has the property; with several the row is left as it is.
567
+ */
568
+ ownerDeclaration(kind, node, symbol) {
569
+ if (kind !== 'member' && kind !== 'call')
570
+ return undefined;
571
+ const named = nameNodeOf(kind, node);
572
+ const access = named?.parent;
573
+ if (!access || !(ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access))) {
574
+ return undefined;
575
+ }
576
+ const receiver = this.checker.getTypeAtLocation(access.expression);
577
+ const holders = (receiver.isUnionOrIntersection() ? receiver.types : [receiver]).filter((constituent) => constituent.getProperty(symbol.name) !== undefined);
578
+ if (holders.length !== 1)
579
+ return undefined;
580
+ const owner = holders[0].getSymbol() ?? holders[0].aliasSymbol;
581
+ return attributedDeclaration(() => this.program, owner?.declarations ?? []);
582
+ }
583
+ /** The row's symbol, following aliases to a target that has a declaration. */
584
+ symbolOf(kind, node) {
585
+ let symbol;
586
+ if (kind === 'ident' && ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node) {
587
+ symbol = this.checker.getShorthandAssignmentValueSymbol(node.parent);
588
+ }
589
+ else {
590
+ const named = nameNodeOf(kind, node);
591
+ symbol = named ? this.checker.getSymbolAtLocation(named) : undefined;
592
+ if (named && /\.[cm]?tsx?$/.test(node.getSourceFile().fileName)) {
593
+ symbol = this.requiredExport(named, symbol) ?? symbol;
594
+ }
595
+ }
596
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
597
+ const target = this.checker.getAliasedSymbol(symbol);
598
+ // An import of a module that does not resolve aliases nothing: the
599
+ // import itself is all there is.
600
+ if (target.declarations && target.declarations.length > 0)
601
+ symbol = target;
602
+ }
603
+ return symbol;
604
+ }
605
+ /**
606
+ * In a TypeScript file `const { a } = require("m")` binds `a` as `any` with
607
+ * the binding as its only declaration (TypeScript resolves `require` itself
608
+ * only in JavaScript files), so a trace stops there. Name the export the
609
+ * call resolves to instead (carrick#2454): for the binding itself, and for
610
+ * `m.x` where `m` is a whole-module binding. `undefined` when the name is
611
+ * not such a binding, the specifier is not a literal or does not resolve,
612
+ * or the module has no such export.
613
+ */
614
+ requiredExport(named, symbol) {
615
+ try {
616
+ const binding = symbol ? this.requireBinding(symbol) : undefined;
617
+ if (binding)
618
+ return this.exportOfRequired(binding);
619
+ // `m.x` / `m["x"]`: the symbol of `x` is unknown on an `any` object.
620
+ const access = named.parent;
621
+ let member;
622
+ let object;
623
+ if (ts.isPropertyAccessExpression(access) && access.name === named) {
624
+ member = access.name.text;
625
+ object = access.expression;
626
+ }
627
+ else if (ts.isElementAccessExpression(access) &&
628
+ access.argumentExpression === named &&
629
+ ts.isStringLiteral(named)) {
630
+ member = named.text;
631
+ object = access.expression;
632
+ }
633
+ if (member === undefined || !object || !ts.isIdentifier(object))
634
+ return undefined;
635
+ const objectSymbol = this.checker.getSymbolAtLocation(object);
636
+ const whole = objectSymbol ? this.requireBinding(objectSymbol) : undefined;
637
+ if (!whole || whole.exportName !== undefined)
638
+ return undefined;
639
+ return this.exportOfRequired({ ...whole, exportName: member });
640
+ }
641
+ catch {
642
+ return undefined;
643
+ }
644
+ }
645
+ /** The `require` binding a symbol is, when its only declaration is one. */
646
+ requireBinding(symbol) {
647
+ const declarations = symbol.declarations ?? [];
648
+ if (declarations.length !== 1)
649
+ return undefined;
650
+ const declaration = declarations[0];
651
+ let initializer;
652
+ let exportName;
653
+ if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name)) {
654
+ initializer = declaration.initializer;
655
+ if (initializer && ts.isPropertyAccessExpression(initializer) && ts.isIdentifier(initializer.name)) {
656
+ exportName = initializer.name.text;
657
+ initializer = initializer.expression;
658
+ }
659
+ }
660
+ else if (ts.isBindingElement(declaration) &&
661
+ ts.isIdentifier(declaration.name) &&
662
+ !declaration.dotDotDotToken &&
663
+ !declaration.initializer &&
664
+ ts.isObjectBindingPattern(declaration.parent) &&
665
+ ts.isVariableDeclaration(declaration.parent.parent)) {
666
+ const key = declaration.propertyName;
667
+ if (key && !ts.isIdentifier(key) && !ts.isStringLiteral(key))
668
+ return undefined;
669
+ exportName = key ? key.text : declaration.name.text;
670
+ initializer = declaration.parent.parent.initializer;
671
+ }
672
+ const specifier = initializer ? this.requireSpecifier(initializer) : undefined;
673
+ if (specifier === undefined)
674
+ return undefined;
675
+ const from = declaration.getSourceFile();
676
+ return exportName === undefined ? { specifier, from } : { specifier, from, exportName };
677
+ }
678
+ /** `"m"` of a call `require("m")` to the global `require`, else `undefined`. */
679
+ requireSpecifier(expression) {
680
+ if (!ts.isCallExpression(expression) || expression.arguments.length !== 1)
681
+ return undefined;
682
+ const callee = expression.expression;
683
+ const argument = expression.arguments[0];
684
+ if (!ts.isIdentifier(callee) || callee.text !== 'require' || !ts.isStringLiteral(argument))
685
+ return undefined;
686
+ // The global `require` is declared in a declaration file (or nowhere); a
687
+ // `require` the service writes is a declaration in its own source.
688
+ const own = (this.checker.getSymbolAtLocation(callee)?.declarations ?? []).some((d) => !d.getSourceFile().isDeclarationFile);
689
+ return own ? undefined : argument.text;
690
+ }
691
+ exportOfRequired(binding) {
692
+ const resolved = ts.resolveModuleName(binding.specifier, binding.from.fileName, this.program.getCompilerOptions(), this.resolutionHost(), undefined, undefined, ts.ModuleKind.CommonJS).resolvedModule;
693
+ const target = resolved ? this.program.getSourceFile(resolved.resolvedFileName) : undefined;
694
+ if (!target)
695
+ return undefined;
696
+ if (binding.exportName === undefined)
697
+ return this.checker.getSymbolAtLocation(target);
698
+ return moduleExport(this.checker, target, binding.exportName);
699
+ }
700
+ host;
701
+ resolutionHost() {
702
+ this.host ??= ts.createCompilerHost(this.program.getCompilerOptions());
703
+ return this.host;
704
+ }
705
+ }
706
+ /** A row's fields after its kind and span. */
707
+ function tail(row) {
708
+ return [row[3], row[4], row[5], row[6], row[7], row[8]];
709
+ }
710
+ /** Order two `decl_file` names, `null` last. */
711
+ function compareNames(a, b) {
712
+ if (a === b)
713
+ return 0;
714
+ if (a === null)
715
+ return 1;
716
+ if (b === null)
717
+ return -1;
718
+ return a < b ? -1 : 1;
719
+ }
720
+ /**
721
+ * Whether a declaration has no body for a call to run: an interface's or type
722
+ * literal's method, a function-typed property signature, an abstract method or
723
+ * property, or a function-typed class property or parameter with no
724
+ * initializer. Its implementations are what go-to-implementation names.
725
+ */
726
+ function isBodyless(declaration, checker) {
727
+ const functionTyped = () => checker.getTypeAtLocation(declaration).getNonNullableType().getCallSignatures().length > 0;
728
+ if (ts.isMethodSignature(declaration))
729
+ return true;
730
+ if (ts.isPropertySignature(declaration))
731
+ return functionTyped();
732
+ if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
733
+ if (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Abstract)
734
+ return true;
735
+ return ts.isPropertyDeclaration(declaration) && !declaration.initializer && functionTyped();
736
+ }
737
+ if (ts.isParameter(declaration))
738
+ return !declaration.initializer && functionTyped();
739
+ return false;
740
+ }
741
+ /** The innermost node of `file` that starts at `position` (an implementation's name). */
742
+ function nodeStartingAt(file, position) {
743
+ let found;
744
+ const visit = (node) => {
745
+ if (position < node.getStart(file) || position >= node.getEnd())
746
+ return;
747
+ if (node.getStart(file) === position)
748
+ found = node;
749
+ ts.forEachChild(node, visit);
750
+ };
751
+ ts.forEachChild(file, visit);
752
+ return found;
753
+ }
754
+ /** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
755
+ export function kindOf(node) {
756
+ if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isTaggedTemplateExpression(node)) {
757
+ return 'call';
758
+ }
759
+ if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node))
760
+ return 'member';
761
+ if (ts.isBinaryExpression(node) && COMPARE_OPERATORS.has(node.operatorToken.kind))
762
+ return 'compare';
763
+ if (ts.isIdentifier(node) && isExpressionIdentifier(node))
764
+ return 'ident';
765
+ return undefined;
766
+ }
767
+ /** The node whose symbol a row of `kind` names. */
768
+ function nameNodeOf(kind, node) {
769
+ switch (kind) {
770
+ case 'call':
771
+ return referenceName(ts.isTaggedTemplateExpression(node) ? node.tag : node.expression);
772
+ case 'member':
773
+ return ts.isPropertyAccessExpression(node)
774
+ ? node.name
775
+ : node.argumentExpression;
776
+ case 'compare':
777
+ return referenceName(node.left);
778
+ case 'ident':
779
+ return node;
780
+ case 'impl':
781
+ return undefined;
782
+ case 'jsx':
783
+ // The tag: `<Badge>`, `<ui.Button>`, `<this.Panel>`; a namespaced
784
+ // name (`<svg:path>`) names nothing.
785
+ return ts.isJsxNamespacedName(node) ? undefined : (referenceName(node) ?? node);
786
+ }
787
+ }
788
+ /**
789
+ * The name an expression refers by, through the wrappers that do not change
790
+ * what it refers to (`(x)`, `x as T`, `<T>x`, `x!`, `x satisfies T`): an
791
+ * identifier, a member's name or argument, `super`. `undefined` for anything
792
+ * else (a call's result, a literal).
793
+ */
794
+ function referenceName(expression) {
795
+ let inner = expression;
796
+ while (ts.isParenthesizedExpression(inner) ||
797
+ ts.isAsExpression(inner) ||
798
+ ts.isTypeAssertionExpression(inner) ||
799
+ ts.isNonNullExpression(inner) ||
800
+ ts.isSatisfiesExpression(inner)) {
801
+ inner = inner.expression;
802
+ }
803
+ if (ts.isIdentifier(inner) || ts.isPrivateIdentifier(inner))
804
+ return inner;
805
+ if (ts.isPropertyAccessExpression(inner))
806
+ return inner.name;
807
+ if (ts.isElementAccessExpression(inner))
808
+ return inner.argumentExpression;
809
+ if (inner.kind === ts.SyntaxKind.SuperKeyword)
810
+ return inner;
811
+ return undefined;
812
+ }
813
+ /**
814
+ * Whether an identifier is in expression position: a reference to a value,
815
+ * including a callee, a member's object and a shorthand property `{ a }`.
816
+ * Not a declaration's or binding's name, a type-position name, an import or
817
+ * export specifier, a property key, a member's name, a label, a meta-property
818
+ * (`import.meta`), or a JSX tag or attribute name.
819
+ */
820
+ function isExpressionIdentifier(node) {
821
+ const parent = node.parent;
822
+ if (ts.isShorthandPropertyAssignment(parent) && parent.name === node)
823
+ return true;
824
+ if (ts.isPartOfTypeNode(node))
825
+ return false;
826
+ const named = parent;
827
+ if (named.name === node || named.propertyName === node || named.label === node)
828
+ return false;
829
+ if (named.tagName === node)
830
+ return false;
831
+ if (ts.isQualifiedName(parent) || ts.isMetaProperty(parent))
832
+ return false;
833
+ // The tag of a JSX element written as a member (`<ui.Button>`).
834
+ if (ts.isPropertyAccessExpression(parent) && isJsxTagName(parent))
835
+ return false;
836
+ return true;
837
+ }
838
+ function isJsxTagName(node) {
839
+ let tag = node;
840
+ while (ts.isPropertyAccessExpression(tag.parent) && tag.parent.expression === tag)
841
+ tag = tag.parent;
842
+ const holder = tag.parent;
843
+ return holder.tagName === tag;
844
+ }