purecontext-mcp 1.2.0 → 1.5.0
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/AGENT_INSTRUCTIONS.md +110 -784
- package/AGENT_REFERENCE.md +561 -0
- package/CHANGELOG.md +177 -6
- package/FRAMEWORK-ADAPTERS.md +351 -0
- package/LANGUAGE-SUPPORT.md +144 -0
- package/README.md +92 -12
- package/USER-GUIDE.md +8 -0
- package/dist/cli/hooks.d.ts +28 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +570 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/install-detect.d.ts +16 -0
- package/dist/cli/install-detect.d.ts.map +1 -0
- package/dist/cli/install-detect.js +70 -0
- package/dist/cli/install-detect.js.map +1 -0
- package/dist/cli/install-writers.d.ts +59 -0
- package/dist/cli/install-writers.d.ts.map +1 -0
- package/dist/cli/install-writers.js +292 -0
- package/dist/cli/install-writers.js.map +1 -0
- package/dist/cli/install.d.ts +14 -0
- package/dist/cli/install.d.ts.map +1 -0
- package/dist/cli/install.js +150 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/config/config-loader.js +3 -0
- package/dist/config/config-loader.js.map +1 -1
- package/dist/config/config-schema.d.ts +11 -0
- package/dist/config/config-schema.d.ts.map +1 -1
- package/dist/config/config-schema.js +15 -0
- package/dist/config/config-schema.js.map +1 -1
- package/dist/core/db/symbol-store.d.ts +1 -0
- package/dist/core/db/symbol-store.d.ts.map +1 -1
- package/dist/core/db/symbol-store.js +120 -6
- package/dist/core/db/symbol-store.js.map +1 -1
- package/dist/core/file-discovery.d.ts +6 -0
- package/dist/core/file-discovery.d.ts.map +1 -1
- package/dist/core/file-discovery.js +20 -13
- package/dist/core/file-discovery.js.map +1 -1
- package/dist/core/file-processor.d.ts.map +1 -1
- package/dist/core/file-processor.js +26 -1
- package/dist/core/file-processor.js.map +1 -1
- package/dist/core/git-log-reader.d.ts.map +1 -1
- package/dist/core/git-log-reader.js +21 -0
- package/dist/core/git-log-reader.js.map +1 -1
- package/dist/core/index-manager.d.ts.map +1 -1
- package/dist/core/index-manager.js +21 -7
- package/dist/core/index-manager.js.map +1 -1
- package/dist/core/indexing-worker.d.ts.map +1 -1
- package/dist/core/indexing-worker.js +14 -0
- package/dist/core/indexing-worker.js.map +1 -1
- package/dist/core/parse-dispatcher.d.ts.map +1 -1
- package/dist/core/parse-dispatcher.js +20 -5
- package/dist/core/parse-dispatcher.js.map +1 -1
- package/dist/core/search/query-preprocessor.d.ts +69 -3
- package/dist/core/search/query-preprocessor.d.ts.map +1 -1
- package/dist/core/search/query-preprocessor.js +450 -17
- package/dist/core/search/query-preprocessor.js.map +1 -1
- package/dist/core/search/relevance-ranker.d.ts +60 -5
- package/dist/core/search/relevance-ranker.d.ts.map +1 -1
- package/dist/core/search/relevance-ranker.js +931 -33
- package/dist/core/search/relevance-ranker.js.map +1 -1
- package/dist/core/test-mapper.d.ts.map +1 -1
- package/dist/core/test-mapper.js +7 -1
- package/dist/core/test-mapper.js.map +1 -1
- package/dist/core/types.d.ts +28 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/handlers/angular-html.d.ts +3 -0
- package/dist/handlers/angular-html.d.ts.map +1 -0
- package/dist/handlers/angular-html.js +215 -0
- package/dist/handlers/angular-html.js.map +1 -0
- package/dist/handlers/c.d.ts.map +1 -1
- package/dist/handlers/c.js +19 -0
- package/dist/handlers/c.js.map +1 -1
- package/dist/handlers/cpp-macro-registry.d.ts +21 -0
- package/dist/handlers/cpp-macro-registry.d.ts.map +1 -0
- package/dist/handlers/cpp-macro-registry.js +44 -0
- package/dist/handlers/cpp-macro-registry.js.map +1 -0
- package/dist/handlers/cpp.d.ts.map +1 -1
- package/dist/handlers/cpp.js +579 -10
- package/dist/handlers/cpp.js.map +1 -1
- package/dist/handlers/csharp.d.ts.map +1 -1
- package/dist/handlers/csharp.js +39 -2
- package/dist/handlers/csharp.js.map +1 -1
- package/dist/handlers/css.d.ts +3 -0
- package/dist/handlers/css.d.ts.map +1 -0
- package/dist/handlers/css.js +154 -0
- package/dist/handlers/css.js.map +1 -0
- package/dist/handlers/erlang.d.ts.map +1 -1
- package/dist/handlers/erlang.js +8 -1
- package/dist/handlers/erlang.js.map +1 -1
- package/dist/handlers/fortran.js +1 -1
- package/dist/handlers/fortran.js.map +1 -1
- package/dist/handlers/go.d.ts.map +1 -1
- package/dist/handlers/go.js +87 -2
- package/dist/handlers/go.js.map +1 -1
- package/dist/handlers/handler-registry.d.ts.map +1 -1
- package/dist/handlers/handler-registry.js +4 -0
- package/dist/handlers/handler-registry.js.map +1 -1
- package/dist/handlers/hcl.d.ts +3 -0
- package/dist/handlers/hcl.d.ts.map +1 -0
- package/dist/handlers/hcl.js +193 -0
- package/dist/handlers/hcl.js.map +1 -0
- package/dist/handlers/java.d.ts.map +1 -1
- package/dist/handlers/java.js +33 -16
- package/dist/handlers/java.js.map +1 -1
- package/dist/handlers/kotlin.d.ts.map +1 -1
- package/dist/handlers/kotlin.js +48 -3
- package/dist/handlers/kotlin.js.map +1 -1
- package/dist/handlers/less.d.ts +3 -0
- package/dist/handlers/less.d.ts.map +1 -0
- package/dist/handlers/less.js +255 -0
- package/dist/handlers/less.js.map +1 -0
- package/dist/handlers/objective-c.d.ts.map +1 -1
- package/dist/handlers/objective-c.js +122 -64
- package/dist/handlers/objective-c.js.map +1 -1
- package/dist/handlers/openapi.d.ts.map +1 -1
- package/dist/handlers/openapi.js +30 -5
- package/dist/handlers/openapi.js.map +1 -1
- package/dist/handlers/php.d.ts.map +1 -1
- package/dist/handlers/php.js +287 -41
- package/dist/handlers/php.js.map +1 -1
- package/dist/handlers/protobuf.d.ts.map +1 -1
- package/dist/handlers/protobuf.js +1 -0
- package/dist/handlers/protobuf.js.map +1 -1
- package/dist/handlers/python.d.ts.map +1 -1
- package/dist/handlers/python.js +1 -3
- package/dist/handlers/python.js.map +1 -1
- package/dist/handlers/ruby-dsl.d.ts +23 -0
- package/dist/handlers/ruby-dsl.d.ts.map +1 -0
- package/dist/handlers/ruby-dsl.js +251 -0
- package/dist/handlers/ruby-dsl.js.map +1 -0
- package/dist/handlers/ruby.d.ts.map +1 -1
- package/dist/handlers/ruby.js +29 -4
- package/dist/handlers/ruby.js.map +1 -1
- package/dist/handlers/rust.d.ts.map +1 -1
- package/dist/handlers/rust.js +98 -2
- package/dist/handlers/rust.js.map +1 -1
- package/dist/handlers/scss.d.ts +3 -0
- package/dist/handlers/scss.d.ts.map +1 -0
- package/dist/handlers/scss.js +290 -0
- package/dist/handlers/scss.js.map +1 -0
- package/dist/handlers/sql.d.ts.map +1 -1
- package/dist/handlers/sql.js +37 -18
- package/dist/handlers/sql.js.map +1 -1
- package/dist/handlers/typescript.d.ts.map +1 -1
- package/dist/handlers/typescript.js +65 -17
- package/dist/handlers/typescript.js.map +1 -1
- package/dist/handlers/xml.d.ts.map +1 -1
- package/dist/handlers/xml.js +35 -2
- package/dist/handlers/xml.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -1
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +10 -0
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/server/tools/detect-antipatterns.d.ts +1 -1
- package/dist/server/tools/get-architecture-snapshot.d.ts +1 -1
- package/dist/server/tools/get-entry-points.d.ts +1 -1
- package/dist/server/tools/get-lexical-scope-matches.d.ts +54 -0
- package/dist/server/tools/get-lexical-scope-matches.d.ts.map +1 -0
- package/dist/server/tools/get-lexical-scope-matches.js +470 -0
- package/dist/server/tools/get-lexical-scope-matches.js.map +1 -0
- package/dist/server/tools/search-symbols.d.ts +10 -0
- package/dist/server/tools/search-symbols.d.ts.map +1 -1
- package/dist/server/tools/search-symbols.js +353 -8
- package/dist/server/tools/search-symbols.js.map +1 -1
- package/dist/server/tools/trace-invocation-chain.d.ts +53 -0
- package/dist/server/tools/trace-invocation-chain.d.ts.map +1 -0
- package/dist/server/tools/trace-invocation-chain.js +280 -0
- package/dist/server/tools/trace-invocation-chain.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/02-installation.md +89 -17
- package/docs/05-cli-reference.md +89 -0
- package/docs/dev/benchmark-findings-eu-za-tebe.md +210 -0
- package/docs/dev/phase-35-coverage-audit.md +469 -0
- package/package.json +4 -1
package/dist/handlers/cpp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { dirname, resolve } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { findMacroEntry } from './cpp-macro-registry.js';
|
|
4
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
const GRAMMARS_DIR = resolve(__dirname, '../../grammars');
|
|
6
7
|
// ─── Symbol ID ────────────────────────────────────────────────────────────────
|
|
@@ -18,7 +19,53 @@ function trunc(s, max = 120) {
|
|
|
18
19
|
s = s.replace(/\s+/g, ' ').trim();
|
|
19
20
|
return s.length > max ? s.slice(0, max - 3) + '...' : s;
|
|
20
21
|
}
|
|
22
|
+
// ─── Body snippet ─────────────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Extract a short text snippet from a class/struct body or function body.
|
|
25
|
+
* Normalises whitespace and caps at 200 characters.
|
|
26
|
+
*/
|
|
27
|
+
function extractBodySnippet(bodyNode, src) {
|
|
28
|
+
// Skip the opening '{' (+1 char); grab up to 600 raw chars
|
|
29
|
+
const start = bodyNode.startIndex + 1;
|
|
30
|
+
const end = Math.min(start + 600, bodyNode.endIndex - 1);
|
|
31
|
+
if (start >= end)
|
|
32
|
+
return '';
|
|
33
|
+
return src
|
|
34
|
+
.slice(start, end)
|
|
35
|
+
.replace(/\s+/g, ' ')
|
|
36
|
+
.trim()
|
|
37
|
+
.slice(0, 200);
|
|
38
|
+
}
|
|
21
39
|
// ─── Declarator name extraction ───────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Strip template arguments from a qualified identifier scope part.
|
|
42
|
+
* Handles: `Scene<Float, Spectrum>::render` → `Scene::render`
|
|
43
|
+
* `Outer::Inner<T>::method` → `Outer::Inner::method`
|
|
44
|
+
* `Foo::bar` → `Foo::bar` (unchanged)
|
|
45
|
+
*/
|
|
46
|
+
function stripTemplateArgsFromQualified(node, src) {
|
|
47
|
+
// template_type like `Scene<Float, Spectrum>` → return just the base name
|
|
48
|
+
if (node.type === 'template_type') {
|
|
49
|
+
const nameNode = node.children.find((c) => c.isNamed && (c.type === 'type_identifier' || c.type === 'namespace_identifier'));
|
|
50
|
+
return nameNode ? nodeText(nameNode, src) : nodeText(node, src);
|
|
51
|
+
}
|
|
52
|
+
// qualified_identifier: recurse to strip template args at each scope level
|
|
53
|
+
if (node.type === 'qualified_identifier') {
|
|
54
|
+
// Find the '::' separator (first unnamed '::' child)
|
|
55
|
+
const dcolonIdx = node.children.findIndex((c) => !c.isNamed && nodeText(c, src) === '::');
|
|
56
|
+
if (dcolonIdx < 0)
|
|
57
|
+
return nodeText(node, src);
|
|
58
|
+
// Scope is the last named child before '::'
|
|
59
|
+
const scopeChild = node.children.slice(0, dcolonIdx).findLast((c) => c.isNamed);
|
|
60
|
+
// Name is the first named child after '::'
|
|
61
|
+
const nameChild = node.children.slice(dcolonIdx + 1).find((c) => c.isNamed);
|
|
62
|
+
const scope = scopeChild ? stripTemplateArgsFromQualified(scopeChild, src) : '';
|
|
63
|
+
// Also recurse on the name part to handle template_type / qualified_identifier names
|
|
64
|
+
const name = nameChild ? stripTemplateArgsFromQualified(nameChild, src) : '';
|
|
65
|
+
return scope ? `${scope}::${name}` : name;
|
|
66
|
+
}
|
|
67
|
+
return nodeText(node, src);
|
|
68
|
+
}
|
|
22
69
|
/**
|
|
23
70
|
* Recursively unwrap C++ declarator nodes to find the innermost name.
|
|
24
71
|
* Handles qualified identifiers, destructor names, operator names,
|
|
@@ -30,8 +77,9 @@ function extractDeclaratorName(declarator, src) {
|
|
|
30
77
|
case 'field_identifier':
|
|
31
78
|
return nodeText(declarator, src);
|
|
32
79
|
case 'qualified_identifier':
|
|
33
|
-
// Out-of-class definition like `Foo::bar
|
|
34
|
-
|
|
80
|
+
// Out-of-class definition like `Foo::bar`, `Foo::~Foo`, or
|
|
81
|
+
// `Scene<Float, Spectrum>::render` — strip template args from scope parts.
|
|
82
|
+
return stripTemplateArgsFromQualified(declarator, src);
|
|
35
83
|
case 'destructor_name':
|
|
36
84
|
// `~Foo` — return as-is
|
|
37
85
|
return nodeText(declarator, src);
|
|
@@ -142,8 +190,7 @@ function extractDocstring(node) {
|
|
|
142
190
|
}
|
|
143
191
|
if (lineComments.length > 0) {
|
|
144
192
|
const joined = lineComments.join(' ');
|
|
145
|
-
|
|
146
|
-
return (match ? match[1].trim() : joined) || null;
|
|
193
|
+
return joined.slice(0, 400) || null;
|
|
147
194
|
}
|
|
148
195
|
// Block comment /* ... */ or /** ... */
|
|
149
196
|
if (prev && prev.type === 'comment') {
|
|
@@ -158,8 +205,7 @@ function extractDocstring(node) {
|
|
|
158
205
|
.join(' ');
|
|
159
206
|
if (!inner)
|
|
160
207
|
return null;
|
|
161
|
-
|
|
162
|
-
return (match ? match[1].trim() : inner) || null;
|
|
208
|
+
return inner.slice(0, 400) || null;
|
|
163
209
|
}
|
|
164
210
|
}
|
|
165
211
|
return null;
|
|
@@ -173,8 +219,83 @@ function extractSymbols(tree, source, filePath) {
|
|
|
173
219
|
return symbols;
|
|
174
220
|
}
|
|
175
221
|
function walkNodes(nodes, ctx, filePath, src, symbols, templatePrefix) {
|
|
176
|
-
|
|
222
|
+
let i = 0;
|
|
223
|
+
while (i < nodes.length) {
|
|
224
|
+
const node = nodes[i];
|
|
225
|
+
// ── Detect sibling-level class/struct declaration (NAMESPACE_BEGIN pattern) ──
|
|
226
|
+
// When NAMESPACE_BEGIN macros (or other constructs) prevent proper parsing,
|
|
227
|
+
// tree-sitter may scatter the class declaration across consecutive sibling nodes:
|
|
228
|
+
// [i] class_specifier (no field_declaration_list body — e.g. "class MI_EXPORT_LIB")
|
|
229
|
+
// [i+1] ERROR (contains the class name as first identifier/type_identifier)
|
|
230
|
+
// [i+k] { (opening brace)
|
|
231
|
+
// [i+k+1..j-1] body children
|
|
232
|
+
// [j] } (closing brace)
|
|
233
|
+
//
|
|
234
|
+
// This fires only when the class_specifier has no body (otherwise walkNode handles it).
|
|
235
|
+
if ((node.type === 'class_specifier' || node.type === 'struct_specifier') &&
|
|
236
|
+
!node.children.some((c) => c.type === 'field_declaration_list')) {
|
|
237
|
+
const nextSib = nodes[i + 1];
|
|
238
|
+
if (nextSib && nextSib.type === 'ERROR') {
|
|
239
|
+
const nameIdent = nextSib.children.find((c) => c.isNamed && (c.type === 'identifier' || c.type === 'type_identifier'));
|
|
240
|
+
const localName = nameIdent ? nodeText(nameIdent, src) : null;
|
|
241
|
+
if (localName && localName !== 'final' && localName !== 'override') {
|
|
242
|
+
// Find the opening brace in subsequent siblings (skipping base-class / template args)
|
|
243
|
+
let braceAbsIdx = -1;
|
|
244
|
+
for (let j = i + 2; j < nodes.length && j < i + 10; j++) {
|
|
245
|
+
if (nodes[j].type === '{') {
|
|
246
|
+
braceAbsIdx = j;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (braceAbsIdx >= 0) {
|
|
251
|
+
const isStruct = node.type === 'struct_specifier';
|
|
252
|
+
// Find closing brace
|
|
253
|
+
let closeAbsIdx = nodes.length;
|
|
254
|
+
for (let j = braceAbsIdx + 1; j < nodes.length; j++) {
|
|
255
|
+
if (nodes[j].type === '}') {
|
|
256
|
+
closeAbsIdx = j;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const bodyChildren = nodes.slice(braceAbsIdx + 1, closeAbsIdx);
|
|
261
|
+
// Body snippet
|
|
262
|
+
const braceNode = nodes[braceAbsIdx];
|
|
263
|
+
const closeNode = closeAbsIdx < nodes.length ? nodes[closeAbsIdx] : null;
|
|
264
|
+
const bodyRaw = src.slice(braceNode.endIndex, Math.min(braceNode.endIndex + 600, closeNode ? closeNode.startIndex : node.endIndex));
|
|
265
|
+
const bodySnippet = bodyRaw.replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
266
|
+
const nsPart = ctx.nsStack.length > 0 ? ctx.nsStack.join('::') + '::' : '';
|
|
267
|
+
const qualName = nsPart + localName;
|
|
268
|
+
const kind = isStruct ? 'struct' : 'class';
|
|
269
|
+
// Look back for template_parameter_list among preceding siblings
|
|
270
|
+
const tmplParamsNode = nodes.slice(0, i).findLast((c) => c.type === 'template_parameter_list');
|
|
271
|
+
const tmplParamText = tmplParamsNode ? nodeText(tmplParamsNode, src) : '';
|
|
272
|
+
const tmplPrefixLocal = tmplParamText
|
|
273
|
+
? `template${tmplParamText}`
|
|
274
|
+
: (templatePrefix ?? null);
|
|
275
|
+
let sig = `${isStruct ? 'struct' : 'class'} ${qualName}`;
|
|
276
|
+
if (tmplPrefixLocal)
|
|
277
|
+
sig = `${tmplPrefixLocal} ${sig}`;
|
|
278
|
+
symbols.push({
|
|
279
|
+
id: makeId(filePath, qualName, kind),
|
|
280
|
+
name: qualName,
|
|
281
|
+
kind,
|
|
282
|
+
filePath,
|
|
283
|
+
startByte: node.startIndex,
|
|
284
|
+
endByte: closeNode ? closeNode.endIndex : node.endIndex,
|
|
285
|
+
signature: trunc(sig),
|
|
286
|
+
summary: extractDocstring(node) ?? `C++ ${kind}: ${qualName}`,
|
|
287
|
+
...(bodySnippet ? { bodySnippet } : {}),
|
|
288
|
+
});
|
|
289
|
+
walkErrorClassBody(bodyChildren, { nsStack: ctx.nsStack, className: qualName }, filePath, src, symbols, isStruct);
|
|
290
|
+
// Advance past all consumed siblings (ERROR, template_function, {, body, })
|
|
291
|
+
i = closeAbsIdx + 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
177
297
|
walkNode(node, ctx, filePath, src, symbols, templatePrefix);
|
|
298
|
+
i++;
|
|
178
299
|
}
|
|
179
300
|
}
|
|
180
301
|
function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
@@ -219,7 +340,16 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
219
340
|
}
|
|
220
341
|
// ── class_specifier ──────────────────────────────────────────────────────
|
|
221
342
|
case 'class_specifier': {
|
|
222
|
-
|
|
343
|
+
// Use the LAST type_identifier before the base clause or body.
|
|
344
|
+
// This handles `class MI_EXPORT_LIB ClassName` patterns correctly —
|
|
345
|
+
// the export macro appears as the first type_identifier, the real name last.
|
|
346
|
+
let nameNode;
|
|
347
|
+
for (const child of node.children) {
|
|
348
|
+
if (child.type === 'base_class_clause' || child.type === 'field_declaration_list')
|
|
349
|
+
break;
|
|
350
|
+
if (child.type === 'type_identifier')
|
|
351
|
+
nameNode = child;
|
|
352
|
+
}
|
|
223
353
|
if (!nameNode)
|
|
224
354
|
break; // anonymous class
|
|
225
355
|
const localName = nodeText(nameNode, src);
|
|
@@ -237,6 +367,7 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
237
367
|
endByte: node.endIndex,
|
|
238
368
|
signature: buildClassSignature(node, src, qualName, false, templatePrefix ?? null),
|
|
239
369
|
summary: extractDocstring(node) ?? `C++ class: ${qualName}`,
|
|
370
|
+
bodySnippet: extractBodySnippet(bodyNode, src),
|
|
240
371
|
});
|
|
241
372
|
// Walk class body with private as default access
|
|
242
373
|
walkClassBody(bodyNode, { nsStack: ctx.nsStack, className: qualName }, filePath, src, symbols, false);
|
|
@@ -244,7 +375,15 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
244
375
|
}
|
|
245
376
|
// ── struct_specifier ─────────────────────────────────────────────────────
|
|
246
377
|
case 'struct_specifier': {
|
|
247
|
-
|
|
378
|
+
// Use the LAST type_identifier before the base clause or body.
|
|
379
|
+
// Handles `struct MI_EXPORT_LIB StructName` patterns correctly.
|
|
380
|
+
let nameNode;
|
|
381
|
+
for (const child of node.children) {
|
|
382
|
+
if (child.type === 'base_class_clause' || child.type === 'field_declaration_list')
|
|
383
|
+
break;
|
|
384
|
+
if (child.type === 'type_identifier')
|
|
385
|
+
nameNode = child;
|
|
386
|
+
}
|
|
248
387
|
if (!nameNode)
|
|
249
388
|
break;
|
|
250
389
|
const localName = nodeText(nameNode, src);
|
|
@@ -262,6 +401,7 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
262
401
|
endByte: node.endIndex,
|
|
263
402
|
signature: buildClassSignature(node, src, qualName, true, templatePrefix ?? null),
|
|
264
403
|
summary: extractDocstring(node) ?? `C++ struct: ${qualName}`,
|
|
404
|
+
bodySnippet: extractBodySnippet(bodyNode, src),
|
|
265
405
|
});
|
|
266
406
|
// Walk struct body with public as default access
|
|
267
407
|
walkClassBody(bodyNode, { nsStack: ctx.nsStack, className: qualName }, filePath, src, symbols, true);
|
|
@@ -326,6 +466,65 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
326
466
|
}
|
|
327
467
|
// ── function_definition at namespace / global scope ──────────────────────
|
|
328
468
|
case 'function_definition': {
|
|
469
|
+
// ── Detect `class/struct MACRO ClassName [: Base] { ... }` misparse ───
|
|
470
|
+
// tree-sitter-cpp misparsed these as function_definition where the
|
|
471
|
+
// "return type" is class_specifier(MACRO) and body is compound_statement.
|
|
472
|
+
// e.g. `class MI_EXPORT_LIB Scene : public Base { ... }`.
|
|
473
|
+
const exportMacroType = node.children.find((c) => c.isNamed && (c.type === 'class_specifier' || c.type === 'struct_specifier'));
|
|
474
|
+
if (exportMacroType) {
|
|
475
|
+
const isStruct = exportMacroType.type === 'struct_specifier';
|
|
476
|
+
const typeIdx = node.children.indexOf(exportMacroType);
|
|
477
|
+
// Find real class name: identifier after specifier, or inside ERROR node
|
|
478
|
+
let localName = null;
|
|
479
|
+
for (let i = typeIdx + 1; i < node.children.length; i++) {
|
|
480
|
+
const c = node.children[i];
|
|
481
|
+
if (c.type === 'compound_statement')
|
|
482
|
+
break; // hit the body
|
|
483
|
+
if (c.isNamed && c.type === 'identifier') {
|
|
484
|
+
localName = nodeText(c, src);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (c.type === 'ERROR') {
|
|
488
|
+
// Class name lands in ERROR when there's a base-class clause after it
|
|
489
|
+
const inner = c.children.find((ec) => ec.isNamed && ec.type === 'identifier');
|
|
490
|
+
if (inner) {
|
|
491
|
+
localName = nodeText(inner, src);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Guard: if no class name found, this is NOT an export macro class —
|
|
497
|
+
// it's a regular C function whose return type happens to be a
|
|
498
|
+
// struct/class specifier (e.g. `struct AP_info *get_ap_info(...)`).
|
|
499
|
+
// Fall through to the normal function_definition extraction below.
|
|
500
|
+
if (localName) {
|
|
501
|
+
const nsPart = ctx.nsStack.length > 0 ? ctx.nsStack.join('::') + '::' : '';
|
|
502
|
+
const qualName = nsPart + localName;
|
|
503
|
+
const kind = isStruct ? 'struct' : 'class';
|
|
504
|
+
const keyword = isStruct ? 'struct' : 'class';
|
|
505
|
+
let sig = `${keyword} ${qualName}`;
|
|
506
|
+
if (templatePrefix)
|
|
507
|
+
sig = trunc(`${templatePrefix} ${sig}`);
|
|
508
|
+
const body = node.children.find((c) => c.type === 'compound_statement');
|
|
509
|
+
symbols.push({
|
|
510
|
+
id: makeId(filePath, qualName, kind),
|
|
511
|
+
name: qualName,
|
|
512
|
+
kind,
|
|
513
|
+
filePath,
|
|
514
|
+
startByte: node.startIndex,
|
|
515
|
+
endByte: node.endIndex,
|
|
516
|
+
signature: trunc(sig),
|
|
517
|
+
summary: extractDocstring(node) ?? `C++ ${kind}: ${qualName}`,
|
|
518
|
+
...(body ? { bodySnippet: extractBodySnippet(body, src) } : {}),
|
|
519
|
+
});
|
|
520
|
+
// Walk the compound_statement to extract public method declarations
|
|
521
|
+
if (body) {
|
|
522
|
+
walkExportMacroClassBody(body, { nsStack: ctx.nsStack, className: qualName }, filePath, src, symbols, isStruct);
|
|
523
|
+
}
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
// localName was null → fall through to normal function extraction
|
|
527
|
+
}
|
|
329
528
|
const declaratorChild = node.children.find((c) => c.isNamed &&
|
|
330
529
|
[
|
|
331
530
|
'function_declarator',
|
|
@@ -381,6 +580,175 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
381
580
|
});
|
|
382
581
|
break;
|
|
383
582
|
}
|
|
583
|
+
// ── type_definition: C-style typedef struct/enum/union ───────────────────
|
|
584
|
+
// Handles: typedef struct { ... } Name; and typedef enum { ... } Name;
|
|
585
|
+
// Important for C headers (.h) included in C++ projects.
|
|
586
|
+
case 'type_definition': {
|
|
587
|
+
const typeChild = node.children.find((c) => c.isNamed && (c.type === 'struct_specifier' ||
|
|
588
|
+
c.type === 'union_specifier' ||
|
|
589
|
+
c.type === 'enum_specifier'));
|
|
590
|
+
if (!typeChild)
|
|
591
|
+
break;
|
|
592
|
+
// The typedef alias is the last type_identifier child of the type_definition
|
|
593
|
+
// (not inside the type specifier), e.g. `typedef struct { ... } AuthSession;`
|
|
594
|
+
const declaratorNode = node.children.findLast((c) => c.type === 'type_identifier');
|
|
595
|
+
if (!declaratorNode)
|
|
596
|
+
break;
|
|
597
|
+
const alias = nodeText(declaratorNode, src);
|
|
598
|
+
const nsPart = ctx.nsStack.length > 0 ? ctx.nsStack.join('::') + '::' : '';
|
|
599
|
+
const qualName = nsPart + alias;
|
|
600
|
+
if (typeChild.type === 'enum_specifier') {
|
|
601
|
+
const enumeratorList = typeChild.children.find((c) => c.type === 'enumerator_list');
|
|
602
|
+
let sig;
|
|
603
|
+
if (enumeratorList) {
|
|
604
|
+
const vals = enumeratorList.children
|
|
605
|
+
.filter((c) => c.isNamed && c.type === 'enumerator')
|
|
606
|
+
.slice(0, 3)
|
|
607
|
+
.map((c) => {
|
|
608
|
+
const id = c.children.find((cc) => cc.type === 'identifier');
|
|
609
|
+
return id ? nodeText(id, src) : nodeText(c, src);
|
|
610
|
+
});
|
|
611
|
+
const suffix = enumeratorList.children.filter((c) => c.isNamed && c.type === 'enumerator').length > 3 ? ', ...' : '';
|
|
612
|
+
sig = trunc(`typedef enum ${alias} { ${vals.join(', ')}${suffix} }`);
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
sig = trunc(`typedef enum ${alias}`);
|
|
616
|
+
}
|
|
617
|
+
symbols.push({
|
|
618
|
+
id: makeId(filePath, qualName, 'enum'),
|
|
619
|
+
name: qualName,
|
|
620
|
+
kind: 'enum',
|
|
621
|
+
filePath,
|
|
622
|
+
startByte: node.startIndex,
|
|
623
|
+
endByte: node.endIndex,
|
|
624
|
+
signature: sig,
|
|
625
|
+
summary: extractDocstring(node) ?? `C enum: ${qualName}`,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
// struct or union
|
|
630
|
+
const isUnion = typeChild.type === 'union_specifier';
|
|
631
|
+
symbols.push({
|
|
632
|
+
id: makeId(filePath, qualName, 'struct'),
|
|
633
|
+
name: qualName,
|
|
634
|
+
kind: 'struct',
|
|
635
|
+
filePath,
|
|
636
|
+
startByte: node.startIndex,
|
|
637
|
+
endByte: node.endIndex,
|
|
638
|
+
signature: trunc(`typedef ${isUnion ? 'union' : 'struct'} ${alias}`),
|
|
639
|
+
summary: extractDocstring(node) ?? `C ${isUnion ? 'union' : 'struct'}: ${qualName}`,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
// ── ERROR recovery: template class declarations misparsed by tree-sitter ──
|
|
645
|
+
// Fires for mitsuba3-style headers where NAMESPACE_BEGIN(mitsuba) macros
|
|
646
|
+
// (or preceding #include directives) confuse tree-sitter so the whole
|
|
647
|
+
// `template <Ts...> class MI_EXPORT_LIB ClassName final : public Base { ... }`
|
|
648
|
+
// becomes an ERROR node instead of a proper template_declaration.
|
|
649
|
+
//
|
|
650
|
+
// Two child patterns observed:
|
|
651
|
+
//
|
|
652
|
+
// Pattern 1 — export macro (scene.h, MI_EXPORT_LIB):
|
|
653
|
+
// template
|
|
654
|
+
// template_parameter_list <typename Float, typename Spectrum>
|
|
655
|
+
// class_specifier "class MI_EXPORT_LIB"
|
|
656
|
+
// ERROR "Scene final : public ..."
|
|
657
|
+
// identifier "Scene" ← class name (clean)
|
|
658
|
+
// {
|
|
659
|
+
// ... labeled_statement / ERROR / declaration ... (body)
|
|
660
|
+
//
|
|
661
|
+
// Pattern 2 — no export macro (microfacet.h, MicrofacetDistribution):
|
|
662
|
+
// template
|
|
663
|
+
// template_parameter_list <typename Float, typename Spectrum>
|
|
664
|
+
// class (bare keyword, unnamed)
|
|
665
|
+
// type_identifier "MicrofacetDistribution" ← class name directly
|
|
666
|
+
// base_class_clause
|
|
667
|
+
// {
|
|
668
|
+
// access_specifier public
|
|
669
|
+
// ... declaration / function_definition ... (body)
|
|
670
|
+
case 'ERROR': {
|
|
671
|
+
const children = node.children;
|
|
672
|
+
// Must have 'template' keyword child
|
|
673
|
+
if (!children.some((c) => c.type === 'template'))
|
|
674
|
+
break;
|
|
675
|
+
const tmplParams = children.find((c) => c.type === 'template_parameter_list');
|
|
676
|
+
if (!tmplParams)
|
|
677
|
+
break;
|
|
678
|
+
let isStruct = false;
|
|
679
|
+
let localName = null;
|
|
680
|
+
// Pattern 1: class_specifier (export macro: "class MI_EXPORT_LIB")
|
|
681
|
+
const classSpec = children.find((c) => c.type === 'class_specifier' || c.type === 'struct_specifier');
|
|
682
|
+
if (classSpec) {
|
|
683
|
+
isStruct = classSpec.type === 'struct_specifier';
|
|
684
|
+
const classSpecIdx = children.indexOf(classSpec);
|
|
685
|
+
for (let i = classSpecIdx + 1; i < children.length; i++) {
|
|
686
|
+
const c = children[i];
|
|
687
|
+
if (c.type === '{')
|
|
688
|
+
break;
|
|
689
|
+
if (c.isNamed && c.type === 'identifier') {
|
|
690
|
+
localName = nodeText(c, src);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
if (c.type === 'ERROR') {
|
|
694
|
+
const inner = c.children.find((ec) => ec.isNamed && ec.type === 'identifier');
|
|
695
|
+
if (inner) {
|
|
696
|
+
localName = nodeText(inner, src);
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
// Pattern 2: bare 'class'/'struct' keyword followed by type_identifier
|
|
704
|
+
const classKwIdx = children.findIndex((c) => c.type === 'class' || c.type === 'struct');
|
|
705
|
+
if (classKwIdx < 0)
|
|
706
|
+
break;
|
|
707
|
+
isStruct = children[classKwIdx].type === 'struct';
|
|
708
|
+
for (let i = classKwIdx + 1; i < children.length; i++) {
|
|
709
|
+
const c = children[i];
|
|
710
|
+
if (c.type === '{')
|
|
711
|
+
break;
|
|
712
|
+
if (c.isNamed && (c.type === 'type_identifier' || c.type === 'identifier')) {
|
|
713
|
+
localName = nodeText(c, src);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (!localName || localName === 'final' || localName === 'override')
|
|
719
|
+
break;
|
|
720
|
+
const nsPart = ctx.nsStack.length > 0 ? ctx.nsStack.join('::') + '::' : '';
|
|
721
|
+
const qualName = nsPart + localName;
|
|
722
|
+
const kind = isStruct ? 'struct' : 'class';
|
|
723
|
+
const tmplParamText = nodeText(tmplParams, src);
|
|
724
|
+
const tmplPrefix = `template${tmplParamText}`;
|
|
725
|
+
// Build body snippet from the content between '{' and the node end
|
|
726
|
+
const braceIdx = children.findIndex((c) => c.type === '{');
|
|
727
|
+
let bodySnippet = '';
|
|
728
|
+
if (braceIdx >= 0) {
|
|
729
|
+
const afterBrace = children[braceIdx].endIndex;
|
|
730
|
+
const closingBrace = children.findLast((c) => c.type === '}');
|
|
731
|
+
const bodyEnd = closingBrace ? closingBrace.startIndex : node.endIndex;
|
|
732
|
+
const rawBody = src.slice(afterBrace, Math.min(afterBrace + 600, bodyEnd));
|
|
733
|
+
bodySnippet = rawBody.replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
734
|
+
}
|
|
735
|
+
symbols.push({
|
|
736
|
+
id: makeId(filePath, qualName, kind),
|
|
737
|
+
name: qualName,
|
|
738
|
+
kind,
|
|
739
|
+
filePath,
|
|
740
|
+
startByte: node.startIndex,
|
|
741
|
+
endByte: node.endIndex,
|
|
742
|
+
signature: trunc(`${tmplPrefix} ${isStruct ? 'struct' : 'class'} ${qualName}`),
|
|
743
|
+
summary: extractDocstring(node) ?? `C++ ${kind}: ${qualName}`,
|
|
744
|
+
...(bodySnippet ? { bodySnippet } : {}),
|
|
745
|
+
});
|
|
746
|
+
// Walk body children for method symbols
|
|
747
|
+
if (braceIdx >= 0) {
|
|
748
|
+
walkErrorClassBody(children.slice(braceIdx + 1), { nsStack: ctx.nsStack, className: qualName }, filePath, src, symbols, isStruct);
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
384
752
|
// ── preproc_def: object-like #define macros ──────────────────────────────
|
|
385
753
|
case 'preproc_def': {
|
|
386
754
|
const nameNode = node.children.find((c) => c.type === 'identifier');
|
|
@@ -405,10 +773,211 @@ function walkNode(node, ctx, filePath, src, symbols, templatePrefix) {
|
|
|
405
773
|
});
|
|
406
774
|
break;
|
|
407
775
|
}
|
|
776
|
+
// ── expression_statement: may wrap a registration macro call_expression ────
|
|
777
|
+
// tree-sitter-cpp parses REGISTER_OP("Abs") and FUNCTION_REGISTER(Foo) as
|
|
778
|
+
// expression_statement > call_expression, NOT as macro_invocation nodes.
|
|
779
|
+
case 'expression_statement': {
|
|
780
|
+
const callExpr = node.children.find((c) => c.type === 'call_expression');
|
|
781
|
+
if (!callExpr)
|
|
782
|
+
break;
|
|
783
|
+
const macroNameNode = callExpr.children.find((c) => c.type === 'identifier');
|
|
784
|
+
if (!macroNameNode)
|
|
785
|
+
break;
|
|
786
|
+
const macroName = nodeText(macroNameNode, src);
|
|
787
|
+
const entry = findMacroEntry(macroName);
|
|
788
|
+
if (!entry)
|
|
789
|
+
break;
|
|
790
|
+
const argList = callExpr.children.find((c) => c.type === 'argument_list');
|
|
791
|
+
if (!argList)
|
|
792
|
+
break;
|
|
793
|
+
let symbolName = null;
|
|
794
|
+
if (entry.argKind === 'identifier' || entry.argKind === 'any') {
|
|
795
|
+
const identArg = argList.children.find((c) => c.type === 'identifier');
|
|
796
|
+
if (identArg)
|
|
797
|
+
symbolName = nodeText(identArg, src);
|
|
798
|
+
}
|
|
799
|
+
if (!symbolName && (entry.argKind === 'string' || entry.argKind === 'any')) {
|
|
800
|
+
const strArg = argList.children.find((c) => c.type === 'string_literal' || c.type === 'raw_string_literal');
|
|
801
|
+
if (strArg) {
|
|
802
|
+
const content = strArg.children.find((c) => c.type === 'string_content');
|
|
803
|
+
symbolName = content
|
|
804
|
+
? nodeText(content, src)
|
|
805
|
+
: nodeText(strArg, src).replace(/^["']|["']$/g, '').trim();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (!symbolName)
|
|
809
|
+
break;
|
|
810
|
+
const nsPart = ctx.nsStack.length > 0 ? ctx.nsStack.join('::') + '::' : '';
|
|
811
|
+
const qualName = nsPart + symbolName;
|
|
812
|
+
symbols.push({
|
|
813
|
+
id: makeId(filePath, qualName, entry.symbolKind),
|
|
814
|
+
name: qualName,
|
|
815
|
+
kind: entry.symbolKind,
|
|
816
|
+
filePath,
|
|
817
|
+
startByte: node.startIndex,
|
|
818
|
+
endByte: node.endIndex,
|
|
819
|
+
signature: trunc(`${macroName}(${symbolName})`),
|
|
820
|
+
summary: extractDocstring(node) ?? `C++ ${entry.symbolKind} registered via ${macroName}`,
|
|
821
|
+
frameworkMeta: { registeredViaMacro: macroName },
|
|
822
|
+
});
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
408
825
|
default:
|
|
409
826
|
break;
|
|
410
827
|
}
|
|
411
828
|
}
|
|
829
|
+
/**
|
|
830
|
+
* Walk a `compound_statement` that was misparsed as a function body but is
|
|
831
|
+
* actually the body of a class/struct declared with an export macro.
|
|
832
|
+
* e.g. `class MI_EXPORT_LIB Scene { public: void render(); };`
|
|
833
|
+
*
|
|
834
|
+
* tree-sitter wraps `public:` as `labeled_statement` nodes inside the compound.
|
|
835
|
+
*/
|
|
836
|
+
function walkExportMacroClassBody(bodyNode, ctx, filePath, src, symbols, isStruct) {
|
|
837
|
+
let accessLevel = isStruct ? 'public' : 'private';
|
|
838
|
+
for (const child of bodyNode.children) {
|
|
839
|
+
if (!child.isNamed)
|
|
840
|
+
continue;
|
|
841
|
+
if (child.type === 'labeled_statement') {
|
|
842
|
+
// `public: decl...` or `private: decl...`
|
|
843
|
+
const labelNode = child.children.find((c) => c.type === 'statement_identifier');
|
|
844
|
+
if (labelNode) {
|
|
845
|
+
const label = nodeText(labelNode, src);
|
|
846
|
+
if (label === 'public')
|
|
847
|
+
accessLevel = 'public';
|
|
848
|
+
else if (label === 'protected')
|
|
849
|
+
accessLevel = 'protected';
|
|
850
|
+
else if (label === 'private')
|
|
851
|
+
accessLevel = 'private';
|
|
852
|
+
}
|
|
853
|
+
if (accessLevel === 'private')
|
|
854
|
+
continue;
|
|
855
|
+
// Walk the statement body (declarations / function definitions after label)
|
|
856
|
+
for (const stmt of child.children) {
|
|
857
|
+
if (!stmt.isNamed || stmt.type === 'statement_identifier')
|
|
858
|
+
continue;
|
|
859
|
+
if (stmt.type === 'function_definition') {
|
|
860
|
+
emitMethod(stmt, ctx, filePath, src, symbols, null);
|
|
861
|
+
}
|
|
862
|
+
else if (stmt.type === 'declaration') {
|
|
863
|
+
emitDeclMethod(stmt, ctx, filePath, src, symbols);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
else if (child.type === 'function_definition' && accessLevel !== 'private') {
|
|
868
|
+
emitMethod(child, ctx, filePath, src, symbols, null);
|
|
869
|
+
}
|
|
870
|
+
else if (child.type === 'declaration' && accessLevel !== 'private') {
|
|
871
|
+
emitDeclMethod(child, ctx, filePath, src, symbols);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Walk body children of a class/struct that tree-sitter parsed inside an ERROR node.
|
|
877
|
+
* Body children are direct children of the ERROR node that come after the `{` token.
|
|
878
|
+
* Handles labeled_statement (access specifiers), ERROR (misparsed method stubs),
|
|
879
|
+
* function_definition, and declaration nodes.
|
|
880
|
+
*/
|
|
881
|
+
function walkErrorClassBody(bodyChildren, ctx, filePath, src, symbols, isStruct) {
|
|
882
|
+
let accessLevel = isStruct ? 'public' : 'private';
|
|
883
|
+
for (const child of bodyChildren) {
|
|
884
|
+
// Handle direct access_specifier nodes (Pattern 2: microfacet.h style)
|
|
885
|
+
if (child.type === 'access_specifier') {
|
|
886
|
+
const text = nodeText(child, src);
|
|
887
|
+
if (text.startsWith('public'))
|
|
888
|
+
accessLevel = 'public';
|
|
889
|
+
else if (text.startsWith('protected'))
|
|
890
|
+
accessLevel = 'protected';
|
|
891
|
+
else if (text.startsWith('private'))
|
|
892
|
+
accessLevel = 'private';
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (!child.isNamed)
|
|
896
|
+
continue;
|
|
897
|
+
if (child.type === 'labeled_statement') {
|
|
898
|
+
const labelNode = child.children.find((c) => c.type === 'statement_identifier');
|
|
899
|
+
if (labelNode) {
|
|
900
|
+
const label = nodeText(labelNode, src);
|
|
901
|
+
if (label === 'public')
|
|
902
|
+
accessLevel = 'public';
|
|
903
|
+
else if (label === 'protected')
|
|
904
|
+
accessLevel = 'protected';
|
|
905
|
+
else if (label === 'private')
|
|
906
|
+
accessLevel = 'private';
|
|
907
|
+
}
|
|
908
|
+
if (accessLevel === 'private')
|
|
909
|
+
continue;
|
|
910
|
+
// Walk statement body (declarations / definitions after the access label)
|
|
911
|
+
for (const stmt of child.children) {
|
|
912
|
+
if (!stmt.isNamed || stmt.type === 'statement_identifier')
|
|
913
|
+
continue;
|
|
914
|
+
if (stmt.type === 'function_definition') {
|
|
915
|
+
emitMethod(stmt, ctx, filePath, src, symbols, null);
|
|
916
|
+
}
|
|
917
|
+
else if (stmt.type === 'declaration') {
|
|
918
|
+
emitDeclMethod(stmt, ctx, filePath, src, symbols);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
else if (child.type === 'function_definition' && accessLevel !== 'private') {
|
|
923
|
+
emitMethod(child, ctx, filePath, src, symbols, null);
|
|
924
|
+
}
|
|
925
|
+
else if (child.type === 'declaration' && accessLevel !== 'private') {
|
|
926
|
+
emitDeclMethod(child, ctx, filePath, src, symbols);
|
|
927
|
+
}
|
|
928
|
+
else if (child.type === 'ERROR' && accessLevel !== 'private') {
|
|
929
|
+
// Method declarations misparsed as ERROR — look for function_declarator child
|
|
930
|
+
const funcDecl = child.children.find((c) => c.type === 'function_declarator');
|
|
931
|
+
if (funcDecl) {
|
|
932
|
+
const rawName = extractDeclaratorName(funcDecl, src);
|
|
933
|
+
if (rawName) {
|
|
934
|
+
const methodName = ctx.className ? ctx.className + '::' + rawName : rawName;
|
|
935
|
+
symbols.push({
|
|
936
|
+
id: makeId(filePath, methodName, 'method'),
|
|
937
|
+
name: methodName,
|
|
938
|
+
kind: 'method',
|
|
939
|
+
filePath,
|
|
940
|
+
startByte: child.startIndex,
|
|
941
|
+
endByte: child.endIndex,
|
|
942
|
+
signature: trunc(nodeText(child, src).replace(/\s+/g, ' ')),
|
|
943
|
+
summary: extractDocstring(child) ?? `C++ method: ${methodName}`,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
/** Extract a method symbol from a `declaration` node (header-only method stub). */
|
|
951
|
+
function emitDeclMethod(node, ctx, filePath, src, symbols) {
|
|
952
|
+
const hasFuncDecl = node.children.some((c) => c.isNamed &&
|
|
953
|
+
(c.type === 'function_declarator' || findFunctionDeclarator(c) !== null));
|
|
954
|
+
if (!hasFuncDecl)
|
|
955
|
+
return;
|
|
956
|
+
const declaratorChild = node.children.find((c) => c.isNamed &&
|
|
957
|
+
[
|
|
958
|
+
'function_declarator',
|
|
959
|
+
'pointer_declarator',
|
|
960
|
+
'reference_declarator',
|
|
961
|
+
'parenthesized_declarator',
|
|
962
|
+
].includes(c.type));
|
|
963
|
+
if (!declaratorChild)
|
|
964
|
+
return;
|
|
965
|
+
const rawName = extractDeclaratorName(declaratorChild, src);
|
|
966
|
+
if (!rawName)
|
|
967
|
+
return;
|
|
968
|
+
const methodName = ctx.className ? ctx.className + '::' + rawName : rawName;
|
|
969
|
+
const declText = nodeText(node, src).replace(/;\s*$/, '').trim();
|
|
970
|
+
symbols.push({
|
|
971
|
+
id: makeId(filePath, methodName, 'method'),
|
|
972
|
+
name: methodName,
|
|
973
|
+
kind: 'method',
|
|
974
|
+
filePath,
|
|
975
|
+
startByte: node.startIndex,
|
|
976
|
+
endByte: node.endIndex,
|
|
977
|
+
signature: trunc(declText),
|
|
978
|
+
summary: extractDocstring(node) ?? `C++ method: ${methodName}`,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
412
981
|
/**
|
|
413
982
|
* Walk the body of a class or struct, tracking the current access level.
|
|
414
983
|
* Emits methods (definitions and declarations), nested classes/structs.
|
|
@@ -673,7 +1242,7 @@ function collectImportNodes(nodes, src, imports) {
|
|
|
673
1242
|
}
|
|
674
1243
|
// ─── Handler export ───────────────────────────────────────────────────────────
|
|
675
1244
|
export const cppHandler = {
|
|
676
|
-
extensions: () => ['.cpp', '.cxx', '.cc', '.c++', '.hpp', '.hxx', '.hh', '.h++'],
|
|
1245
|
+
extensions: () => ['.cpp', '.cxx', '.cc', '.c++', '.hpp', '.hxx', '.hh', '.h++', '.h'],
|
|
677
1246
|
grammarPath: () => resolve(GRAMMARS_DIR, 'tree-sitter-cpp.wasm'),
|
|
678
1247
|
extractSymbols,
|
|
679
1248
|
extractImports,
|