gitnexus 1.6.4 → 1.6.5-rc.10
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/README.md +1 -1
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +30 -12
- package/dist/cli/analyze.d.ts +18 -0
- package/dist/cli/analyze.js +33 -4
- package/dist/cli/augment.d.ts +1 -1
- package/dist/cli/augment.js +1 -1
- package/dist/cli/index.js +6 -1
- package/dist/core/augmentation/engine.d.ts +2 -2
- package/dist/core/augmentation/engine.js +2 -2
- package/dist/core/embeddings/ast-utils.js +2 -1
- package/dist/core/embeddings/http-client.js +22 -6
- package/dist/core/group/extractors/grpc-extractor.js +2 -1
- package/dist/core/group/extractors/http-patterns/python.js +175 -0
- 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/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +5 -1
- package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
- package/dist/core/tree-sitter/safe-parse.js +32 -0
- package/dist/server/git-clone.js +10 -13
- package/dist/storage/git.d.ts +17 -6
- package/dist/storage/git.js +46 -12
- package/package.json +1 -1
|
@@ -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';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Interpret a C #include capture into a ParsedImport.
|
|
4
|
+
* C includes are always wildcard imports (all symbols from the header).
|
|
5
|
+
*/
|
|
6
|
+
export declare function interpretCImport(captures: CaptureMatch): ParsedImport | null;
|
|
7
|
+
/**
|
|
8
|
+
* Interpret a C type-binding capture into a ParsedTypeBinding.
|
|
9
|
+
*/
|
|
10
|
+
export declare function interpretCTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a C type name: strip pointer/array syntax, qualifiers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeCTypeName(text: string): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpret a C #include capture into a ParsedImport.
|
|
3
|
+
* C includes are always wildcard imports (all symbols from the header).
|
|
4
|
+
*/
|
|
5
|
+
export function interpretCImport(captures) {
|
|
6
|
+
const source = captures['@import.source']?.text;
|
|
7
|
+
if (source === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
// System headers (e.g. <stdio.h>) are not resolved to local files
|
|
10
|
+
if (captures['@import.system'] !== undefined)
|
|
11
|
+
return null;
|
|
12
|
+
return { kind: 'wildcard', targetRaw: source };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Interpret a C type-binding capture into a ParsedTypeBinding.
|
|
16
|
+
*/
|
|
17
|
+
export function interpretCTypeBinding(captures) {
|
|
18
|
+
const name = captures['@type-binding.name']?.text;
|
|
19
|
+
const type = captures['@type-binding.type']?.text;
|
|
20
|
+
if (name === undefined || type === undefined)
|
|
21
|
+
return null;
|
|
22
|
+
let source = 'annotation';
|
|
23
|
+
if (captures['@type-binding.parameter'] !== undefined) {
|
|
24
|
+
source = 'parameter-annotation';
|
|
25
|
+
}
|
|
26
|
+
else if (captures['@type-binding.assignment'] !== undefined) {
|
|
27
|
+
source = 'assignment-inferred';
|
|
28
|
+
}
|
|
29
|
+
return { boundName: name, rawTypeName: normalizeCTypeName(type), source };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalize a C type name: strip pointer/array syntax, qualifiers.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeCTypeName(text) {
|
|
35
|
+
let t = text.trim();
|
|
36
|
+
// Strip const, volatile, restrict qualifiers
|
|
37
|
+
t = t.replace(/\b(const|volatile|restrict|static|extern|inline)\b/g, '').trim();
|
|
38
|
+
// Strip pointer stars
|
|
39
|
+
while (t.endsWith('*'))
|
|
40
|
+
t = t.slice(0, -1).trim();
|
|
41
|
+
while (t.startsWith('*'))
|
|
42
|
+
t = t.slice(1).trim();
|
|
43
|
+
// Strip array brackets
|
|
44
|
+
t = t.replace(/\[.*?\]/g, '').trim();
|
|
45
|
+
// Strip struct/union/enum prefixes
|
|
46
|
+
t = t.replace(/^(struct|union|enum)\s+/, '');
|
|
47
|
+
return t;
|
|
48
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BindingRef } from '../../../../_shared/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* C merge bindings: simple first-wins by tier (local > import > wildcard).
|
|
4
|
+
* C has no namespaces or reexports, but the tiers are defined for
|
|
5
|
+
* compatibility with the shared infrastructure.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cMergeBindings(existing: readonly BindingRef[], incoming: readonly BindingRef[], _scopeId: string): BindingRef[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const TIER = {
|
|
2
|
+
local: 0,
|
|
3
|
+
namespace: 1,
|
|
4
|
+
import: 2,
|
|
5
|
+
reexport: 3,
|
|
6
|
+
wildcard: 4,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* C merge bindings: simple first-wins by tier (local > import > wildcard).
|
|
10
|
+
* C has no namespaces or reexports, but the tiers are defined for
|
|
11
|
+
* compatibility with the shared infrastructure.
|
|
12
|
+
*/
|
|
13
|
+
export function cMergeBindings(existing, incoming, _scopeId) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
return [...existing, ...incoming]
|
|
16
|
+
.sort((a, b) => (TIER[a.origin] ?? 99) - (TIER[b.origin] ?? 99) || a.def.nodeId.localeCompare(b.def.nodeId))
|
|
17
|
+
.filter((binding) => {
|
|
18
|
+
if (seen.has(binding.def.nodeId))
|
|
19
|
+
return false;
|
|
20
|
+
seen.add(binding.def.nodeId);
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
}
|