python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.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.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
delphi_lsp/agent_cpg.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import posixpath
|
|
6
|
+
import sys
|
|
7
|
+
import unicodedata
|
|
8
|
+
from collections import deque
|
|
9
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
_GRAPH_EDGE_LABELS = {
|
|
13
|
+
"ast": frozenset({"AST", "CONTAINS"}),
|
|
14
|
+
"cfg": frozenset({"CFG"}),
|
|
15
|
+
"dfg": frozenset({"DEF", "USE", "REACHING_DEF"}),
|
|
16
|
+
"call": frozenset({"CALL"}),
|
|
17
|
+
"full": None,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class CpgTarget:
|
|
23
|
+
target_id: str
|
|
24
|
+
source_path: str
|
|
25
|
+
path: str
|
|
26
|
+
unit_id: str
|
|
27
|
+
name: str
|
|
28
|
+
qualified_name: str
|
|
29
|
+
kind: str
|
|
30
|
+
line: int
|
|
31
|
+
column: int
|
|
32
|
+
end_line: int = 0
|
|
33
|
+
end_column: int = 0
|
|
34
|
+
visibility: str = "unknown"
|
|
35
|
+
type_name: str = ""
|
|
36
|
+
owner: str = ""
|
|
37
|
+
parent_target_id: str = ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalized_identity(value: object) -> object:
|
|
41
|
+
if isinstance(value, str):
|
|
42
|
+
normalized = unicodedata.normalize("NFC", value).replace("\\", "/")
|
|
43
|
+
if "/" in normalized:
|
|
44
|
+
normalized = posixpath.normpath(normalized)
|
|
45
|
+
return normalized.casefold()
|
|
46
|
+
if isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray)):
|
|
47
|
+
return [_normalized_identity(item) for item in value]
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _stable_id(prefix: str, *identity: object) -> str:
|
|
52
|
+
payload = json.dumps(
|
|
53
|
+
[_normalized_identity(value) for value in identity],
|
|
54
|
+
ensure_ascii=False,
|
|
55
|
+
separators=(",", ":"),
|
|
56
|
+
).encode("utf-8")
|
|
57
|
+
return f"{prefix}_{hashlib.sha256(payload).hexdigest()}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _frozen_properties(
|
|
61
|
+
properties: Mapping[str, object] | None,
|
|
62
|
+
) -> tuple[tuple[str, object], ...]:
|
|
63
|
+
if properties is None:
|
|
64
|
+
return ()
|
|
65
|
+
return tuple(sorted(((str(key), value) for key, value in properties.items())))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True, slots=True)
|
|
69
|
+
class CpgNode:
|
|
70
|
+
node_id: str
|
|
71
|
+
label: str
|
|
72
|
+
properties: tuple[tuple[str, object], ...] = ()
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def create(
|
|
76
|
+
cls,
|
|
77
|
+
*,
|
|
78
|
+
label: str,
|
|
79
|
+
identity: Sequence[object],
|
|
80
|
+
properties: Mapping[str, object] | None = None,
|
|
81
|
+
) -> CpgNode:
|
|
82
|
+
normalized_label = label.upper()
|
|
83
|
+
return cls(
|
|
84
|
+
_stable_id("cpg_node", normalized_label, tuple(identity)),
|
|
85
|
+
normalized_label,
|
|
86
|
+
_frozen_properties(properties),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def to_mapping(self) -> dict[str, object]:
|
|
90
|
+
return {
|
|
91
|
+
"item_type": "cpg_node",
|
|
92
|
+
"node_id": self.node_id,
|
|
93
|
+
"label": self.label,
|
|
94
|
+
"properties": dict(self.properties),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True, slots=True)
|
|
99
|
+
class CpgEdge:
|
|
100
|
+
edge_id: str
|
|
101
|
+
label: str
|
|
102
|
+
source: str
|
|
103
|
+
target: str
|
|
104
|
+
properties: tuple[tuple[str, object], ...] = ()
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def create(
|
|
108
|
+
cls,
|
|
109
|
+
*,
|
|
110
|
+
label: str,
|
|
111
|
+
source: str,
|
|
112
|
+
target: str,
|
|
113
|
+
identity: Sequence[object] = (),
|
|
114
|
+
properties: Mapping[str, object] | None = None,
|
|
115
|
+
) -> CpgEdge:
|
|
116
|
+
normalized_label = label.upper()
|
|
117
|
+
frozen = _frozen_properties(properties)
|
|
118
|
+
return cls(
|
|
119
|
+
_stable_id(
|
|
120
|
+
"cpg_edge",
|
|
121
|
+
normalized_label,
|
|
122
|
+
source,
|
|
123
|
+
target,
|
|
124
|
+
tuple(identity),
|
|
125
|
+
frozen,
|
|
126
|
+
),
|
|
127
|
+
normalized_label,
|
|
128
|
+
source,
|
|
129
|
+
target,
|
|
130
|
+
frozen,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def to_mapping(self) -> dict[str, object]:
|
|
134
|
+
result: dict[str, object] = {
|
|
135
|
+
"item_type": "cpg_edge",
|
|
136
|
+
"edge_id": self.edge_id,
|
|
137
|
+
"label": self.label,
|
|
138
|
+
"source": self.source,
|
|
139
|
+
"target": self.target,
|
|
140
|
+
}
|
|
141
|
+
if self.properties:
|
|
142
|
+
result["properties"] = dict(self.properties)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True, slots=True)
|
|
147
|
+
class CpgProblem:
|
|
148
|
+
kind: str
|
|
149
|
+
message: str
|
|
150
|
+
path: str = ""
|
|
151
|
+
line: int = 0
|
|
152
|
+
column: int = 0
|
|
153
|
+
|
|
154
|
+
def to_mapping(self) -> dict[str, object]:
|
|
155
|
+
result: dict[str, object] = {
|
|
156
|
+
"item_type": "cpg_problem",
|
|
157
|
+
"kind": self.kind,
|
|
158
|
+
"message": self.message,
|
|
159
|
+
}
|
|
160
|
+
if self.path:
|
|
161
|
+
result["path"] = self.path
|
|
162
|
+
if self.line > 0:
|
|
163
|
+
result["line"] = self.line
|
|
164
|
+
if self.column > 0:
|
|
165
|
+
result["column"] = self.column
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass(frozen=True, slots=True)
|
|
170
|
+
class CpgSubgraph:
|
|
171
|
+
target_id: str
|
|
172
|
+
graph: str
|
|
173
|
+
root_node_id: str
|
|
174
|
+
nodes: tuple[CpgNode, ...]
|
|
175
|
+
edges: tuple[CpgEdge, ...]
|
|
176
|
+
problems: tuple[CpgProblem, ...] = ()
|
|
177
|
+
truncated: bool = False
|
|
178
|
+
unresolved: int = 0
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def create(
|
|
182
|
+
cls,
|
|
183
|
+
*,
|
|
184
|
+
target_id: str,
|
|
185
|
+
graph: str,
|
|
186
|
+
root_node_id: str,
|
|
187
|
+
nodes: Iterable[CpgNode] = (),
|
|
188
|
+
edges: Iterable[CpgEdge] = (),
|
|
189
|
+
problems: Iterable[CpgProblem] = (),
|
|
190
|
+
truncated: bool = False,
|
|
191
|
+
unresolved: int = 0,
|
|
192
|
+
record_limit: int = 50_000,
|
|
193
|
+
) -> CpgSubgraph:
|
|
194
|
+
unique_nodes = {node.node_id: node for node in nodes}
|
|
195
|
+
ordered_nodes = sorted(unique_nodes.values(), key=lambda item: item.node_id)
|
|
196
|
+
unique_edges = {edge.edge_id: edge for edge in edges}
|
|
197
|
+
ordered_edges = sorted(unique_edges.values(), key=lambda item: item.edge_id)
|
|
198
|
+
unique_problems = {
|
|
199
|
+
(problem.kind, problem.message, problem.path, problem.line, problem.column): problem
|
|
200
|
+
for problem in problems
|
|
201
|
+
}
|
|
202
|
+
ordered_problems = sorted(
|
|
203
|
+
unique_problems.values(),
|
|
204
|
+
key=lambda item: (
|
|
205
|
+
item.path.casefold(),
|
|
206
|
+
item.line,
|
|
207
|
+
item.column,
|
|
208
|
+
item.kind,
|
|
209
|
+
item.message,
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
limit = max(1, record_limit)
|
|
214
|
+
overflow = len(ordered_nodes) + len(ordered_edges) > limit
|
|
215
|
+
if overflow:
|
|
216
|
+
if len(ordered_nodes) > limit:
|
|
217
|
+
selected_nodes = ordered_nodes[:limit]
|
|
218
|
+
if root_node_id in unique_nodes and all(
|
|
219
|
+
node.node_id != root_node_id for node in selected_nodes
|
|
220
|
+
):
|
|
221
|
+
selected_nodes[-1] = unique_nodes[root_node_id]
|
|
222
|
+
selected_nodes.sort(key=lambda item: item.node_id)
|
|
223
|
+
ordered_nodes = selected_nodes
|
|
224
|
+
ordered_edges = []
|
|
225
|
+
else:
|
|
226
|
+
remaining = limit - len(ordered_nodes)
|
|
227
|
+
ordered_edges = ordered_edges[:remaining]
|
|
228
|
+
retained_ids = {node.node_id for node in ordered_nodes}
|
|
229
|
+
ordered_edges = [
|
|
230
|
+
edge
|
|
231
|
+
for edge in ordered_edges
|
|
232
|
+
if edge.source in retained_ids and edge.target in retained_ids
|
|
233
|
+
]
|
|
234
|
+
return cls(
|
|
235
|
+
target_id=target_id,
|
|
236
|
+
graph=graph,
|
|
237
|
+
root_node_id=root_node_id,
|
|
238
|
+
nodes=tuple(ordered_nodes),
|
|
239
|
+
edges=tuple(ordered_edges),
|
|
240
|
+
problems=tuple(ordered_problems),
|
|
241
|
+
truncated=truncated or overflow,
|
|
242
|
+
unresolved=max(0, unresolved),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def retained_bytes(self) -> int:
|
|
247
|
+
property_slots = sum(len(node.properties) for node in self.nodes) + sum(
|
|
248
|
+
len(edge.properties) for edge in self.edges
|
|
249
|
+
)
|
|
250
|
+
text_bytes = sum(
|
|
251
|
+
len(node.node_id) + len(node.label)
|
|
252
|
+
for node in self.nodes
|
|
253
|
+
) + sum(
|
|
254
|
+
len(edge.edge_id)
|
|
255
|
+
+ len(edge.label)
|
|
256
|
+
+ len(edge.source)
|
|
257
|
+
+ len(edge.target)
|
|
258
|
+
for edge in self.edges
|
|
259
|
+
)
|
|
260
|
+
problem_bytes = sum(
|
|
261
|
+
len(problem.kind) + len(problem.message) + len(problem.path)
|
|
262
|
+
for problem in self.problems
|
|
263
|
+
)
|
|
264
|
+
return (
|
|
265
|
+
sys.getsizeof(self)
|
|
266
|
+
+ len(self.nodes) * 256
|
|
267
|
+
+ len(self.edges) * 320
|
|
268
|
+
+ len(self.problems) * 256
|
|
269
|
+
+ property_slots * 96
|
|
270
|
+
+ text_bytes
|
|
271
|
+
+ problem_bytes
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def to_items(self) -> list[dict[str, object]]:
|
|
275
|
+
metadata: dict[str, object] = {
|
|
276
|
+
"item_type": "cpg_metadata",
|
|
277
|
+
"target_id": self.target_id,
|
|
278
|
+
"graph": self.graph,
|
|
279
|
+
"root_node_id": self.root_node_id,
|
|
280
|
+
"completeness": "sound_partial",
|
|
281
|
+
"nodes": len(self.nodes),
|
|
282
|
+
"edges": len(self.edges),
|
|
283
|
+
"unresolved": self.unresolved,
|
|
284
|
+
"problem_count": len(self.problems),
|
|
285
|
+
"truncated": self.truncated,
|
|
286
|
+
"retained_bytes": self.retained_bytes,
|
|
287
|
+
}
|
|
288
|
+
return [
|
|
289
|
+
metadata,
|
|
290
|
+
*(node.to_mapping() for node in self.nodes),
|
|
291
|
+
*(edge.to_mapping() for edge in self.edges),
|
|
292
|
+
*(problem.to_mapping() for problem in self.problems),
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
def select(
|
|
296
|
+
self,
|
|
297
|
+
*,
|
|
298
|
+
graph: str,
|
|
299
|
+
direction: str,
|
|
300
|
+
depth: int,
|
|
301
|
+
root_node_id: str | None = None,
|
|
302
|
+
) -> CpgSubgraph:
|
|
303
|
+
allowed = _GRAPH_EDGE_LABELS[graph]
|
|
304
|
+
candidate_edges = tuple(
|
|
305
|
+
edge for edge in self.edges if allowed is None or edge.label in allowed
|
|
306
|
+
)
|
|
307
|
+
root = root_node_id or self.root_node_id
|
|
308
|
+
reached = {root}
|
|
309
|
+
frontier = deque([(root, 0)])
|
|
310
|
+
selected_edges: dict[str, CpgEdge] = {}
|
|
311
|
+
outgoing: dict[str, list[CpgEdge]] = {}
|
|
312
|
+
incoming: dict[str, list[CpgEdge]] = {}
|
|
313
|
+
for edge in candidate_edges:
|
|
314
|
+
outgoing.setdefault(edge.source, []).append(edge)
|
|
315
|
+
incoming.setdefault(edge.target, []).append(edge)
|
|
316
|
+
while frontier:
|
|
317
|
+
current, distance = frontier.popleft()
|
|
318
|
+
if distance >= depth:
|
|
319
|
+
continue
|
|
320
|
+
adjacent: list[tuple[CpgEdge, str]] = []
|
|
321
|
+
if direction in {"out", "both"}:
|
|
322
|
+
adjacent.extend((edge, edge.target) for edge in outgoing.get(current, ()))
|
|
323
|
+
if direction in {"in", "both"}:
|
|
324
|
+
adjacent.extend((edge, edge.source) for edge in incoming.get(current, ()))
|
|
325
|
+
for edge, neighbor in adjacent:
|
|
326
|
+
selected_edges[edge.edge_id] = edge
|
|
327
|
+
if neighbor not in reached:
|
|
328
|
+
reached.add(neighbor)
|
|
329
|
+
frontier.append((neighbor, distance + 1))
|
|
330
|
+
selected_nodes = tuple(node for node in self.nodes if node.node_id in reached)
|
|
331
|
+
return CpgSubgraph.create(
|
|
332
|
+
target_id=self.target_id,
|
|
333
|
+
graph=graph,
|
|
334
|
+
root_node_id=root,
|
|
335
|
+
nodes=selected_nodes,
|
|
336
|
+
edges=selected_edges.values(),
|
|
337
|
+
problems=self.problems,
|
|
338
|
+
truncated=self.truncated,
|
|
339
|
+
unresolved=self.unresolved,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
__all__ = [
|
|
344
|
+
"CpgEdge",
|
|
345
|
+
"CpgNode",
|
|
346
|
+
"CpgProblem",
|
|
347
|
+
"CpgSubgraph",
|
|
348
|
+
"CpgTarget",
|
|
349
|
+
]
|