Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 46 additions & 6 deletions graphify/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,14 +727,41 @@ def _import_scala(node, source: bytes, file_nid: str, stem: str, edges: list, st
break


def _php_import_kind(node) -> str | None:
kinds = ("function", "const")
local_kind = next((child.type for child in node.children if child.type in kinds), None)
if local_kind:
return local_kind

parent = node.parent
declaration = parent.parent if parent is not None and parent.type == "namespace_use_group" else parent
if declaration is None or declaration.type != "namespace_use_declaration":
return None

declaration_kind = next(
(child.type for child in declaration.children if child.type in kinds),
None,
)
if declaration_kind:
return declaration_kind

first_clause = next(
(child for child in declaration.children if child.type == "namespace_use_clause"),
None,
)
if first_clause is None:
return None
return next((child.type for child in first_clause.children if child.type in kinds), None)


def _import_php(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str, scope_stack: list[str] | None = None) -> None:
for child in node.children:
if child.type in ("qualified_name", "name", "identifier"):
raw = _read_text(child, source)
module_name = raw.split("\\")[-1].strip()
if module_name:
tgt_nid = _make_id(module_name)
edges.append({
edge = {
"source": file_nid,
"target": tgt_nid,
"relation": "imports",
Expand All @@ -743,7 +770,10 @@ def _import_php(node, source: bytes, file_nid: str, stem: str, edges: list, str_
"source_file": str_path,
"source_location": f"L{node.start_point[0] + 1}",
"weight": 1.0,
})
}
if _php_import_kind(node) in ("function", "const"):
edge["_php_symbol_import"] = True
edges.append(edge)
break


Expand Down Expand Up @@ -2415,9 +2445,16 @@ def extract_scala(path: Path) -> dict:
return _extract_generic(path, _SCALA_CONFIG)


def _extract_php_with_symbol_markers(path: Path) -> dict:
return _extract_generic(path, _PHP_CONFIG)


def extract_php(path: Path) -> dict:
"""Extract classes, functions, methods, namespace uses, and calls from a .php file."""
return _extract_generic(path, _PHP_CONFIG)
result = _extract_php_with_symbol_markers(path)
for edge in result.get("edges", []):
edge.pop("_php_symbol_import", None)
return result


# One level of balanced parens (e.g. `Foo #(Bar #(int))`) — bounded so malformed
Expand Down Expand Up @@ -2721,6 +2758,8 @@ def _rewire_unique_stub_nodes(nodes: list[dict], edges: list[dict]) -> None:
remap[stub_id] = target_id

if not remap:
for edge in edges:
edge.pop("_php_symbol_import", None)
return

by_id = {node.get("id"): node for node in nodes if node.get("id")}
Expand Down Expand Up @@ -2770,7 +2809,8 @@ def _names_own_builtin_base(edge: dict, stub_id: str, remapped_id: str) -> bool:
):
edge["source"] = remapped_source
target = edge.get("target")
if target in remap:
is_php_symbol_import = bool(edge.pop("_php_symbol_import", False))
if target in remap and not is_php_symbol_import:
remapped_target = remap[str(target)]
if not (
is_csharp_scoped_edge
Expand Down Expand Up @@ -5715,7 +5755,7 @@ def add_existing_edge(edge: dict) -> None:
".kt": extract_kotlin,
".kts": extract_kotlin,
".scala": extract_scala,
".php": extract_php,
".php": _extract_php_with_symbol_markers,
".swift": extract_swift,
".lua": extract_lua,
".luau": extract_lua,
Expand Down Expand Up @@ -5840,7 +5880,7 @@ def add_existing_edge(edge: dict) -> None:
"nodejs": extract_js,
"ruby": extract_ruby,
"lua": extract_lua,
"php": extract_php,
"php": _extract_php_with_symbol_markers,
"julia": extract_julia,
}

Expand Down
37 changes: 32 additions & 5 deletions graphify/extractors/resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -3197,14 +3197,19 @@ def _record_raw(relation: str, raw: str) -> None:
else:
raws.setdefault(key, raw)

def _record_use_clause(clause, prefix: str) -> None:
def _record_use_clause(
clause,
prefix: str,
declaration_kind: str | None = None,
) -> None:
clause_kind = declaration_kind
target = None
alias = None
saw_as = False
for c in clause.children:
if c.type in ("function", "const"):
return # not a class import
if c.type == "as":
clause_kind = c.type
elif c.type == "as":
saw_as = True
elif c.type in ("qualified_name", "name"):
if saw_as:
Expand All @@ -3215,6 +3220,8 @@ def _record_use_clause(clause, prefix: str) -> None:
return
fqn = (f"{prefix}\\{target}" if prefix else target).lstrip("\\")
key = (alias or fqn.rsplit("\\", 1)[-1]).strip().lower()
if clause_kind in ("function", "const"):
return
if key:
uses.setdefault(key, fqn)

Expand All @@ -3228,17 +3235,35 @@ def walk(n) -> None:
elif t == "namespace_use_declaration":
prefix = ""
group = None
declaration_kind = next(
(c.type for c in n.children if c.type in ("function", "const")),
None,
)
if declaration_kind is None:
first_clause = next(
(c for c in n.children if c.type == "namespace_use_clause"),
None,
)
if first_clause is not None:
declaration_kind = next(
(
c.type
for c in first_clause.children
if c.type in ("function", "const")
),
None,
)
for c in n.children:
if c.type == "namespace_name":
prefix = _read_text(c, source) # group-use prefix
elif c.type == "namespace_use_group":
group = c
elif c.type == "namespace_use_clause":
_record_use_clause(c, "")
_record_use_clause(c, "", declaration_kind)
if group is not None:
for c in group.children:
if c.type == "namespace_use_clause":
_record_use_clause(c, prefix)
_record_use_clause(c, prefix, declaration_kind)
return
elif t == "class_declaration":
for child in n.children:
Expand Down Expand Up @@ -3323,6 +3348,8 @@ def _external_stub(fqn: str) -> str:
if ref_file not in ns_by_file:
continue
tgt = edge.get("target")
if relation == "imports" and edge.get("_php_symbol_import"):
continue
label = stub_label.get(tgt)
uses = uses_by_file.get(ref_file, {})
if not label and relation == "imports":
Expand Down
Loading