syncstaff-mcp 0.2.3

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 (52) hide show
  1. package/README.md +86 -0
  2. package/dist/lib/agent-state.js +119 -0
  3. package/dist/lib/blast.js +462 -0
  4. package/dist/lib/client-config.js +81 -0
  5. package/dist/lib/env-compat.js +66 -0
  6. package/dist/lib/globs.js +0 -0
  7. package/dist/lib/ids.js +24 -0
  8. package/dist/lib/index/aliases.js +244 -0
  9. package/dist/lib/index/call-sites.js +178 -0
  10. package/dist/lib/index/checker-resolver.js +257 -0
  11. package/dist/lib/index/context-card.js +140 -0
  12. package/dist/lib/index/coverage.js +218 -0
  13. package/dist/lib/index/delivery.js +66 -0
  14. package/dist/lib/index/discovery.js +90 -0
  15. package/dist/lib/index/embedding.js +110 -0
  16. package/dist/lib/index/file-index.js +222 -0
  17. package/dist/lib/index/fingerprint.js +0 -0
  18. package/dist/lib/index/git-history.js +136 -0
  19. package/dist/lib/index/graph.js +234 -0
  20. package/dist/lib/index/impact.js +174 -0
  21. package/dist/lib/index/incremental.js +332 -0
  22. package/dist/lib/index/lexical.js +462 -0
  23. package/dist/lib/index/order.js +43 -0
  24. package/dist/lib/index/pages.js +357 -0
  25. package/dist/lib/index/persistence.js +233 -0
  26. package/dist/lib/index/pipeline.js +527 -0
  27. package/dist/lib/index/registry.js +106 -0
  28. package/dist/lib/index/resolve.js +280 -0
  29. package/dist/lib/index/semantic.js +381 -0
  30. package/dist/lib/index/surfaces.js +27 -0
  31. package/dist/lib/index/symbols.js +426 -0
  32. package/dist/lib/index/transformers-embedder.js +73 -0
  33. package/dist/lib/index/typescript-parser.js +532 -0
  34. package/dist/lib/index/vector-cache.js +176 -0
  35. package/dist/lib/index/verification.js +58 -0
  36. package/dist/lib/mcp-compaction.js +241 -0
  37. package/dist/lib/model-roles.js +206 -0
  38. package/dist/lib/path-warnings.js +90 -0
  39. package/dist/lib/protocol.js +95 -0
  40. package/dist/lib/types.js +69 -0
  41. package/dist/lib/version.js +21 -0
  42. package/dist/lib/worktree.js +211 -0
  43. package/dist/mcp/approval.js +0 -0
  44. package/dist/mcp/cloud-connector.js +99 -0
  45. package/dist/mcp/daemon-client.js +156 -0
  46. package/dist/mcp/daemon-protocol.js +100 -0
  47. package/dist/mcp/escalation-waiter.js +183 -0
  48. package/dist/mcp/graph-ops.js +169 -0
  49. package/dist/mcp/index.js +1151 -0
  50. package/dist/mcp/login.js +169 -0
  51. package/dist/mcp/setup.js +90 -0
  52. package/package.json +42 -0
@@ -0,0 +1,532 @@
1
+ const EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
2
+ /**
3
+ * Bump on any change to extraction rules.
4
+ *
5
+ * This is part of the index fingerprint by design: an index built by a smarter
6
+ * parser is a different index over identical bytes, and forgetting to bump
7
+ * would let the two claim equality.
8
+ */
9
+ const PARSER_VERSION = "4";
10
+ export function createTypeScriptParser(tsApi) {
11
+ return {
12
+ id: "keel-typescript",
13
+ version: PARSER_VERSION,
14
+ language: "typescript",
15
+ extensions: EXTENSIONS,
16
+ parse: (source, path) => parseSource(tsApi, source, path),
17
+ };
18
+ }
19
+ /**
20
+ * Load the backend if `typescript` is installed, or return null.
21
+ *
22
+ * The published adapter must stay installable with `npx` by someone who has
23
+ * never built a native module, and `typescript` is large. Rather than force it
24
+ * on everyone, the caller registers a backend only when one is available —
25
+ * and the registry already models a missing backend as `unsupported`, which is
26
+ * a gap in coverage rather than a failure. So an adapter without typescript
27
+ * still coordinates; it simply reports that it understood no files, honestly,
28
+ * instead of pretending to a graph it does not have.
29
+ */
30
+ export async function loadTypeScriptParser() {
31
+ try {
32
+ const loaded = (await import("typescript"));
33
+ const api = loaded.default ?? loaded;
34
+ if (typeof api.createSourceFile !== "function")
35
+ return null;
36
+ return createTypeScriptParser(api);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ function parseSource(tsApi, source, path) {
43
+ const imports = [];
44
+ const exports = [];
45
+ const definitions = [];
46
+ const calls = [];
47
+ const references = [];
48
+ const diagnostics = [];
49
+ const file = tsApi.createSourceFile(path, source, tsApi.ScriptTarget.Latest,
50
+ /* setParentNodes */ true, scriptKind(tsApi, path));
51
+ const lineOf = (pos) => file.getLineAndCharacterOfPosition(pos).line + 1;
52
+ const columnOf = (pos) => file.getLineAndCharacterOfPosition(pos).character + 1;
53
+ // parseDiagnostics is internal but stable, and it is the only way to learn a
54
+ // syntactic parse recovered from errors. Guarded so a compiler upgrade that
55
+ // renames it degrades to "no diagnostics" rather than throwing.
56
+ const parseErrors = file
57
+ .parseDiagnostics ?? [];
58
+ for (const error of parseErrors.slice(0, 20)) {
59
+ diagnostics.push({
60
+ // The compiler's own code, never its message: TS messages routinely
61
+ // quote the offending source.
62
+ code: error.code === undefined ? "syntax" : `TS${error.code}`,
63
+ message: "syntax error recovered by the parser",
64
+ ...(error.start === undefined ? {} : { line: lineOf(error.start), column: columnOf(error.start) }),
65
+ });
66
+ }
67
+ /** Names of the functions/classes we are currently inside, outermost first. */
68
+ const scope = [];
69
+ const enclosing = () => (scope.length === 0 ? undefined : scope[scope.length - 1]);
70
+ /** Class names only, so `this` can be attributed without a type checker. */
71
+ const classes = [];
72
+ const enclosingClass = () => classes.length === 0 ? undefined : classes[classes.length - 1];
73
+ const isExported = (node) => {
74
+ const modifiers = node.modifiers;
75
+ return (modifiers ?? []).some((m) => m.kind === tsApi.SyntaxKind.ExportKeyword);
76
+ };
77
+ const addDefinition = (name, kind, node, owner) => {
78
+ const exported = isExported(node);
79
+ definitions.push({
80
+ name,
81
+ kind,
82
+ exported,
83
+ ...(owner ? { owner } : {}),
84
+ line: lineOf(node.getStart(file)),
85
+ });
86
+ if (exported) {
87
+ exports.push({ name, kind, confidence: "exact", line: lineOf(node.getStart(file)) });
88
+ }
89
+ };
90
+ const addReference = (name, kind, node, extra = {}) => {
91
+ references.push({
92
+ name,
93
+ kind,
94
+ ...(extra.receiver ? { receiver: extra.receiver } : {}),
95
+ ...(extra.ownerClass ? { ownerClass: extra.ownerClass } : {}),
96
+ ...(enclosing() ? { enclosing: enclosing() } : {}),
97
+ line: lineOf(node.getStart(file)),
98
+ confidence: extra.confidence ?? "exact",
99
+ });
100
+ };
101
+ const visit = (node) => {
102
+ // ---- imports -------------------------------------------------------
103
+ if (tsApi.isImportDeclaration(node)) {
104
+ const specifier = literalText(tsApi, node.moduleSpecifier);
105
+ if (specifier !== null) {
106
+ const names = [];
107
+ const aliases = {};
108
+ let confidence = "exact";
109
+ const clause = node.importClause;
110
+ // `import type { X }` and `import { type X }` are compile-time only.
111
+ let typeOnly = clause?.isTypeOnly === true;
112
+ if (clause?.name)
113
+ names.push(clause.name.text);
114
+ const bindings = clause?.namedBindings;
115
+ if (bindings && tsApi.isNamedImports(bindings)) {
116
+ // `import { a as b }` binds `b` locally while the target exports `a`.
117
+ // Recording both is what lets resolution follow the alias instead of
118
+ // failing to find `b` in the target and calling the edge unresolved.
119
+ let allTypeOnly = bindings.elements.length > 0;
120
+ for (const element of bindings.elements) {
121
+ names.push(element.name.text);
122
+ if (element.propertyName)
123
+ aliases[element.name.text] = element.propertyName.text;
124
+ if (!element.isTypeOnly)
125
+ allTypeOnly = false;
126
+ // The line a rename has to touch. Recorded under the LOCAL name,
127
+ // exactly like a call: resolution owns the alias mapping and
128
+ // reports the target's own name for the symbol, so writing the
129
+ // target's name here would instead produce an unresolvable
130
+ // reference to a binding this file does not have.
131
+ addReference(element.name.text, "import", element);
132
+ }
133
+ if (allTypeOnly)
134
+ typeOnly = true;
135
+ }
136
+ else if (bindings && tsApi.isNamespaceImport(bindings)) {
137
+ names.push(bindings.name.text);
138
+ // A namespace binding is one name standing for a whole module; a
139
+ // call through it is knowable but not certain.
140
+ confidence = "probable";
141
+ }
142
+ imports.push({
143
+ specifier,
144
+ names,
145
+ ...(Object.keys(aliases).length > 0 ? { aliases } : {}),
146
+ ...(typeOnly ? { typeOnly: true } : {}),
147
+ dynamic: false,
148
+ confidence,
149
+ line: lineOf(node.getStart(file)),
150
+ });
151
+ }
152
+ }
153
+ // ---- re-exports ----------------------------------------------------
154
+ if (tsApi.isExportDeclaration(node)) {
155
+ const specifier = node.moduleSpecifier ? literalText(tsApi, node.moduleSpecifier) : null;
156
+ if (specifier !== null) {
157
+ const names = [];
158
+ if (node.exportClause && tsApi.isNamedExports(node.exportClause)) {
159
+ for (const element of node.exportClause.elements) {
160
+ names.push(element.name.text);
161
+ addReference(element.name.text, "re-export", element);
162
+ exports.push({
163
+ name: element.name.text,
164
+ kind: "re-export",
165
+ from: specifier,
166
+ // The name is certain; what it ultimately refers to is not.
167
+ confidence: "probable",
168
+ line: lineOf(element.getStart(file)),
169
+ });
170
+ }
171
+ }
172
+ else {
173
+ // `export * from "./x.js"` — the names cannot be known without
174
+ // reading the target, which this parser will not do. Recorded as a
175
+ // diagnostic so coverage reflects the gap instead of hiding it.
176
+ diagnostics.push({
177
+ code: "star-reexport",
178
+ message: "export * cannot be enumerated without cross-file resolution",
179
+ line: lineOf(node.getStart(file)),
180
+ });
181
+ }
182
+ imports.push({
183
+ specifier,
184
+ names,
185
+ ...(node.isTypeOnly ? { typeOnly: true } : {}),
186
+ dynamic: false,
187
+ confidence: "probable",
188
+ line: lineOf(node.getStart(file)),
189
+ });
190
+ }
191
+ else if (node.exportClause && tsApi.isNamedExports(node.exportClause)) {
192
+ // `export { a, b }` re-exporting local declarations.
193
+ for (const element of node.exportClause.elements) {
194
+ addReference(element.propertyName?.text ?? element.name.text, "re-export", element);
195
+ exports.push({
196
+ name: element.name.text,
197
+ kind: "binding",
198
+ confidence: "exact",
199
+ line: lineOf(element.getStart(file)),
200
+ });
201
+ }
202
+ }
203
+ }
204
+ if (tsApi.isExportAssignment(node)) {
205
+ exports.push({
206
+ name: "default",
207
+ kind: node.isExportEquals ? "export=" : "default",
208
+ confidence: "exact",
209
+ line: lineOf(node.getStart(file)),
210
+ });
211
+ }
212
+ // ---- declarations ---------------------------------------------------
213
+ if (tsApi.isFunctionDeclaration(node) && node.name) {
214
+ addDefinition(node.name.text, "function", node);
215
+ }
216
+ else if (tsApi.isClassDeclaration(node) && node.name) {
217
+ addDefinition(node.name.text, "class", node);
218
+ }
219
+ else if (tsApi.isInterfaceDeclaration(node)) {
220
+ addDefinition(node.name.text, "interface", node);
221
+ }
222
+ else if (tsApi.isTypeAliasDeclaration(node)) {
223
+ addDefinition(node.name.text, "type", node);
224
+ }
225
+ else if (tsApi.isEnumDeclaration(node)) {
226
+ addDefinition(node.name.text, "enum", node);
227
+ }
228
+ else if (tsApi.isVariableStatement(node)) {
229
+ for (const declaration of node.declarationList.declarations) {
230
+ if (!tsApi.isIdentifier(declaration.name))
231
+ continue;
232
+ const initializer = declaration.initializer;
233
+ const kind = initializer && (tsApi.isArrowFunction(initializer) || tsApi.isFunctionExpression(initializer))
234
+ ? "function"
235
+ : "const";
236
+ addDefinition(declaration.name.text, kind, node);
237
+ }
238
+ }
239
+ else if (tsApi.isMethodDeclaration(node) && tsApi.isIdentifier(node.name)) {
240
+ const owner = scope[scope.length - 1];
241
+ definitions.push({
242
+ name: node.name.text,
243
+ kind: "method",
244
+ exported: false,
245
+ ...(owner ? { owner } : {}),
246
+ line: lineOf(node.getStart(file)),
247
+ });
248
+ }
249
+ // ---- calls -----------------------------------------------------------
250
+ if (tsApi.isCallExpression(node)) {
251
+ const callee = node.expression;
252
+ if (callee.kind === tsApi.SyntaxKind.ImportKeyword) {
253
+ const argument = node.arguments[0];
254
+ const specifier = argument ? literalText(tsApi, argument) : null;
255
+ imports.push({
256
+ // A dynamic import with a literal argument is still statically
257
+ // knowable; one built from a variable is not. Both are marked
258
+ // dynamic so graph.ts records rather than resolves them.
259
+ specifier: specifier ?? "<computed>",
260
+ names: [],
261
+ dynamic: true,
262
+ confidence: specifier === null ? "speculative" : "probable",
263
+ line: lineOf(node.getStart(file)),
264
+ });
265
+ }
266
+ else if (tsApi.isIdentifier(callee)) {
267
+ if (callee.text === "require") {
268
+ const argument = node.arguments[0];
269
+ const specifier = argument ? literalText(tsApi, argument) : null;
270
+ if (specifier !== null) {
271
+ imports.push({
272
+ specifier,
273
+ names: [],
274
+ dynamic: false,
275
+ confidence: "probable",
276
+ line: lineOf(node.getStart(file)),
277
+ });
278
+ }
279
+ }
280
+ else {
281
+ calls.push({
282
+ callee: callee.text,
283
+ ...(enclosing() ? { enclosing: enclosing() } : {}),
284
+ line: lineOf(node.getStart(file)),
285
+ confidence: "exact",
286
+ });
287
+ addReference(callee.text, "call", node);
288
+ }
289
+ }
290
+ else if (tsApi.isPropertyAccessExpression(callee) && tsApi.isIdentifier(callee.name)) {
291
+ const receiver = tsApi.isIdentifier(callee.expression)
292
+ ? callee.expression.text
293
+ : callee.expression.kind === tsApi.SyntaxKind.ThisKeyword
294
+ ? "this"
295
+ : undefined;
296
+ // `this` needs no type inference: the enclosing class is in the AST.
297
+ const owner = receiver === "this" ? enclosingClass() : undefined;
298
+ calls.push({
299
+ callee: receiver ? `${receiver}.${callee.name.text}` : callee.name.text,
300
+ ...(receiver ? { receiver } : {}),
301
+ ...(owner ? { ownerClass: owner } : {}),
302
+ ...(enclosing() ? { enclosing: enclosing() } : {}),
303
+ line: lineOf(node.getStart(file)),
304
+ // A this-call is exact once the class is known; any other receiver
305
+ // would need the type checker this backend deliberately avoids.
306
+ confidence: owner ? "exact" : "probable",
307
+ });
308
+ addReference(receiver ? `${receiver}.${callee.name.text}` : callee.name.text, "call", node, { ...(receiver ? { receiver } : {}), ...(owner ? { ownerClass: owner } : {}), confidence: owner ? "exact" : "probable" });
309
+ }
310
+ // Anything else — an IIFE, a call on a call — is not attributable to a
311
+ // name, and inventing one would be the whole failure this avoids.
312
+ }
313
+ // ---- construction ------------------------------------------------------
314
+ // `new Store()` is a NewExpression, not a CallExpression, so a backend
315
+ // looking only for calls never saw a single instantiation in this
316
+ // repository. Nothing about it is less of a use than an invocation.
317
+ if (tsApi.isNewExpression(node)) {
318
+ const constructed = node.expression;
319
+ if (tsApi.isIdentifier(constructed)) {
320
+ addReference(constructed.text, "new", node);
321
+ }
322
+ else if (tsApi.isPropertyAccessExpression(constructed) &&
323
+ tsApi.isIdentifier(constructed.name) &&
324
+ tsApi.isIdentifier(constructed.expression)) {
325
+ addReference(`${constructed.expression.text}.${constructed.name.text}`, "new", node, {
326
+ receiver: constructed.expression.text,
327
+ // The receiver could have been reassigned between the import and
328
+ // here, exactly as for a member call.
329
+ confidence: "probable",
330
+ });
331
+ }
332
+ }
333
+ // ---- type positions ----------------------------------------------------
334
+ // `x: Config` and `class A extends Base` are compile-time references, and
335
+ // changing the target's shape breaks this file's build. `import type` is
336
+ // already carried as typeOnly on the edge, so the distinction survives
337
+ // downstream; what did not survive was the site itself.
338
+ if (tsApi.isTypeReferenceNode(node)) {
339
+ const named = node.typeName;
340
+ if (tsApi.isIdentifier(named)) {
341
+ addReference(named.text, "type", node);
342
+ }
343
+ else if (tsApi.isQualifiedName(named) && tsApi.isIdentifier(named.left)) {
344
+ addReference(`${named.left.text}.${named.right.text}`, "type", node, {
345
+ receiver: named.left.text,
346
+ confidence: "probable",
347
+ });
348
+ }
349
+ }
350
+ else if (tsApi.isExpressionWithTypeArguments(node) && tsApi.isIdentifier(node.expression)) {
351
+ // The heritage clause of `extends`/`implements`.
352
+ addReference(node.expression.text, "type", node);
353
+ }
354
+ else if (tsApi.isTypeQueryNode(node) && tsApi.isIdentifier(node.exprName)) {
355
+ // `typeof CONFIG` reads the binding to talk about its type.
356
+ addReference(node.exprName.text, "type", node);
357
+ }
358
+ // ---- JSX ---------------------------------------------------------------
359
+ // <Editor /> invokes the component; React calls it. It is not a
360
+ // CallExpression, so a backend looking only for calls sees nothing, which
361
+ // left every component use in this repo's web app invisible. Lowercase
362
+ // names are intrinsic elements (<div>) and reference nothing.
363
+ if (tsApi.isJsxOpeningElement(node) || tsApi.isJsxSelfClosingElement(node)) {
364
+ const tag = node.tagName;
365
+ const name = tsApi.isIdentifier(tag)
366
+ ? tag.text
367
+ : tsApi.isPropertyAccessExpression(tag) && tsApi.isIdentifier(tag.name)
368
+ ? tag.name.text
369
+ : null;
370
+ if (name && /^[A-Z]/.test(name)) {
371
+ const receiver = tsApi.isPropertyAccessExpression(tag) && tsApi.isIdentifier(tag.expression)
372
+ ? tag.expression.text
373
+ : undefined;
374
+ calls.push({
375
+ callee: receiver ? `${receiver}.${name}` : name,
376
+ ...(receiver ? { receiver } : {}),
377
+ jsx: true,
378
+ ...(enclosing() ? { enclosing: enclosing() } : {}),
379
+ line: lineOf(node.getStart(file)),
380
+ confidence: "exact",
381
+ });
382
+ addReference(receiver ? `${receiver}.${name}` : name, "jsx", node, {
383
+ ...(receiver ? { receiver } : {}),
384
+ });
385
+ }
386
+ }
387
+ // ---- plain reads -------------------------------------------------------
388
+ // The branch that was missing entirely. `KEEL_RELEASE_VERSION` is read in
389
+ // four files and invoked in none, so every extractor above walks straight
390
+ // past it and the graph reports that nothing uses a constant three modules
391
+ // depend on. Everything already handled above is excluded here rather than
392
+ // recorded twice — see isPlainRead.
393
+ if (tsApi.isIdentifier(node) && isPlainRead(tsApi, node)) {
394
+ addReference(node.text, "read", node);
395
+ }
396
+ // ---- descend, tracking scope -----------------------------------------
397
+ const scopeName = scopeNameOf(tsApi, node);
398
+ if (scopeName)
399
+ scope.push(scopeName);
400
+ const className = tsApi.isClassDeclaration(node) || tsApi.isClassExpression(node)
401
+ ? node.name?.text ?? "(anonymous class)"
402
+ : null;
403
+ if (className)
404
+ classes.push(className);
405
+ tsApi.forEachChild(node, visit);
406
+ if (className)
407
+ classes.pop();
408
+ if (scopeName)
409
+ scope.pop();
410
+ };
411
+ tsApi.forEachChild(file, visit);
412
+ // `partial`, not `failed`: declarations either side of a syntax error are
413
+ // real, and discarding them would lose most of a file over one bad line.
414
+ // `failed` is reserved for a parse that yielded nothing usable.
415
+ const recovered = imports.length + exports.length + definitions.length + calls.length > 0;
416
+ const status = parseErrors.length === 0 ? "parsed" : recovered ? "partial" : "failed";
417
+ return { status, imports, exports, definitions, calls, references, diagnostics };
418
+ }
419
+ /**
420
+ * Whether an identifier is a *use* of some binding, rather than the place a
421
+ * binding is introduced or a member name that belongs to someone else's type.
422
+ *
423
+ * Written as an exclusion list because the inclusive version is unbounded and
424
+ * the exclusive one is checkable: every case below is a position where treating
425
+ * the identifier as a reference would produce a claim the parser cannot
426
+ * support. Two categories, and they fail differently:
427
+ *
428
+ * Declaration names (`function parse`, `const parse`, a parameter named
429
+ * `parse`) would make every file that happens to declare the name look like a
430
+ * file that uses it. That is `grep-v1` again, wearing an AST.
431
+ *
432
+ * Member names (`obj.parse`, `A.B` in a type, `{ parse: fn }`) name a
433
+ * property of a value whose type this backend does not infer. Resolving
434
+ * `obj.parse` against a local function called `parse` is a guess, and the
435
+ * member-call path already refuses that same guess deliberately.
436
+ *
437
+ * Everything the walk records under another kind — calls, `new`, type
438
+ * references, JSX tags, import and export specifiers — is excluded here too, so
439
+ * a site is reported exactly once with the most specific kind that fits.
440
+ */
441
+ function isPlainRead(tsApi, id) {
442
+ const parent = id.parent;
443
+ if (!parent)
444
+ return false;
445
+ // The name side of any declaration: `function f`, `const f`, `class F`,
446
+ // `f(param)`, `{ key: value }`, `interface I`, `<T>`, `enum E { Member }`.
447
+ // Object shorthand is the one exception — in `{ VERSION }` the identifier is
448
+ // both the key and a genuine read of the binding, and shorthand is one of the
449
+ // ordinary ways a constant travels between modules.
450
+ const named = parent.name;
451
+ if (named === id)
452
+ return tsApi.isShorthandPropertyAssignment(parent);
453
+ // `const { a: b } = x` — `a` is a property of x, not a binding in scope.
454
+ if (tsApi.isBindingElement(parent) && parent.propertyName === id)
455
+ return false;
456
+ // `config.timeout` is two halves and only one of them is knowable. The
457
+ // member needs the receiver's type, which this backend does not infer — but
458
+ // the receiver itself is an ordinary binding, and it is frequently the
459
+ // imported constant somebody is actually asking about. Dropping both halves
460
+ // would lose the reference the whole phase exists to find.
461
+ if (tsApi.isPropertyAccessExpression(parent))
462
+ return parent.name !== id;
463
+ if (tsApi.isQualifiedName(parent))
464
+ return parent.right !== id;
465
+ switch (parent.kind) {
466
+ // Recorded above with a more specific kind.
467
+ case tsApi.SyntaxKind.ImportSpecifier:
468
+ case tsApi.SyntaxKind.ExportSpecifier:
469
+ case tsApi.SyntaxKind.ImportClause:
470
+ case tsApi.SyntaxKind.NamespaceImport:
471
+ case tsApi.SyntaxKind.NamespaceExport:
472
+ case tsApi.SyntaxKind.TypeReference:
473
+ case tsApi.SyntaxKind.ExpressionWithTypeArguments:
474
+ case tsApi.SyntaxKind.TypeQuery:
475
+ case tsApi.SyntaxKind.JsxOpeningElement:
476
+ case tsApi.SyntaxKind.JsxClosingElement:
477
+ case tsApi.SyntaxKind.JsxSelfClosingElement:
478
+ return false;
479
+ // Not references to anything: labels, and `import.meta`.
480
+ case tsApi.SyntaxKind.LabeledStatement:
481
+ case tsApi.SyntaxKind.BreakStatement:
482
+ case tsApi.SyntaxKind.ContinueStatement:
483
+ case tsApi.SyntaxKind.MetaProperty:
484
+ return false;
485
+ default:
486
+ break;
487
+ }
488
+ // The callee of `f()` and the constructor of `new F()` are already recorded
489
+ // as "call" and "new". Arguments to either are ordinary reads and fall
490
+ // through, which is the point of testing the position rather than the parent.
491
+ if (tsApi.isCallExpression(parent) && parent.expression === id)
492
+ return false;
493
+ if (tsApi.isNewExpression(parent) && parent.expression === id)
494
+ return false;
495
+ return true;
496
+ }
497
+ /** The text of a string literal, or null for anything computed. */
498
+ function literalText(tsApi, node) {
499
+ if (tsApi.isStringLiteral(node))
500
+ return node.text;
501
+ if (tsApi.isNoSubstitutionTemplateLiteral(node))
502
+ return node.text;
503
+ return null;
504
+ }
505
+ /** The name a node contributes to the enclosing-scope stack, if any. */
506
+ function scopeNameOf(tsApi, node) {
507
+ if (tsApi.isFunctionDeclaration(node) && node.name)
508
+ return node.name.text;
509
+ if (tsApi.isClassDeclaration(node) && node.name)
510
+ return node.name.text;
511
+ if (tsApi.isMethodDeclaration(node) && tsApi.isIdentifier(node.name))
512
+ return node.name.text;
513
+ if (tsApi.isConstructorDeclaration(node))
514
+ return "constructor";
515
+ if ((tsApi.isArrowFunction(node) || tsApi.isFunctionExpression(node)) &&
516
+ node.parent &&
517
+ tsApi.isVariableDeclaration(node.parent) &&
518
+ tsApi.isIdentifier(node.parent.name)) {
519
+ return node.parent.name.text;
520
+ }
521
+ return null;
522
+ }
523
+ function scriptKind(tsApi, path) {
524
+ const lower = path.toLowerCase();
525
+ if (lower.endsWith(".tsx"))
526
+ return tsApi.ScriptKind.TSX;
527
+ if (lower.endsWith(".jsx"))
528
+ return tsApi.ScriptKind.JSX;
529
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs"))
530
+ return tsApi.ScriptKind.JS;
531
+ return tsApi.ScriptKind.TS;
532
+ }