gitnexus 1.6.6-rc.103 → 1.6.6-rc.105

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 (26) hide show
  1. package/dist/core/ingestion/call-processor.js +46 -8
  2. package/dist/core/ingestion/languages/javascript/captures.js +10 -0
  3. package/dist/core/ingestion/languages/javascript/query.js +105 -0
  4. package/dist/core/ingestion/languages/typescript/array-callback.d.ts +3 -29
  5. package/dist/core/ingestion/languages/typescript/array-callback.js +4 -43
  6. package/dist/core/ingestion/languages/typescript/captures.js +10 -0
  7. package/dist/core/ingestion/languages/typescript/query.js +84 -0
  8. package/dist/core/ingestion/languages/typescript.js +44 -14
  9. package/dist/core/ingestion/method-types.d.ts +2 -2
  10. package/dist/core/ingestion/parsing-processor.js +32 -2
  11. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +23 -0
  12. package/dist/core/ingestion/pipeline-phases/parse-impl.js +88 -16
  13. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  14. package/dist/core/ingestion/tree-sitter-queries.js +203 -0
  15. package/dist/core/ingestion/ts-js-hoc-utils.d.ts +12 -0
  16. package/dist/core/ingestion/ts-js-hoc-utils.js +105 -0
  17. package/dist/core/ingestion/type-env.d.ts +2 -1
  18. package/dist/core/ingestion/type-env.js +5 -5
  19. package/dist/core/ingestion/utils/ast-helpers.js +3 -1
  20. package/dist/core/ingestion/utils/deferred-resolution-profile.d.ts +10 -0
  21. package/dist/core/ingestion/utils/deferred-resolution-profile.js +30 -0
  22. package/dist/core/ingestion/workers/parse-worker.js +58 -4
  23. package/dist/core/ingestion/workers/worker-pool.d.ts +38 -3
  24. package/dist/core/ingestion/workers/worker-pool.js +282 -43
  25. package/dist/core/run-analyze.js +4 -1
  26. package/package.json +1 -1
@@ -28,7 +28,7 @@ import { generateId } from '../../lib/utils.js';
28
28
  import { getLanguageFromFilename, SupportedLanguages } from '../../_shared/index.js';
29
29
  import { isRegistryPrimary } from './registry-primary-flag.js';
30
30
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
31
- import { deferredCallFileSlowMs, deferredCallLogEveryN, getDeferredProfileDroppedCount, isDeferredResolutionProfileEnabled, logDeferredProfile, profileElapsedMs, resetDeferredProfileDroppedCount, startTimer, } from './utils/deferred-resolution-profile.js';
31
+ import { ALWAYS_ON_SLOW_FILE_WARN_THROTTLE_MS, alwaysOnSlowFileWarnMs, deferredCallFileSlowMs, deferredCallLogEveryN, getDeferredProfileDroppedCount, isDeferredResolutionProfileEnabled, logDeferredProfile, profileElapsedMs, resetDeferredProfileDroppedCount, startTimer, } from './utils/deferred-resolution-profile.js';
32
32
  import { yieldToEventLoop } from './utils/event-loop.js';
33
33
  import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
34
34
  import { CLASS_CONTAINER_TYPES, FUNCTION_NODE_TYPES, findEnclosingClassInfo, genericFuncName, inferFunctionLabel, } from './utils/ast-helpers.js';
@@ -311,7 +311,7 @@ const findEnclosingFunction = (node, filePath, ctx, provider) => {
311
311
  let current = node.parent;
312
312
  while (current) {
313
313
  if (FUNCTION_NODE_TYPES.has(current.type)) {
314
- const efnResult = provider.methodExtractor?.extractFunctionName?.(current);
314
+ const efnResult = provider.methodExtractor?.extractFunctionName?.(current, filePath);
315
315
  const funcName = efnResult?.funcName ?? genericFuncName(current);
316
316
  const label = efnResult?.label ?? inferFunctionLabel(current.type);
317
317
  if (funcName) {
@@ -749,6 +749,7 @@ compiledQueryCache) => {
749
749
  const importedReturnTypes = importedReturnTypesMap?.get(file.path);
750
750
  const importedRawReturnTypes = importedRawReturnTypesMap?.get(file.path);
751
751
  const typeEnv = buildTypeEnv(tree, language, {
752
+ filePath: file.path,
752
753
  model: ctx.model,
753
754
  parentMap,
754
755
  importedBindings,
@@ -1050,7 +1051,8 @@ compiledQueryCache) => {
1050
1051
  let p = callNode.parent;
1051
1052
  while (p) {
1052
1053
  if (FUNCTION_NODE_TYPES.has(p.type)) {
1053
- const funcName = provider.methodExtractor?.extractFunctionName?.(p)?.funcName ?? genericFuncName(p);
1054
+ const funcName = provider.methodExtractor?.extractFunctionName?.(p, file.path)?.funcName ??
1055
+ genericFuncName(p);
1054
1056
  if (funcName) {
1055
1057
  scope = `${funcName}@${p.startIndex}`;
1056
1058
  break;
@@ -2303,6 +2305,14 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
2303
2305
  const slowFileMs = profileCalls ? deferredCallFileSlowMs() : 0;
2304
2306
  const logEveryN = profileCalls ? deferredCallLogEveryN() : 0;
2305
2307
  let skippedRegistryPrimaryFiles = 0;
2308
+ // Always-on slow-file watchdog (#1741). Independent of the verbose/profile
2309
+ // gate above: even a plain `analyze` run surfaces ONE actionable warning
2310
+ // when a single file's call resolution is pathologically slow — turning the
2311
+ // silent "stuck at Resolving calls (N/M)" symptom into a named culprit.
2312
+ // Throttled so a genuinely slow repo can't produce a warn storm.
2313
+ const alwaysSlowFileMs = alwaysOnSlowFileWarnMs();
2314
+ let lastSlowFileWarnAt = 0;
2315
+ let suppressedSlowFileWarnings = 0;
2306
2316
  // Fresh dropped-log counter per analyze run — the module-private counter
2307
2317
  // in deferred-resolution-profile.ts is process-lived, so without a reset
2308
2318
  // here it would accumulate across consecutive analyze invocations in the
@@ -2314,12 +2324,16 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
2314
2324
  // denominator stays stable as the loop iterates. Otherwise `${totalFiles -
2315
2325
  // skippedRegistryPrimaryFiles}` drifts upward — files iterated before later
2316
2326
  // registry-primary skips have been seen carry an inflated denominator, and
2317
- // the ratio only self-corrects after every file has been classified. Pre-
2318
- // count runs only on the enabled path so the disabled path stays free of
2319
- // the extra Map iteration. Defaults to 0 on the disabled path; the live log
2320
- // gate is also disabled there, so the value is never read.
2327
+ // the ratio only self-corrects after every file has been classified.
2328
+ //
2329
+ // Runs whenever its result will actually be read: on the profile path (the
2330
+ // live deferred-profile log) OR when the always-on slow-file watchdog is
2331
+ // active (#1741) — the watchdog's warning prints `${resolvedFiles}/${resolvedTotal}`
2332
+ // unconditionally, so leaving resolvedTotal at 0 on a plain run produced a
2333
+ // bogus "Resolved N/0 files" denominator on exactly the unprofiled runs the
2334
+ // watchdog exists for. When both gates are off, skip the extra Map pass.
2321
2335
  let resolvedTotal = 0;
2322
- if (profileCalls) {
2336
+ if (profileCalls || alwaysSlowFileMs > 0) {
2323
2337
  for (const filePath of byFile.keys()) {
2324
2338
  const lang = getLanguageFromFilename(filePath);
2325
2339
  if (!lang || !isRegistryPrimary(lang))
@@ -2341,6 +2355,9 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
2341
2355
  }
2342
2356
  resolvedFiles++;
2343
2357
  const tFile = startTimer(profileCalls);
2358
+ // Always-on timer (cheap: one hrtime read) feeding the slow-file watchdog
2359
+ // below. Distinct from `tFile`, which is null unless profiling is on.
2360
+ const tFileAlways = alwaysSlowFileMs > 0 ? process.hrtime.bigint() : null;
2344
2361
  if (profileCalls && (resolvedFiles === 1 || resolvedFiles % logEveryN === 0)) {
2345
2362
  logDeferredProfile(`calls ${resolvedFiles}/${resolvedTotal} file=${filePath} sites=${calls.length}`);
2346
2363
  }
@@ -2455,6 +2472,27 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
2455
2472
  logDeferredProfile(`slow file ${elapsed.toFixed(0)}ms path=${filePath} calls=${calls.length} lang=${fileLanguage ?? 'unknown'}`);
2456
2473
  }
2457
2474
  }
2475
+ // Always-on slow-file watchdog (#1741) — fires regardless of verbose.
2476
+ if (tFileAlways !== null) {
2477
+ const elapsedAlways = profileElapsedMs(tFileAlways);
2478
+ if (elapsedAlways >= alwaysSlowFileMs) {
2479
+ const now = Date.now();
2480
+ if (now - lastSlowFileWarnAt >= ALWAYS_ON_SLOW_FILE_WARN_THROTTLE_MS) {
2481
+ lastSlowFileWarnAt = now;
2482
+ const suppressedNote = suppressedSlowFileWarnings > 0
2483
+ ? ` (+${suppressedSlowFileWarnings} more slow files since the last warning)`
2484
+ : '';
2485
+ logger.warn(`⏳ Call resolution for ${filePath} took ${(elapsedAlways / 1000).toFixed(1)}s ` +
2486
+ `(${calls.length} call sites, ${fileLanguage ?? 'unknown'}). The run is not frozen — ` +
2487
+ `this file is unusually expensive to resolve. Resolved ${resolvedFiles}/${resolvedTotal} ` +
2488
+ `files so far.${suppressedNote} Pass -v for per-file deferred-resolution timing.`);
2489
+ suppressedSlowFileWarnings = 0;
2490
+ }
2491
+ else {
2492
+ suppressedSlowFileWarnings++;
2493
+ }
2494
+ }
2495
+ }
2458
2496
  }
2459
2497
  if (profileCalls) {
2460
2498
  logDeferredProfile(`processCallsFromExtracted done: ${totalFiles} files, ${extractedCalls.length} call sites, skipped registry-primary files=${skippedRegistryPrimaryFiles}`);
@@ -34,6 +34,7 @@ import { synthesizeTsReceiverBinding } from '../typescript/receiver-binding.js';
34
34
  import { isArrayMethodCallbackArrow } from '../typescript/array-callback.js';
35
35
  import { getTreeSitterBufferSize } from '../../constants.js';
36
36
  import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
37
+ import { deriveDefaultExportHocName, isBlockedDefaultExportHoc, isDefaultExportHocFunctionNode, } from '../../ts-js-hoc-utils.js';
37
38
  /** JS function-like node types that may carry a synthesized `this` binding.
38
39
  * Kept in sync with the `@scope.function` patterns in `query.ts`. */
39
40
  const FUNCTION_NODE_TYPES = [
@@ -615,6 +616,15 @@ export function emitJsScopeCaptures(sourceText, filePath, cachedTree) {
615
616
  if (arrowNode !== null && isArrayMethodCallbackArrow(arrowNode)) {
616
617
  continue;
617
618
  }
619
+ if (arrowNode !== null && isBlockedDefaultExportHoc(arrowNode)) {
620
+ continue;
621
+ }
622
+ }
623
+ if (fnDeclAnchor !== undefined) {
624
+ const fnNode = findFunctionNode(tree.rootNode, fnDeclAnchor.range);
625
+ if (fnNode !== null && isDefaultExportHocFunctionNode(fnNode)) {
626
+ grouped['@declaration.name'] = syntheticCapture('@declaration.name', fnNode, deriveDefaultExportHocName(filePath));
627
+ }
618
628
  }
619
629
  // Synthesize arity metadata on function-like declarations.
620
630
  const declAnchor = pickFirstDefined(grouped, FUNCTION_DECL_TAGS);
@@ -47,6 +47,7 @@
47
47
  */
48
48
  import Parser from 'tree-sitter';
49
49
  import JS from 'tree-sitter-javascript';
50
+ import { ARRAY_METHOD_NOT_ANY_OF_PREDICATE, DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE, } from '../../ts-js-hoc-utils.js';
50
51
  const JS_GRAMMAR = JS;
51
52
  /** True when the file should be parsed with the JSX-extended query. */
52
53
  function isJsxFile(filePath) {
@@ -150,10 +151,13 @@ const JAVASCRIPT_SCOPE_QUERY = `
150
151
  ;; Those are filtered out emit-side in captures.ts via
151
152
  ;; isArrayMethodCallbackArrow (member-expression callee whose property
152
153
  ;; is a known Array method), so only the @declaration.const survives.
154
+ ;; Excludes common array methods (map, filter, reduce, etc.) to avoid
155
+ ;; false positives like \`const x = arr.map(a => ...)\`.
153
156
  (lexical_declaration
154
157
  (variable_declarator
155
158
  name: (identifier) @declaration.name
156
159
  value: (call_expression
160
+ function: (identifier)
157
161
  arguments: (arguments
158
162
  (arrow_function) @declaration.function))))
159
163
 
@@ -161,14 +165,36 @@ const JAVASCRIPT_SCOPE_QUERY = `
161
165
  (variable_declarator
162
166
  name: (identifier) @declaration.name
163
167
  value: (call_expression
168
+ function: (identifier)
164
169
  arguments: (arguments
165
170
  (function_expression) @declaration.function))))
166
171
 
172
+ (lexical_declaration
173
+ (variable_declarator
174
+ name: (identifier) @declaration.name
175
+ value: (call_expression
176
+ function: (member_expression
177
+ property: (property_identifier) @callee)
178
+ arguments: (arguments
179
+ (arrow_function) @declaration.function)))
180
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
181
+
182
+ (lexical_declaration
183
+ (variable_declarator
184
+ name: (identifier) @declaration.name
185
+ value: (call_expression
186
+ function: (member_expression
187
+ property: (property_identifier) @callee)
188
+ arguments: (arguments
189
+ (function_expression) @declaration.function)))
190
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
191
+
167
192
  (export_statement
168
193
  declaration: (lexical_declaration
169
194
  (variable_declarator
170
195
  name: (identifier) @declaration.name
171
196
  value: (call_expression
197
+ function: (identifier)
172
198
  arguments: (arguments
173
199
  (arrow_function) @declaration.function)))))
174
200
 
@@ -177,13 +203,37 @@ const JAVASCRIPT_SCOPE_QUERY = `
177
203
  (variable_declarator
178
204
  name: (identifier) @declaration.name
179
205
  value: (call_expression
206
+ function: (identifier)
180
207
  arguments: (arguments
181
208
  (function_expression) @declaration.function)))))
182
209
 
210
+ (export_statement
211
+ declaration: (lexical_declaration
212
+ (variable_declarator
213
+ name: (identifier) @declaration.name
214
+ value: (call_expression
215
+ function: (member_expression
216
+ property: (property_identifier) @callee)
217
+ arguments: (arguments
218
+ (arrow_function) @declaration.function))))
219
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
220
+
221
+ (export_statement
222
+ declaration: (lexical_declaration
223
+ (variable_declarator
224
+ name: (identifier) @declaration.name
225
+ value: (call_expression
226
+ function: (member_expression
227
+ property: (property_identifier) @callee)
228
+ arguments: (arguments
229
+ (function_expression) @declaration.function))))
230
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
231
+
183
232
  (variable_declaration
184
233
  (variable_declarator
185
234
  name: (identifier) @declaration.name
186
235
  value: (call_expression
236
+ function: (identifier)
187
237
  arguments: (arguments
188
238
  (arrow_function) @declaration.function))))
189
239
 
@@ -191,9 +241,64 @@ const JAVASCRIPT_SCOPE_QUERY = `
191
241
  (variable_declarator
192
242
  name: (identifier) @declaration.name
193
243
  value: (call_expression
244
+ function: (identifier)
194
245
  arguments: (arguments
195
246
  (function_expression) @declaration.function))))
196
247
 
248
+ (variable_declaration
249
+ (variable_declarator
250
+ name: (identifier) @declaration.name
251
+ value: (call_expression
252
+ function: (member_expression
253
+ property: (property_identifier) @callee)
254
+ arguments: (arguments
255
+ (arrow_function) @declaration.function)))
256
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
257
+
258
+ (variable_declaration
259
+ (variable_declarator
260
+ name: (identifier) @declaration.name
261
+ value: (call_expression
262
+ function: (member_expression
263
+ property: (property_identifier) @callee)
264
+ arguments: (arguments
265
+ (function_expression) @declaration.function)))
266
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
267
+
268
+ ;; HOC-wrapped default exports (JS parity with TS patterns in
269
+ ;; languages/typescript/query.ts). The emit phase rewrites
270
+ ;; @declaration.name to a file-derived name so wrapper helpers do not
271
+ ;; become the graph-visible symbol name.
272
+ ((export_statement
273
+ value: (call_expression
274
+ function: (identifier) @hoc
275
+ arguments: (arguments
276
+ (arrow_function) @declaration.function)))
277
+ ${DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE})
278
+
279
+ ((export_statement
280
+ value: (call_expression
281
+ function: (identifier) @hoc
282
+ arguments: (arguments
283
+ (function_expression) @declaration.function)))
284
+ ${DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE})
285
+
286
+ ((export_statement
287
+ value: (call_expression
288
+ function: (member_expression
289
+ property: (property_identifier) @callee)
290
+ arguments: (arguments
291
+ (arrow_function) @declaration.function)))
292
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
293
+
294
+ ((export_statement
295
+ value: (call_expression
296
+ function: (member_expression
297
+ property: (property_identifier) @callee)
298
+ arguments: (arguments
299
+ (function_expression) @declaration.function)))
300
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
301
+
197
302
  ;; Variable / constant declarations (non-function values).
198
303
  (lexical_declaration
199
304
  (variable_declarator
@@ -23,32 +23,6 @@
23
23
  * Pure given the input node. No I/O, no globals.
24
24
  */
25
25
  import type { SyntaxNode } from '../../utils/ast-helpers.js';
26
- /**
27
- * Array prototype higher-order methods whose result is a value, not a
28
- * function. A callback passed to one of these is an anonymous callback,
29
- * never a top-level function definition. Identifier-callee HOCs
30
- * (`forwardRef(...)`, `useCallback(...)`, custom factories) are
31
- * deliberately NOT listed — they keep their `Function` classification.
32
- *
33
- * Trade-off (unchanged from before #1876): a custom *fluent-API* member
34
- * call with a callback whose method name is not in this set
35
- * (`qb.where(x => …)`) still classifies as `Function`. There is no clean
36
- * syntactic line beyond the well-known Array surface, so the set is
37
- * intentionally closed and easy to extend.
38
- *
39
- * Receiver-blind, by design: the match keys on the method NAME only, never
40
- * the receiver type (tree-sitter has no type information here). So an in-set
41
- * name on a NON-array receiver — `Map`/`Set` `.forEach`, an RxJS
42
- * `observable.map(…)`, a query builder `.sort(…)`, a lodash chain
43
- * `.filter(…)` — is ALSO treated as a callback and has its
44
- * `@declaration.function` dropped. This is an accepted limitation, not a
45
- * regression: those bindings hold the call's *result value*, not a callable,
46
- * so a value def is the correct classification anyway. The only genuine loss
47
- * is a bespoke DSL whose in-set-named method returns something callable —
48
- * rare enough to accept rather than guard with type inference. Pinned by the
49
- * "in-set method on a non-array receiver" case in `*-captures.test.ts`.
50
- */
51
- export declare const ARRAY_CALLBACK_METHODS: ReadonlySet<string>;
52
26
  /**
53
27
  * True when `node` (an `arrow_function` / `function_expression`) is the
54
28
  * callback argument of an array higher-order-method call, i.e. the
@@ -60,8 +34,8 @@ export declare const ARRAY_CALLBACK_METHODS: ReadonlySet<string>;
60
34
  * (`forwardRef(() => …)` — callee is an `identifier`, not a
61
35
  * `member_expression`), so neither is ever suppressed.
62
36
  *
63
- * Intentional non-suppressing gaps (preserve current behavior, no
64
- * regression): parenthesized callee `(arr.map)(cb)` (`parenthesized_expression`)
65
- * and computed callee `arr['map'](cb)` (`subscript_expression`).
37
+ * The helper itself only handles direct `member_expression` callees. Broader
38
+ * shapes like `(arr.map)(cb)` and `arr['map'](cb)` are now filtered at the
39
+ * query layer, so this emit-side check stays focused on the direct fallback.
66
40
  */
67
41
  export declare function isArrayMethodCallbackArrow(node: SyntaxNode): boolean;
@@ -22,46 +22,7 @@
22
22
  *
23
23
  * Pure given the input node. No I/O, no globals.
24
24
  */
25
- /**
26
- * Array prototype higher-order methods whose result is a value, not a
27
- * function. A callback passed to one of these is an anonymous callback,
28
- * never a top-level function definition. Identifier-callee HOCs
29
- * (`forwardRef(...)`, `useCallback(...)`, custom factories) are
30
- * deliberately NOT listed — they keep their `Function` classification.
31
- *
32
- * Trade-off (unchanged from before #1876): a custom *fluent-API* member
33
- * call with a callback whose method name is not in this set
34
- * (`qb.where(x => …)`) still classifies as `Function`. There is no clean
35
- * syntactic line beyond the well-known Array surface, so the set is
36
- * intentionally closed and easy to extend.
37
- *
38
- * Receiver-blind, by design: the match keys on the method NAME only, never
39
- * the receiver type (tree-sitter has no type information here). So an in-set
40
- * name on a NON-array receiver — `Map`/`Set` `.forEach`, an RxJS
41
- * `observable.map(…)`, a query builder `.sort(…)`, a lodash chain
42
- * `.filter(…)` — is ALSO treated as a callback and has its
43
- * `@declaration.function` dropped. This is an accepted limitation, not a
44
- * regression: those bindings hold the call's *result value*, not a callable,
45
- * so a value def is the correct classification anyway. The only genuine loss
46
- * is a bespoke DSL whose in-set-named method returns something callable —
47
- * rare enough to accept rather than guard with type inference. Pinned by the
48
- * "in-set method on a non-array receiver" case in `*-captures.test.ts`.
49
- */
50
- export const ARRAY_CALLBACK_METHODS = new Set([
51
- 'map',
52
- 'filter',
53
- 'find',
54
- 'findIndex',
55
- 'findLast',
56
- 'findLastIndex',
57
- 'forEach',
58
- 'reduce',
59
- 'reduceRight',
60
- 'some',
61
- 'every',
62
- 'flatMap',
63
- 'sort',
64
- ]);
25
+ import { ARRAY_CALLBACK_METHODS } from '../../ts-js-hoc-utils.js';
65
26
  /**
66
27
  * True when `node` (an `arrow_function` / `function_expression`) is the
67
28
  * callback argument of an array higher-order-method call, i.e. the
@@ -73,9 +34,9 @@ export const ARRAY_CALLBACK_METHODS = new Set([
73
34
  * (`forwardRef(() => …)` — callee is an `identifier`, not a
74
35
  * `member_expression`), so neither is ever suppressed.
75
36
  *
76
- * Intentional non-suppressing gaps (preserve current behavior, no
77
- * regression): parenthesized callee `(arr.map)(cb)` (`parenthesized_expression`)
78
- * and computed callee `arr['map'](cb)` (`subscript_expression`).
37
+ * The helper itself only handles direct `member_expression` callees. Broader
38
+ * shapes like `(arr.map)(cb)` and `arr['map'](cb)` are now filtered at the
39
+ * query layer, so this emit-side check stays focused on the direct fallback.
79
40
  */
80
41
  export function isArrayMethodCallbackArrow(node) {
81
42
  const args = node.parent;
@@ -33,6 +33,7 @@ import { computeTsArityMetadata } from './arity-metadata.js';
33
33
  import { isArrayMethodCallbackArrow } from './array-callback.js';
34
34
  import { getTreeSitterBufferSize } from '../../constants.js';
35
35
  import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
36
+ import { deriveDefaultExportHocName, isBlockedDefaultExportHoc, isDefaultExportHocFunctionNode, } from '../../ts-js-hoc-utils.js';
36
37
  /** tree-sitter-typescript node types for function-like scopes that may
37
38
  * carry a synthesized `this` binding. Kept in sync with the
38
39
  * `@scope.function` patterns in `query.ts`. */
@@ -242,6 +243,15 @@ export function emitTsScopeCaptures(sourceText, filePath, cachedTree) {
242
243
  if (arrowNode !== null && isArrayMethodCallbackArrow(arrowNode)) {
243
244
  continue;
244
245
  }
246
+ if (arrowNode !== null && isBlockedDefaultExportHoc(arrowNode)) {
247
+ continue;
248
+ }
249
+ }
250
+ if (fnDeclAnchor !== undefined) {
251
+ const fnNode = findFunctionNode(tree.rootNode, fnDeclAnchor.range, groupedNodes['@declaration.function']);
252
+ if (fnNode !== null && isDefaultExportHocFunctionNode(fnNode)) {
253
+ grouped['@declaration.name'] = syntheticCapture('@declaration.name', fnNode, deriveDefaultExportHocName(filePath));
254
+ }
245
255
  }
246
256
  // Synthesize arity metadata on function-like declaration anchors
247
257
  // before pushing the match. The registry uses these to narrow
@@ -52,6 +52,7 @@
52
52
  */
53
53
  import Parser from 'tree-sitter';
54
54
  import TS from 'tree-sitter-typescript';
55
+ import { ARRAY_METHOD_NOT_ANY_OF_PREDICATE, DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE, } from '../../ts-js-hoc-utils.js';
55
56
  // tree-sitter-typescript exports both `typescript` and `tsx` grammars on
56
57
  // the default export. The package's `.d.ts` types the default export
57
58
  // loosely; we narrow at the use site. The two grammars are NOT
@@ -273,10 +274,16 @@ const TYPESCRIPT_SCOPE_QUERY = `
273
274
  ;; via \`(filePath, type, qualifiedName)\` — second wins. Acceptable;
274
275
  ;; multi-arrow-callback APIs are rare (\`new Promise(executor)\` is the
275
276
  ;; main one and takes a single executor).
277
+ ;;
278
+ ;; NOTE: Split into identifier vs member_expression patterns. Member
279
+ ;; expressions are filtered with a blocklist of common array methods
280
+ ;; (map, filter, reduce, etc.) to avoid false positives like
281
+ ;; \`const x = arr.map(a => ...)\` being classified as Function.
276
282
  (lexical_declaration
277
283
  (variable_declarator
278
284
  name: (identifier) @declaration.name
279
285
  value: (call_expression
286
+ function: (identifier)
280
287
  arguments: (arguments
281
288
  (arrow_function) @declaration.function))))
282
289
 
@@ -284,13 +291,35 @@ const TYPESCRIPT_SCOPE_QUERY = `
284
291
  (variable_declarator
285
292
  name: (identifier) @declaration.name
286
293
  value: (call_expression
294
+ function: (identifier)
287
295
  arguments: (arguments
288
296
  (function_expression) @declaration.function))))
289
297
 
298
+ (lexical_declaration
299
+ (variable_declarator
300
+ name: (identifier) @declaration.name
301
+ value: (call_expression
302
+ function: (member_expression
303
+ property: (property_identifier) @callee)
304
+ arguments: (arguments
305
+ (arrow_function) @declaration.function)))
306
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
307
+
308
+ (lexical_declaration
309
+ (variable_declarator
310
+ name: (identifier) @declaration.name
311
+ value: (call_expression
312
+ function: (member_expression
313
+ property: (property_identifier) @callee)
314
+ arguments: (arguments
315
+ (function_expression) @declaration.function)))
316
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
317
+
290
318
  (variable_declaration
291
319
  (variable_declarator
292
320
  name: (identifier) @declaration.name
293
321
  value: (call_expression
322
+ function: (identifier)
294
323
  arguments: (arguments
295
324
  (arrow_function) @declaration.function))))
296
325
 
@@ -298,9 +327,64 @@ const TYPESCRIPT_SCOPE_QUERY = `
298
327
  (variable_declarator
299
328
  name: (identifier) @declaration.name
300
329
  value: (call_expression
330
+ function: (identifier)
301
331
  arguments: (arguments
302
332
  (function_expression) @declaration.function))))
303
333
 
334
+ (variable_declaration
335
+ (variable_declarator
336
+ name: (identifier) @declaration.name
337
+ value: (call_expression
338
+ function: (member_expression
339
+ property: (property_identifier) @callee)
340
+ arguments: (arguments
341
+ (arrow_function) @declaration.function)))
342
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
343
+
344
+ (variable_declaration
345
+ (variable_declarator
346
+ name: (identifier) @declaration.name
347
+ value: (call_expression
348
+ function: (member_expression
349
+ property: (property_identifier) @callee)
350
+ arguments: (arguments
351
+ (function_expression) @declaration.function)))
352
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
353
+
354
+ ;; HOC-wrapped default exports: \`export default defineEventHandler(async (e) => { ... })\`.
355
+ ;; The emit phase rewrites @declaration.name to a file-derived name so
356
+ ;; wrappers like \`defineEventHandler\` / \`React.memo\` do not collapse
357
+ ;; unrelated modules onto the same symbol name.
358
+ ((export_statement
359
+ value: (call_expression
360
+ function: (identifier) @hoc
361
+ arguments: (arguments
362
+ (arrow_function) @declaration.function)))
363
+ ${DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE})
364
+
365
+ ((export_statement
366
+ value: (call_expression
367
+ function: (identifier) @hoc
368
+ arguments: (arguments
369
+ (function_expression) @declaration.function)))
370
+ ${DEFAULT_EXPORT_IDENTIFIER_NOT_ANY_OF_PREDICATE})
371
+
372
+ ((export_statement
373
+ value: (call_expression
374
+ function: (member_expression
375
+ property: (property_identifier) @callee)
376
+ arguments: (arguments
377
+ (arrow_function) @declaration.function)))
378
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
379
+
380
+ ((export_statement
381
+ value: (call_expression
382
+ function: (member_expression
383
+ property: (property_identifier) @callee)
384
+ arguments: (arguments
385
+ (function_expression) @declaration.function)))
386
+ ${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
387
+
304
388
  ;; Method definitions — regular + private (#field) methods.
305
389
  (method_definition
306
390
  name: (property_identifier) @declaration.name) @declaration.method
@@ -26,6 +26,7 @@ import { typescriptVariableConfig, javascriptVariableConfig, } from '../variable
26
26
  import { createCallExtractor } from '../call-extractors/generic.js';
27
27
  import { typescriptCallConfig, javascriptCallConfig, } from '../call-extractors/configs/typescript-javascript.js';
28
28
  import { createHeritageExtractor } from '../heritage-extractors/generic.js';
29
+ import { ARRAY_METHOD_HOC_BLOCKLIST_SET, DEFAULT_EXPORT_IDENTIFIER_BLOCKLIST_SET, deriveDefaultExportHocName, } from '../ts-js-hoc-utils.js';
29
30
  import { emitTsScopeCaptures, interpretTsImport, interpretTsTypeBinding, tsBindingScopeFor, tsImportOwningScope, tsReceiverBinding, typescriptMergeBindings, typescriptArityCompatibility, resolveTsImportTarget, } from './typescript/index.js';
30
31
  import { emitJsScopeCaptures, interpretJsImport, interpretJsTypeBinding, jsBindingScopeFor, jsImportOwningScope, jsReceiverBinding, jsMergeBindings, jsArityCompatibility, } from './javascript/index.js';
31
32
  /**
@@ -54,7 +55,7 @@ import { emitJsScopeCaptures, interpretJsImport, interpretJsTypeBinding, jsBindi
54
55
  * findEnclosingFunctionId then continues up to the next named ancestor
55
56
  * (or to the file).
56
57
  */
57
- const tsExtractFunctionName = (node) => {
58
+ const tsExtractFunctionName = (node, filePath) => {
58
59
  if (node.type !== 'arrow_function' && node.type !== 'function_expression')
59
60
  return null;
60
61
  const parent = node.parent;
@@ -99,27 +100,56 @@ const tsExtractFunctionName = (node) => {
99
100
  // `arguments`, grandparent is `call_expression`, great-grandparent is
100
101
  // `variable_declarator`. Walk the chain up and take the variable's name
101
102
  // — the meaningful identifier the developer wrote on the LHS. Mirrors
102
- // the four registry-primary patterns in `typescript/query.ts`. The
103
- // wrapping callee (`forwardRef`, `memo`, `React.memo`, `useCallback`,
104
- // user-defined HOCs) is intentionally NOT constrained: any function
105
- // call whose result is bound to a const and whose first/positional
106
- // argument is an arrow takes the const's name. Chained array-method
107
- // calls (`const x = arr.find((y) => p(y))`) match too and produce a
108
- // mostly-harmless `Function:x` (consumed as a value, never invoked),
109
- // accepted as a small false-positive cost vs. the much larger gain of
110
- // capturing the React UI-component idiom.
103
+ // the four registry-primary patterns in `typescript/query.ts`.
104
+ //
105
+ // NOTE: Excludes common array methods (map, filter, reduce, etc.) to avoid
106
+ // false positives like `const x = arr.map(a => ...)` being classified as
107
+ // Function when it's actually a Const holding an array.
111
108
  if (parent.type === 'arguments') {
112
109
  const callExpr = parent.parent;
113
110
  if (!callExpr || callExpr.type !== 'call_expression') {
114
111
  return { funcName: null, label: 'Function' };
115
112
  }
113
+ // Check if callee is a member_expression calling an array method
114
+ const callee = callExpr.childForFieldName?.('function');
115
+ if (callee?.type === 'member_expression') {
116
+ const property = callee.childForFieldName?.('property');
117
+ if (property?.type === 'property_identifier' &&
118
+ ARRAY_METHOD_HOC_BLOCKLIST_SET.has(property.text)) {
119
+ return { funcName: null, label: 'Function' };
120
+ }
121
+ }
116
122
  const declarator = callExpr.parent;
117
- if (!declarator || declarator.type !== 'variable_declarator') {
123
+ // Existing path: const X = HOC(arrow)
124
+ if (declarator?.type === 'variable_declarator') {
125
+ const nameNode = declarator.childForFieldName?.('name');
126
+ if (nameNode?.type === 'identifier') {
127
+ return { funcName: nameNode.text, label: 'Function' };
128
+ }
118
129
  return { funcName: null, label: 'Function' };
119
130
  }
120
- const nameNode = declarator.childForFieldName?.('name');
121
- if (nameNode?.type === 'identifier') {
122
- return { funcName: nameNode.text, label: 'Function' };
131
+ // export default HOC(arrow) — name it from the file, not the wrapper.
132
+ // This keeps route handlers and wrapped defaults navigable without
133
+ // collapsing every file onto names like `memo` or `defineEventHandler`.
134
+ if (declarator?.type === 'export_statement') {
135
+ if (callee?.type === 'identifier') {
136
+ if (DEFAULT_EXPORT_IDENTIFIER_BLOCKLIST_SET.has(callee.text)) {
137
+ return { funcName: null, label: 'Function' };
138
+ }
139
+ return {
140
+ funcName: filePath ? deriveDefaultExportHocName(filePath) : null,
141
+ label: 'Function',
142
+ };
143
+ }
144
+ // Member-expression callees like React.memo keep the same file-derived
145
+ // name, with array-like helpers excluded above.
146
+ if (callee?.type === 'member_expression') {
147
+ return {
148
+ funcName: filePath ? deriveDefaultExportHocName(filePath) : null,
149
+ label: 'Function',
150
+ };
151
+ }
152
+ return { funcName: null, label: 'Function' };
123
153
  }
124
154
  return { funcName: null, label: 'Function' };
125
155
  }
@@ -50,7 +50,7 @@ export interface MethodExtractor {
50
50
  * unwrapping, Swift init/deinit, Rust impl_item) provide this hook
51
51
  * to replace the generic name-field lookup.
52
52
  * Return null to fall through to the generic extractor. */
53
- extractFunctionName?(node: SyntaxNode): {
53
+ extractFunctionName?(node: SyntaxNode, filePath?: string): {
54
54
  funcName: string | null;
55
55
  label: import('../../_shared/index.js').NodeLabel;
56
56
  } | null;
@@ -84,7 +84,7 @@ export interface MethodExtractionConfig {
84
84
  extractPrimaryConstructor?: (ownerNode: SyntaxNode, context: MethodExtractorContext) => MethodInfo | null;
85
85
  /** Extract function name + label from an AST node during parent-walk.
86
86
  * Passed through to the MethodExtractor by createMethodExtractor. */
87
- extractFunctionName?: (node: SyntaxNode) => {
87
+ extractFunctionName?: (node: SyntaxNode, filePath?: string) => {
88
88
  funcName: string | null;
89
89
  label: import('../../_shared/index.js').NodeLabel;
90
90
  } | null;