gitnexus 1.6.6-rc.156 → 1.6.6-rc.158

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 (46) hide show
  1. package/dist/cli/analyze.js +16 -0
  2. package/dist/core/ingestion/call-processor.d.ts +2 -13
  3. package/dist/core/ingestion/call-processor.js +2 -79
  4. package/dist/core/ingestion/export-detection.d.ts +2 -1
  5. package/dist/core/ingestion/export-detection.js +2 -1
  6. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +0 -6
  7. package/dist/core/ingestion/field-extractors/configs/dart.js +61 -40
  8. package/dist/core/ingestion/field-extractors/configs/jvm.js +43 -4
  9. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +12 -1
  10. package/dist/core/ingestion/field-extractors/configs/swift.js +30 -5
  11. package/dist/core/ingestion/filesystem-walker.d.ts +0 -9
  12. package/dist/core/ingestion/filesystem-walker.js +0 -11
  13. package/dist/core/ingestion/languages/c/import-decomposer.d.ts +9 -0
  14. package/dist/core/ingestion/languages/c/import-decomposer.js +15 -0
  15. package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +7 -0
  16. package/dist/core/ingestion/languages/cpp/import-decomposer.js +12 -0
  17. package/dist/core/ingestion/languages/dart/query.js +40 -0
  18. package/dist/core/ingestion/languages/kotlin/captures.js +105 -0
  19. package/dist/core/ingestion/languages/kotlin/query.js +30 -0
  20. package/dist/core/ingestion/languages/python/captures.js +4 -3
  21. package/dist/core/ingestion/method-extractors/configs/jvm.js +25 -10
  22. package/dist/core/ingestion/method-extractors/configs/swift.js +11 -4
  23. package/dist/core/ingestion/pipeline-phases/parse-impl.js +0 -10
  24. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  25. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +10 -0
  26. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +15 -0
  27. package/dist/core/ingestion/tree-sitter-queries.d.ts +4 -4
  28. package/dist/core/ingestion/tree-sitter-queries.js +125 -6
  29. package/dist/core/ingestion/utils/ast-helpers.d.ts +36 -0
  30. package/dist/core/ingestion/utils/ast-helpers.js +68 -0
  31. package/dist/core/ingestion/variable-extractors/configs/c-cpp.js +49 -0
  32. package/dist/core/ingestion/variable-extractors/configs/dart.js +76 -53
  33. package/dist/core/ingestion/variable-extractors/configs/jvm.js +35 -8
  34. package/dist/core/ingestion/workers/parse-worker.js +46 -4
  35. package/dist/core/ingestion/workers/worker-pool.js +25 -2
  36. package/dist/core/lbug/lbug-adapter.js +29 -10
  37. package/dist/core/lbug/pool-adapter.js +38 -7
  38. package/dist/core/lbug/query-result-utils.d.ts +19 -0
  39. package/dist/core/lbug/query-result-utils.js +28 -0
  40. package/dist/mcp/local/local-backend.js +14 -1
  41. package/dist/types/pipeline.d.ts +6 -3
  42. package/package.json +1 -2
  43. package/dist/core/ingestion/ast-cache.d.ts +0 -26
  44. package/dist/core/ingestion/ast-cache.js +0 -48
  45. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +0 -22
  46. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +0 -92
@@ -41,6 +41,22 @@ const writeFatalToStderr = (label, err) => {
41
41
  realStderrWrite(`\n ${label}: ${message}\n`);
42
42
  if (isErr && err.stack)
43
43
  realStderrWrite(`${err.stack}\n`);
44
+ // Walk and print the `cause` chain. The phase runner wraps the underlying
45
+ // failure as `new Error("Phase 'X' failed: …", { cause })`, so the original
46
+ // error (e.g. a WorkerPoolDispatchError carrying the worker-side stack from
47
+ // #2068) is only reachable via `.cause`. Without this the user sees the
48
+ // wrapper's main-thread stack and never the real frame. `cause.stack` already
49
+ // begins with the cause's message, so we print the stack alone (not message +
50
+ // stack) to avoid repeating it. Depth-bounded so a cyclic `cause` can't loop
51
+ // (the phase runner wraps one level; the bound leaves headroom for future
52
+ // nesting); uses realStderrWrite so the redirected console.error's ANSI
53
+ // clear-line wrapping can't erase it (#1169).
54
+ const MAX_CAUSE_DEPTH = 5;
55
+ let cause = isErr ? err.cause : undefined;
56
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && cause instanceof Error; depth++) {
57
+ realStderrWrite(`\n Caused by: ${cause.stack ?? cause.message}\n`);
58
+ cause = cause.cause;
59
+ }
44
60
  };
45
61
  let fatalHandlersInstalled = false;
46
62
  /**
@@ -9,14 +9,12 @@
9
9
  *
10
10
  * - `processRoutesFromExtracted` — CALLS edges from framework routes
11
11
  * (e.g. Laravel) to their controller methods.
12
- * - `processNextjsFetchRoutes` / `extractFetchCallsFromFiles` /
13
- * `extractConsumerAccessedKeys` — FETCHES edges from `fetch()` calls to
14
- * Next.js Route nodes.
12
+ * - `processNextjsFetchRoutes` / `extractConsumerAccessedKeys` — FETCHES edges
13
+ * from `fetch()` calls to Next.js Route nodes.
15
14
  * - `buildExportedTypeMapFromGraph` — exported symbol → return/declared type
16
15
  * map, consumed by the cross-file enrichment pass.
17
16
  */
18
17
  import { KnowledgeGraph } from '../graph/types.js';
19
- import { ASTCache } from './ast-cache.js';
20
18
  import type { SemanticModel, SymbolTableReader } from './model/index.js';
21
19
  import type { ExtractedRoute, ExtractedFetchCall } from './workers/parse-worker.js';
22
20
  /** Per-file resolved type bindings for exported symbols.
@@ -76,12 +74,3 @@ export declare const extractConsumerAccessedKeys: (content: string) => string[];
76
74
  */
77
75
  export declare const processNextjsFetchRoutes: (graph: KnowledgeGraph, fetchCalls: ExtractedFetchCall[], routeRegistry: Map<string, string>, // routeURL → handlerFilePath
78
76
  consumerContents?: Map<string, string>) => void;
79
- /**
80
- * Extract fetch() calls from source files (sequential path).
81
- * Workers handle this via tree-sitter captures in parse-worker; this function
82
- * provides the same extraction for the sequential fallback path.
83
- */
84
- export declare const extractFetchCallsFromFiles: (files: {
85
- path: string;
86
- content: string;
87
- }[], astCache: ASTCache) => Promise<ExtractedFetchCall[]>;
@@ -9,20 +9,13 @@
9
9
  *
10
10
  * - `processRoutesFromExtracted` — CALLS edges from framework routes
11
11
  * (e.g. Laravel) to their controller methods.
12
- * - `processNextjsFetchRoutes` / `extractFetchCallsFromFiles` /
13
- * `extractConsumerAccessedKeys` — FETCHES edges from `fetch()` calls to
14
- * Next.js Route nodes.
12
+ * - `processNextjsFetchRoutes` / `extractConsumerAccessedKeys` — FETCHES edges
13
+ * from `fetch()` calls to Next.js Route nodes.
15
14
  * - `buildExportedTypeMapFromGraph` — exported symbol → return/declared type
16
15
  * map, consumed by the cross-file enrichment pass.
17
16
  */
18
- import Parser from 'tree-sitter';
19
- import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
20
- import { getProvider } from './languages/index.js';
21
17
  import { generateId } from '../../lib/utils.js';
22
- import { getLanguageFromFilename } from '../../_shared/index.js';
23
18
  import { yieldToEventLoop } from './utils/event-loop.js';
24
- import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
25
- import { getTreeSitterBufferSize } from './constants.js';
26
19
  import { normalizeFetchURL, routeMatches } from './route-extractors/nextjs.js';
27
20
  import { extractReturnTypeName } from './type-extractors/shared.js';
28
21
  const MAX_EXPORTS_PER_FILE = 500;
@@ -408,73 +401,3 @@ consumerContents) => {
408
401
  }
409
402
  }
410
403
  };
411
- /**
412
- * Extract fetch() calls from source files (sequential path).
413
- * Workers handle this via tree-sitter captures in parse-worker; this function
414
- * provides the same extraction for the sequential fallback path.
415
- */
416
- export const extractFetchCallsFromFiles = async (files, astCache) => {
417
- const parser = await loadParser();
418
- const result = [];
419
- for (const file of files) {
420
- const language = getLanguageFromFilename(file.path);
421
- if (!language)
422
- continue;
423
- if (!isLanguageAvailable(language))
424
- continue;
425
- const provider = getProvider(language);
426
- const queryStr = provider.treeSitterQueries;
427
- if (!queryStr)
428
- continue;
429
- await loadLanguage(language, file.path);
430
- let tree = astCache.get(file.path);
431
- if (!tree) {
432
- const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
433
- try {
434
- tree = parseSourceSafe(parser, parseContent, undefined, {
435
- bufferSize: getTreeSitterBufferSize(parseContent),
436
- });
437
- }
438
- catch {
439
- continue;
440
- }
441
- astCache.set(file.path, tree);
442
- }
443
- let matches;
444
- try {
445
- const lang = parser.getLanguage();
446
- const query = new Parser.Query(lang, queryStr);
447
- matches = query.matches(tree.rootNode);
448
- }
449
- catch {
450
- continue;
451
- }
452
- for (const match of matches) {
453
- const captureMap = {};
454
- match.captures.forEach((c) => (captureMap[c.name] = c.node));
455
- if (captureMap['route.fetch']) {
456
- const urlNode = captureMap['route.url'] ?? captureMap['route.template_url'];
457
- if (urlNode) {
458
- result.push({
459
- filePath: file.path,
460
- fetchURL: urlNode.text,
461
- lineNumber: captureMap['route.fetch'].startPosition.row,
462
- });
463
- }
464
- }
465
- else if (captureMap['http_client'] && captureMap['http_client.url']) {
466
- const method = captureMap['http_client.method']?.text;
467
- const url = captureMap['http_client.url'].text;
468
- const HTTP_CLIENT_ONLY = new Set(['head', 'options', 'request', 'ajax']);
469
- if (method && HTTP_CLIENT_ONLY.has(method) && url.startsWith('/')) {
470
- result.push({
471
- filePath: file.path,
472
- fetchURL: url,
473
- lineNumber: captureMap['http_client'].startPosition.row,
474
- });
475
- }
476
- }
477
- }
478
- }
479
- return result;
480
- };
@@ -4,7 +4,8 @@
4
4
  * Determines whether a symbol (function, class, etc.) is exported/public
5
5
  * in its language. This is a pure function — safe for use in worker threads.
6
6
  *
7
- * Shared between parse-worker.ts (worker pool) and parsing-processor.ts (sequential fallback).
7
+ * Used by the language providers during worker parsing (parse-worker.ts) — the
8
+ * sole parse path. (Sequential parsing was removed.)
8
9
  */
9
10
  import { type SyntaxNode } from './utils/ast-helpers.js';
10
11
  /** Handler type: given a node and symbol name, return true if the symbol is exported/public. */
@@ -4,7 +4,8 @@
4
4
  * Determines whether a symbol (function, class, etc.) is exported/public
5
5
  * in its language. This is a pure function — safe for use in worker threads.
6
6
  *
7
- * Shared between parse-worker.ts (worker pool) and parsing-processor.ts (sequential fallback).
7
+ * Used by the language providers during worker parsing (parse-worker.ts) — the
8
+ * sole parse path. (Sequential parsing was removed.)
8
9
  */
9
10
  import { findSiblingChild } from './utils/ast-helpers.js';
10
11
  // ============================================================================
@@ -1,8 +1,2 @@
1
1
  import type { FieldExtractionConfig } from '../generic.js';
2
- /**
3
- * Dart field extraction config.
4
- *
5
- * Dart class fields appear as declaration nodes inside class_body.
6
- * Visibility is convention-based: underscore prefix = private.
7
- */
8
2
  export declare const dartConfig: FieldExtractionConfig;
@@ -5,41 +5,68 @@ import { extractSimpleTypeName } from '../../type-extractors/shared.js';
5
5
  /**
6
6
  * Dart field extraction config.
7
7
  *
8
- * Dart class fields appear as declaration nodes inside class_body.
8
+ * Dart class fields appear as `declaration` nodes inside `class_body`.
9
+ * Two shapes carry the field name(s):
10
+ * - instance / plain fields → `initialized_identifier_list`
11
+ * (`int z = 0;`, `int a = 1, b = 2;`)
12
+ * - `static const` / `static final` / `const` fields → `static_final_declaration_list`
13
+ * (`static const a = 1;`, `static final String b = 'x', c = 'y';`)
14
+ * Both shapes may declare SEVERAL fields in one declaration, so name extraction
15
+ * is multi-name (`extractNames`). The structure query (`DART_QUERIES`) emits one
16
+ * `@definition.property` per name for both shapes; this config enriches each.
17
+ *
9
18
  * Visibility is convention-based: underscore prefix = private.
10
19
  */
20
+ /** All field names declared by a `declaration` node, across both Dart shapes. */
21
+ function extractDartFieldNames(node) {
22
+ const names = [];
23
+ for (let i = 0; i < node.namedChildCount; i++) {
24
+ const child = node.namedChild(i);
25
+ if (!child)
26
+ continue;
27
+ // instance / plain fields: initialized_identifier_list > initialized_identifier > identifier
28
+ if (child.type === 'initialized_identifier_list') {
29
+ for (let j = 0; j < child.namedChildCount; j++) {
30
+ const init = child.namedChild(j);
31
+ if (init?.type === 'initialized_identifier') {
32
+ const ident = init.firstNamedChild;
33
+ if (ident?.type === 'identifier')
34
+ names.push(ident.text);
35
+ }
36
+ }
37
+ }
38
+ // static const / final fields: static_final_declaration_list > static_final_declaration > identifier
39
+ if (child.type === 'static_final_declaration_list') {
40
+ for (let j = 0; j < child.namedChildCount; j++) {
41
+ const decl = child.namedChild(j);
42
+ if (decl?.type === 'static_final_declaration') {
43
+ const ident = decl.firstNamedChild;
44
+ if (ident?.type === 'identifier')
45
+ names.push(ident.text);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return names;
51
+ }
11
52
  export const dartConfig = {
12
53
  language: SupportedLanguages.Dart,
13
54
  typeDeclarationNodes: ['class_definition'],
14
55
  fieldNodeTypes: ['declaration'],
15
56
  bodyNodeTypes: ['class_body'],
16
57
  defaultVisibility: 'public',
58
+ // One AST `declaration` node may declare several fields (`int a, b;`,
59
+ // `static final String b = 'x', c = 'y';`), so use the multi-name path.
17
60
  extractName(node) {
18
- // declaration > initialized_identifier_list > initialized_identifier > identifier
19
- for (let i = 0; i < node.namedChildCount; i++) {
20
- const child = node.namedChild(i);
21
- if (child?.type === 'initialized_identifier_list') {
22
- for (let j = 0; j < child.namedChildCount; j++) {
23
- const init = child.namedChild(j);
24
- if (init?.type === 'initialized_identifier') {
25
- const ident = init.firstNamedChild;
26
- if (ident?.type === 'identifier')
27
- return ident.text;
28
- }
29
- }
30
- }
31
- if (child?.type === 'initialized_identifier') {
32
- const ident = child.firstNamedChild;
33
- if (ident?.type === 'identifier')
34
- return ident.text;
35
- }
36
- }
37
- // fallback: look for direct identifier
38
- const name = node.childForFieldName('name');
39
- return name?.text;
61
+ return extractDartFieldNames(node)[0];
62
+ },
63
+ extractNames(node) {
64
+ return extractDartFieldNames(node);
40
65
  },
41
66
  extractType(node) {
42
- // declaration > type_identifier (first named child usually)
67
+ // declaration > type_identifier (the type annotation, present for both the
68
+ // instance-field shape and `static final String b = …`). `static const a = 1;`
69
+ // has no annotation → undefined (untyped).
43
70
  for (let i = 0; i < node.namedChildCount; i++) {
44
71
  const child = node.namedChild(i);
45
72
  if (child && (child.type === 'type_identifier' || child.type === 'function_type')) {
@@ -48,28 +75,22 @@ export const dartConfig = {
48
75
  }
49
76
  return undefined;
50
77
  },
78
+ // Per-name: Dart convention is underscore-prefixed = private. A single
79
+ // declaration can mix visibilities (`static const _p = 1, q = 2;`), so the
80
+ // decision is keyed on the individual field name.
81
+ extractVisibilityForName(_node, name) {
82
+ return name.startsWith('_') ? 'private' : 'public';
83
+ },
51
84
  extractVisibility(node) {
52
- // Dart uses _ prefix for private
53
- // Walk to find the identifier name
54
- for (let i = 0; i < node.namedChildCount; i++) {
55
- const child = node.namedChild(i);
56
- if (child?.type === 'initialized_identifier_list') {
57
- for (let j = 0; j < child.namedChildCount; j++) {
58
- const init = child.namedChild(j);
59
- if (init?.type === 'initialized_identifier') {
60
- const ident = init.firstNamedChild;
61
- if (ident?.text?.startsWith('_'))
62
- return 'private';
63
- }
64
- }
65
- }
66
- }
67
- return 'public';
85
+ const first = extractDartFieldNames(node)[0];
86
+ return first?.startsWith('_') ? 'private' : 'public';
68
87
  },
69
88
  isStatic(node) {
70
89
  return hasKeyword(node, 'static');
71
90
  },
72
91
  isReadonly(node) {
92
+ // `final` / `const` (both `final_builtin`/`const_builtin` nodes whose text
93
+ // is `final`/`const`) are read-only.
73
94
  return hasKeyword(node, 'final') || hasKeyword(node, 'const');
74
95
  },
75
96
  };
@@ -60,12 +60,49 @@ export const javaConfig = {
60
60
  // Kotlin
61
61
  // ---------------------------------------------------------------------------
62
62
  const KOTLIN_VIS = new Set(['public', 'private', 'protected', 'internal']);
63
+ /** A property_declaration is a companion-object member when its nearest
64
+ * class-body ancestor is the body of a companion_object (F52, issue #1919).
65
+ * Companion members are addressed statically through the enclosing class
66
+ * (`C.TAG`), so they are marked static. */
67
+ function isInsideKotlinCompanion(node) {
68
+ for (let cur = node.parent; cur !== null; cur = cur.parent) {
69
+ if (cur.type === 'class_body')
70
+ return cur.parent?.type === 'companion_object';
71
+ if (cur.type === 'companion_object')
72
+ return true;
73
+ }
74
+ return false;
75
+ }
63
76
  export const kotlinConfig = {
64
77
  language: SupportedLanguages.Kotlin,
65
- typeDeclarationNodes: ['class_declaration', 'object_declaration'],
78
+ // F52: include companion_object so a companion property's innermost
79
+ // class-container owner (findEnclosingClassNode returns the companion_object)
80
+ // is recognized as a type declaration and its nested class_body is walked.
81
+ // The structure query already creates the Property node and owns it on the
82
+ // ENCLOSING class for anonymous companions / on the named companion Class —
83
+ // this entry only drives field-metadata enrichment, so it does NOT change
84
+ // ownership or emit a second node (no double-count).
85
+ typeDeclarationNodes: ['class_declaration', 'object_declaration', 'companion_object'],
66
86
  fieldNodeTypes: ['property_declaration'],
67
87
  bodyNodeTypes: ['class_body'],
68
88
  defaultVisibility: 'public',
89
+ // F52: an anonymous `companion object { ... }` has no name child, so the
90
+ // generic factory's `childForFieldName('name')` owner lookup is empty and
91
+ // `extract()` would bail before walking the body. Supply a stable owner
92
+ // name (the named companion's identifier, else "Companion") so the body IS
93
+ // walked; the resulting FieldInfo map is keyed by field NAME only, so the
94
+ // owner name does not affect which Property node gets enriched.
95
+ extractOwnerName(node) {
96
+ const typeIdentifierText = node.namedChildren.find((c) => c.type === 'type_identifier')?.text;
97
+ if (node.type === 'companion_object') {
98
+ // Anonymous companions have no type_identifier — fall back to "Companion".
99
+ return typeIdentifierText ?? 'Companion';
100
+ }
101
+ const name = node.childForFieldName('name');
102
+ if (name)
103
+ return name.text;
104
+ return typeIdentifierText;
105
+ },
69
106
  extractName(node) {
70
107
  // property_declaration > variable_declaration > simple_identifier
71
108
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -107,9 +144,11 @@ export const kotlinConfig = {
107
144
  extractVisibility(node) {
108
145
  return findVisibility(node, KOTLIN_VIS, 'public', 'modifiers');
109
146
  },
110
- isStatic(_node) {
111
- // Kotlin doesn't have static; companion object members are handled separately
112
- return false;
147
+ isStatic(node) {
148
+ // Kotlin has no `static`, but companion-object members are accessed
149
+ // statically through the enclosing class (`C.TAG`) — mark them static
150
+ // so the field metadata reflects that (F52).
151
+ return isInsideKotlinCompanion(node);
113
152
  },
114
153
  isReadonly(node) {
115
154
  // 'val' = readonly, 'var' = mutable
@@ -2,7 +2,18 @@ import type { FieldExtractionConfig } from '../generic.js';
2
2
  /**
3
3
  * Swift field extraction config.
4
4
  *
5
- * Handles property_declaration inside class_body / protocol_body.
5
+ * Handles property_declaration inside class_body / protocol_body and
6
+ * protocol_property_declaration inside protocol_body (F75 — protocol property
7
+ * requirements like "var title: String { get }").
8
+ *
6
9
  * tree-sitter-swift uses property_declaration for stored/computed properties.
10
+ * A protocol property requirement parses to its own node type,
11
+ * protocol_property_declaration, whose name lives in a "name:" pattern field
12
+ * (pattern > value_binding_pattern + simple_identifier(bound_identifier)), its
13
+ * type in a sibling type_annotation, and its "{ get }" / "{ get set }" in a
14
+ * protocol_property_requirements child. Note: Swift reuses the "name:" field
15
+ * across many positions (func name, every parameter label, parameter/return
16
+ * type), so the name is synthesized from the simple_identifier inside the
17
+ * pattern rather than read blindly off "name:".
7
18
  */
8
19
  export declare const swiftConfig: FieldExtractionConfig;
@@ -1,6 +1,6 @@
1
1
  // gitnexus/src/core/ingestion/field-extractors/configs/swift.ts
2
2
  import { SupportedLanguages } from '../../../../_shared/index.js';
3
- import { hasKeyword, findVisibility } from './helpers.js';
3
+ import { hasKeyword, hasModifier, findVisibility } from './helpers.js';
4
4
  import { extractSimpleTypeName } from '../../type-extractors/shared.js';
5
5
  const SWIFT_VIS = new Set([
6
6
  'public',
@@ -12,17 +12,32 @@ const SWIFT_VIS = new Set([
12
12
  /**
13
13
  * Swift field extraction config.
14
14
  *
15
- * Handles property_declaration inside class_body / protocol_body.
15
+ * Handles property_declaration inside class_body / protocol_body and
16
+ * protocol_property_declaration inside protocol_body (F75 — protocol property
17
+ * requirements like "var title: String { get }").
18
+ *
16
19
  * tree-sitter-swift uses property_declaration for stored/computed properties.
20
+ * A protocol property requirement parses to its own node type,
21
+ * protocol_property_declaration, whose name lives in a "name:" pattern field
22
+ * (pattern > value_binding_pattern + simple_identifier(bound_identifier)), its
23
+ * type in a sibling type_annotation, and its "{ get }" / "{ get set }" in a
24
+ * protocol_property_requirements child. Note: Swift reuses the "name:" field
25
+ * across many positions (func name, every parameter label, parameter/return
26
+ * type), so the name is synthesized from the simple_identifier inside the
27
+ * pattern rather than read blindly off "name:".
17
28
  */
18
29
  export const swiftConfig = {
19
30
  language: SupportedLanguages.Swift,
20
31
  typeDeclarationNodes: ['class_declaration', 'protocol_declaration'],
21
- fieldNodeTypes: ['property_declaration'],
32
+ fieldNodeTypes: ['property_declaration', 'protocol_property_declaration'],
22
33
  bodyNodeTypes: ['class_body', 'protocol_body'],
23
34
  defaultVisibility: 'internal',
24
35
  extractName(node) {
25
- // property_declaration > pattern > simple_identifier
36
+ // property_declaration > pattern > simple_identifier, and
37
+ // protocol_property_declaration > name: (pattern ... simple_identifier).
38
+ // For protocol_property_declaration the pattern wraps a leading
39
+ // value_binding_pattern ("var") plus the simple_identifier — the loop
40
+ // below skips the binding keyword and returns the identifier.
26
41
  for (let i = 0; i < node.namedChildCount; i++) {
27
42
  const child = node.namedChild(i);
28
43
  if (child?.type === 'pattern') {
@@ -56,7 +71,17 @@ export const swiftConfig = {
56
71
  return findVisibility(node, SWIFT_VIS, 'internal', 'modifiers');
57
72
  },
58
73
  isStatic(node) {
59
- return hasKeyword(node, 'static') || hasKeyword(node, 'class');
74
+ // `static`/`class` (type-level) modifiers live inside a `modifiers`
75
+ // wrapper for both property_declaration and protocol_property_declaration
76
+ // (e.g. `static var shared: P { get }`), so check the wrapper too.
77
+ // `hasKeyword` compares each direct child by `.text` equality: it matches a
78
+ // single-modifier wrapper (`modifiers.text === 'static'`) but fails for a
79
+ // multi-modifier wrapper (`private static` → `modifiers.text === 'private static'`),
80
+ // which `hasModifier` handles by descending into the wrapper's children.
81
+ return (hasKeyword(node, 'static') ||
82
+ hasKeyword(node, 'class') ||
83
+ hasModifier(node, 'modifiers', 'static') ||
84
+ hasModifier(node, 'modifiers', 'class'));
60
85
  },
61
86
  isReadonly(node) {
62
87
  // 'let' = constant/readonly, 'var' = variable
@@ -1,7 +1,3 @@
1
- export interface FileEntry {
2
- path: string;
3
- content: string;
4
- }
5
1
  /** Lightweight entry — path + size from stat, no content in memory */
6
2
  export interface ScannedFile {
7
3
  path: string;
@@ -21,8 +17,3 @@ export declare const walkRepositoryPaths: (repoPath: string, onProgress?: (curre
21
17
  * Returns a Map for O(1) lookup. Silently skips files that fail to read.
22
18
  */
23
19
  export declare const readFileContents: (repoPath: string, relativePaths: string[]) => Promise<Map<string, string>>;
24
- /**
25
- * Legacy API — scans and reads everything into memory.
26
- * Used by sequential fallback path only.
27
- */
28
- export declare const walkRepository: (repoPath: string, onProgress?: (current: number, total: number, filePath: string) => void) => Promise<FileEntry[]>;
@@ -111,14 +111,3 @@ export const readFileContents = async (repoPath, relativePaths) => {
111
111
  }
112
112
  return contents;
113
113
  };
114
- /**
115
- * Legacy API — scans and reads everything into memory.
116
- * Used by sequential fallback path only.
117
- */
118
- export const walkRepository = async (repoPath, onProgress) => {
119
- const scanned = await walkRepositoryPaths(repoPath, onProgress);
120
- const contents = await readFileContents(repoPath, scanned.map((f) => f.path));
121
- return scanned
122
- .filter((f) => contents.has(f.path))
123
- .map((f) => ({ path: f.path, content: contents.get(f.path) }));
124
- };
@@ -4,5 +4,14 @@ import { type SyntaxNode } from '../../utils/ast-helpers.js';
4
4
  * Decompose a `preproc_include` node into a CaptureMatch with structured
5
5
  * import captures. C #include maps to a wildcard import (all symbols
6
6
  * from the header are visible).
7
+ *
8
+ * Only literal include paths are emitted as import sources:
9
+ * #include <stdio.h> → system_lib_string
10
+ * #include "local.h" → string_literal
11
+ * A computed include like `#include HEADER_MACRO` carries an `identifier`
12
+ * path node (the macro name, not a header path). Emitting it as an import
13
+ * source produces a garbage literal edge, so we skip it entirely — matching
14
+ * the convention in interpretCImport, which drops imports with no resolvable
15
+ * source (issue #1919 F5).
7
16
  */
8
17
  export declare function splitCInclude(node: SyntaxNode): CaptureMatch | null;
@@ -3,10 +3,20 @@ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
3
3
  * Decompose a `preproc_include` node into a CaptureMatch with structured
4
4
  * import captures. C #include maps to a wildcard import (all symbols
5
5
  * from the header are visible).
6
+ *
7
+ * Only literal include paths are emitted as import sources:
8
+ * #include <stdio.h> → system_lib_string
9
+ * #include "local.h" → string_literal
10
+ * A computed include like `#include HEADER_MACRO` carries an `identifier`
11
+ * path node (the macro name, not a header path). Emitting it as an import
12
+ * source produces a garbage literal edge, so we skip it entirely — matching
13
+ * the convention in interpretCImport, which drops imports with no resolvable
14
+ * source (issue #1919 F5).
6
15
  */
7
16
  export function splitCInclude(node) {
8
17
  // node.type === 'preproc_include'
9
18
  // path field: (string_literal (string_content)) | (system_lib_string)
19
+ // | (identifier) ← computed macro include, NOT a header path
10
20
  const pathNode = node.childForFieldName?.('path') ?? null;
11
21
  if (pathNode === null) {
12
22
  // Fallback: scan children
@@ -23,6 +33,11 @@ export function splitCInclude(node) {
23
33
  return buildIncludeCapture(node, pathNode);
24
34
  }
25
35
  function buildIncludeCapture(node, pathNode) {
36
+ // Skip computed includes (`#include MACRO`) — the path is an `identifier`,
37
+ // not a literal header path. Emitting it would create a garbage import.
38
+ if (pathNode.type !== 'string_literal' && pathNode.type !== 'system_lib_string') {
39
+ return null;
40
+ }
26
41
  let raw;
27
42
  if (pathNode.type === 'string_literal') {
28
43
  // string_literal has children: `"`, string_content, `"`
@@ -4,6 +4,13 @@ import { type SyntaxNode } from '../../utils/ast-helpers.js';
4
4
  * Decompose a `preproc_include` node into a CaptureMatch with structured
5
5
  * import captures. C++ #include maps to a wildcard import (all symbols
6
6
  * from the header are visible). Identical to C's splitCInclude.
7
+ *
8
+ * Only literal include paths are emitted as import sources:
9
+ * #include <map> → system_lib_string
10
+ * #include "User.h" → string_literal
11
+ * A computed include like `#include HEADER_MACRO` carries an `identifier`
12
+ * path node (the macro name, not a header path); we skip it so it never
13
+ * becomes a garbage literal import source (issue #1919 F5).
7
14
  */
8
15
  export declare function splitCppInclude(node: SyntaxNode): CaptureMatch | null;
9
16
  /**
@@ -3,6 +3,13 @@ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
3
3
  * Decompose a `preproc_include` node into a CaptureMatch with structured
4
4
  * import captures. C++ #include maps to a wildcard import (all symbols
5
5
  * from the header are visible). Identical to C's splitCInclude.
6
+ *
7
+ * Only literal include paths are emitted as import sources:
8
+ * #include <map> → system_lib_string
9
+ * #include "User.h" → string_literal
10
+ * A computed include like `#include HEADER_MACRO` carries an `identifier`
11
+ * path node (the macro name, not a header path); we skip it so it never
12
+ * becomes a garbage literal import source (issue #1919 F5).
6
13
  */
7
14
  export function splitCppInclude(node) {
8
15
  const pathNode = node.childForFieldName?.('path') ?? null;
@@ -20,6 +27,11 @@ export function splitCppInclude(node) {
20
27
  return buildIncludeCapture(node, pathNode);
21
28
  }
22
29
  function buildIncludeCapture(node, pathNode) {
30
+ // Skip computed includes (`#include MACRO`) — the path is an `identifier`,
31
+ // not a literal header path. Emitting it would create a garbage import.
32
+ if (pathNode.type !== 'string_literal' && pathNode.type !== 'system_lib_string') {
33
+ return null;
34
+ }
23
35
  let raw;
24
36
  if (pathNode.type === 'string_literal') {
25
37
  const content = pathNode.namedChildren.find((c) => c.type === 'string_content');
@@ -37,6 +37,46 @@ const DART_SCOPE_QUERY = `
37
37
  (extension_declaration name: (identifier) @declaration.name) @declaration.class
38
38
  (enum_declaration name: (identifier) @declaration.name) @declaration.enum
39
39
 
40
+ ; ── Declarations — type aliases (old-style + new-style function typedefs) ────
41
+ ; Both forms parse as type_alias; the name position differs, and a generic
42
+ ; <T> parameter list intervenes for the generic variants. Per #1919 review CF2,
43
+ ; a generic type_parameters node sits between the name and the next anchor, so
44
+ ; the non-generic adjacency patterns silently drop the generic forms. Four
45
+ ; standalone patterns (NOT one alternation — the tree-sitter 0.21 hazard drops
46
+ ; sibling branches) keep the name capture unambiguous and single-match per form:
47
+ ; non-generic old-style typedef int Cmp(int a, int b);
48
+ ; children: return-type, NAME, formal_parameter_list
49
+ ; generic old-style typedef int Cmp<T>(T a, T b); (CF2)
50
+ ; children: return-type, NAME, type_parameters, formal_parameter_list
51
+ ; non-generic new-style typedef Pred = bool Function(int);
52
+ ; children: NAME, "=", function_type
53
+ ; generic new-style typedef Mapper<T> = T Function(T);
54
+ ; children: NAME, type_parameters, "=", function_type
55
+ ; The alias name is the type_identifier immediately before the param list (old)
56
+ ; or before "=" (new); for the generic forms it is the one immediately before
57
+ ; the intervening type_parameters. Mirrors Kotlin's @declaration.type_alias
58
+ ; rule; the generic scope-extractor maps "type_alias" → TypeAlias.
59
+ (type_alias
60
+ (type_identifier) @declaration.name
61
+ .
62
+ (formal_parameter_list)) @declaration.type_alias
63
+ (type_alias
64
+ (type_identifier) @declaration.name
65
+ .
66
+ (type_parameters)
67
+ .
68
+ (formal_parameter_list)) @declaration.type_alias
69
+ (type_alias
70
+ (type_identifier) @declaration.name
71
+ .
72
+ "=") @declaration.type_alias
73
+ (type_alias
74
+ (type_identifier) @declaration.name
75
+ .
76
+ (type_parameters)
77
+ .
78
+ "=") @declaration.type_alias
79
+
40
80
  ; ── Declarations — top-level functions (parent is program, not method) ───────
41
81
  (program
42
82
  (function_signature