mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-21 04:56:18 +00:00
Merge pull request #1071 from TheFedaikin/v8
Fix CI and ddd semantic contexts for ObjC, Julia, C, C++, Scala, Fortran, and PowerShell
This commit is contained in:
+451
-28
@@ -1017,6 +1017,106 @@ def _swift_property_type_node(property_node):
|
||||
return None
|
||||
|
||||
|
||||
# ── C / C++ type-ref helpers ─────────────────────────────────────────────────
|
||||
|
||||
_C_PRIMITIVE_TYPE_NODES = frozenset({
|
||||
"primitive_type", "sized_type_specifier", "auto", "placeholder_type_specifier",
|
||||
})
|
||||
|
||||
|
||||
def _c_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
|
||||
"""Walk a C type expression; append (name, role) tuples for user-defined types.
|
||||
Skips primitive types and qualifiers; recognises type_identifier."""
|
||||
if node is None or node.type in _C_PRIMITIVE_TYPE_NODES:
|
||||
return
|
||||
t = node.type
|
||||
if t == "type_identifier":
|
||||
text = _read_text(node, source)
|
||||
if text:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
return
|
||||
if t in ("pointer_declarator", "reference_declarator", "array_declarator",
|
||||
"type_qualifier", "type_descriptor", "abstract_pointer_declarator",
|
||||
"abstract_reference_declarator", "abstract_array_declarator"):
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_c_collect_type_refs(c, source, generic, out)
|
||||
|
||||
|
||||
def _cpp_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
|
||||
"""Walk a C++ type expression; append (name, role) tuples.
|
||||
Resolves qualified_identifier tails (std::string → string) and template_type
|
||||
base + arguments (std::vector<HttpClient> → vector + HttpClient as generic_arg)."""
|
||||
if node is None or node.type in _C_PRIMITIVE_TYPE_NODES:
|
||||
return
|
||||
t = node.type
|
||||
if t == "type_identifier":
|
||||
text = _read_text(node, source)
|
||||
if text:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
return
|
||||
if t == "qualified_identifier":
|
||||
name_node = node.child_by_field_name("name")
|
||||
if name_node is not None:
|
||||
_cpp_collect_type_refs(name_node, source, generic, out)
|
||||
return
|
||||
if t == "template_type":
|
||||
name_node = node.child_by_field_name("name")
|
||||
if name_node is not None:
|
||||
text = _read_text(name_node, source)
|
||||
if text:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
args_node = node.child_by_field_name("arguments")
|
||||
if args_node is not None:
|
||||
for c in args_node.children:
|
||||
if c.is_named:
|
||||
_cpp_collect_type_refs(c, source, True, out)
|
||||
return
|
||||
if t in ("type_descriptor", "pointer_declarator", "reference_declarator",
|
||||
"array_declarator", "type_qualifier", "abstract_pointer_declarator",
|
||||
"abstract_reference_declarator", "abstract_array_declarator"):
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_cpp_collect_type_refs(c, source, generic, out)
|
||||
|
||||
|
||||
# ── Scala type-ref helpers ───────────────────────────────────────────────────
|
||||
|
||||
def _scala_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
|
||||
"""Walk a Scala type expression; append (name, role) tuples.
|
||||
Handles type_identifier, generic_type (List[T]), and common type wrappers."""
|
||||
if node is None:
|
||||
return
|
||||
t = node.type
|
||||
if t == "type_identifier":
|
||||
text = _read_text(node, source)
|
||||
if text:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
return
|
||||
if t == "generic_type":
|
||||
base = node.child_by_field_name("type")
|
||||
if base is None:
|
||||
for c in node.children:
|
||||
if c.type == "type_identifier":
|
||||
base = c
|
||||
break
|
||||
if base is not None and base.type == "type_identifier":
|
||||
text = _read_text(base, source)
|
||||
if text:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
for c in node.children:
|
||||
if c.type == "type_arguments":
|
||||
for arg in c.children:
|
||||
if arg.is_named:
|
||||
_scala_collect_type_refs(arg, source, True, out)
|
||||
return
|
||||
if t in ("compound_type", "infix_type", "function_type", "tuple_type",
|
||||
"annotated_type", "projected_type"):
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_scala_collect_type_refs(c, source, generic, out)
|
||||
|
||||
|
||||
def _python_collect_param_refs(params_node, source: bytes) -> list[tuple[str, str]]:
|
||||
"""Collect type refs from each typed parameter under a `parameters` node."""
|
||||
out: list[tuple[str, str]] = []
|
||||
@@ -2370,6 +2470,55 @@ def _extract_generic(path: Path, config: LanguageConfig) -> dict:
|
||||
if tid.type == "type_identifier":
|
||||
_emit_java_parent(_read_text(tid, source), "inherits", line)
|
||||
|
||||
# Scala: extends_clause carries `extends Base with Trait1 with Trait2`.
|
||||
# The first base after `extends` is `inherits`; each subsequent
|
||||
# type after `with` is `mixes_in`. Also walk class_parameters for
|
||||
# constructor-as-field type references.
|
||||
if config.ts_module == "tree_sitter_scala":
|
||||
extend = node.child_by_field_name("extend")
|
||||
if extend is None:
|
||||
for c in node.children:
|
||||
if c.type == "extends_clause":
|
||||
extend = c
|
||||
break
|
||||
if extend is not None:
|
||||
bases: list[tuple[str, int]] = []
|
||||
for c in extend.children:
|
||||
if c.type == "type_identifier":
|
||||
bases.append((_read_text(c, source), c.start_point[0] + 1))
|
||||
elif c.type == "generic_type":
|
||||
base = c.child_by_field_name("type")
|
||||
if base is None:
|
||||
for sc in c.children:
|
||||
if sc.type == "type_identifier":
|
||||
base = sc
|
||||
break
|
||||
if base is not None:
|
||||
bases.append((_read_text(base, source), c.start_point[0] + 1))
|
||||
for idx, (base_name, base_line) in enumerate(bases):
|
||||
rel = "inherits" if idx == 0 else "mixes_in"
|
||||
base_nid = ensure_named_node(base_name, base_line)
|
||||
if base_nid != class_nid:
|
||||
add_edge(class_nid, base_nid, rel, base_line)
|
||||
for c in node.children:
|
||||
if c.type != "class_parameters":
|
||||
continue
|
||||
for cp in c.children:
|
||||
if cp.type != "class_parameter":
|
||||
continue
|
||||
ptype = cp.child_by_field_name("type")
|
||||
if ptype is None:
|
||||
continue
|
||||
cp_line = cp.start_point[0] + 1
|
||||
refs: list[tuple[str, str]] = []
|
||||
_scala_collect_type_refs(ptype, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "field"
|
||||
target_nid = ensure_named_node(ref_name, cp_line)
|
||||
if target_nid != class_nid:
|
||||
add_edge(class_nid, target_nid, "references",
|
||||
cp_line, context=ctx)
|
||||
|
||||
# C++-specific: inheritance via base_class_clause (class and struct).
|
||||
# tree-sitter-cpp shape:
|
||||
# class_specifier / struct_specifier
|
||||
@@ -2541,15 +2690,52 @@ def _extract_generic(path: Path, config: LanguageConfig) -> dict:
|
||||
add_edge(parent_class_nid, target_nid, "references", line, context=ctx)
|
||||
return
|
||||
|
||||
if (config.ts_module == "tree_sitter_scala"
|
||||
and t == "val_definition"
|
||||
and parent_class_nid):
|
||||
type_node = node.child_by_field_name("type")
|
||||
if type_node is not None:
|
||||
line = node.start_point[0] + 1
|
||||
refs: list[tuple[str, str]] = []
|
||||
_scala_collect_type_refs(type_node, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "field"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != parent_class_nid:
|
||||
add_edge(parent_class_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
# fall through so any call expressions in the initializer get walked
|
||||
|
||||
if (config.ts_module == "tree_sitter_cpp"
|
||||
and t == "field_declaration"
|
||||
and parent_class_nid):
|
||||
# Skip method prototypes (field_declaration with a function_declarator
|
||||
# is a member-function declaration, not a data member).
|
||||
decls = list(node.children_by_field_name("declarator"))
|
||||
is_method = any(
|
||||
d.type == "function_declarator"
|
||||
or (d.type in ("pointer_declarator", "reference_declarator")
|
||||
and any(c.type == "function_declarator" for c in d.children))
|
||||
for d in decls
|
||||
)
|
||||
if not is_method:
|
||||
type_node = node.child_by_field_name("type")
|
||||
if type_node is not None:
|
||||
line = node.start_point[0] + 1
|
||||
refs: list[tuple[str, str]] = []
|
||||
_cpp_collect_type_refs(type_node, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "field"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != parent_class_nid:
|
||||
add_edge(parent_class_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
# Emit a node for each data member. Use children_by_field_name so we
|
||||
# only visit declarator children, not the type node (which would give
|
||||
# us the type name, not the field name). Handles int x, y; via
|
||||
# multiple declarator fields and static const int MAX = 100; via the
|
||||
# init_declarator → field_identifier recursion in _get_cpp_func_name.
|
||||
for decl in node.children_by_field_name("declarator"):
|
||||
for decl in decls:
|
||||
name = _get_cpp_func_name(decl, source)
|
||||
if name:
|
||||
line = decl.start_point[0] + 1
|
||||
@@ -2757,6 +2943,73 @@ def _extract_generic(path: Path, config: LanguageConfig) -> dict:
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references", line, context=ctx)
|
||||
|
||||
if config.ts_module in ("tree_sitter_c", "tree_sitter_cpp"):
|
||||
collect = (_cpp_collect_type_refs if config.ts_module == "tree_sitter_cpp"
|
||||
else _c_collect_type_refs)
|
||||
return_node = node.child_by_field_name("type")
|
||||
if return_node is not None:
|
||||
refs: list[tuple[str, str]] = []
|
||||
collect(return_node, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "return_type"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references", line, context=ctx)
|
||||
# function_declarator may be wrapped in pointer/reference declarators
|
||||
decl = node.child_by_field_name("declarator")
|
||||
while decl is not None and decl.type in (
|
||||
"pointer_declarator", "reference_declarator"):
|
||||
decl = decl.child_by_field_name("declarator")
|
||||
if decl is not None and decl.type == "function_declarator":
|
||||
params_node = decl.child_by_field_name("parameters")
|
||||
if params_node is not None:
|
||||
for p in params_node.children:
|
||||
if p.type != "parameter_declaration":
|
||||
continue
|
||||
ptype = p.child_by_field_name("type")
|
||||
if ptype is None:
|
||||
continue
|
||||
refs = []
|
||||
collect(ptype, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "parameter_type"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
|
||||
if config.ts_module == "tree_sitter_scala":
|
||||
params_node = None
|
||||
for c in node.children:
|
||||
if c.type == "parameters":
|
||||
params_node = c
|
||||
break
|
||||
if params_node is not None:
|
||||
for p in params_node.children:
|
||||
if p.type != "parameter":
|
||||
continue
|
||||
ptype = p.child_by_field_name("type")
|
||||
if ptype is None:
|
||||
continue
|
||||
refs: list[tuple[str, str]] = []
|
||||
_scala_collect_type_refs(ptype, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "parameter_type"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
return_node = node.child_by_field_name("return_type")
|
||||
if return_node is not None:
|
||||
refs = []
|
||||
_scala_collect_type_refs(return_node, source, False, refs)
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "return_type"
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
|
||||
body = _find_body(node, config)
|
||||
if body:
|
||||
function_bodies.append((func_nid, body))
|
||||
@@ -4257,6 +4510,15 @@ def extract_julia(path: Path) -> dict:
|
||||
file_nid = _make_id(str(path))
|
||||
add_node(file_nid, path.name, 1)
|
||||
|
||||
def ensure_named_node(name: str, line: int) -> str:
|
||||
nid = _make_id(stem, name)
|
||||
if nid in seen_ids:
|
||||
return nid
|
||||
nid = _make_id(name)
|
||||
if nid not in seen_ids:
|
||||
add_node(nid, name, line)
|
||||
return nid
|
||||
|
||||
def _func_name_from_signature(sig_node) -> str | None:
|
||||
"""Extract function name from a Julia signature node (call_expression > identifier)."""
|
||||
for child in sig_node.children:
|
||||
@@ -4310,29 +4572,40 @@ def extract_julia(path: Path) -> dict:
|
||||
if t == "struct_definition":
|
||||
# type_head may contain: identifier (simple) or binary_expression (Foo <: Bar)
|
||||
type_head = next((c for c in node.children if c.type == "type_head"), None)
|
||||
if type_head:
|
||||
bin_expr = next((c for c in type_head.children if c.type == "binary_expression"), None)
|
||||
if bin_expr:
|
||||
# First identifier is the struct name, last is the supertype
|
||||
identifiers = [c for c in bin_expr.children if c.type == "identifier"]
|
||||
if identifiers:
|
||||
struct_name = _read_text(identifiers[0], source)
|
||||
struct_nid = _make_id(stem, struct_name)
|
||||
line = node.start_point[0] + 1
|
||||
add_node(struct_nid, struct_name, line)
|
||||
add_edge(scope_nid, struct_nid, "defines", line)
|
||||
if len(identifiers) >= 2:
|
||||
super_name = _read_text(identifiers[-1], source)
|
||||
add_edge(struct_nid, _make_id(stem, super_name), "inherits",
|
||||
line, confidence="EXTRACTED")
|
||||
else:
|
||||
name_node = next((c for c in type_head.children if c.type == "identifier"), None)
|
||||
if name_node:
|
||||
struct_name = _read_text(name_node, source)
|
||||
struct_nid = _make_id(stem, struct_name)
|
||||
line = node.start_point[0] + 1
|
||||
add_node(struct_nid, struct_name, line)
|
||||
add_edge(scope_nid, struct_nid, "defines", line)
|
||||
if not type_head:
|
||||
return
|
||||
struct_name: str | None = None
|
||||
super_name: str | None = None
|
||||
bin_expr = next((c for c in type_head.children if c.type == "binary_expression"), None)
|
||||
if bin_expr:
|
||||
identifiers = [c for c in bin_expr.children if c.type == "identifier"]
|
||||
if identifiers:
|
||||
struct_name = _read_text(identifiers[0], source)
|
||||
if len(identifiers) >= 2:
|
||||
super_name = _read_text(identifiers[-1], source)
|
||||
else:
|
||||
name_node = next((c for c in type_head.children if c.type == "identifier"), None)
|
||||
if name_node:
|
||||
struct_name = _read_text(name_node, source)
|
||||
if not struct_name:
|
||||
return
|
||||
struct_nid = _make_id(stem, struct_name)
|
||||
line = node.start_point[0] + 1
|
||||
add_node(struct_nid, struct_name, line)
|
||||
add_edge(scope_nid, struct_nid, "defines", line)
|
||||
if super_name:
|
||||
add_edge(struct_nid, ensure_named_node(super_name, line),
|
||||
"inherits", line, confidence="EXTRACTED")
|
||||
# Field types: each `name::Type` lowers to a typed_expression child of struct_definition
|
||||
for child in node.children:
|
||||
if child.type == "typed_expression":
|
||||
type_ids = [c for c in child.children if c.type == "identifier"]
|
||||
if len(type_ids) >= 2:
|
||||
field_line = child.start_point[0] + 1
|
||||
type_name = _read_text(type_ids[-1], source)
|
||||
type_nid = ensure_named_node(type_name, field_line)
|
||||
edges.append(_semantic_reference_edge(
|
||||
struct_nid, type_nid, "field", str_path, field_line))
|
||||
return
|
||||
|
||||
# Abstract type
|
||||
@@ -4516,6 +4789,62 @@ def extract_fortran(path: Path) -> dict:
|
||||
return _read_text(child, source).lower()
|
||||
return None
|
||||
|
||||
def ensure_named_node(name: str, line: int) -> str:
|
||||
nid = _make_id(stem, name)
|
||||
if nid in seen_ids:
|
||||
return nid
|
||||
nid = _make_id(name)
|
||||
if nid not in seen_ids:
|
||||
add_node(nid, name, line)
|
||||
return nid
|
||||
|
||||
def emit_signature_refs(scope_node, fn_nid: str, is_function: bool) -> None:
|
||||
"""Emit references[parameter_type] / references[return_type] edges for
|
||||
a subroutine/function based on its variable_declaration siblings."""
|
||||
stmt_type = "function_statement" if is_function else "subroutine_statement"
|
||||
stmt = next((c for c in scope_node.children if c.type == stmt_type), None)
|
||||
if stmt is None:
|
||||
return
|
||||
param_names: set[str] = set()
|
||||
params_node = next((c for c in stmt.children if c.type == "parameters"), None)
|
||||
if params_node is not None:
|
||||
for c in params_node.children:
|
||||
if c.type == "identifier":
|
||||
param_names.add(_read_text(c, source).lower())
|
||||
result_name: str | None = None
|
||||
if is_function:
|
||||
result_node = next((c for c in stmt.children if c.type == "function_result"), None)
|
||||
if result_node is not None:
|
||||
res_id = next((c for c in result_node.children if c.type == "identifier"), None)
|
||||
if res_id is not None:
|
||||
result_name = _read_text(res_id, source).lower()
|
||||
else:
|
||||
# implicit result variable: same name as the function
|
||||
result_name = _fortran_name(stmt)
|
||||
for child in scope_node.children:
|
||||
if child.type != "variable_declaration":
|
||||
continue
|
||||
derived = next((c for c in child.children if c.type == "derived_type"), None)
|
||||
if derived is None:
|
||||
continue
|
||||
type_name_node = next((c for c in derived.children if c.type == "type_name"), None)
|
||||
if type_name_node is None:
|
||||
continue
|
||||
type_name = _read_text(type_name_node, source).lower()
|
||||
for var in child.children:
|
||||
if var.type != "identifier":
|
||||
continue
|
||||
var_name = _read_text(var, source).lower()
|
||||
var_line = var.start_point[0] + 1
|
||||
if var_name in param_names:
|
||||
tgt = ensure_named_node(type_name, var_line)
|
||||
if tgt != fn_nid:
|
||||
add_edge(fn_nid, tgt, "references", var_line, context="parameter_type")
|
||||
elif is_function and var_name == result_name:
|
||||
tgt = ensure_named_node(type_name, var_line)
|
||||
if tgt != fn_nid:
|
||||
add_edge(fn_nid, tgt, "references", var_line, context="return_type")
|
||||
|
||||
def walk_calls(node, scope_nid: str) -> None:
|
||||
if node is None:
|
||||
return
|
||||
@@ -4567,6 +4896,18 @@ def extract_fortran(path: Path) -> dict:
|
||||
walk(child, scope_nid)
|
||||
return
|
||||
|
||||
if t == "derived_type_definition":
|
||||
stmt = next((c for c in node.children if c.type == "derived_type_statement"), None)
|
||||
if stmt is not None:
|
||||
name_node = next((c for c in stmt.children if c.type == "type_name"), None)
|
||||
if name_node is not None:
|
||||
type_name = _read_text(name_node, source).lower()
|
||||
type_nid = _make_id(stem, type_name)
|
||||
line = node.start_point[0] + 1
|
||||
add_node(type_nid, type_name, line)
|
||||
add_edge(scope_nid, type_nid, "defines", line)
|
||||
return
|
||||
|
||||
if t == "subroutine":
|
||||
stmt = next((c for c in node.children if c.type == "subroutine_statement"), None)
|
||||
name = _fortran_name(stmt) if stmt else None
|
||||
@@ -4576,6 +4917,7 @@ def extract_fortran(path: Path) -> dict:
|
||||
add_node(nid, f"{name}()", line)
|
||||
add_edge(scope_nid, nid, "defines", line)
|
||||
scope_bodies.append((nid, node))
|
||||
emit_signature_refs(node, nid, is_function=False)
|
||||
for child in node.children:
|
||||
walk(child, nid)
|
||||
return
|
||||
@@ -4589,6 +4931,7 @@ def extract_fortran(path: Path) -> dict:
|
||||
add_node(nid, f"{name}()", line)
|
||||
add_edge(scope_nid, nid, "defines", line)
|
||||
scope_bodies.append((nid, node))
|
||||
emit_signature_refs(node, nid, is_function=True)
|
||||
for child in node.children:
|
||||
walk(child, nid)
|
||||
return
|
||||
@@ -5474,6 +5817,30 @@ def extract_powershell(path: Path) -> dict:
|
||||
return child
|
||||
return None
|
||||
|
||||
def ensure_named_node(name: str, line: int) -> str:
|
||||
nid = _make_id(stem, name)
|
||||
if nid in seen_ids:
|
||||
return nid
|
||||
nid = _make_id(name)
|
||||
if nid not in seen_ids:
|
||||
add_node(nid, name, line)
|
||||
return nid
|
||||
|
||||
def _ps_type_name(type_literal_node) -> str | None:
|
||||
"""Drill into a type_literal node and return the inner type_identifier text."""
|
||||
if type_literal_node is None:
|
||||
return None
|
||||
for spec in type_literal_node.children:
|
||||
if spec.type != "type_spec":
|
||||
continue
|
||||
for tname in spec.children:
|
||||
if tname.type != "type_name":
|
||||
continue
|
||||
for tid in tname.children:
|
||||
if tid.type == "type_identifier":
|
||||
return _read_text(tid, source)
|
||||
return None
|
||||
|
||||
def walk(node, parent_class_nid: str | None = None) -> None:
|
||||
t = node.type
|
||||
|
||||
@@ -5502,6 +5869,17 @@ def extract_powershell(path: Path) -> dict:
|
||||
walk(child, parent_class_nid=class_nid)
|
||||
return
|
||||
|
||||
if t == "class_property_definition" and parent_class_nid:
|
||||
type_literal = next((c for c in node.children if c.type == "type_literal"), None)
|
||||
type_name = _ps_type_name(type_literal)
|
||||
if type_name:
|
||||
line = node.start_point[0] + 1
|
||||
target_nid = ensure_named_node(type_name, line)
|
||||
if target_nid != parent_class_nid:
|
||||
add_edge(parent_class_nid, target_nid, "references",
|
||||
line, context="field")
|
||||
return
|
||||
|
||||
if t == "class_method_definition":
|
||||
name_node = next((c for c in node.children if c.type == "simple_name"), None)
|
||||
if name_node:
|
||||
@@ -5515,6 +5893,32 @@ def extract_powershell(path: Path) -> dict:
|
||||
method_nid = _make_id(stem, method_name)
|
||||
add_node(method_nid, f"{method_name}()", line)
|
||||
add_edge(file_nid, method_nid, "contains", line)
|
||||
# Return type: type_literal sibling of simple_name
|
||||
return_type_literal = next(
|
||||
(c for c in node.children if c.type == "type_literal"), None)
|
||||
return_type_name = _ps_type_name(return_type_literal)
|
||||
if return_type_name:
|
||||
target_nid = ensure_named_node(return_type_name, line)
|
||||
if target_nid != method_nid:
|
||||
add_edge(method_nid, target_nid, "references",
|
||||
line, context="return_type")
|
||||
# Parameter types: class_method_parameter_list
|
||||
param_list = next(
|
||||
(c for c in node.children if c.type == "class_method_parameter_list"), None)
|
||||
if param_list is not None:
|
||||
for p in param_list.children:
|
||||
if p.type != "class_method_parameter":
|
||||
continue
|
||||
ptype_literal = next(
|
||||
(c for c in p.children if c.type == "type_literal"), None)
|
||||
ptype_name = _ps_type_name(ptype_literal)
|
||||
if not ptype_name:
|
||||
continue
|
||||
p_line = p.start_point[0] + 1
|
||||
target_nid = ensure_named_node(ptype_name, p_line)
|
||||
if target_nid != method_nid:
|
||||
add_edge(method_nid, target_nid, "references",
|
||||
p_line, context="parameter_type")
|
||||
body = _find_script_block_body(node)
|
||||
if body:
|
||||
function_bodies.append((method_nid, body))
|
||||
@@ -7005,6 +7409,15 @@ def extract_objc(path: Path) -> dict:
|
||||
n = node.child_by_field_name(field)
|
||||
return _read(n) if n else None
|
||||
|
||||
def ensure_named_node(name: str, line: int) -> str:
|
||||
nid = _make_id(stem, name)
|
||||
if nid in seen_ids:
|
||||
return nid
|
||||
nid = _make_id(name)
|
||||
if nid not in seen_ids:
|
||||
add_node(nid, name, line)
|
||||
return nid
|
||||
|
||||
def walk(node, parent_nid: str | None = None) -> None:
|
||||
t = node.type
|
||||
line = node.start_point[0] + 1
|
||||
@@ -7047,17 +7460,27 @@ def extract_objc(path: Path) -> dict:
|
||||
if child.type == ":":
|
||||
colon_seen = True
|
||||
elif colon_seen and child.type == "identifier":
|
||||
super_nid = _make_id(_read(child))
|
||||
super_nid = ensure_named_node(_read(child), line)
|
||||
add_edge(cls_nid, super_nid, "inherits", line)
|
||||
colon_seen = False
|
||||
elif child.type == "parameterized_arguments":
|
||||
# protocols adopted
|
||||
# protocols adopted: @interface Foo : Bar <Proto1, Proto2>
|
||||
for sub in child.children:
|
||||
if sub.type == "type_name":
|
||||
for s in sub.children:
|
||||
if s.type == "type_identifier":
|
||||
proto_nid = _make_id(_read(s))
|
||||
add_edge(cls_nid, proto_nid, "imports", line, context="import")
|
||||
proto_nid = ensure_named_node(_read(s), line)
|
||||
add_edge(cls_nid, proto_nid, "implements", line)
|
||||
elif child.type == "property_declaration":
|
||||
prop_line = child.start_point[0] + 1
|
||||
for sub in child.children:
|
||||
if sub.type == "struct_declaration":
|
||||
for s in sub.children:
|
||||
if s.type == "type_identifier":
|
||||
type_nid = ensure_named_node(_read(s), prop_line)
|
||||
edges.append(_semantic_reference_edge(
|
||||
cls_nid, type_nid, "field", str_path, prop_line))
|
||||
break
|
||||
elif child.type == "method_declaration":
|
||||
walk(child, cls_nid)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user