feat(mcp): Finalize C/C++ AST tools with robust testing and bug fixes
This commit is contained in:
@@ -62,7 +62,7 @@ Focus: Implement definitions, signatures, and update tools
|
|||||||
## Phase 6: Robust Testing with gencpp
|
## Phase 6: Robust Testing with gencpp
|
||||||
Focus: Verify against real-world C++ components
|
Focus: Verify against real-world C++ components
|
||||||
|
|
||||||
- [ ] Task 6.1: Define test corpus from gencpp samples
|
- [~] Task 6.1: Define test corpus from gencpp samples
|
||||||
- WHERE: tests/assets/gencpp_samples/
|
- WHERE: tests/assets/gencpp_samples/
|
||||||
- WHAT: Collect complex C++ headers and implementations (templates, nested namespaces, multi-line macros)
|
- WHAT: Collect complex C++ headers and implementations (templates, nested namespaces, multi-line macros)
|
||||||
- HOW: Copy selected files from gencpp repo into test assets
|
- HOW: Copy selected files from gencpp repo into test assets
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ separate_tool_calls_panel = false
|
|||||||
bg_shader_enabled = false
|
bg_shader_enabled = false
|
||||||
crt_filter_enabled = false
|
crt_filter_enabled = false
|
||||||
separate_task_dag = false
|
separate_task_dag = false
|
||||||
separate_usage_analytics = true
|
separate_usage_analytics = false
|
||||||
separate_tier1 = false
|
separate_tier1 = false
|
||||||
separate_tier2 = false
|
separate_tier2 = false
|
||||||
separate_tier3 = false
|
separate_tier3 = false
|
||||||
@@ -36,7 +36,7 @@ separate_external_tools = false
|
|||||||
"AI Settings" = true
|
"AI Settings" = true
|
||||||
"MMA Dashboard" = true
|
"MMA Dashboard" = true
|
||||||
"Task DAG" = false
|
"Task DAG" = false
|
||||||
"Usage Analytics" = true
|
"Usage Analytics" = false
|
||||||
"Tier 1" = false
|
"Tier 1" = false
|
||||||
"Tier 2" = false
|
"Tier 2" = false
|
||||||
"Tier 3" = false
|
"Tier 3" = false
|
||||||
|
|||||||
+27
-17
@@ -44,13 +44,13 @@ Collapsed=0
|
|||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Message]
|
[Window][Message]
|
||||||
Pos=1756,543
|
Pos=475,163
|
||||||
Size=327,652
|
Size=327,652
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Response]
|
[Window][Response]
|
||||||
Pos=1771,288
|
Pos=447,143
|
||||||
Size=757,1001
|
Size=1442,1129
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Calls]
|
[Window][Tool Calls]
|
||||||
@@ -102,26 +102,26 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=756,24
|
Pos=1537,24
|
||||||
Size=924,1176
|
Size=1023,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=0,24
|
Pos=0,24
|
||||||
Size=754,1176
|
Size=1535,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,2
|
DockId=0x00000005,2
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=756,24
|
Pos=1537,24
|
||||||
Size=924,1176
|
Size=1023,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,24
|
Pos=0,24
|
||||||
Size=754,1176
|
Size=1535,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
@@ -131,14 +131,14 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=756,24
|
Pos=1537,24
|
||||||
Size=924,1176
|
Size=1023,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,2
|
DockId=0x00000006,2
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=756,24
|
Pos=1537,24
|
||||||
Size=924,1176
|
Size=1023,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,3
|
DockId=0x00000006,3
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ DockId=0x00000006,1
|
|||||||
|
|
||||||
[Window][Project Settings]
|
[Window][Project Settings]
|
||||||
Pos=0,24
|
Pos=0,24
|
||||||
Size=754,1176
|
Size=1535,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,1
|
DockId=0x00000005,1
|
||||||
|
|
||||||
@@ -417,6 +417,16 @@ Size=924,1176
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,4
|
DockId=0x00000006,4
|
||||||
|
|
||||||
|
[Window][Text Viewer - ts_cpp_get_skeleton]
|
||||||
|
Pos=60,58
|
||||||
|
Size=1422,1259
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - ts_cpp_get_code_outline]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0xFB6E3870,4]
|
[Table][0xFB6E3870,4]
|
||||||
RefScale=13
|
RefScale=13
|
||||||
Column 0 Width=80
|
Column 0 Width=80
|
||||||
@@ -541,12 +551,12 @@ Column 2 Width=150
|
|||||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=1680,1176 Split=X
|
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=2560,1416 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2175,1183 Split=X
|
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2175,1183 Split=X
|
||||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=1512,858 Split=X Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=1512,858 Split=X Selected=0x8CA2375C
|
||||||
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=754,1681 CentralNode=1 Selected=0x418C7449
|
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=655,1681 CentralNode=1 Selected=0x418C7449
|
||||||
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=924,1681 Selected=0x6F2B5B04
|
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=1023,1681 Selected=0x6F2B5B04
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1777,858 Selected=0x418C7449
|
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1777,858 Selected=0x418C7449
|
||||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1162,1183 Split=X Selected=0x3AEC3498
|
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1162,1183 Split=X Selected=0x3AEC3498
|
||||||
|
|||||||
+164
-209
@@ -94,6 +94,39 @@ class ASTParser:
|
|||||||
_ast_cache[path] = (mtime, tree)
|
_ast_cache[path] = (mtime, tree)
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
|
def _get_name(self, node: tree_sitter.Node, code: str) -> str:
|
||||||
|
name_node = node.child_by_field_name("name")
|
||||||
|
if name_node:
|
||||||
|
return code[name_node.start_byte:name_node.end_byte]
|
||||||
|
|
||||||
|
if node.type in ("function_definition", "field_declaration"):
|
||||||
|
def find_id(n: tree_sitter.Node) -> str:
|
||||||
|
if n.type in ("identifier", "field_identifier", "qualified_identifier", "destructor_name"):
|
||||||
|
return code[n.start_byte:n.end_byte]
|
||||||
|
# Try field name 'declarator' first
|
||||||
|
d = n.child_by_field_name("declarator")
|
||||||
|
if d:
|
||||||
|
res = find_id(d)
|
||||||
|
if res: return res
|
||||||
|
# Fallback to all children
|
||||||
|
for child in n.children:
|
||||||
|
if child.type == "compound_statement": continue # Don't look in body
|
||||||
|
res = find_id(child)
|
||||||
|
if res: return res
|
||||||
|
return ""
|
||||||
|
return find_id(node)
|
||||||
|
|
||||||
|
if node.type == "template_declaration":
|
||||||
|
for child in node.children:
|
||||||
|
if child.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "field_declaration"):
|
||||||
|
return self._get_name(child, code)
|
||||||
|
|
||||||
|
if node.type in ("struct_specifier", "class_specifier", "class_definition", "namespace_definition"):
|
||||||
|
for child in node.children:
|
||||||
|
if child.type in ("type_identifier", "identifier", "namespace_identifier"):
|
||||||
|
return code[child.start_byte:child.end_byte]
|
||||||
|
return ""
|
||||||
|
|
||||||
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
|
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
|
||||||
@@ -381,36 +414,6 @@ class ASTParser:
|
|||||||
"""
|
"""
|
||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
|
|
||||||
def get_name(node: tree_sitter.Node) -> str:
|
|
||||||
name_node = node.child_by_field_name("name")
|
|
||||||
if name_node:
|
|
||||||
return code[name_node.start_byte:name_node.end_byte]
|
|
||||||
|
|
||||||
if node.type == "function_definition":
|
|
||||||
decl = node.child_by_field_name("declarator")
|
|
||||||
while decl:
|
|
||||||
if decl.type in ("identifier", "field_identifier"):
|
|
||||||
return code[decl.start_byte:decl.end_byte]
|
|
||||||
next_decl = decl.child_by_field_name("declarator")
|
|
||||||
if not next_decl and decl.child_count > 0:
|
|
||||||
for child in decl.children:
|
|
||||||
if child.type in ("identifier", "field_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
decl = decl.children[0]
|
|
||||||
else:
|
|
||||||
decl = next_decl
|
|
||||||
|
|
||||||
if node.type == "template_declaration":
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("function_definition", "class_definition"):
|
|
||||||
return get_name(child)
|
|
||||||
|
|
||||||
if node.type in ("struct_specifier", "class_specifier", "class_definition", "namespace_definition"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("type_identifier", "identifier", "namespace_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
parts = re.split(r'::|\.', name)
|
parts = re.split(r'::|\.', name)
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
||||||
@@ -418,42 +421,54 @@ class ASTParser:
|
|||||||
return None
|
return None
|
||||||
target = target_parts[0]
|
target = target_parts[0]
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
if child.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
# If it's a field_declaration, it might wrap a class/struct/enum definition
|
||||||
if get_name(child) == target:
|
check_node = child
|
||||||
|
if child.type == "field_declaration":
|
||||||
|
for sub in child.children:
|
||||||
|
if sub.type in ("class_specifier", "struct_specifier", "enum_specifier"):
|
||||||
|
check_node = sub
|
||||||
|
break
|
||||||
|
|
||||||
|
is_interesting = check_node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration", "field_declaration")
|
||||||
|
if is_interesting:
|
||||||
|
node_name = self._get_name(check_node, code)
|
||||||
|
if node_name == target:
|
||||||
if len(target_parts) == 1:
|
if len(target_parts) == 1:
|
||||||
return child
|
return check_node if child.type != "field_declaration" else child
|
||||||
body = child.child_by_field_name("body")
|
next_parts = target_parts[1:]
|
||||||
if not body and child.type == "template_declaration":
|
else:
|
||||||
for sub in child.children:
|
next_parts = target_parts
|
||||||
if sub.type in ("function_definition", "class_definition"):
|
|
||||||
body = sub.child_by_field_name("body")
|
body = check_node.child_by_field_name("body")
|
||||||
break
|
if not body and check_node.type == "template_declaration":
|
||||||
if body:
|
for sub in check_node.children:
|
||||||
found = walk(body, target_parts[1:])
|
if sub.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier"):
|
||||||
if found: return found
|
body = sub.child_by_field_name("body")
|
||||||
for sub in child.children:
|
break
|
||||||
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
if body:
|
||||||
found = walk(sub, target_parts[1:])
|
found = walk(body, next_parts)
|
||||||
if found: return found
|
|
||||||
# Recurse for top-level or namespaces
|
|
||||||
if node.type in ("module", "translation_unit", "namespace_definition", "declaration_list"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type not in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
|
||||||
found = walk(child, target_parts)
|
|
||||||
if found: return found
|
if found: return found
|
||||||
|
for sub in check_node.children:
|
||||||
|
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
||||||
|
found = walk(sub, next_parts)
|
||||||
|
if found: return found
|
||||||
|
elif child.type in ("module", "translation_unit", "namespace_definition", "declaration_list", "field_declaration_list", "class_body"):
|
||||||
|
found = walk(child, target_parts)
|
||||||
|
if found: return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
||||||
|
if node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
||||||
|
if self._get_name(node, code) == target:
|
||||||
|
return node
|
||||||
|
for child in node.children:
|
||||||
|
res = deep_search(child, target)
|
||||||
|
if res: return res
|
||||||
return None
|
return None
|
||||||
|
|
||||||
found_node = walk(tree.root_node, parts)
|
found_node = walk(tree.root_node, parts)
|
||||||
if not found_node and len(parts) == 1:
|
if not found_node:
|
||||||
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
found_node = deep_search(tree.root_node, name)
|
||||||
if node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
|
||||||
if get_name(node) == target:
|
|
||||||
return node
|
|
||||||
for child in node.children:
|
|
||||||
res = deep_search(child, target)
|
|
||||||
if res: return res
|
|
||||||
return None
|
|
||||||
found_node = deep_search(tree.root_node, parts[0])
|
|
||||||
|
|
||||||
if found_node:
|
if found_node:
|
||||||
return code[found_node.start_byte:found_node.end_byte]
|
return code[found_node.start_byte:found_node.end_byte]
|
||||||
@@ -466,36 +481,6 @@ class ASTParser:
|
|||||||
"""
|
"""
|
||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
|
|
||||||
def get_name(node: tree_sitter.Node) -> str:
|
|
||||||
name_node = node.child_by_field_name("name")
|
|
||||||
if name_node:
|
|
||||||
return code[name_node.start_byte:name_node.end_byte]
|
|
||||||
|
|
||||||
if node.type == "function_definition":
|
|
||||||
decl = node.child_by_field_name("declarator")
|
|
||||||
while decl:
|
|
||||||
if decl.type in ("identifier", "field_identifier"):
|
|
||||||
return code[decl.start_byte:decl.end_byte]
|
|
||||||
next_decl = decl.child_by_field_name("declarator")
|
|
||||||
if not next_decl and decl.child_count > 0:
|
|
||||||
for child in decl.children:
|
|
||||||
if child.type in ("identifier", "field_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
decl = decl.children[0]
|
|
||||||
else:
|
|
||||||
decl = next_decl
|
|
||||||
|
|
||||||
if node.type == "template_declaration":
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("function_definition", "class_definition"):
|
|
||||||
return get_name(child)
|
|
||||||
|
|
||||||
if node.type in ("struct_specifier", "class_specifier", "class_definition", "namespace_definition"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("type_identifier", "identifier", "namespace_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
parts = re.split(r'::|\.', name)
|
parts = re.split(r'::|\.', name)
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
||||||
@@ -503,47 +488,60 @@ class ASTParser:
|
|||||||
return None
|
return None
|
||||||
target = target_parts[0]
|
target = target_parts[0]
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
if child.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
# If it's a field_declaration, it might wrap a class/struct/enum definition
|
||||||
if get_name(child) == target:
|
check_node = child
|
||||||
|
if child.type == "field_declaration":
|
||||||
|
for sub in child.children:
|
||||||
|
if sub.type in ("class_specifier", "struct_specifier", "enum_specifier"):
|
||||||
|
check_node = sub
|
||||||
|
break
|
||||||
|
|
||||||
|
is_interesting = check_node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration", "field_declaration")
|
||||||
|
if is_interesting:
|
||||||
|
node_name = self._get_name(check_node, code)
|
||||||
|
if node_name == target:
|
||||||
if len(target_parts) == 1:
|
if len(target_parts) == 1:
|
||||||
return child
|
return check_node if child.type != "field_declaration" else child
|
||||||
body = child.child_by_field_name("body")
|
next_parts = target_parts[1:]
|
||||||
if not body and child.type == "template_declaration":
|
else:
|
||||||
for sub in child.children:
|
next_parts = target_parts
|
||||||
if sub.type in ("function_definition", "class_definition"):
|
|
||||||
body = sub.child_by_field_name("body")
|
body = check_node.child_by_field_name("body")
|
||||||
break
|
if not body and check_node.type == "template_declaration":
|
||||||
if body:
|
for sub in check_node.children:
|
||||||
found = walk(body, target_parts[1:])
|
if sub.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier"):
|
||||||
if found: return found
|
body = sub.child_by_field_name("body")
|
||||||
for sub in child.children:
|
break
|
||||||
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
if body:
|
||||||
found = walk(sub, target_parts[1:])
|
found = walk(body, next_parts)
|
||||||
if found: return found
|
|
||||||
if node.type in ("module", "translation_unit", "namespace_definition", "declaration_list"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type not in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
|
||||||
found = walk(child, target_parts)
|
|
||||||
if found: return found
|
if found: return found
|
||||||
|
for sub in check_node.children:
|
||||||
|
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
||||||
|
found = walk(sub, next_parts)
|
||||||
|
if found: return found
|
||||||
|
elif child.type in ("module", "translation_unit", "namespace_definition", "declaration_list", "field_declaration_list", "class_body"):
|
||||||
|
found = walk(child, target_parts)
|
||||||
|
if found: return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
||||||
|
if node.type in ("function_definition", "template_declaration"):
|
||||||
|
if self._get_name(node, code) == target:
|
||||||
|
return node
|
||||||
|
for child in node.children:
|
||||||
|
res = deep_search(child, target)
|
||||||
|
if res: return res
|
||||||
return None
|
return None
|
||||||
|
|
||||||
found_node = walk(tree.root_node, parts)
|
found_node = walk(tree.root_node, parts)
|
||||||
if not found_node and len(parts) == 1:
|
if not found_node:
|
||||||
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
found_node = deep_search(tree.root_node, name)
|
||||||
if node.type in ("function_definition", "template_declaration"):
|
|
||||||
if get_name(node) == target:
|
|
||||||
return node
|
|
||||||
for child in node.children:
|
|
||||||
res = deep_search(child, target)
|
|
||||||
if res: return res
|
|
||||||
return None
|
|
||||||
found_node = deep_search(tree.root_node, parts[0])
|
|
||||||
|
|
||||||
if found_node:
|
if found_node:
|
||||||
target_node = found_node
|
target_node = found_node
|
||||||
if found_node.type == "template_declaration":
|
if found_node.type == "template_declaration":
|
||||||
for child in found_node.children:
|
for child in found_node.children:
|
||||||
if child.type in ("function_definition", "class_definition"):
|
if child.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier"):
|
||||||
target_node = child
|
target_node = child
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -561,31 +559,6 @@ class ASTParser:
|
|||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
def get_name(node: tree_sitter.Node) -> str:
|
|
||||||
name_node = node.child_by_field_name("name")
|
|
||||||
if name_node:
|
|
||||||
return code[name_node.start_byte:name_node.end_byte]
|
|
||||||
|
|
||||||
if node.type == "function_definition":
|
|
||||||
decl = node.child_by_field_name("declarator")
|
|
||||||
while decl:
|
|
||||||
if decl.type in ("identifier", "field_identifier"):
|
|
||||||
return code[decl.start_byte:decl.end_byte]
|
|
||||||
next_decl = decl.child_by_field_name("declarator")
|
|
||||||
if not next_decl and decl.child_count > 0:
|
|
||||||
for child in decl.children:
|
|
||||||
if child.type in ("identifier", "field_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
decl = decl.children[0]
|
|
||||||
else:
|
|
||||||
decl = next_decl
|
|
||||||
|
|
||||||
if node.type in ("struct_specifier", "class_specifier"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("type_identifier", "identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, indent: int = 0) -> None:
|
def walk(node: tree_sitter.Node, indent: int = 0) -> None:
|
||||||
ntype = node.type
|
ntype = node.type
|
||||||
label = ""
|
label = ""
|
||||||
@@ -597,7 +570,7 @@ class ASTParser:
|
|||||||
label = "[Method]" if indent > 0 else "[Func]"
|
label = "[Method]" if indent > 0 else "[Func]"
|
||||||
|
|
||||||
if label:
|
if label:
|
||||||
name = get_name(node)
|
name = self._get_name(node, code)
|
||||||
if name:
|
if name:
|
||||||
start = node.start_point.row + 1
|
start = node.start_point.row + 1
|
||||||
end = node.end_point.row + 1
|
end = node.end_point.row + 1
|
||||||
@@ -620,36 +593,6 @@ class ASTParser:
|
|||||||
"""
|
"""
|
||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
|
|
||||||
def get_name(node: tree_sitter.Node) -> str:
|
|
||||||
name_node = node.child_by_field_name("name")
|
|
||||||
if name_node:
|
|
||||||
return code[name_node.start_byte:name_node.end_byte]
|
|
||||||
|
|
||||||
if node.type == "function_definition":
|
|
||||||
decl = node.child_by_field_name("declarator")
|
|
||||||
while decl:
|
|
||||||
if decl.type in ("identifier", "field_identifier"):
|
|
||||||
return code[decl.start_byte:decl.end_byte]
|
|
||||||
next_decl = decl.child_by_field_name("declarator")
|
|
||||||
if not next_decl and decl.child_count > 0:
|
|
||||||
for child in decl.children:
|
|
||||||
if child.type in ("identifier", "field_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
decl = decl.children[0]
|
|
||||||
else:
|
|
||||||
decl = next_decl
|
|
||||||
|
|
||||||
if node.type == "template_declaration":
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("function_definition", "class_definition"):
|
|
||||||
return get_name(child)
|
|
||||||
|
|
||||||
if node.type in ("struct_specifier", "class_specifier", "class_definition", "namespace_definition"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type in ("type_identifier", "identifier", "namespace_identifier"):
|
|
||||||
return code[child.start_byte:child.end_byte]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
parts = re.split(r'::|\.', name)
|
parts = re.split(r'::|\.', name)
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
||||||
@@ -657,42 +600,54 @@ class ASTParser:
|
|||||||
return None
|
return None
|
||||||
target = target_parts[0]
|
target = target_parts[0]
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
if child.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
# If it's a field_declaration, it might wrap a class/struct/enum definition
|
||||||
if get_name(child) == target:
|
check_node = child
|
||||||
|
if child.type == "field_declaration":
|
||||||
|
for sub in child.children:
|
||||||
|
if sub.type in ("class_specifier", "struct_specifier", "enum_specifier"):
|
||||||
|
check_node = sub
|
||||||
|
break
|
||||||
|
|
||||||
|
is_interesting = check_node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration", "field_declaration")
|
||||||
|
if is_interesting:
|
||||||
|
node_name = self._get_name(check_node, code)
|
||||||
|
if node_name == target:
|
||||||
if len(target_parts) == 1:
|
if len(target_parts) == 1:
|
||||||
return child
|
return check_node if child.type != "field_declaration" else child
|
||||||
body = child.child_by_field_name("body")
|
next_parts = target_parts[1:]
|
||||||
if not body and child.type == "template_declaration":
|
else:
|
||||||
for sub in child.children:
|
next_parts = target_parts
|
||||||
if sub.type in ("function_definition", "class_definition"):
|
|
||||||
body = sub.child_by_field_name("body")
|
body = check_node.child_by_field_name("body")
|
||||||
break
|
if not body and check_node.type == "template_declaration":
|
||||||
if body:
|
for sub in check_node.children:
|
||||||
found = walk(body, target_parts[1:])
|
if sub.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier"):
|
||||||
if found: return found
|
body = sub.child_by_field_name("body")
|
||||||
for sub in child.children:
|
break
|
||||||
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
if body:
|
||||||
found = walk(sub, target_parts[1:])
|
found = walk(body, next_parts)
|
||||||
if found: return found
|
|
||||||
# Recurse for top-level or namespaces
|
|
||||||
if node.type in ("module", "translation_unit", "namespace_definition", "declaration_list"):
|
|
||||||
for child in node.children:
|
|
||||||
if child.type not in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
|
||||||
found = walk(child, target_parts)
|
|
||||||
if found: return found
|
if found: return found
|
||||||
|
for sub in check_node.children:
|
||||||
|
if sub.type in ("field_declaration_list", "class_body", "declaration_list"):
|
||||||
|
found = walk(sub, next_parts)
|
||||||
|
if found: return found
|
||||||
|
elif child.type in ("module", "translation_unit", "namespace_definition", "declaration_list", "field_declaration_list", "class_body"):
|
||||||
|
found = walk(child, target_parts)
|
||||||
|
if found: return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
||||||
|
if node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
||||||
|
if self._get_name(node, code) == target:
|
||||||
|
return node
|
||||||
|
for child in node.children:
|
||||||
|
res = deep_search(child, target)
|
||||||
|
if res: return res
|
||||||
return None
|
return None
|
||||||
|
|
||||||
found_node = walk(tree.root_node, parts)
|
found_node = walk(tree.root_node, parts)
|
||||||
if not found_node and len(parts) == 1:
|
if not found_node:
|
||||||
def deep_search(node: tree_sitter.Node, target: str) -> Optional[tree_sitter.Node]:
|
found_node = deep_search(tree.root_node, name)
|
||||||
if node.type in ("function_definition", "class_definition", "class_specifier", "struct_specifier", "namespace_definition", "template_declaration"):
|
|
||||||
if get_name(node) == target:
|
|
||||||
return node
|
|
||||||
for child in node.children:
|
|
||||||
res = deep_search(child, target)
|
|
||||||
if res: return res
|
|
||||||
return None
|
|
||||||
found_node = deep_search(tree.root_node, parts[0])
|
|
||||||
|
|
||||||
if found_node:
|
if found_node:
|
||||||
code_bytes = bytearray(code, "utf8")
|
code_bytes = bytearray(code, "utf8")
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gencpp {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base class for all components in the system.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class BaseComponent {
|
||||||
|
public:
|
||||||
|
virtual ~BaseComponent() = default;
|
||||||
|
|
||||||
|
virtual void Initialize() = 0;
|
||||||
|
virtual void Shutdown() = 0;
|
||||||
|
|
||||||
|
virtual const std::string& GetName() const = 0;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
std::string name;
|
||||||
|
int priority;
|
||||||
|
bool enabled;
|
||||||
|
|
||||||
|
class Metadata {
|
||||||
|
public:
|
||||||
|
std::string author;
|
||||||
|
std::string version;
|
||||||
|
};
|
||||||
|
Metadata metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BaseComponent(const Config& config) : m_config(config) {}
|
||||||
|
Config m_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace gencpp
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace gencpp {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A complex template class demonstrating variadic templates.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
class MultiBuffer {
|
||||||
|
public:
|
||||||
|
void SetData(Args... args) {
|
||||||
|
m_data = std::make_tuple(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t I>
|
||||||
|
auto Get() const -> const typename std::tuple_element<I, std::tuple<Args...>>::type& {
|
||||||
|
return std::get<I>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::tuple<Args...> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Template specialization example.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct TypeTraits {
|
||||||
|
static constexpr bool IsPointer = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TypeTraits<T*> {
|
||||||
|
static constexpr bool IsPointer = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nested template class.
|
||||||
|
*/
|
||||||
|
template <typename Outer>
|
||||||
|
struct Container {
|
||||||
|
template <typename Inner>
|
||||||
|
struct Wrapper {
|
||||||
|
Inner value;
|
||||||
|
Outer context;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace gencpp
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include "component_registry.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace gencpp {
|
||||||
|
namespace registry {
|
||||||
|
|
||||||
|
ComponentRegistry& ComponentRegistry::Instance() {
|
||||||
|
static ComponentRegistry instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComponentRegistry::Register(const std::string& type, ComponentCreator creator) {
|
||||||
|
std::cout << "Registering component type: " << type << std::endl;
|
||||||
|
m_creators[type] = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<core::BaseComponent<void*>> ComponentRegistry::Create(const std::string& type) {
|
||||||
|
auto it = m_creators.find(type);
|
||||||
|
if (it != m_creators.end()) {
|
||||||
|
return it->second();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace registry
|
||||||
|
} // namespace gencpp
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base_component.h"
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace gencpp {
|
||||||
|
namespace registry {
|
||||||
|
|
||||||
|
class ComponentRegistry {
|
||||||
|
public:
|
||||||
|
using ComponentCreator = std::function<std::unique_ptr<core::BaseComponent<void*>>()>;
|
||||||
|
|
||||||
|
static ComponentRegistry& Instance();
|
||||||
|
|
||||||
|
void Register(const std::string& type, ComponentCreator creator);
|
||||||
|
std::unique_ptr<core::BaseComponent<void*>> Create(const std::string& type);
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
using MapIterator = std::map<std::string, ComponentCreator>::iterator;
|
||||||
|
|
||||||
|
Iterator(MapIterator it) : m_it(it) {}
|
||||||
|
|
||||||
|
bool operator!=(const Iterator& other) const { return m_it != other.m_it; }
|
||||||
|
void operator++() { ++m_it; }
|
||||||
|
const std::string& GetType() const { return m_it->first; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
MapIterator m_it;
|
||||||
|
};
|
||||||
|
|
||||||
|
Iterator Begin() { return Iterator(m_creators.begin()); }
|
||||||
|
Iterator End() { return Iterator(m_creators.end()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ComponentRegistry() = default;
|
||||||
|
std::map<std::string, ComponentCreator> m_creators;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace registry
|
||||||
|
} // namespace gencpp
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
def verify_files():
|
||||||
|
files = [
|
||||||
|
"base_component.h",
|
||||||
|
"component_registry.h",
|
||||||
|
"component_registry.cpp",
|
||||||
|
"complex_template.h"
|
||||||
|
]
|
||||||
|
base_path = "tests/assets/gencpp_samples"
|
||||||
|
for f in files:
|
||||||
|
p = os.path.join(base_path, f)
|
||||||
|
if os.path.exists(p):
|
||||||
|
print(f"Verified: {f}")
|
||||||
|
else:
|
||||||
|
print(f"Missing: {f}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
verify_files()
|
||||||
@@ -6,7 +6,7 @@ import sys
|
|||||||
# Add project root to sys.path
|
# Add project root to sys.path
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from src.mcp_client import ts_cpp_get_skeleton, ts_cpp_get_code_outline
|
from src.mcp_client import ts_cpp_get_skeleton, ts_cpp_get_code_outline, ts_cpp_get_definition, ts_cpp_update_definition
|
||||||
|
|
||||||
def test_ts_cpp_get_skeleton(tmp_path):
|
def test_ts_cpp_get_skeleton(tmp_path):
|
||||||
cpp_code = """#include <iostream>
|
cpp_code = """#include <iostream>
|
||||||
@@ -69,3 +69,91 @@ void templateFunc(T t) {
|
|||||||
assert "[Func] templateFunc (Lines 8-9)" in outline
|
assert "[Func] templateFunc (Lines 8-9)" in outline
|
||||||
finally:
|
finally:
|
||||||
mcp_client._resolve_and_check = original_resolve
|
mcp_client._resolve_and_check = original_resolve
|
||||||
|
|
||||||
|
def test_exhaustive_gencpp_corpus():
|
||||||
|
base_dir = Path(__file__).parent / "assets" / "gencpp_samples"
|
||||||
|
files_to_test = {
|
||||||
|
"base_component.h": [
|
||||||
|
"BaseComponent",
|
||||||
|
"BaseComponent::Config",
|
||||||
|
"BaseComponent::Config::Metadata"
|
||||||
|
],
|
||||||
|
"complex_template.h": [
|
||||||
|
"MultiBuffer",
|
||||||
|
"MultiBuffer::SetData",
|
||||||
|
"MultiBuffer::Get",
|
||||||
|
"TypeTraits",
|
||||||
|
"Container::Wrapper"
|
||||||
|
],
|
||||||
|
"component_registry.h": [
|
||||||
|
"ComponentRegistry",
|
||||||
|
"ComponentRegistry::Iterator",
|
||||||
|
"ComponentRegistry::Iterator::GetType",
|
||||||
|
"ComponentRegistry::Instance"
|
||||||
|
],
|
||||||
|
"component_registry.cpp": [
|
||||||
|
"ComponentRegistry::Instance",
|
||||||
|
"ComponentRegistry::Register",
|
||||||
|
"ComponentRegistry::Create"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
from src import mcp_client
|
||||||
|
original_resolve = mcp_client._resolve_and_check
|
||||||
|
mcp_client._resolve_and_check = lambda path: (Path(path), None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for filename, symbols in files_to_test.items():
|
||||||
|
path = base_dir / filename
|
||||||
|
assert path.exists(), f"{path} does not exist"
|
||||||
|
|
||||||
|
# 1. Verify skeleton executes without errors
|
||||||
|
skeleton = ts_cpp_get_skeleton(str(path))
|
||||||
|
assert skeleton and "ERROR" not in skeleton
|
||||||
|
|
||||||
|
# 2. Verify code outline executes without errors
|
||||||
|
outline = ts_cpp_get_code_outline(str(path))
|
||||||
|
assert outline and "ERROR" not in outline
|
||||||
|
|
||||||
|
# 3. Verify specific complex symbols can be retrieved via ts_cpp_get_definition
|
||||||
|
for symbol in symbols:
|
||||||
|
definition = ts_cpp_get_definition(str(path), symbol)
|
||||||
|
assert definition and "ERROR" not in definition, f"Failed to get definition for {symbol} in {filename}: {definition}"
|
||||||
|
# Basic check that the symbol name is in the definition (allowing for namespaces)
|
||||||
|
simple_name = symbol.split("::")[-1]
|
||||||
|
assert simple_name in definition
|
||||||
|
finally:
|
||||||
|
mcp_client._resolve_and_check = original_resolve
|
||||||
|
|
||||||
|
def test_ts_cpp_update_definition(tmp_path):
|
||||||
|
asset_path = Path(__file__).parent / "assets" / "gencpp_samples" / "component_registry.cpp"
|
||||||
|
cpp_content = asset_path.read_text(encoding="utf-8")
|
||||||
|
cpp_file = tmp_path / "component_registry.cpp"
|
||||||
|
cpp_file.write_text(cpp_content, encoding="utf-8")
|
||||||
|
|
||||||
|
from src import mcp_client
|
||||||
|
original_resolve = mcp_client._resolve_and_check
|
||||||
|
mcp_client._resolve_and_check = lambda path: (Path(path), None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_register_body = """void ComponentRegistry::Register(const std::string& type, ComponentCreator creator) {
|
||||||
|
// Updated body
|
||||||
|
std::cout << "DEBUG: Registering " << type << std::endl;
|
||||||
|
m_creators[type] = creator;
|
||||||
|
if (type == "secret") {
|
||||||
|
std::cout << "Special component" << std::endl;
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = ts_cpp_update_definition(str(cpp_file), "ComponentRegistry::Register", new_register_body)
|
||||||
|
assert "Successfully updated" in result
|
||||||
|
|
||||||
|
updated_content = cpp_file.read_text(encoding="utf-8")
|
||||||
|
assert "// Updated body" in updated_content
|
||||||
|
assert "DEBUG: Registering" in updated_content
|
||||||
|
|
||||||
|
# Verify it still parses by getting definition again
|
||||||
|
definition = ts_cpp_get_definition(str(cpp_file), "ComponentRegistry::Register")
|
||||||
|
assert "// Updated body" in definition
|
||||||
|
assert "DEBUG: Registering" in definition
|
||||||
|
finally:
|
||||||
|
mcp_client._resolve_and_check = original_resolve
|
||||||
|
|||||||
Reference in New Issue
Block a user