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.
- package/dist/core/embeddings/ast-utils.js +2 -1
- package/dist/core/group/extractors/grpc-extractor.js +2 -1
- package/dist/core/group/extractors/http-route-extractor.js +2 -1
- package/dist/core/group/extractors/include-extractor.js +2 -1
- package/dist/core/group/extractors/thrift-extractor.js +2 -1
- package/dist/core/group/extractors/tree-sitter-scanner.js +2 -1
- package/dist/core/ingestion/call-processor.js +3 -2
- package/dist/core/ingestion/heritage-processor.js +3 -2
- package/dist/core/ingestion/import-processor.js +2 -1
- package/dist/core/ingestion/languages/c/arity-metadata.d.ts +14 -0
- package/dist/core/ingestion/languages/c/arity-metadata.js +94 -0
- package/dist/core/ingestion/languages/c/arity.d.ts +6 -0
- package/dist/core/ingestion/languages/c/arity.js +18 -0
- package/dist/core/ingestion/languages/c/captures.d.ts +2 -0
- package/dist/core/ingestion/languages/c/captures.js +105 -0
- package/dist/core/ingestion/languages/c/header-scan.d.ts +7 -0
- package/dist/core/ingestion/languages/c/header-scan.js +55 -0
- package/dist/core/ingestion/languages/c/import-decomposer.d.ts +8 -0
- package/dist/core/ingestion/languages/c/import-decomposer.js +50 -0
- package/dist/core/ingestion/languages/c/import-target.d.ts +14 -0
- package/dist/core/ingestion/languages/c/import-target.js +57 -0
- package/dist/core/ingestion/languages/c/index.d.ts +11 -0
- package/dist/core/ingestion/languages/c/index.js +11 -0
- package/dist/core/ingestion/languages/c/interpret.d.ts +14 -0
- package/dist/core/ingestion/languages/c/interpret.js +48 -0
- package/dist/core/ingestion/languages/c/merge-bindings.d.ts +7 -0
- package/dist/core/ingestion/languages/c/merge-bindings.js +23 -0
- package/dist/core/ingestion/languages/c/query.d.ts +3 -0
- package/dist/core/ingestion/languages/c/query.js +161 -0
- package/dist/core/ingestion/languages/c/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/languages/c/scope-resolver.js +60 -0
- package/dist/core/ingestion/languages/c/simple-hooks.d.ts +14 -0
- package/dist/core/ingestion/languages/c/simple-hooks.js +19 -0
- package/dist/core/ingestion/languages/c/static-linkage.d.ts +13 -0
- package/dist/core/ingestion/languages/c/static-linkage.js +57 -0
- package/dist/core/ingestion/languages/c-cpp.js +10 -0
- package/dist/core/ingestion/languages/csharp/captures.js +2 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +3 -2
- package/dist/core/ingestion/languages/go/captures.js +2 -1
- package/dist/core/ingestion/languages/go/range-binding.js +2 -1
- package/dist/core/ingestion/languages/python/captures.js +2 -1
- package/dist/core/ingestion/languages/typescript/captures.js +2 -1
- package/dist/core/ingestion/parsing-processor.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.js +1 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +2 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +13 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +4 -1
- package/dist/core/ingestion/workers/parse-worker.js +2 -1
- package/dist/core/lbug/lbug-adapter.js +8 -0
- package/dist/core/lbug/pool-adapter.js +19 -2
- package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
- package/dist/core/tree-sitter/safe-parse.js +32 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,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';
|