codegraph-nav 0.1.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_nav/__init__.py +194 -0
- codegraph_nav/ast_grep_analyzer.py +448 -0
- codegraph_nav/cli.py +223 -0
- codegraph_nav/code_navigator.py +1328 -0
- codegraph_nav/code_search.py +1009 -0
- codegraph_nav/colors.py +209 -0
- codegraph_nav/completions.py +354 -0
- codegraph_nav/dart_analyzer.py +301 -0
- codegraph_nav/dependency_graph.py +814 -0
- codegraph_nav/domain/__init__.py +20 -0
- codegraph_nav/domain/routes.py +337 -0
- codegraph_nav/domain/schemas.py +229 -0
- codegraph_nav/domain/tags.py +87 -0
- codegraph_nav/exporters.py +563 -0
- codegraph_nav/go_analyzer.py +273 -0
- codegraph_nav/graph/__init__.py +72 -0
- codegraph_nav/graph/builder.py +409 -0
- codegraph_nav/graph/communities.py +402 -0
- codegraph_nav/graph/flows.py +311 -0
- codegraph_nav/graph/query.py +380 -0
- codegraph_nav/graph/schema.py +266 -0
- codegraph_nav/graph/search.py +257 -0
- codegraph_nav/graph/store.py +517 -0
- codegraph_nav/hints.py +195 -0
- codegraph_nav/import_resolver.py +891 -0
- codegraph_nav/js_ts_analyzer.py +564 -0
- codegraph_nav/line_reader.py +664 -0
- codegraph_nav/mcp/__init__.py +39 -0
- codegraph_nav/mcp/__main__.py +5 -0
- codegraph_nav/mcp/server.py +2228 -0
- codegraph_nav/py.typed +2 -0
- codegraph_nav/ruby_analyzer.py +259 -0
- codegraph_nav/rust_analyzer.py +379 -0
- codegraph_nav/token_efficient_renderer.py +743 -0
- codegraph_nav/watcher.py +382 -0
- codegraph_nav-0.1.0.dist-info/METADATA +487 -0
- codegraph_nav-0.1.0.dist-info/RECORD +41 -0
- codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
- codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
- codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
- codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Go analyzer using tree-sitter for AST-based symbol extraction.
|
|
2
|
+
|
|
3
|
+
Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from codegraph_nav.go_analyzer import GoAnalyzer
|
|
7
|
+
>>> source = '''
|
|
8
|
+
... func greet(name string) string {
|
|
9
|
+
... return "Hello, " + name
|
|
10
|
+
... }
|
|
11
|
+
... '''
|
|
12
|
+
>>> analyzer = GoAnalyzer('example.go', source)
|
|
13
|
+
>>> symbols = analyzer.analyze()
|
|
14
|
+
>>> print(symbols[0].name)
|
|
15
|
+
'greet'
|
|
16
|
+
|
|
17
|
+
Installation:
|
|
18
|
+
To enable AST support, install with the 'ast' extra:
|
|
19
|
+
pip install codegraph-nav[ast]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tree_sitter import Node
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import tree_sitter_go as ts_go
|
|
30
|
+
from tree_sitter import Language, Parser
|
|
31
|
+
|
|
32
|
+
TREE_SITTER_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
TREE_SITTER_AVAILABLE = False
|
|
35
|
+
|
|
36
|
+
from .code_navigator import GenericAnalyzer, Symbol
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GoAnalyzer:
|
|
40
|
+
"""Analyzes Go files using tree-sitter for accurate symbol extraction.
|
|
41
|
+
|
|
42
|
+
When tree-sitter is not available, automatically falls back to regex-based
|
|
43
|
+
GenericAnalyzer.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
file_path: Path to the file being analyzed.
|
|
47
|
+
source: Source code content.
|
|
48
|
+
symbols: Extracted symbols.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> source = '''
|
|
52
|
+
... type User struct {
|
|
53
|
+
... Name string
|
|
54
|
+
... }
|
|
55
|
+
...
|
|
56
|
+
... func (u *User) Greet() string {
|
|
57
|
+
... return "Hello, " + u.Name
|
|
58
|
+
... }
|
|
59
|
+
... '''
|
|
60
|
+
>>> analyzer = GoAnalyzer('user.go', source)
|
|
61
|
+
>>> symbols = analyzer.analyze()
|
|
62
|
+
>>> print([s.name for s in symbols])
|
|
63
|
+
['User', 'Greet']
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, file_path: str, source: str):
|
|
67
|
+
self.file_path = file_path
|
|
68
|
+
self.source = source
|
|
69
|
+
self.lines = source.split("\n")
|
|
70
|
+
self.symbols: list[Symbol] = []
|
|
71
|
+
|
|
72
|
+
def analyze(self) -> list[Symbol]:
|
|
73
|
+
"""Parse and analyze the file.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of Symbol objects found in the file.
|
|
77
|
+
"""
|
|
78
|
+
if not TREE_SITTER_AVAILABLE:
|
|
79
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "go")
|
|
80
|
+
return fallback.analyze()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
parser = Parser(Language(ts_go.language()))
|
|
84
|
+
tree = parser.parse(bytes(self.source, "utf-8"))
|
|
85
|
+
self._visit_node(tree.root_node)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
|
|
88
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "go")
|
|
89
|
+
return fallback.analyze()
|
|
90
|
+
|
|
91
|
+
return self.symbols
|
|
92
|
+
|
|
93
|
+
def _visit_node(self, node: "Node") -> None:
|
|
94
|
+
"""Recursively visit AST nodes and extract symbols."""
|
|
95
|
+
node_type = node.type
|
|
96
|
+
|
|
97
|
+
if node_type == "function_declaration":
|
|
98
|
+
self._extract_function(node)
|
|
99
|
+
elif node_type == "method_declaration":
|
|
100
|
+
self._extract_method(node)
|
|
101
|
+
elif node_type == "type_declaration":
|
|
102
|
+
for child in node.children:
|
|
103
|
+
if child.type == "type_spec":
|
|
104
|
+
self._extract_type_spec(child)
|
|
105
|
+
elif node_type == "const_declaration":
|
|
106
|
+
self._extract_const(node)
|
|
107
|
+
|
|
108
|
+
for child in node.children:
|
|
109
|
+
self._visit_node(child)
|
|
110
|
+
|
|
111
|
+
def _get_node_text(self, node: "Node") -> str:
|
|
112
|
+
return self.source[node.start_byte : node.end_byte]
|
|
113
|
+
|
|
114
|
+
def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
|
|
115
|
+
for child in node.children:
|
|
116
|
+
if child.type == type_name:
|
|
117
|
+
return child
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def _extract_function(self, node: "Node") -> None:
|
|
121
|
+
"""Extract a function declaration."""
|
|
122
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
123
|
+
if not name_node:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
name = self._get_node_text(name_node)
|
|
127
|
+
|
|
128
|
+
# Get type parameters (generics)
|
|
129
|
+
type_params = ""
|
|
130
|
+
tp_node = self._get_child_by_type(node, "type_parameter_list")
|
|
131
|
+
if tp_node:
|
|
132
|
+
type_params = self._get_node_text(tp_node)
|
|
133
|
+
|
|
134
|
+
# Get parameters
|
|
135
|
+
params = ""
|
|
136
|
+
for child in node.children:
|
|
137
|
+
if child.type == "parameter_list":
|
|
138
|
+
params = self._get_node_text(child)
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
# Get return type
|
|
142
|
+
result = ""
|
|
143
|
+
for child in node.children:
|
|
144
|
+
if child.type in (
|
|
145
|
+
"type_identifier",
|
|
146
|
+
"slice_type",
|
|
147
|
+
"pointer_type",
|
|
148
|
+
"qualified_type",
|
|
149
|
+
"map_type",
|
|
150
|
+
"parameter_list",
|
|
151
|
+
):
|
|
152
|
+
# Skip the first parameter_list (that's the params)
|
|
153
|
+
if child.type == "parameter_list" and child == node.children[2]:
|
|
154
|
+
continue
|
|
155
|
+
if child.start_byte > (node.children[2].end_byte if len(node.children) > 2 else 0):
|
|
156
|
+
result = " " + self._get_node_text(child)
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
signature = f"func {name}{type_params}{params}{result}"
|
|
160
|
+
|
|
161
|
+
self.symbols.append(
|
|
162
|
+
Symbol(
|
|
163
|
+
name=name,
|
|
164
|
+
type="function",
|
|
165
|
+
file_path=self.file_path,
|
|
166
|
+
line_start=node.start_point[0] + 1,
|
|
167
|
+
line_end=node.end_point[0] + 1,
|
|
168
|
+
signature=signature[:100],
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def _extract_method(self, node: "Node") -> None:
|
|
173
|
+
"""Extract a method declaration with receiver."""
|
|
174
|
+
# Get method name (field_identifier)
|
|
175
|
+
name_node = self._get_child_by_type(node, "field_identifier")
|
|
176
|
+
if not name_node:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
name = self._get_node_text(name_node)
|
|
180
|
+
|
|
181
|
+
# Get receiver type (parent)
|
|
182
|
+
parent = None
|
|
183
|
+
param_lists = [c for c in node.children if c.type == "parameter_list"]
|
|
184
|
+
if param_lists:
|
|
185
|
+
receiver_list = param_lists[0]
|
|
186
|
+
for child in receiver_list.children:
|
|
187
|
+
if child.type == "parameter_declaration":
|
|
188
|
+
for rc in child.children:
|
|
189
|
+
if rc.type == "pointer_type":
|
|
190
|
+
# *User -> User
|
|
191
|
+
for pt_child in rc.children:
|
|
192
|
+
if pt_child.type == "type_identifier":
|
|
193
|
+
parent = self._get_node_text(pt_child)
|
|
194
|
+
elif rc.type == "type_identifier":
|
|
195
|
+
parent = self._get_node_text(rc)
|
|
196
|
+
|
|
197
|
+
# Get parameters (second parameter_list)
|
|
198
|
+
params = ""
|
|
199
|
+
if len(param_lists) > 1:
|
|
200
|
+
params = self._get_node_text(param_lists[1])
|
|
201
|
+
|
|
202
|
+
receiver_str = f"({self._get_node_text(param_lists[0])})" if param_lists else ""
|
|
203
|
+
signature = f"func {receiver_str} {name}{params}"
|
|
204
|
+
|
|
205
|
+
self.symbols.append(
|
|
206
|
+
Symbol(
|
|
207
|
+
name=name,
|
|
208
|
+
type="method",
|
|
209
|
+
file_path=self.file_path,
|
|
210
|
+
line_start=node.start_point[0] + 1,
|
|
211
|
+
line_end=node.end_point[0] + 1,
|
|
212
|
+
signature=signature[:100],
|
|
213
|
+
parent=parent,
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _extract_type_spec(self, node: "Node") -> None:
|
|
218
|
+
"""Extract a type specification (struct, interface, or alias)."""
|
|
219
|
+
name_node = self._get_child_by_type(node, "type_identifier")
|
|
220
|
+
if not name_node:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
name = self._get_node_text(name_node)
|
|
224
|
+
|
|
225
|
+
# Determine type kind
|
|
226
|
+
struct_node = self._get_child_by_type(node, "struct_type")
|
|
227
|
+
iface_node = self._get_child_by_type(node, "interface_type")
|
|
228
|
+
|
|
229
|
+
if struct_node:
|
|
230
|
+
symbol_type = "struct"
|
|
231
|
+
signature = f"type {name} struct"
|
|
232
|
+
elif iface_node:
|
|
233
|
+
symbol_type = "interface"
|
|
234
|
+
signature = f"type {name} interface"
|
|
235
|
+
else:
|
|
236
|
+
symbol_type = "type"
|
|
237
|
+
# Get the aliased type
|
|
238
|
+
for child in node.children:
|
|
239
|
+
if child.type == "type_identifier" and child != name_node:
|
|
240
|
+
signature = f"type {name} {self._get_node_text(child)}"
|
|
241
|
+
break
|
|
242
|
+
else:
|
|
243
|
+
signature = f"type {name}"
|
|
244
|
+
|
|
245
|
+
self.symbols.append(
|
|
246
|
+
Symbol(
|
|
247
|
+
name=name,
|
|
248
|
+
type=symbol_type,
|
|
249
|
+
file_path=self.file_path,
|
|
250
|
+
line_start=node.start_point[0] + 1,
|
|
251
|
+
line_end=node.end_point[0] + 1,
|
|
252
|
+
signature=signature[:100],
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _extract_const(self, node: "Node") -> None:
|
|
257
|
+
"""Extract const declarations."""
|
|
258
|
+
for child in node.children:
|
|
259
|
+
if child.type == "const_spec":
|
|
260
|
+
for spec_child in child.children:
|
|
261
|
+
if spec_child.type == "identifier":
|
|
262
|
+
name = self._get_node_text(spec_child)
|
|
263
|
+
self.symbols.append(
|
|
264
|
+
Symbol(
|
|
265
|
+
name=name,
|
|
266
|
+
type="const",
|
|
267
|
+
file_path=self.file_path,
|
|
268
|
+
line_start=child.start_point[0] + 1,
|
|
269
|
+
line_end=child.end_point[0] + 1,
|
|
270
|
+
signature=self._get_node_text(child).strip()[:100],
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
break
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Graph intelligence layer for codegraph-nav.
|
|
2
|
+
|
|
3
|
+
Optional SQLite-backed graph for advanced analysis: blast radius,
|
|
4
|
+
execution flows, risk scoring, and hybrid FTS5 search.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from codegraph_nav.graph import GraphStore, GraphBuilder
|
|
8
|
+
|
|
9
|
+
store = GraphStore(".codegraph.db")
|
|
10
|
+
builder = GraphBuilder(store)
|
|
11
|
+
stats = builder.build_from_code_map(code_map)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .builder import GraphBuilder
|
|
15
|
+
from .flows import (
|
|
16
|
+
compute_criticality,
|
|
17
|
+
detect_entry_points,
|
|
18
|
+
format_flow_minimal,
|
|
19
|
+
format_flows_minimal,
|
|
20
|
+
store_flows,
|
|
21
|
+
trace_flows,
|
|
22
|
+
)
|
|
23
|
+
from .query import (
|
|
24
|
+
detect_changes,
|
|
25
|
+
format_blast_radius_minimal,
|
|
26
|
+
format_changes_minimal,
|
|
27
|
+
get_blast_radius,
|
|
28
|
+
)
|
|
29
|
+
from .schema import (
|
|
30
|
+
EDGE_KINDS,
|
|
31
|
+
MAX_BFS_DEPTH,
|
|
32
|
+
MAX_IMPACT_DEPTH,
|
|
33
|
+
MAX_IMPACT_NODES,
|
|
34
|
+
NODE_KINDS,
|
|
35
|
+
SCHEMA_VERSION,
|
|
36
|
+
SECURITY_KEYWORDS,
|
|
37
|
+
make_qualified_name,
|
|
38
|
+
)
|
|
39
|
+
from .search import (
|
|
40
|
+
format_search_results_minimal,
|
|
41
|
+
hybrid_search,
|
|
42
|
+
rebuild_fts_index,
|
|
43
|
+
rrf_merge,
|
|
44
|
+
)
|
|
45
|
+
from .store import GraphStore
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"GraphStore",
|
|
49
|
+
"GraphBuilder",
|
|
50
|
+
"get_blast_radius",
|
|
51
|
+
"format_blast_radius_minimal",
|
|
52
|
+
"detect_changes",
|
|
53
|
+
"format_changes_minimal",
|
|
54
|
+
"detect_entry_points",
|
|
55
|
+
"trace_flows",
|
|
56
|
+
"store_flows",
|
|
57
|
+
"compute_criticality",
|
|
58
|
+
"format_flow_minimal",
|
|
59
|
+
"format_flows_minimal",
|
|
60
|
+
"hybrid_search",
|
|
61
|
+
"rebuild_fts_index",
|
|
62
|
+
"rrf_merge",
|
|
63
|
+
"format_search_results_minimal",
|
|
64
|
+
"make_qualified_name",
|
|
65
|
+
"SCHEMA_VERSION",
|
|
66
|
+
"EDGE_KINDS",
|
|
67
|
+
"NODE_KINDS",
|
|
68
|
+
"SECURITY_KEYWORDS",
|
|
69
|
+
"MAX_IMPACT_DEPTH",
|
|
70
|
+
"MAX_IMPACT_NODES",
|
|
71
|
+
"MAX_BFS_DEPTH",
|
|
72
|
+
]
|