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,458 @@
|
|
|
1
|
+
"""Python structural extraction from a raw tree-sitter tree.
|
|
2
|
+
|
|
3
|
+
Collect phase (:func:`collect_python_file`): walks once with a single
|
|
4
|
+
``active_definition_stack`` (byte-offset expiry), emits nodes +
|
|
5
|
+
``contains`` / ``imports`` edges inline with ``base:name:start:end``
|
|
6
|
+
ids, and buffers bare-name call sites unresolved. Call resolution
|
|
7
|
+
happens later in the link phase (:mod:`codegraph.parser.links`)
|
|
8
|
+
against the global definition registry + per-file import map, so this
|
|
9
|
+
module never resolves modules and never emits ``calls`` edges.
|
|
10
|
+
Decorator call sites are buffered per decorated definition and drained
|
|
11
|
+
into the next ``function_definition`` / ``class_definition`` node as
|
|
12
|
+
``ParsedCall`` rows carrying the @-line; the link phase resolves them
|
|
13
|
+
like any other call (attribute decorators are skipped, builtins drop).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
from tree_sitter import Node
|
|
24
|
+
|
|
25
|
+
from codegraph.models import Edge, EdgeKind, Node as GraphNode, NodeKind
|
|
26
|
+
from codegraph.parser.base import ParsedCall, ParsedImport
|
|
27
|
+
from codegraph.parser.raw_core import field_text, node_text, span, walk
|
|
28
|
+
|
|
29
|
+
LANGUAGE: str = "python"
|
|
30
|
+
|
|
31
|
+
DefinitionKind = Literal["class", "function"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _definition_name(syntax_node: Node) -> str:
|
|
35
|
+
return field_text(syntax_node, "name")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _bare_callee_name(syntax_node: Node) -> str | None:
|
|
39
|
+
"""Return the callee name for a bare-name call, else None."""
|
|
40
|
+
if syntax_node.type != "call":
|
|
41
|
+
return None
|
|
42
|
+
function_node: Node | None = syntax_node.child_by_field_name("function")
|
|
43
|
+
if function_node is None or function_node.type != "identifier":
|
|
44
|
+
return None
|
|
45
|
+
return node_text(function_node) or None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _decorator_callee_name(syntax_node: Node) -> str | None:
|
|
49
|
+
"""Return the decorator callee for a ``decorator`` node, else None.
|
|
50
|
+
|
|
51
|
+
Bare ``@retry`` unwraps the single ``identifier`` child; arg-form
|
|
52
|
+
``@with_logging("debug")`` unwraps one ``call`` layer via its
|
|
53
|
+
``function`` field. Attribute decorators (``@app.get``) return None
|
|
54
|
+
(skipped, per the no-attribute rule).
|
|
55
|
+
"""
|
|
56
|
+
if syntax_node.type != "decorator":
|
|
57
|
+
return None
|
|
58
|
+
callee_nodes: list[Node] = syntax_node.named_children
|
|
59
|
+
if len(callee_nodes) != 1:
|
|
60
|
+
return None
|
|
61
|
+
callee_node: Node = callee_nodes[0]
|
|
62
|
+
if callee_node.type == "identifier":
|
|
63
|
+
return node_text(callee_node) or None
|
|
64
|
+
if callee_node.type == "call":
|
|
65
|
+
return _bare_callee_name(callee_node)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(slots=True)
|
|
70
|
+
class _ActiveDefinition:
|
|
71
|
+
"""One live entry on the definition nesting stack: enclosing class or function."""
|
|
72
|
+
|
|
73
|
+
def_kind: DefinitionKind # "class" | "function"
|
|
74
|
+
def_name: str
|
|
75
|
+
node_id: str
|
|
76
|
+
end_byte: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(slots=True)
|
|
80
|
+
class ExtractedFileGraph:
|
|
81
|
+
"""Per-file collect output: rows plus unresolved call sites.
|
|
82
|
+
|
|
83
|
+
``definitions`` maps def name -> node id (first registration wins);
|
|
84
|
+
``imports`` / ``calls`` are the raw IR the link phase resolves.
|
|
85
|
+
No ``calls`` edges are emitted here.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
rel_path: str
|
|
89
|
+
language: str
|
|
90
|
+
total_lines: int
|
|
91
|
+
nodes: dict[str, GraphNode] = field(
|
|
92
|
+
default_factory=lambda: dict[str, GraphNode]()
|
|
93
|
+
)
|
|
94
|
+
edges: list[Edge] = field(default_factory=lambda: list[Edge]())
|
|
95
|
+
definitions: dict[str, str] = field(
|
|
96
|
+
default_factory=lambda: dict[str, str]()
|
|
97
|
+
)
|
|
98
|
+
imports: list[ParsedImport] = field(
|
|
99
|
+
default_factory=lambda: list[ParsedImport]()
|
|
100
|
+
)
|
|
101
|
+
calls: list[ParsedCall] = field(default_factory=lambda: list[ParsedCall]())
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def build_defined_node_id(
|
|
105
|
+
rel_path: str, def_name: str, start_line: int, end_line: int
|
|
106
|
+
) -> str:
|
|
107
|
+
"""Return the symbolic def id ``base:name:start:end``."""
|
|
108
|
+
return f"{rel_path}:{def_name}:{start_line}:{end_line}"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _pop_finished_definitions(
|
|
112
|
+
active_definition_stack: list[_ActiveDefinition], byte_offset: int
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Pop stack tops whose def ended before byte offset ``byte_offset``."""
|
|
115
|
+
while active_definition_stack and byte_offset > active_definition_stack[-1].end_byte:
|
|
116
|
+
active_definition_stack.pop()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _find_innermost_definition_of_kind(
|
|
120
|
+
active_definition_stack: list[_ActiveDefinition], def_kind: DefinitionKind
|
|
121
|
+
) -> _ActiveDefinition | None:
|
|
122
|
+
"""Return the innermost live stack entry of ``def_kind``."""
|
|
123
|
+
for active_definition in reversed(active_definition_stack):
|
|
124
|
+
if active_definition.def_kind == def_kind:
|
|
125
|
+
return active_definition
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _imports_from_statement(
|
|
130
|
+
source: str, start_line: int, end_line: int
|
|
131
|
+
) -> list[ParsedImport]:
|
|
132
|
+
"""Parse one Python import statement into per-name import rows.
|
|
133
|
+
|
|
134
|
+
``from`` imports keep both the bound name and the defining-module
|
|
135
|
+
name (``from utils import helper as h`` -> bound ``h``,
|
|
136
|
+
original ``helper``); the link phase joins on the original.
|
|
137
|
+
"""
|
|
138
|
+
parsed_imports: list[ParsedImport] = []
|
|
139
|
+
text: str = " ".join(source.replace("(", " ").replace(")", " ").split())
|
|
140
|
+
if text.startswith("from "):
|
|
141
|
+
rest: str = text[5:]
|
|
142
|
+
module, sep, names_part = rest.partition(" import ")
|
|
143
|
+
module = module.strip()
|
|
144
|
+
if not sep or not module:
|
|
145
|
+
return parsed_imports
|
|
146
|
+
for raw in names_part.split(","):
|
|
147
|
+
symbol: str = raw.strip().rstrip(";")
|
|
148
|
+
if not symbol:
|
|
149
|
+
continue
|
|
150
|
+
if symbol == "*":
|
|
151
|
+
parsed_imports.append(
|
|
152
|
+
ParsedImport(
|
|
153
|
+
module=module,
|
|
154
|
+
name="*",
|
|
155
|
+
start_line=start_line,
|
|
156
|
+
end_line=end_line,
|
|
157
|
+
original="*",
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
continue
|
|
161
|
+
original, alias_sep, alias = symbol.partition(" as ")
|
|
162
|
+
original = original.strip()
|
|
163
|
+
alias = alias.strip()
|
|
164
|
+
if not original:
|
|
165
|
+
continue
|
|
166
|
+
bound_name: str = alias if alias_sep else original
|
|
167
|
+
if not bound_name:
|
|
168
|
+
continue
|
|
169
|
+
parsed_imports.append(
|
|
170
|
+
ParsedImport(
|
|
171
|
+
module=module,
|
|
172
|
+
name=bound_name,
|
|
173
|
+
start_line=start_line,
|
|
174
|
+
end_line=end_line,
|
|
175
|
+
original="" if bound_name == original else original,
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
elif text.startswith("import "):
|
|
179
|
+
rest = text[7:]
|
|
180
|
+
for raw in rest.split(","):
|
|
181
|
+
symbol = raw.strip().rstrip(";")
|
|
182
|
+
if not symbol:
|
|
183
|
+
continue
|
|
184
|
+
dotted, sep, alias = symbol.partition(" as ")
|
|
185
|
+
dotted = dotted.strip()
|
|
186
|
+
if not dotted:
|
|
187
|
+
continue
|
|
188
|
+
bound_name = alias.strip() if sep else dotted.split(".")[0]
|
|
189
|
+
if bound_name:
|
|
190
|
+
parsed_imports.append(
|
|
191
|
+
ParsedImport(
|
|
192
|
+
module=dotted,
|
|
193
|
+
name=bound_name,
|
|
194
|
+
start_line=start_line,
|
|
195
|
+
end_line=end_line,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
return parsed_imports
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_IMPORT_RE: re.Pattern[str] = re.compile(r"^\s*(import\s+|from\s+)")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _register_node_once(
|
|
205
|
+
nodes_by_id: dict[str, GraphNode],
|
|
206
|
+
*,
|
|
207
|
+
node_id: str,
|
|
208
|
+
graph_root: str,
|
|
209
|
+
rel_path: str,
|
|
210
|
+
language: str,
|
|
211
|
+
kind: NodeKind,
|
|
212
|
+
name: str,
|
|
213
|
+
start_line: int,
|
|
214
|
+
end_line: int,
|
|
215
|
+
parent_id: str | None,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Insert a node row (first registration wins)."""
|
|
218
|
+
if node_id in nodes_by_id:
|
|
219
|
+
return
|
|
220
|
+
nodes_by_id[node_id] = GraphNode(
|
|
221
|
+
id=node_id,
|
|
222
|
+
root=graph_root,
|
|
223
|
+
file_path=rel_path,
|
|
224
|
+
kind=kind,
|
|
225
|
+
name=name,
|
|
226
|
+
language=language,
|
|
227
|
+
start_line=start_line,
|
|
228
|
+
end_line=end_line,
|
|
229
|
+
parent_id=parent_id,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _register_edge_once(
|
|
234
|
+
emitted_edges: list[Edge],
|
|
235
|
+
emitted_edge_keys: set[tuple[str, str, str]],
|
|
236
|
+
*,
|
|
237
|
+
graph_root: str,
|
|
238
|
+
src_id: str,
|
|
239
|
+
dst_id: str,
|
|
240
|
+
kind: EdgeKind,
|
|
241
|
+
target_module: str | None = None,
|
|
242
|
+
site_line: int | None = None,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Append an edge row, deduped by ``(src, dst, kind)``."""
|
|
245
|
+
key: tuple[str, str, str] = (src_id, dst_id, kind.value)
|
|
246
|
+
if key in emitted_edge_keys:
|
|
247
|
+
return
|
|
248
|
+
emitted_edge_keys.add(key)
|
|
249
|
+
emitted_edges.append(
|
|
250
|
+
Edge(
|
|
251
|
+
id=f"{src_id}::{kind.value}::{dst_id}",
|
|
252
|
+
root=graph_root,
|
|
253
|
+
src_id=src_id,
|
|
254
|
+
dst_id=dst_id,
|
|
255
|
+
kind=kind,
|
|
256
|
+
target_module=target_module,
|
|
257
|
+
site_line=site_line,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def collect_python_file(
|
|
263
|
+
rel_path: str,
|
|
264
|
+
syntax_root: Node,
|
|
265
|
+
graph_root: str,
|
|
266
|
+
total_lines: int,
|
|
267
|
+
) -> ExtractedFileGraph:
|
|
268
|
+
"""Walk one Python file once, collecting defs + imports + call sites.
|
|
269
|
+
|
|
270
|
+
- defs → ``Node`` + ``Edge(CONTAINS)`` with ``base:name:start:end`` ids.
|
|
271
|
+
- imports → ``Node(IMPORT)`` + ``Edge(IMPORTS)`` (existing statement rules).
|
|
272
|
+
- bare calls with a live enclosing def → buffered as
|
|
273
|
+
``ParsedCall(caller=name, callee, site_line)`` for the link phase.
|
|
274
|
+
Module-level call sites are dropped here.
|
|
275
|
+
|
|
276
|
+
Pure given the parsed tree: no module resolution, no ``calls`` edges.
|
|
277
|
+
"""
|
|
278
|
+
language: str = LANGUAGE
|
|
279
|
+
extracted_file = ExtractedFileGraph(
|
|
280
|
+
rel_path=rel_path, language=language, total_lines=total_lines
|
|
281
|
+
)
|
|
282
|
+
_register_node_once(
|
|
283
|
+
extracted_file.nodes,
|
|
284
|
+
node_id=rel_path,
|
|
285
|
+
graph_root=graph_root,
|
|
286
|
+
rel_path=rel_path,
|
|
287
|
+
language=language,
|
|
288
|
+
kind=NodeKind.FILE,
|
|
289
|
+
name=Path(rel_path).name,
|
|
290
|
+
start_line=1,
|
|
291
|
+
end_line=total_lines,
|
|
292
|
+
parent_id=None,
|
|
293
|
+
)
|
|
294
|
+
# Single unified LIFO stack preserving nesting order (class + function
|
|
295
|
+
# entries interleaved). Separate class/function stacks would lose the
|
|
296
|
+
# parent chain for closures (def-in-method-in-class) and double the
|
|
297
|
+
# byte-offset prune logic, so one stack is kept and named as such.
|
|
298
|
+
active_definition_stack: list[_ActiveDefinition] = []
|
|
299
|
+
# Decorator call sites buffered here and drained into the next
|
|
300
|
+
# def/class node: (callee_name, at_line). A skipped attribute
|
|
301
|
+
# decorator buffers nothing, so the drain always pairs exactly
|
|
302
|
+
# with the wrapped definition.
|
|
303
|
+
pending_decorator_sites: list[tuple[str, int]] = []
|
|
304
|
+
emitted_edge_keys: set[tuple[str, str, str]] = set()
|
|
305
|
+
first_def_node_id_by_name: dict[str, str] = {}
|
|
306
|
+
|
|
307
|
+
for syntax_node in walk(syntax_root):
|
|
308
|
+
if syntax_node.type == "decorator":
|
|
309
|
+
decorator_callee: str | None = _decorator_callee_name(syntax_node)
|
|
310
|
+
if decorator_callee is not None:
|
|
311
|
+
decorator_line: int = span(syntax_node)[0] # the @-line
|
|
312
|
+
pending_decorator_sites.append((decorator_callee, decorator_line))
|
|
313
|
+
continue
|
|
314
|
+
if syntax_node.type in ("class_definition", "function_definition"):
|
|
315
|
+
_pop_finished_definitions(
|
|
316
|
+
active_definition_stack, syntax_node.start_byte
|
|
317
|
+
)
|
|
318
|
+
def_name: str = _definition_name(syntax_node)
|
|
319
|
+
if not def_name:
|
|
320
|
+
continue
|
|
321
|
+
for decorator_callee, decorator_line in pending_decorator_sites:
|
|
322
|
+
extracted_file.calls.append(
|
|
323
|
+
ParsedCall(
|
|
324
|
+
caller=def_name,
|
|
325
|
+
callee=decorator_callee,
|
|
326
|
+
site_line=decorator_line,
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
pending_decorator_sites.clear()
|
|
330
|
+
if syntax_node.type == "class_definition":
|
|
331
|
+
definition_kind_label: str = "class"
|
|
332
|
+
graph_node_kind: NodeKind = NodeKind.CLASS
|
|
333
|
+
enclosing_definition: _ActiveDefinition | None = (
|
|
334
|
+
active_definition_stack[-1] if active_definition_stack else None
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
enclosing_class_definition: _ActiveDefinition | None = (
|
|
338
|
+
_find_innermost_definition_of_kind(active_definition_stack, "class")
|
|
339
|
+
)
|
|
340
|
+
is_direct_method_of_class: bool = (
|
|
341
|
+
enclosing_class_definition is not None
|
|
342
|
+
and bool(active_definition_stack)
|
|
343
|
+
and active_definition_stack[-1] is enclosing_class_definition
|
|
344
|
+
)
|
|
345
|
+
definition_kind_label = (
|
|
346
|
+
"method" if is_direct_method_of_class else "function"
|
|
347
|
+
)
|
|
348
|
+
graph_node_kind = (
|
|
349
|
+
NodeKind.METHOD if is_direct_method_of_class else NodeKind.FUNCTION
|
|
350
|
+
)
|
|
351
|
+
enclosing_definition = (
|
|
352
|
+
active_definition_stack[-1] if active_definition_stack else None
|
|
353
|
+
)
|
|
354
|
+
start_line, end_line = span(syntax_node)
|
|
355
|
+
definition_node_id: str = build_defined_node_id(
|
|
356
|
+
rel_path, def_name, start_line, end_line
|
|
357
|
+
)
|
|
358
|
+
parent_node_id: str = (
|
|
359
|
+
enclosing_definition.node_id
|
|
360
|
+
if enclosing_definition is not None
|
|
361
|
+
else rel_path
|
|
362
|
+
)
|
|
363
|
+
_register_node_once(
|
|
364
|
+
extracted_file.nodes,
|
|
365
|
+
node_id=definition_node_id,
|
|
366
|
+
graph_root=graph_root,
|
|
367
|
+
rel_path=rel_path,
|
|
368
|
+
language=language,
|
|
369
|
+
kind=graph_node_kind,
|
|
370
|
+
name=def_name,
|
|
371
|
+
start_line=start_line,
|
|
372
|
+
end_line=end_line,
|
|
373
|
+
parent_id=parent_node_id,
|
|
374
|
+
)
|
|
375
|
+
_register_edge_once(
|
|
376
|
+
extracted_file.edges,
|
|
377
|
+
emitted_edge_keys,
|
|
378
|
+
graph_root=graph_root,
|
|
379
|
+
src_id=parent_node_id,
|
|
380
|
+
dst_id=definition_node_id,
|
|
381
|
+
kind=EdgeKind.CONTAINS,
|
|
382
|
+
)
|
|
383
|
+
first_def_node_id_by_name.setdefault(def_name, definition_node_id)
|
|
384
|
+
active_definition_stack.append(
|
|
385
|
+
_ActiveDefinition(
|
|
386
|
+
def_kind="class" if definition_kind_label == "class" else "function",
|
|
387
|
+
def_name=def_name,
|
|
388
|
+
node_id=definition_node_id,
|
|
389
|
+
end_byte=syntax_node.end_byte,
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
elif syntax_node.type in ("import_statement", "import_from_statement"):
|
|
393
|
+
import_statement_text: str = node_text(syntax_node)
|
|
394
|
+
if not _IMPORT_RE.match(import_statement_text):
|
|
395
|
+
continue
|
|
396
|
+
start_line, end_line = span(syntax_node)
|
|
397
|
+
for parsed_import in _imports_from_statement(
|
|
398
|
+
import_statement_text, start_line, end_line
|
|
399
|
+
):
|
|
400
|
+
import_node_id: str = (
|
|
401
|
+
f"{rel_path}:import:{parsed_import.name}:{start_line}"
|
|
402
|
+
)
|
|
403
|
+
_register_node_once(
|
|
404
|
+
extracted_file.nodes,
|
|
405
|
+
node_id=import_node_id,
|
|
406
|
+
graph_root=graph_root,
|
|
407
|
+
rel_path=rel_path,
|
|
408
|
+
language=language,
|
|
409
|
+
kind=NodeKind.IMPORT,
|
|
410
|
+
name=parsed_import.name,
|
|
411
|
+
start_line=start_line,
|
|
412
|
+
end_line=end_line,
|
|
413
|
+
parent_id=rel_path,
|
|
414
|
+
)
|
|
415
|
+
_register_edge_once(
|
|
416
|
+
extracted_file.edges,
|
|
417
|
+
emitted_edge_keys,
|
|
418
|
+
graph_root=graph_root,
|
|
419
|
+
src_id=rel_path,
|
|
420
|
+
dst_id=import_node_id,
|
|
421
|
+
kind=EdgeKind.IMPORTS,
|
|
422
|
+
target_module=parsed_import.module,
|
|
423
|
+
)
|
|
424
|
+
extracted_file.imports.append(parsed_import)
|
|
425
|
+
else:
|
|
426
|
+
if syntax_node.type == "call" and (
|
|
427
|
+
syntax_node.parent is not None
|
|
428
|
+
and syntax_node.parent.type == "decorator"
|
|
429
|
+
):
|
|
430
|
+
continue # arg-form wrapper (e.g. with_logging("debug")):
|
|
431
|
+
# already buffered by the decorator branch above
|
|
432
|
+
callee_name: str | None = _bare_callee_name(syntax_node)
|
|
433
|
+
if callee_name is None:
|
|
434
|
+
continue
|
|
435
|
+
_pop_finished_definitions(
|
|
436
|
+
active_definition_stack, syntax_node.start_byte
|
|
437
|
+
)
|
|
438
|
+
if not active_definition_stack:
|
|
439
|
+
continue # module-level call: drop
|
|
440
|
+
call_site_line: int = span(syntax_node)[0]
|
|
441
|
+
extracted_file.calls.append(
|
|
442
|
+
ParsedCall(
|
|
443
|
+
caller=active_definition_stack[-1].def_name,
|
|
444
|
+
callee=callee_name,
|
|
445
|
+
site_line=call_site_line,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
extracted_file.definitions.update(first_def_node_id_by_name)
|
|
450
|
+
return extracted_file
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
__all__ = [
|
|
454
|
+
"ExtractedFileGraph",
|
|
455
|
+
"LANGUAGE",
|
|
456
|
+
"build_defined_node_id",
|
|
457
|
+
"collect_python_file",
|
|
458
|
+
]
|