gitnexus 1.6.5-rc.3 → 1.6.5-rc.5

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 (55) hide show
  1. package/dist/core/embeddings/ast-utils.js +2 -1
  2. package/dist/core/group/extractors/grpc-extractor.js +2 -1
  3. package/dist/core/group/extractors/http-route-extractor.js +2 -1
  4. package/dist/core/group/extractors/include-extractor.js +2 -1
  5. package/dist/core/group/extractors/thrift-extractor.js +2 -1
  6. package/dist/core/group/extractors/tree-sitter-scanner.js +2 -1
  7. package/dist/core/ingestion/call-processor.js +3 -2
  8. package/dist/core/ingestion/heritage-processor.js +3 -2
  9. package/dist/core/ingestion/import-processor.js +2 -1
  10. package/dist/core/ingestion/languages/c/arity-metadata.d.ts +14 -0
  11. package/dist/core/ingestion/languages/c/arity-metadata.js +94 -0
  12. package/dist/core/ingestion/languages/c/arity.d.ts +6 -0
  13. package/dist/core/ingestion/languages/c/arity.js +18 -0
  14. package/dist/core/ingestion/languages/c/captures.d.ts +2 -0
  15. package/dist/core/ingestion/languages/c/captures.js +105 -0
  16. package/dist/core/ingestion/languages/c/header-scan.d.ts +7 -0
  17. package/dist/core/ingestion/languages/c/header-scan.js +55 -0
  18. package/dist/core/ingestion/languages/c/import-decomposer.d.ts +8 -0
  19. package/dist/core/ingestion/languages/c/import-decomposer.js +50 -0
  20. package/dist/core/ingestion/languages/c/import-target.d.ts +14 -0
  21. package/dist/core/ingestion/languages/c/import-target.js +57 -0
  22. package/dist/core/ingestion/languages/c/index.d.ts +11 -0
  23. package/dist/core/ingestion/languages/c/index.js +11 -0
  24. package/dist/core/ingestion/languages/c/interpret.d.ts +14 -0
  25. package/dist/core/ingestion/languages/c/interpret.js +48 -0
  26. package/dist/core/ingestion/languages/c/merge-bindings.d.ts +7 -0
  27. package/dist/core/ingestion/languages/c/merge-bindings.js +23 -0
  28. package/dist/core/ingestion/languages/c/query.d.ts +3 -0
  29. package/dist/core/ingestion/languages/c/query.js +161 -0
  30. package/dist/core/ingestion/languages/c/scope-resolver.d.ts +13 -0
  31. package/dist/core/ingestion/languages/c/scope-resolver.js +60 -0
  32. package/dist/core/ingestion/languages/c/simple-hooks.d.ts +14 -0
  33. package/dist/core/ingestion/languages/c/simple-hooks.js +19 -0
  34. package/dist/core/ingestion/languages/c/static-linkage.d.ts +13 -0
  35. package/dist/core/ingestion/languages/c/static-linkage.js +57 -0
  36. package/dist/core/ingestion/languages/c-cpp.js +10 -0
  37. package/dist/core/ingestion/languages/csharp/captures.js +2 -1
  38. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +3 -2
  39. package/dist/core/ingestion/languages/go/captures.js +2 -1
  40. package/dist/core/ingestion/languages/go/range-binding.js +2 -1
  41. package/dist/core/ingestion/languages/python/captures.js +2 -1
  42. package/dist/core/ingestion/languages/typescript/captures.js +2 -1
  43. package/dist/core/ingestion/parsing-processor.js +2 -1
  44. package/dist/core/ingestion/registry-primary-flag.js +1 -0
  45. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +11 -0
  46. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +2 -1
  47. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +13 -2
  48. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  49. package/dist/core/ingestion/scope-resolution/pipeline/run.js +4 -1
  50. package/dist/core/ingestion/workers/parse-worker.js +2 -1
  51. package/dist/core/lbug/lbug-adapter.js +8 -0
  52. package/dist/core/lbug/pool-adapter.js +19 -2
  53. package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
  54. package/dist/core/tree-sitter/safe-parse.js +32 -0
  55. package/package.json +1 -1
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { getLanguageFromFilename } from '../../_shared/index.js';
7
7
  import { createParserForLanguage, isLanguageAvailable, resolveLanguageKey, } from '../tree-sitter/parser-loader.js';
8
+ import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
8
9
  const parserCache = new Map();
9
10
  /**
10
11
  * Ensure parser is initialized and language is loaded, then parse content.
@@ -22,7 +23,7 @@ export const ensureAndParse = async (content, filePath) => {
22
23
  parserInstance = await createParserForLanguage(language, filePath);
23
24
  parserCache.set(parserKey, parserInstance);
24
25
  }
25
- return parserInstance.parse(content);
26
+ return parseSourceSafe(parserInstance, content);
26
27
  };
27
28
  const FUNCTION_LIKE_TYPES = new Set([
28
29
  'function_declaration',
@@ -3,6 +3,7 @@ import { glob } from 'glob';
3
3
  import Parser from 'tree-sitter';
4
4
  import { createIgnoreFilter } from '../../../config/ignore-service.js';
5
5
  import { readSafe } from './fs-utils.js';
6
+ import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
6
7
  import { logger } from '../../logger.js';
7
8
  import { GRPC_SCAN_GLOB, getPluginForFile, hasProtoPlugin, } from './grpc-patterns/index.js';
8
9
  /**
@@ -352,7 +353,7 @@ export class GrpcExtractor {
352
353
  let detections = [];
353
354
  try {
354
355
  parser.setLanguage(plugin.language);
355
- const tree = parser.parse(content);
356
+ const tree = parseSourceSafe(parser, content);
356
357
  detections = plugin.scan(tree);
357
358
  }
358
359
  catch {
@@ -3,6 +3,7 @@ import { glob } from 'glob';
3
3
  import Parser from 'tree-sitter';
4
4
  import { createIgnoreFilter } from '../../../config/ignore-service.js';
5
5
  import { readSafe } from './fs-utils.js';
6
+ import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
6
7
  import { getPluginForFile, HTTP_SCAN_GLOB } from './http-patterns/index.js';
7
8
  /**
8
9
  * Language-agnostic orchestrator for HTTP route (provider + consumer)
@@ -154,7 +155,7 @@ export class HttpRouteExtractor {
154
155
  }
155
156
  try {
156
157
  parser.setLanguage(plugin.language);
157
- const tree = parser.parse(content);
158
+ const tree = parseSourceSafe(parser, content);
158
159
  const detections = plugin.scan(tree);
159
160
  cachedDetections.set(rel, detections);
160
161
  return detections;
@@ -8,6 +8,7 @@ import { readSafe } from './fs-utils.js';
8
8
  import { buildSuffixIndex } from '../../ingestion/import-resolvers/utils.js';
9
9
  import { createIgnoreFilter } from '../../../config/ignore-service.js';
10
10
  import { getMaxFileSizeBytes } from '../../ingestion/utils/max-file-size.js';
11
+ import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
11
12
  import { logger } from '../../logger.js';
12
13
  /**
13
14
  * Cross-repo C/C++ `#include` dependency extractor.
@@ -457,7 +458,7 @@ export class IncludeExtractor {
457
458
  let extractionSource;
458
459
  try {
459
460
  parser.setLanguage(lang);
460
- const tree = parser.parse(content);
461
+ const tree = parseSourceSafe(parser, content);
461
462
  let matches;
462
463
  try {
463
464
  matches = query.matches(tree.rootNode);
@@ -1,6 +1,7 @@
1
1
  import { glob } from 'glob';
2
2
  import Parser from 'tree-sitter';
3
3
  import { readSafe } from './fs-utils.js';
4
+ import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
4
5
  import { getPluginForFile, THRIFT_SCAN_GLOB, } from './thrift-patterns/index.js';
5
6
  function normalizeThriftPath(rel) {
6
7
  return rel.replace(/\\/g, '/');
@@ -228,7 +229,7 @@ export class ThriftExtractor {
228
229
  let detections = [];
229
230
  try {
230
231
  parser.setLanguage(plugin.language);
231
- const tree = parser.parse(content);
232
+ const tree = parseSourceSafe(parser, content);
232
233
  detections = plugin.scan(tree);
233
234
  }
234
235
  catch {
@@ -1,4 +1,5 @@
1
1
  import Parser from 'tree-sitter';
2
+ import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
2
3
  /**
3
4
  * Compile a LanguagePatterns bundle. Call this once per plugin, at
4
5
  * module load time, and export the result. Throws if any pattern
@@ -60,7 +61,7 @@ export function scanFile(parser, plugin, content) {
60
61
  let tree;
61
62
  try {
62
63
  parser.setLanguage(plugin.language);
63
- tree = parser.parse(content);
64
+ tree = parseSourceSafe(parser, content);
64
65
  }
65
66
  catch {
66
67
  return [];
@@ -29,6 +29,7 @@ import { getLanguageFromFilename, SupportedLanguages } from '../../_shared/index
29
29
  import { isRegistryPrimary } from './registry-primary-flag.js';
30
30
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
31
31
  import { yieldToEventLoop } from './utils/event-loop.js';
32
+ import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
32
33
  import { FUNCTION_NODE_TYPES, findEnclosingClassId, findEnclosingClassInfo, genericFuncName, inferFunctionLabel, } from './utils/ast-helpers.js';
33
34
  import { typeTagForId, constTagForId, buildCollisionGroups } from './utils/method-props.js';
34
35
  import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from './utils/call-analysis.js';
@@ -614,7 +615,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
614
615
  if (!tree) {
615
616
  const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
616
617
  try {
617
- tree = parser.parse(parseContent, undefined, {
618
+ tree = parseSourceSafe(parser, parseContent, undefined, {
618
619
  bufferSize: getTreeSitterBufferSize(parseContent),
619
620
  });
620
621
  }
@@ -2586,7 +2587,7 @@ export const extractFetchCallsFromFiles = async (files, astCache) => {
2586
2587
  if (!tree) {
2587
2588
  const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
2588
2589
  try {
2589
- tree = parser.parse(parseContent, undefined, {
2590
+ tree = parseSourceSafe(parser, parseContent, undefined, {
2590
2591
  bufferSize: getTreeSitterBufferSize(parseContent),
2591
2592
  });
2592
2593
  }
@@ -19,6 +19,7 @@ import { generateId } from '../../lib/utils.js';
19
19
  import { getLanguageFromFilename } from '../../_shared/index.js';
20
20
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
21
21
  import { yieldToEventLoop } from './utils/event-loop.js';
22
+ import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
22
23
  import { getProvider } from './languages/index.js';
23
24
  import { getTreeSitterBufferSize } from './constants.js';
24
25
  import { resolveExtendsType } from './model/heritage-map.js';
@@ -152,7 +153,7 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
152
153
  // re-parses see the same input as the cached AST.
153
154
  const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
154
155
  try {
155
- tree = parser.parse(parseContent, undefined, {
156
+ tree = parseSourceSafe(parser, parseContent, undefined, {
156
157
  bufferSize: getTreeSitterBufferSize(parseContent),
157
158
  });
158
159
  }
@@ -300,7 +301,7 @@ export async function extractExtractedHeritageFromFiles(files, astCache) {
300
301
  if (!tree) {
301
302
  const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
302
303
  try {
303
- tree = parser.parse(parseContent, undefined, {
304
+ tree = parseSourceSafe(parser, parseContent, undefined, {
304
305
  bufferSize: getTreeSitterBufferSize(parseContent),
305
306
  });
306
307
  }
@@ -5,6 +5,7 @@ import { generateId } from '../../lib/utils.js';
5
5
  import { getLanguageFromFilename } from '../../_shared/index.js';
6
6
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
7
7
  import { yieldToEventLoop } from './utils/event-loop.js';
8
+ import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
8
9
  import { getTreeSitterBufferSize } from './constants.js';
9
10
  import { loadImportConfigs } from './language-config.js';
10
11
  import { buildSuffixIndex } from './import-resolvers/utils.js';
@@ -243,7 +244,7 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
243
244
  if (!tree) {
244
245
  const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
245
246
  try {
246
- tree = parser.parse(parseContent, undefined, {
247
+ tree = parseSourceSafe(parser, parseContent, undefined, {
247
248
  bufferSize: getTreeSitterBufferSize(parseContent),
248
249
  });
249
250
  }
@@ -0,0 +1,14 @@
1
+ import type { SyntaxNode } from '../../utils/ast-helpers.js';
2
+ export interface CArityInfo {
3
+ parameterCount?: number;
4
+ requiredParameterCount?: number;
5
+ parameterTypes?: string[];
6
+ }
7
+ /**
8
+ * Compute declaration arity from a C function definition or declaration node.
9
+ */
10
+ export declare function computeCDeclarationArity(node: SyntaxNode): CArityInfo;
11
+ /**
12
+ * Compute call-site arity from a call_expression node.
13
+ */
14
+ export declare function computeCCallArity(node: SyntaxNode): number;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Compute declaration arity from a C function definition or declaration node.
3
+ */
4
+ export function computeCDeclarationArity(node) {
5
+ // Find the function_declarator child (may be wrapped in pointer_declarator)
6
+ const funcDecl = findFuncDeclarator(node);
7
+ if (funcDecl === null)
8
+ return {};
9
+ const paramList = funcDecl.childForFieldName('parameters');
10
+ if (paramList === null)
11
+ return {};
12
+ const params = [];
13
+ for (let i = 0; i < paramList.childCount; i++) {
14
+ const child = paramList.child(i);
15
+ if (child === null)
16
+ continue;
17
+ if (child.type === 'parameter_declaration' || child.type === 'variadic_parameter') {
18
+ params.push(child);
19
+ }
20
+ }
21
+ // K&R old-style declaration: `int foo()` has an empty parameter_list with
22
+ // no parameter_declaration or variadic_parameter children. Per C89/C99,
23
+ // this means the function accepts an unspecified number/types of arguments —
24
+ // NOT zero arguments. Return unknown arity to avoid false 'incompatible'.
25
+ // `int foo(void)` is the explicit zero-parameter form and is handled below.
26
+ if (params.length === 0)
27
+ return {};
28
+ // (void) means zero parameters
29
+ if (params.length === 1 && params[0].type === 'parameter_declaration') {
30
+ const typeNode = params[0].childForFieldName('type');
31
+ const hasDeclarator = params[0].childForFieldName('declarator') !== null;
32
+ if (typeNode !== null && typeNode.text === 'void' && !hasDeclarator) {
33
+ return { parameterCount: 0, requiredParameterCount: 0, parameterTypes: [] };
34
+ }
35
+ }
36
+ const isVariadic = params.some((p) => p.type === 'variadic_parameter');
37
+ const nonVariadicCount = params.filter((p) => p.type !== 'variadic_parameter').length;
38
+ const types = [];
39
+ for (const p of params) {
40
+ if (p.type === 'variadic_parameter') {
41
+ types.push('...');
42
+ }
43
+ else {
44
+ const typeNode = p.childForFieldName('type');
45
+ types.push(typeNode?.text ?? 'unknown');
46
+ }
47
+ }
48
+ return {
49
+ parameterCount: isVariadic ? undefined : nonVariadicCount,
50
+ requiredParameterCount: nonVariadicCount,
51
+ parameterTypes: types,
52
+ };
53
+ }
54
+ /**
55
+ * Compute call-site arity from a call_expression node.
56
+ */
57
+ export function computeCCallArity(node) {
58
+ const argList = node.childForFieldName('arguments');
59
+ if (argList === null)
60
+ return 0;
61
+ let count = 0;
62
+ for (let i = 0; i < argList.childCount; i++) {
63
+ const child = argList.child(i);
64
+ if (child === null)
65
+ continue;
66
+ // Skip punctuation (commas, parens)
67
+ if (child.type !== ',' && child.type !== '(' && child.type !== ')') {
68
+ count++;
69
+ }
70
+ }
71
+ return count;
72
+ }
73
+ function findFuncDeclarator(node) {
74
+ // Direct child
75
+ let decl = node.childForFieldName('declarator');
76
+ if (decl === null) {
77
+ for (let i = 0; i < node.childCount; i++) {
78
+ const c = node.child(i);
79
+ if (c?.type === 'function_declarator')
80
+ return c;
81
+ }
82
+ return null;
83
+ }
84
+ // Unwrap pointer_declarator
85
+ while (decl.type === 'pointer_declarator') {
86
+ const next = decl.childForFieldName('declarator');
87
+ if (next === null)
88
+ break;
89
+ decl = next;
90
+ }
91
+ if (decl.type === 'function_declarator')
92
+ return decl;
93
+ return null;
94
+ }
@@ -0,0 +1,6 @@
1
+ import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
2
+ /**
3
+ * C arity compatibility: no overloading. Variadic functions detected
4
+ * via '...' in parameterTypes. Otherwise exact match or unknown.
5
+ */
6
+ export declare function cArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * C arity compatibility: no overloading. Variadic functions detected
3
+ * via '...' in parameterTypes. Otherwise exact match or unknown.
4
+ */
5
+ export function cArityCompatibility(def, callsite) {
6
+ const max = def.parameterCount;
7
+ const min = def.requiredParameterCount;
8
+ if (max === undefined && min === undefined)
9
+ return 'unknown';
10
+ if (!Number.isFinite(callsite.arity) || callsite.arity < 0)
11
+ return 'unknown';
12
+ const variadic = def.parameterTypes?.some((t) => t === '...') ?? false;
13
+ if (min !== undefined && callsite.arity < min)
14
+ return 'incompatible';
15
+ if (max !== undefined && callsite.arity > max && !variadic)
16
+ return 'incompatible';
17
+ return 'compatible';
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { CaptureMatch } from '../../../../_shared/index.js';
2
+ export declare function emitCScopeCaptures(sourceText: string, filePath: string, cachedTree?: unknown): readonly CaptureMatch[];
@@ -0,0 +1,105 @@
1
+ import { findNodeAtRange, nodeToCapture, syntheticCapture, } from '../../utils/ast-helpers.js';
2
+ import { getCParser, getCScopeQuery } from './query.js';
3
+ import { getTreeSitterBufferSize } from '../../constants.js';
4
+ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
5
+ import { splitCInclude } from './import-decomposer.js';
6
+ import { computeCDeclarationArity, computeCCallArity } from './arity-metadata.js';
7
+ import { markStaticName } from './static-linkage.js';
8
+ export function emitCScopeCaptures(sourceText, filePath, cachedTree) {
9
+ let tree = cachedTree;
10
+ if (tree === undefined) {
11
+ tree = parseSourceSafe(getCParser(), sourceText, undefined, {
12
+ bufferSize: getTreeSitterBufferSize(sourceText),
13
+ });
14
+ }
15
+ const rawMatches = getCScopeQuery().matches(tree.rootNode);
16
+ const out = [];
17
+ // Track ranges where typedef-struct/union was captured as @declaration.struct/union
18
+ // so we can suppress the duplicate @declaration.typedef match at the same range.
19
+ const structTypedefRanges = new Set();
20
+ for (const m of rawMatches) {
21
+ const grouped = {};
22
+ for (const c of m.captures) {
23
+ const tag = '@' + c.name;
24
+ if (tag.startsWith('@_'))
25
+ continue;
26
+ grouped[tag] = nodeToCapture(tag, c.node);
27
+ }
28
+ if (Object.keys(grouped).length === 0)
29
+ continue;
30
+ // Handle #include statements
31
+ if (grouped['@import.statement'] !== undefined) {
32
+ const anchor = grouped['@import.statement'];
33
+ const includeNode = findNodeAtRange(tree.rootNode, anchor.range, 'preproc_include');
34
+ if (includeNode !== null) {
35
+ const split = splitCInclude(includeNode);
36
+ if (split !== null) {
37
+ out.push(split);
38
+ continue;
39
+ }
40
+ }
41
+ }
42
+ // Track typedef-struct ranges to suppress duplicate typedef declarations
43
+ const structAnchor = grouped['@declaration.struct'] ?? grouped['@declaration.union'];
44
+ if (structAnchor !== undefined) {
45
+ const r = structAnchor.range;
46
+ structTypedefRanges.add(`${r.startLine}:${r.startCol}:${r.endLine}:${r.endCol}`);
47
+ }
48
+ // Suppress @declaration.typedef if the same range was already captured as struct/union
49
+ const typedefAnchor = grouped['@declaration.typedef'];
50
+ if (typedefAnchor !== undefined) {
51
+ const r = typedefAnchor.range;
52
+ const key = `${r.startLine}:${r.startCol}:${r.endLine}:${r.endCol}`;
53
+ if (structTypedefRanges.has(key))
54
+ continue;
55
+ }
56
+ // Enrich function declarations with arity metadata and detect static linkage
57
+ const declAnchor = grouped['@declaration.function'];
58
+ if (declAnchor !== undefined) {
59
+ const fnNode = findNodeAtRange(tree.rootNode, declAnchor.range, 'function_definition') ??
60
+ findNodeAtRange(tree.rootNode, declAnchor.range, 'declaration');
61
+ if (fnNode !== null) {
62
+ const arity = computeCDeclarationArity(fnNode);
63
+ if (arity.parameterCount !== undefined) {
64
+ grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
65
+ }
66
+ if (arity.requiredParameterCount !== undefined) {
67
+ grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
68
+ }
69
+ if (arity.parameterTypes !== undefined) {
70
+ grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
71
+ }
72
+ // Detect static storage class (file-local linkage)
73
+ if (hasStaticStorageClass(fnNode)) {
74
+ const nameText = grouped['@declaration.name']?.text;
75
+ if (nameText !== undefined) {
76
+ markStaticName(filePath, nameText);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ // Enrich call references with arity
82
+ const callAnchor = grouped['@reference.call.free'] ?? grouped['@reference.call.member'];
83
+ if (callAnchor !== undefined && grouped['@reference.arity'] === undefined) {
84
+ const callNode = findNodeAtRange(tree.rootNode, callAnchor.range, 'call_expression');
85
+ if (callNode !== null) {
86
+ grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(computeCCallArity(callNode)));
87
+ }
88
+ }
89
+ out.push(grouped);
90
+ }
91
+ return out;
92
+ }
93
+ /**
94
+ * Check if a C function_definition or declaration has `static` storage class.
95
+ * Walks direct children for a `storage_class_specifier` node with text `static`.
96
+ */
97
+ function hasStaticStorageClass(node) {
98
+ for (let i = 0; i < node.childCount; i++) {
99
+ const child = node.child(i);
100
+ if (child !== null && child.type === 'storage_class_specifier' && child.text === 'static') {
101
+ return true;
102
+ }
103
+ }
104
+ return false;
105
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Walk `repoPath` recursively and return relative paths of all `.h` files.
3
+ * Used by `loadResolutionConfig` so the C resolver can resolve `#include`
4
+ * targets that live in `.h` files (classified as C++ by language detection
5
+ * but importable from `.c` files).
6
+ */
7
+ export declare function scanHeaderFiles(repoPath: string): ReadonlySet<string>;
@@ -0,0 +1,55 @@
1
+ import { readdirSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+ /** C header extensions to scan for in the workspace. */
4
+ const HEADER_EXTENSIONS = new Set(['.h']);
5
+ /**
6
+ * Walk `repoPath` recursively and return relative paths of all `.h` files.
7
+ * Used by `loadResolutionConfig` so the C resolver can resolve `#include`
8
+ * targets that live in `.h` files (classified as C++ by language detection
9
+ * but importable from `.c` files).
10
+ */
11
+ export function scanHeaderFiles(repoPath) {
12
+ const headers = new Set();
13
+ walk(repoPath, repoPath, headers);
14
+ return headers;
15
+ }
16
+ function walk(dir, root, out) {
17
+ let entries;
18
+ try {
19
+ entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf8' });
20
+ }
21
+ catch {
22
+ return; // permission denied, etc.
23
+ }
24
+ for (const entry of entries) {
25
+ const name = entry.name;
26
+ const full = join(dir, name);
27
+ if (entry.isDirectory()) {
28
+ // Skip common non-source directories and build output dirs.
29
+ // Build dirs (dist, build, out, target, _build, .next, cmake-build-*)
30
+ // may contain generated headers that shadow source headers.
31
+ if (name === 'node_modules' ||
32
+ name === '.git' ||
33
+ name === 'vendor' ||
34
+ name === 'dist' ||
35
+ name === 'build' ||
36
+ name === 'out' ||
37
+ name === 'target' ||
38
+ name === '_build' ||
39
+ name === '.next' ||
40
+ name.startsWith('cmake-build')) {
41
+ continue;
42
+ }
43
+ walk(full, root, out);
44
+ }
45
+ else if (entry.isFile()) {
46
+ const ext = name.slice(name.lastIndexOf('.'));
47
+ if (HEADER_EXTENSIONS.has(ext)) {
48
+ // Normalize to forward slashes for cross-platform consistency.
49
+ // path.relative() returns backslash-separated paths on Windows,
50
+ // but the scope-resolution pipeline uses forward slashes uniformly.
51
+ out.add(relative(root, full).replace(/\\/g, '/'));
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,8 @@
1
+ import type { CaptureMatch } from '../../../../_shared/index.js';
2
+ import { type SyntaxNode } from '../../utils/ast-helpers.js';
3
+ /**
4
+ * Decompose a `preproc_include` node into a CaptureMatch with structured
5
+ * import captures. C #include maps to a wildcard import (all symbols
6
+ * from the header are visible).
7
+ */
8
+ export declare function splitCInclude(node: SyntaxNode): CaptureMatch | null;
@@ -0,0 +1,50 @@
1
+ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
2
+ /**
3
+ * Decompose a `preproc_include` node into a CaptureMatch with structured
4
+ * import captures. C #include maps to a wildcard import (all symbols
5
+ * from the header are visible).
6
+ */
7
+ export function splitCInclude(node) {
8
+ // node.type === 'preproc_include'
9
+ // path field: (string_literal (string_content)) | (system_lib_string)
10
+ const pathNode = node.childForFieldName?.('path') ?? null;
11
+ if (pathNode === null) {
12
+ // Fallback: scan children
13
+ for (let i = 0; i < node.childCount; i++) {
14
+ const child = node.child(i);
15
+ if (child === null)
16
+ continue;
17
+ if (child.type === 'string_literal' || child.type === 'system_lib_string') {
18
+ return buildIncludeCapture(node, child);
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ return buildIncludeCapture(node, pathNode);
24
+ }
25
+ function buildIncludeCapture(node, pathNode) {
26
+ let raw;
27
+ if (pathNode.type === 'string_literal') {
28
+ // string_literal has children: `"`, string_content, `"`
29
+ // Use namedChildren to find the string_content node
30
+ const content = pathNode.namedChildren.find((c) => c.type === 'string_content');
31
+ raw = content?.text ?? pathNode.text.replace(/^"|"$/g, '');
32
+ }
33
+ else {
34
+ // system_lib_string: <stdio.h> → strip angle brackets
35
+ raw = pathNode.text;
36
+ if (raw.startsWith('<') && raw.endsWith('>')) {
37
+ raw = raw.slice(1, -1);
38
+ }
39
+ }
40
+ const isSystem = pathNode.type === 'system_lib_string';
41
+ const result = {
42
+ '@import.statement': nodeToCapture('@import.statement', node),
43
+ '@import.kind': syntheticCapture('@import.kind', node, 'wildcard'),
44
+ '@import.source': syntheticCapture('@import.source', node, raw),
45
+ };
46
+ if (isSystem) {
47
+ result['@import.system'] = syntheticCapture('@import.system', node, 'true');
48
+ }
49
+ return result;
50
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve a C #include path to a file in the workspace.
3
+ *
4
+ * Strategy:
5
+ * 1. Check for a same-directory sibling relative to the including file
6
+ * (matches C compiler `#include "…"` relative-lookup semantics).
7
+ * 2. Check for an exact match (path as-is in the workspace).
8
+ * 3. Fall back to suffix matching against all workspace file paths.
9
+ * Tie-breaking: prefer the match with the fewest path components
10
+ * (closest to root). On equal depth, break ties lexicographically
11
+ * by normalized path to ensure deterministic resolution regardless
12
+ * of filesystem iteration order.
13
+ */
14
+ export declare function resolveCImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>): string | null;
@@ -0,0 +1,57 @@
1
+ import { dirname, join } from 'path';
2
+ /**
3
+ * Resolve a C #include path to a file in the workspace.
4
+ *
5
+ * Strategy:
6
+ * 1. Check for a same-directory sibling relative to the including file
7
+ * (matches C compiler `#include "…"` relative-lookup semantics).
8
+ * 2. Check for an exact match (path as-is in the workspace).
9
+ * 3. Fall back to suffix matching against all workspace file paths.
10
+ * Tie-breaking: prefer the match with the fewest path components
11
+ * (closest to root). On equal depth, break ties lexicographically
12
+ * by normalized path to ensure deterministic resolution regardless
13
+ * of filesystem iteration order.
14
+ */
15
+ export function resolveCImportTarget(targetRaw, fromFile, allFilePaths) {
16
+ if (!targetRaw)
17
+ return null;
18
+ const normalizedTarget = targetRaw.replace(/\\/g, '/');
19
+ // Same-directory sibling first: mirrors the C compiler's #include "…"
20
+ // relative-lookup semantics where the directory of the including
21
+ // file is searched before the include-path list.
22
+ if (fromFile) {
23
+ const siblingRaw = join(dirname(fromFile), targetRaw);
24
+ const sibling = siblingRaw.replace(/\\/g, '/');
25
+ if (allFilePaths.has(sibling))
26
+ return sibling;
27
+ // When targetRaw contains backslashes, the normalized form may
28
+ // resolve to a different sibling path — try it as well.
29
+ if (targetRaw !== normalizedTarget) {
30
+ const siblingAlt = join(dirname(fromFile), normalizedTarget);
31
+ const siblingAltNorm = siblingAlt.replace(/\\/g, '/');
32
+ if (allFilePaths.has(siblingAltNorm))
33
+ return siblingAltNorm;
34
+ }
35
+ }
36
+ // Exact match (path as-is in the workspace)
37
+ if (allFilePaths.has(normalizedTarget))
38
+ return normalizedTarget;
39
+ // Suffix match: find files ending with /targetRaw or equal to targetRaw
40
+ const suffix = '/' + normalizedTarget;
41
+ let bestMatch = null;
42
+ let bestDepth = Infinity;
43
+ let bestNormalized = '';
44
+ for (const filePath of allFilePaths) {
45
+ const normalized = filePath.replace(/\\/g, '/');
46
+ if (normalized === normalizedTarget || normalized.endsWith(suffix)) {
47
+ // Prefer shortest path (closest match)
48
+ const depth = normalized.split('/').length;
49
+ if (depth < bestDepth || (depth === bestDepth && normalized < bestNormalized)) {
50
+ bestDepth = depth;
51
+ bestMatch = filePath;
52
+ bestNormalized = normalized;
53
+ }
54
+ }
55
+ }
56
+ return bestMatch;
57
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * C scope-resolution hooks (RFC #909 Ring 3).
3
+ */
4
+ export { emitCScopeCaptures } from './captures.js';
5
+ export { interpretCImport, interpretCTypeBinding, normalizeCTypeName } from './interpret.js';
6
+ export { splitCInclude } from './import-decomposer.js';
7
+ export { cArityCompatibility } from './arity.js';
8
+ export { cMergeBindings } from './merge-bindings.js';
9
+ export { cBindingScopeFor, cImportOwningScope, cReceiverBinding } from './simple-hooks.js';
10
+ export { resolveCImportTarget } from './import-target.js';
11
+ export { markStaticName, isStaticName, clearStaticNames, expandCWildcardNames, } from './static-linkage.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * C scope-resolution hooks (RFC #909 Ring 3).
3
+ */
4
+ export { emitCScopeCaptures } from './captures.js';
5
+ export { interpretCImport, interpretCTypeBinding, normalizeCTypeName } from './interpret.js';
6
+ export { splitCInclude } from './import-decomposer.js';
7
+ export { cArityCompatibility } from './arity.js';
8
+ export { cMergeBindings } from './merge-bindings.js';
9
+ export { cBindingScopeFor, cImportOwningScope, cReceiverBinding } from './simple-hooks.js';
10
+ export { resolveCImportTarget } from './import-target.js';
11
+ export { markStaticName, isStaticName, clearStaticNames, expandCWildcardNames, } from './static-linkage.js';