sentinel-codegraph 0.3.0__py3-none-any.whl
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.
- codegraph/__init__.py +5 -0
- codegraph/__main__.py +6 -0
- codegraph/cli.py +475 -0
- codegraph/config.py +55 -0
- codegraph/graph_store.py +455 -0
- codegraph/models.py +87 -0
- codegraph/parser/__init__.py +27 -0
- codegraph/parser/base.py +114 -0
- codegraph/parser/lang_go.py +524 -0
- codegraph/parser/lang_python.py +458 -0
- codegraph/parser/lang_typescript.py +554 -0
- codegraph/parser/links.py +314 -0
- codegraph/parser/queries.py +114 -0
- codegraph/parser/raw_core.py +66 -0
- codegraph/parser/rows.py +191 -0
- codegraph/pipeline.py +370 -0
- codegraph/query.py +125 -0
- codegraph/tree.py +230 -0
- codegraph/walk.py +114 -0
- sentinel_codegraph-0.3.0.dist-info/METADATA +251 -0
- sentinel_codegraph-0.3.0.dist-info/RECORD +23 -0
- sentinel_codegraph-0.3.0.dist-info/WHEEL +4 -0
- sentinel_codegraph-0.3.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
"""Go structural extraction from a raw tree-sitter tree.
|
|
2
|
+
|
|
3
|
+
Collect phase (:func:`collect_go_file`): walks once, emits struct
|
|
4
|
+
types (``class``), interfaces (``interface``), other named types
|
|
5
|
+
(``type``), functions, and methods (reparented to their receiver
|
|
6
|
+
struct), plus imports — dot imports keep ``.`` as the bound name so
|
|
7
|
+
the link phase can resolve their unqualified call sites. Bare-name
|
|
8
|
+
call sites buffer unresolved for the link phase
|
|
9
|
+
(:mod:`codegraph.parser.links`).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from dataclasses import field as dc_field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from tree_sitter import Node
|
|
21
|
+
|
|
22
|
+
from codegraph.models import Edge, EdgeKind
|
|
23
|
+
from codegraph.models import Node as GraphNode
|
|
24
|
+
from codegraph.models import NodeKind
|
|
25
|
+
from codegraph.parser.base import ParsedCall, ParsedDefinition, ParsedImport
|
|
26
|
+
from codegraph.parser.raw_core import field_text, node_text, parse, span, walk
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from codegraph.parser.rows import FileRows
|
|
30
|
+
|
|
31
|
+
LANGUAGE: str = "go"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def extract_definitions(root: Node) -> list[ParsedDefinition]:
|
|
35
|
+
"""Collect struct types, functions, and methods in source order (preorder)."""
|
|
36
|
+
found: list[ParsedDefinition] = []
|
|
37
|
+
scope: list[tuple[str, str]] = [] # enclosing (kind, name)
|
|
38
|
+
stack: list[tuple[Node, bool]] = [(root, False)] # True = exit, pops scope
|
|
39
|
+
while stack:
|
|
40
|
+
node, exiting = stack.pop()
|
|
41
|
+
if exiting:
|
|
42
|
+
scope.pop()
|
|
43
|
+
continue
|
|
44
|
+
parent: str | None = scope[-1][1] if scope else None
|
|
45
|
+
if node.type == "function_declaration":
|
|
46
|
+
name: str = field_text(node, "name")
|
|
47
|
+
kind: str = "function"
|
|
48
|
+
elif node.type == "method_declaration":
|
|
49
|
+
name_node: Node | None = node.child_by_field_name("name")
|
|
50
|
+
name = node_text(name_node) if name_node is not None else ""
|
|
51
|
+
kind = "method"
|
|
52
|
+
if parent is None:
|
|
53
|
+
parent = _receiver_base(node)
|
|
54
|
+
elif node.type == "type_spec":
|
|
55
|
+
name = field_text(node, "name")
|
|
56
|
+
kind = "class"
|
|
57
|
+
else:
|
|
58
|
+
for child in reversed(node.named_children):
|
|
59
|
+
stack.append((child, False))
|
|
60
|
+
continue
|
|
61
|
+
if not name:
|
|
62
|
+
for child in reversed(node.named_children):
|
|
63
|
+
stack.append((child, False))
|
|
64
|
+
continue
|
|
65
|
+
start, end = span(node)
|
|
66
|
+
found.append(
|
|
67
|
+
ParsedDefinition(
|
|
68
|
+
kind=kind,
|
|
69
|
+
name=name,
|
|
70
|
+
start_line=start,
|
|
71
|
+
end_line=end,
|
|
72
|
+
parent=parent,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
scope.append(("class" if kind == "class" else "function", name))
|
|
76
|
+
stack.append((node, True))
|
|
77
|
+
for child in reversed(node.named_children):
|
|
78
|
+
stack.append((child, False))
|
|
79
|
+
return found
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _receiver_base(node: Node) -> str | None:
|
|
83
|
+
"""Return the receiver's base type name (``*S`` -> ``S``), if any."""
|
|
84
|
+
receiver: Node | None = node.child_by_field_name("receiver")
|
|
85
|
+
if receiver is None:
|
|
86
|
+
return None
|
|
87
|
+
candidates: list[str] = []
|
|
88
|
+
stack: list[Node] = [receiver]
|
|
89
|
+
while stack:
|
|
90
|
+
probe: Node = stack.pop()
|
|
91
|
+
if probe.type == "type_identifier":
|
|
92
|
+
candidates.append(node_text(probe))
|
|
93
|
+
stack.extend(probe.named_children)
|
|
94
|
+
return candidates[-1] if candidates else None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _import_name(module: str, explicit: str) -> str:
|
|
98
|
+
if explicit == "_":
|
|
99
|
+
return "*"
|
|
100
|
+
if explicit == ".":
|
|
101
|
+
# Dot import: the package's exported names enter this file's
|
|
102
|
+
# scope unqualified. ``.`` marks the row so the link phase can
|
|
103
|
+
# resolve bare sites against the target file's definitions.
|
|
104
|
+
return "."
|
|
105
|
+
if explicit:
|
|
106
|
+
return explicit
|
|
107
|
+
# Default: base of the module path (quoted path without quotes).
|
|
108
|
+
base: str = module.rsplit("/", 1)[-1]
|
|
109
|
+
return base or "*"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_imports(root: Node) -> list[ParsedImport]:
|
|
113
|
+
"""Collect imports from ``import_spec`` nodes."""
|
|
114
|
+
found: list[ParsedImport] = []
|
|
115
|
+
for node in walk(root):
|
|
116
|
+
if node.type != "import_spec":
|
|
117
|
+
continue
|
|
118
|
+
module: str = field_text(node, "path").strip().strip('"').strip("`").strip()
|
|
119
|
+
if not module:
|
|
120
|
+
continue
|
|
121
|
+
explicit: str = field_text(node, "name").strip()
|
|
122
|
+
name: str = _import_name(module, explicit)
|
|
123
|
+
start, end = span(node)
|
|
124
|
+
found.append(
|
|
125
|
+
ParsedImport(module=module, name=name, start_line=start, end_line=end)
|
|
126
|
+
)
|
|
127
|
+
return found
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(slots=True)
|
|
131
|
+
class _ActiveDefinition:
|
|
132
|
+
"""One live entry on the definition nesting stack."""
|
|
133
|
+
|
|
134
|
+
def_kind: str # "class" | "function"
|
|
135
|
+
def_name: str
|
|
136
|
+
node_id: str
|
|
137
|
+
end_byte: int
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(slots=True)
|
|
141
|
+
class ExtractedFileGraph:
|
|
142
|
+
"""Per-file collect output: rows plus unresolved call sites.
|
|
143
|
+
|
|
144
|
+
``definitions`` maps def name -> node id (first registration wins);
|
|
145
|
+
``imports`` / ``calls`` are the raw IR the link phase resolves.
|
|
146
|
+
No ``calls`` edges are emitted here.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
rel_path: str
|
|
150
|
+
language: str
|
|
151
|
+
total_lines: int
|
|
152
|
+
nodes: dict[str, GraphNode] = dc_field(
|
|
153
|
+
default_factory=lambda: dict[str, GraphNode]()
|
|
154
|
+
)
|
|
155
|
+
edges: list[Edge] = dc_field(default_factory=lambda: list[Edge]())
|
|
156
|
+
definitions: dict[str, str] = dc_field(
|
|
157
|
+
default_factory=lambda: dict[str, str]()
|
|
158
|
+
)
|
|
159
|
+
imports: list[ParsedImport] = dc_field(
|
|
160
|
+
default_factory=lambda: list[ParsedImport]()
|
|
161
|
+
)
|
|
162
|
+
calls: list[ParsedCall] = dc_field(default_factory=lambda: list[ParsedCall]())
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_defined_node_id(
|
|
166
|
+
rel_path: str, def_name: str, start_line: int, end_line: int
|
|
167
|
+
) -> str:
|
|
168
|
+
"""Return the symbolic def id ``base:name:start:end``."""
|
|
169
|
+
return f"{rel_path}:{def_name}:{start_line}:{end_line}"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _pop_finished_definitions(
|
|
173
|
+
active_definition_stack: list[_ActiveDefinition], byte_offset: int
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Pop stack tops whose def ended before byte offset ``byte_offset``."""
|
|
176
|
+
while active_definition_stack and byte_offset > active_definition_stack[-1].end_byte:
|
|
177
|
+
active_definition_stack.pop()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _register_node_once(
|
|
181
|
+
nodes_by_id: dict[str, GraphNode],
|
|
182
|
+
*,
|
|
183
|
+
node_id: str,
|
|
184
|
+
graph_root: str,
|
|
185
|
+
rel_path: str,
|
|
186
|
+
language: str,
|
|
187
|
+
kind: NodeKind,
|
|
188
|
+
name: str,
|
|
189
|
+
start_line: int,
|
|
190
|
+
end_line: int,
|
|
191
|
+
parent_id: str | None,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Insert a node row (first registration wins)."""
|
|
194
|
+
if node_id in nodes_by_id:
|
|
195
|
+
return
|
|
196
|
+
nodes_by_id[node_id] = GraphNode(
|
|
197
|
+
id=node_id,
|
|
198
|
+
root=graph_root,
|
|
199
|
+
file_path=rel_path,
|
|
200
|
+
kind=kind,
|
|
201
|
+
name=name,
|
|
202
|
+
language=language,
|
|
203
|
+
start_line=start_line,
|
|
204
|
+
end_line=end_line,
|
|
205
|
+
parent_id=parent_id,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _register_edge_once(
|
|
210
|
+
emitted_edges: list[Edge],
|
|
211
|
+
emitted_edge_keys: set[tuple[str, str, str]],
|
|
212
|
+
*,
|
|
213
|
+
graph_root: str,
|
|
214
|
+
src_id: str,
|
|
215
|
+
dst_id: str,
|
|
216
|
+
kind: EdgeKind,
|
|
217
|
+
target_module: str | None = None,
|
|
218
|
+
site_line: int | None = None,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Append an edge row, deduped by ``(src, dst, kind)``."""
|
|
221
|
+
key: tuple[str, str, str] = (src_id, dst_id, kind.value)
|
|
222
|
+
if key in emitted_edge_keys:
|
|
223
|
+
return
|
|
224
|
+
emitted_edge_keys.add(key)
|
|
225
|
+
emitted_edges.append(
|
|
226
|
+
Edge(
|
|
227
|
+
id=f"{src_id}::{kind.value}::{dst_id}",
|
|
228
|
+
root=graph_root,
|
|
229
|
+
src_id=src_id,
|
|
230
|
+
dst_id=dst_id,
|
|
231
|
+
kind=kind,
|
|
232
|
+
target_module=target_module,
|
|
233
|
+
site_line=site_line,
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _is_struct_spec(syntax_node: Node) -> bool:
|
|
239
|
+
"""Return True when a ``type_spec`` declares a struct type."""
|
|
240
|
+
for child in syntax_node.named_children:
|
|
241
|
+
if child.type == "struct_type":
|
|
242
|
+
return True
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _is_interface_spec(syntax_node: Node) -> bool:
|
|
247
|
+
"""Return True when a ``type_spec`` declares an interface type."""
|
|
248
|
+
for child in syntax_node.named_children:
|
|
249
|
+
if child.type == "interface_type":
|
|
250
|
+
return True
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _bare_callee_name(syntax_node: Node) -> str | None:
|
|
255
|
+
"""Return the callee name for a bare-name call, else None.
|
|
256
|
+
|
|
257
|
+
Selector calls (``pkg.Fn()``) never match by construction.
|
|
258
|
+
"""
|
|
259
|
+
if syntax_node.type != "call_expression":
|
|
260
|
+
return None
|
|
261
|
+
function_node: Node | None = syntax_node.child_by_field_name("function")
|
|
262
|
+
if function_node is None or function_node.type != "identifier":
|
|
263
|
+
return None
|
|
264
|
+
return node_text(function_node) or None
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def collect_go_file(
|
|
268
|
+
rel_path: str,
|
|
269
|
+
syntax_root: Node,
|
|
270
|
+
graph_root: str,
|
|
271
|
+
total_lines: int,
|
|
272
|
+
) -> ExtractedFileGraph:
|
|
273
|
+
"""Walk one Go file once, collecting defs + imports + call sites.
|
|
274
|
+
|
|
275
|
+
- struct ``type_spec`` → ``Node(CLASS)``, interface ``type_spec`` →
|
|
276
|
+
``Node(INTERFACE)`` (with ``method_signature`` children as
|
|
277
|
+
``METHOD``), other named ``type_spec`` → ``Node(TYPE)``;
|
|
278
|
+
funcs/methods → ``Node`` + ``Edge(CONTAINS)`` with
|
|
279
|
+
``base:name:start:end`` ids. Methods reparent to their receiver
|
|
280
|
+
struct when it is defined in the same file.
|
|
281
|
+
- ``import_spec`` → ``Node(IMPORT)`` + ``Edge(IMPORTS)`` (blank
|
|
282
|
+
imports bind ``*``, dot imports bind ``.``).
|
|
283
|
+
- bare calls with a live enclosing def → buffered as
|
|
284
|
+
``ParsedCall(caller=name, callee, site_line)`` for the link
|
|
285
|
+
phase. Package-level call sites are dropped here.
|
|
286
|
+
|
|
287
|
+
Pure given the parsed tree: no module resolution, no ``calls``
|
|
288
|
+
edges.
|
|
289
|
+
"""
|
|
290
|
+
language: str = LANGUAGE
|
|
291
|
+
extracted_file = ExtractedFileGraph(
|
|
292
|
+
rel_path=rel_path, language=language, total_lines=total_lines
|
|
293
|
+
)
|
|
294
|
+
_register_node_once(
|
|
295
|
+
extracted_file.nodes,
|
|
296
|
+
node_id=rel_path,
|
|
297
|
+
graph_root=graph_root,
|
|
298
|
+
rel_path=rel_path,
|
|
299
|
+
language=language,
|
|
300
|
+
kind=NodeKind.FILE,
|
|
301
|
+
name=Path(rel_path).name,
|
|
302
|
+
start_line=1,
|
|
303
|
+
end_line=total_lines,
|
|
304
|
+
parent_id=None,
|
|
305
|
+
)
|
|
306
|
+
active_definition_stack: list[_ActiveDefinition] = []
|
|
307
|
+
emitted_edge_keys: set[tuple[str, str, str]] = set()
|
|
308
|
+
first_def_node_id_by_name: dict[str, str] = {}
|
|
309
|
+
struct_node_id_by_name: dict[str, str] = {}
|
|
310
|
+
method_receiver_by_node_id: dict[str, str] = {}
|
|
311
|
+
|
|
312
|
+
for syntax_node in walk(syntax_root):
|
|
313
|
+
if syntax_node.type in (
|
|
314
|
+
"function_declaration",
|
|
315
|
+
"method_declaration",
|
|
316
|
+
"method_elem",
|
|
317
|
+
"type_spec",
|
|
318
|
+
):
|
|
319
|
+
_pop_finished_definitions(
|
|
320
|
+
active_definition_stack, syntax_node.start_byte
|
|
321
|
+
)
|
|
322
|
+
if syntax_node.type == "type_spec":
|
|
323
|
+
if _is_struct_spec(syntax_node):
|
|
324
|
+
graph_node_kind: NodeKind = NodeKind.CLASS
|
|
325
|
+
stack_kind: str = "class"
|
|
326
|
+
elif _is_interface_spec(syntax_node):
|
|
327
|
+
graph_node_kind = NodeKind.INTERFACE
|
|
328
|
+
stack_kind = "class"
|
|
329
|
+
else:
|
|
330
|
+
graph_node_kind = NodeKind.TYPE
|
|
331
|
+
stack_kind = "function"
|
|
332
|
+
elif syntax_node.type in ("method_declaration", "method_elem"):
|
|
333
|
+
graph_node_kind = NodeKind.METHOD
|
|
334
|
+
stack_kind = "function"
|
|
335
|
+
else:
|
|
336
|
+
graph_node_kind = NodeKind.FUNCTION
|
|
337
|
+
stack_kind = "function"
|
|
338
|
+
def_name: str = field_text(syntax_node, "name")
|
|
339
|
+
if not def_name:
|
|
340
|
+
continue
|
|
341
|
+
start_line, end_line = span(syntax_node)
|
|
342
|
+
definition_node_id: str = build_defined_node_id(
|
|
343
|
+
rel_path, def_name, start_line, end_line
|
|
344
|
+
)
|
|
345
|
+
parent_node_id: str = (
|
|
346
|
+
active_definition_stack[-1].node_id
|
|
347
|
+
if active_definition_stack
|
|
348
|
+
else rel_path
|
|
349
|
+
)
|
|
350
|
+
_register_node_once(
|
|
351
|
+
extracted_file.nodes,
|
|
352
|
+
node_id=definition_node_id,
|
|
353
|
+
graph_root=graph_root,
|
|
354
|
+
rel_path=rel_path,
|
|
355
|
+
language=language,
|
|
356
|
+
kind=graph_node_kind,
|
|
357
|
+
name=def_name,
|
|
358
|
+
start_line=start_line,
|
|
359
|
+
end_line=end_line,
|
|
360
|
+
parent_id=parent_node_id,
|
|
361
|
+
)
|
|
362
|
+
_register_edge_once(
|
|
363
|
+
extracted_file.edges,
|
|
364
|
+
emitted_edge_keys,
|
|
365
|
+
graph_root=graph_root,
|
|
366
|
+
src_id=parent_node_id,
|
|
367
|
+
dst_id=definition_node_id,
|
|
368
|
+
kind=EdgeKind.CONTAINS,
|
|
369
|
+
)
|
|
370
|
+
first_def_node_id_by_name.setdefault(def_name, definition_node_id)
|
|
371
|
+
if syntax_node.type == "type_spec":
|
|
372
|
+
struct_node_id_by_name.setdefault(def_name, definition_node_id)
|
|
373
|
+
elif syntax_node.type == "method_declaration":
|
|
374
|
+
receiver: str | None = _receiver_base(syntax_node)
|
|
375
|
+
if receiver:
|
|
376
|
+
method_receiver_by_node_id.setdefault(
|
|
377
|
+
definition_node_id, receiver
|
|
378
|
+
)
|
|
379
|
+
active_definition_stack.append(
|
|
380
|
+
_ActiveDefinition(
|
|
381
|
+
def_kind=stack_kind,
|
|
382
|
+
def_name=def_name,
|
|
383
|
+
node_id=definition_node_id,
|
|
384
|
+
end_byte=syntax_node.end_byte,
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
elif syntax_node.type == "import_spec":
|
|
388
|
+
module: str = (
|
|
389
|
+
field_text(syntax_node, "path").strip().strip('"').strip("`").strip()
|
|
390
|
+
)
|
|
391
|
+
if not module:
|
|
392
|
+
continue
|
|
393
|
+
explicit: str = field_text(syntax_node, "name").strip()
|
|
394
|
+
bound_name: str = _import_name(module, explicit)
|
|
395
|
+
if not bound_name:
|
|
396
|
+
continue
|
|
397
|
+
start_line, end_line = span(syntax_node)
|
|
398
|
+
import_node_id: str = f"{rel_path}:import:{bound_name}:{start_line}"
|
|
399
|
+
_register_node_once(
|
|
400
|
+
extracted_file.nodes,
|
|
401
|
+
node_id=import_node_id,
|
|
402
|
+
graph_root=graph_root,
|
|
403
|
+
rel_path=rel_path,
|
|
404
|
+
language=language,
|
|
405
|
+
kind=NodeKind.IMPORT,
|
|
406
|
+
name=bound_name,
|
|
407
|
+
start_line=start_line,
|
|
408
|
+
end_line=end_line,
|
|
409
|
+
parent_id=rel_path,
|
|
410
|
+
)
|
|
411
|
+
_register_edge_once(
|
|
412
|
+
extracted_file.edges,
|
|
413
|
+
emitted_edge_keys,
|
|
414
|
+
graph_root=graph_root,
|
|
415
|
+
src_id=rel_path,
|
|
416
|
+
dst_id=import_node_id,
|
|
417
|
+
kind=EdgeKind.IMPORTS,
|
|
418
|
+
target_module=module,
|
|
419
|
+
)
|
|
420
|
+
extracted_file.imports.append(
|
|
421
|
+
ParsedImport(
|
|
422
|
+
module=module,
|
|
423
|
+
name=bound_name,
|
|
424
|
+
start_line=start_line,
|
|
425
|
+
end_line=end_line,
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
callee_name: str | None = _bare_callee_name(syntax_node)
|
|
430
|
+
if callee_name is None:
|
|
431
|
+
continue
|
|
432
|
+
_pop_finished_definitions(
|
|
433
|
+
active_definition_stack, syntax_node.start_byte
|
|
434
|
+
)
|
|
435
|
+
if not active_definition_stack:
|
|
436
|
+
continue # package-level call: drop
|
|
437
|
+
call_site_line: int = span(syntax_node)[0]
|
|
438
|
+
extracted_file.calls.append(
|
|
439
|
+
ParsedCall(
|
|
440
|
+
caller=active_definition_stack[-1].def_name,
|
|
441
|
+
callee=callee_name,
|
|
442
|
+
site_line=call_site_line,
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
for method_node_id, receiver_name in method_receiver_by_node_id.items():
|
|
447
|
+
struct_node_id: str | None = struct_node_id_by_name.get(receiver_name)
|
|
448
|
+
if struct_node_id is None:
|
|
449
|
+
continue
|
|
450
|
+
method_node: GraphNode | None = extracted_file.nodes.get(method_node_id)
|
|
451
|
+
if method_node is None or method_node.parent_id != rel_path:
|
|
452
|
+
continue
|
|
453
|
+
method_node.parent_id = struct_node_id
|
|
454
|
+
for edge in extracted_file.edges:
|
|
455
|
+
if (
|
|
456
|
+
edge.kind == EdgeKind.CONTAINS
|
|
457
|
+
and edge.dst_id == method_node_id
|
|
458
|
+
and edge.src_id == rel_path
|
|
459
|
+
):
|
|
460
|
+
edge.src_id = struct_node_id
|
|
461
|
+
edge.id = f"{struct_node_id}::{edge.kind.value}::{method_node_id}"
|
|
462
|
+
break
|
|
463
|
+
|
|
464
|
+
extracted_file.definitions.update(first_def_node_id_by_name)
|
|
465
|
+
return extracted_file
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def build_go_file_rows(
|
|
469
|
+
root: str,
|
|
470
|
+
rel_path: str,
|
|
471
|
+
source_text: str,
|
|
472
|
+
_import_index: Mapping[str, str],
|
|
473
|
+
) -> FileRows:
|
|
474
|
+
"""Build one Go file's rows via the collect phase.
|
|
475
|
+
|
|
476
|
+
Nodes + ``contains`` / ``imports`` edges come out of
|
|
477
|
+
:func:`collect_go_file` directly; ``calls`` edges are never
|
|
478
|
+
emitted here — ``rows.calls`` carries the buffered call sites for
|
|
479
|
+
:func:`codegraph.parser.links.resolve_call_edges`.
|
|
480
|
+
``_import_index`` is accepted for builder-signature uniformity and
|
|
481
|
+
ignored: the link phase resolves modules from the full file set.
|
|
482
|
+
"""
|
|
483
|
+
from codegraph.parser.rows import FileRows
|
|
484
|
+
|
|
485
|
+
total_lines: int = max(source_text.count("\n") + 1, 1)
|
|
486
|
+
rows = FileRows(rel_path=rel_path, language=LANGUAGE)
|
|
487
|
+
if not source_text.strip():
|
|
488
|
+
rows.nodes.append(
|
|
489
|
+
GraphNode(
|
|
490
|
+
id=rel_path,
|
|
491
|
+
root=root,
|
|
492
|
+
file_path=rel_path,
|
|
493
|
+
kind=NodeKind.FILE,
|
|
494
|
+
name=Path(rel_path).name,
|
|
495
|
+
language=LANGUAGE,
|
|
496
|
+
start_line=1,
|
|
497
|
+
end_line=total_lines,
|
|
498
|
+
parent_id=None,
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
return rows
|
|
502
|
+
tree = parse(LANGUAGE, source_text.encode("utf-8"))
|
|
503
|
+
out: ExtractedFileGraph = collect_go_file(
|
|
504
|
+
rel_path, tree.root_node, root, total_lines
|
|
505
|
+
)
|
|
506
|
+
rows.nodes.extend(out.nodes.values())
|
|
507
|
+
rows.edges.extend(out.edges)
|
|
508
|
+
rows.definitions.update(out.definitions)
|
|
509
|
+
for parsed_import in out.imports:
|
|
510
|
+
rows.imports.setdefault(parsed_import.name, parsed_import.module)
|
|
511
|
+
rows.import_details.extend(out.imports)
|
|
512
|
+
rows.calls.extend(out.calls)
|
|
513
|
+
return rows
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
__all__ = [
|
|
517
|
+
"ExtractedFileGraph",
|
|
518
|
+
"LANGUAGE",
|
|
519
|
+
"build_defined_node_id",
|
|
520
|
+
"build_go_file_rows",
|
|
521
|
+
"collect_go_file",
|
|
522
|
+
"extract_definitions",
|
|
523
|
+
"extract_imports",
|
|
524
|
+
]
|