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.
Files changed (41) hide show
  1. codegraph_nav/__init__.py +194 -0
  2. codegraph_nav/ast_grep_analyzer.py +448 -0
  3. codegraph_nav/cli.py +223 -0
  4. codegraph_nav/code_navigator.py +1328 -0
  5. codegraph_nav/code_search.py +1009 -0
  6. codegraph_nav/colors.py +209 -0
  7. codegraph_nav/completions.py +354 -0
  8. codegraph_nav/dart_analyzer.py +301 -0
  9. codegraph_nav/dependency_graph.py +814 -0
  10. codegraph_nav/domain/__init__.py +20 -0
  11. codegraph_nav/domain/routes.py +337 -0
  12. codegraph_nav/domain/schemas.py +229 -0
  13. codegraph_nav/domain/tags.py +87 -0
  14. codegraph_nav/exporters.py +563 -0
  15. codegraph_nav/go_analyzer.py +273 -0
  16. codegraph_nav/graph/__init__.py +72 -0
  17. codegraph_nav/graph/builder.py +409 -0
  18. codegraph_nav/graph/communities.py +402 -0
  19. codegraph_nav/graph/flows.py +311 -0
  20. codegraph_nav/graph/query.py +380 -0
  21. codegraph_nav/graph/schema.py +266 -0
  22. codegraph_nav/graph/search.py +257 -0
  23. codegraph_nav/graph/store.py +517 -0
  24. codegraph_nav/hints.py +195 -0
  25. codegraph_nav/import_resolver.py +891 -0
  26. codegraph_nav/js_ts_analyzer.py +564 -0
  27. codegraph_nav/line_reader.py +664 -0
  28. codegraph_nav/mcp/__init__.py +39 -0
  29. codegraph_nav/mcp/__main__.py +5 -0
  30. codegraph_nav/mcp/server.py +2228 -0
  31. codegraph_nav/py.typed +2 -0
  32. codegraph_nav/ruby_analyzer.py +259 -0
  33. codegraph_nav/rust_analyzer.py +379 -0
  34. codegraph_nav/token_efficient_renderer.py +743 -0
  35. codegraph_nav/watcher.py +382 -0
  36. codegraph_nav-0.1.0.dist-info/METADATA +487 -0
  37. codegraph_nav-0.1.0.dist-info/RECORD +41 -0
  38. codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
  39. codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
  40. codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
  41. codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,194 @@
1
+ """Code Navigator - Token-efficient code navigation for large codebases.
2
+
3
+ This package provides tools for creating structural maps of codebases and
4
+ navigating them efficiently, reducing token usage by up to 97% when working
5
+ with AI coding assistants.
6
+
7
+ Components:
8
+ - CodeNavigator: Generates JSON index of codebase structure
9
+ - CodeSearcher: Searches the pre-built index for symbols
10
+ - LineReader: Reads specific lines from files efficiently
11
+
12
+ Quick Start:
13
+ 1. Generate a code map:
14
+ >>> from codegraph_nav import CodeNavigator
15
+ >>> mapper = CodeNavigator('/path/to/project')
16
+ >>> code_map = mapper.scan()
17
+
18
+ 2. Search for symbols:
19
+ >>> from codegraph_nav import CodeSearcher
20
+ >>> searcher = CodeSearcher('.codegraph.json')
21
+ >>> results = searcher.search_symbol('process_payment')
22
+
23
+ 3. Read specific lines:
24
+ >>> from codegraph_nav import LineReader
25
+ >>> reader = LineReader()
26
+ >>> content = reader.read_lines('src/api.py', 45, 60)
27
+
28
+ Example:
29
+ >>> # Full workflow
30
+ >>> from codegraph_nav import CodeNavigator, CodeSearcher, LineReader
31
+ >>>
32
+ >>> # Step 1: Map the codebase (one-time)
33
+ >>> mapper = CodeNavigator('/my/project')
34
+ >>> mapper.scan()
35
+ >>>
36
+ >>> # Step 2: Search for a symbol
37
+ >>> searcher = CodeSearcher('/my/project/.codegraph.json')
38
+ >>> results = searcher.search_symbol('authenticate', symbol_type='function')
39
+ >>> print(results[0].file, results[0].lines)
40
+ 'src/auth.py' [45, 89]
41
+ >>>
42
+ >>> # Step 3: Read only those lines
43
+ >>> reader = LineReader('/my/project')
44
+ >>> content = reader.read_symbol('src/auth.py', 45, 89)
45
+ """
46
+
47
+ import hashlib
48
+
49
+ from .code_navigator import CodeNavigator, GenericAnalyzer, GitIntegration, PythonAnalyzer, Symbol
50
+ from .code_search import CodeSearcher, SearchResult
51
+ from .completions import generate_bash_completion, generate_zsh_completion
52
+ from .dart_analyzer import DartAnalyzer
53
+ from .exporters import GraphVizExporter, HTMLExporter, MarkdownExporter, get_exporter
54
+ from .go_analyzer import GoAnalyzer
55
+ from .js_ts_analyzer import (
56
+ TREE_SITTER_AVAILABLE,
57
+ JavaScriptAnalyzer,
58
+ TypeScriptAnalyzer,
59
+ )
60
+ from .line_reader import LineReader
61
+ from .ruby_analyzer import RubyAnalyzer
62
+ from .rust_analyzer import RustAnalyzer
63
+ from .watcher import CodegraphWatcher
64
+
65
+ # Optional dependency: networkx for DependencyGraph
66
+ try:
67
+ from .dependency_graph import DependencyGraph, FileNode, analyze_repository
68
+
69
+ HAS_NETWORKX = True
70
+ except ImportError:
71
+ DependencyGraph = None # type: ignore
72
+ FileNode = None # type: ignore
73
+ analyze_repository = None # type: ignore
74
+ HAS_NETWORKX = False
75
+
76
+ # Import resolver (always available - no external dependencies)
77
+ from .import_resolver import (
78
+ AliasConfig,
79
+ ImportResolver,
80
+ ResolveResult,
81
+ ResolveStrategy,
82
+ resolve_import_path,
83
+ )
84
+
85
+ # Token-efficient rendering (always available - no external dependencies)
86
+ from .token_efficient_renderer import (
87
+ FileMicroMeta,
88
+ HubLevel,
89
+ TokenEfficientRenderer,
90
+ render_skeleton_tree,
91
+ )
92
+
93
+ # Optional: ast-grep high-performance analyzer
94
+ try:
95
+ from .ast_grep_analyzer import (
96
+ AstGrepAnalyzer,
97
+ AstGrepSymbol,
98
+ analyze_with_ast_grep,
99
+ is_ast_grep_available,
100
+ )
101
+
102
+ HAS_AST_GREP = is_ast_grep_available()
103
+ except ImportError:
104
+ AstGrepAnalyzer = None # type: ignore
105
+ AstGrepSymbol = None # type: ignore
106
+ analyze_with_ast_grep = None # type: ignore
107
+
108
+ def is_ast_grep_available() -> bool:
109
+ return False
110
+
111
+ HAS_AST_GREP = False
112
+
113
+ __version__ = "0.1.0"
114
+ __author__ = "Efren"
115
+ __license__ = "MIT"
116
+
117
+
118
+ def compute_content_hash(content: str) -> str:
119
+ """Compute a short hash of content for change detection.
120
+
121
+ This is the canonical hash function used across all modules for
122
+ consistent file change detection.
123
+
124
+ Args:
125
+ content: The text content to hash.
126
+
127
+ Returns:
128
+ A 12-character MD5 hash string.
129
+
130
+ Example:
131
+ >>> compute_content_hash("def foo(): pass")
132
+ 'a1b2c3d4e5f6'
133
+ """
134
+ return hashlib.md5(content.encode()).hexdigest()[:12]
135
+
136
+
137
+ __all__ = [
138
+ # Version info
139
+ "__version__",
140
+ "__author__",
141
+ "__license__",
142
+ # Main classes
143
+ "CodeNavigator",
144
+ "CodeSearcher",
145
+ "LineReader",
146
+ "CodegraphWatcher",
147
+ # Dependency Graph (requires networkx)
148
+ "DependencyGraph",
149
+ "FileNode",
150
+ "analyze_repository",
151
+ # Import Resolution
152
+ "ImportResolver",
153
+ "ResolveResult",
154
+ "ResolveStrategy",
155
+ "AliasConfig",
156
+ "resolve_import_path",
157
+ # Token-Efficient Rendering
158
+ "TokenEfficientRenderer",
159
+ "FileMicroMeta",
160
+ "HubLevel",
161
+ "render_skeleton_tree",
162
+ # AST-Grep Analyzer (optional, requires ast-grep-py)
163
+ "AstGrepAnalyzer",
164
+ "AstGrepSymbol",
165
+ "analyze_with_ast_grep",
166
+ "is_ast_grep_available",
167
+ "HAS_AST_GREP",
168
+ # Analyzers
169
+ "PythonAnalyzer",
170
+ "GenericAnalyzer",
171
+ "JavaScriptAnalyzer",
172
+ "TypeScriptAnalyzer",
173
+ "RubyAnalyzer",
174
+ "GoAnalyzer",
175
+ "RustAnalyzer",
176
+ "DartAnalyzer",
177
+ # Exporters
178
+ "MarkdownExporter",
179
+ "HTMLExporter",
180
+ "GraphVizExporter",
181
+ "get_exporter",
182
+ # Supporting classes
183
+ "GitIntegration",
184
+ "Symbol",
185
+ "SearchResult",
186
+ # Completions
187
+ "generate_bash_completion",
188
+ "generate_zsh_completion",
189
+ # Feature flags
190
+ "TREE_SITTER_AVAILABLE",
191
+ "HAS_NETWORKX",
192
+ # Utilities
193
+ "compute_content_hash",
194
+ ]
@@ -0,0 +1,448 @@
1
+ #!/usr/bin/env python3
2
+ """AST-Grep Analyzer - High-performance multi-language code analysis.
3
+
4
+ This module provides an optional high-performance analyzer using ast-grep's
5
+ native Python bindings. It offers superior accuracy compared to regex-based
6
+ analysis and supports 20+ programming languages through tree-sitter.
7
+
8
+ Requirements:
9
+ pip install ast-grep-py
10
+
11
+ Example:
12
+ >>> from codegraph_nav.ast_grep_analyzer import AstGrepAnalyzer
13
+ >>> analyzer = AstGrepAnalyzer("example.py", source_code, "python")
14
+ >>> symbols = analyzer.analyze()
15
+ >>> imports = analyzer.find_imports()
16
+ """
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ # Check for ast-grep availability
22
+ try:
23
+ from ast_grep_py import SgRoot
24
+
25
+ HAS_AST_GREP = True
26
+ except ImportError:
27
+ HAS_AST_GREP = False
28
+ if not TYPE_CHECKING:
29
+ SgRoot = None
30
+
31
+
32
+ @dataclass
33
+ class AstGrepSymbol:
34
+ """Symbol extracted by ast-grep.
35
+
36
+ Attributes:
37
+ name: Symbol name (function, class, variable name).
38
+ type: Symbol type (function, class, method, interface, etc.).
39
+ file_path: Relative file path.
40
+ line_start: Starting line (1-indexed).
41
+ line_end: Ending line (1-indexed).
42
+ signature: Full signature text (truncated).
43
+ parent: Parent class name for methods.
44
+ meta_vars: Captured meta-variables from pattern.
45
+ """
46
+
47
+ name: str
48
+ type: str
49
+ file_path: str
50
+ line_start: int
51
+ line_end: int
52
+ signature: str | None = None
53
+ parent: str | None = None
54
+ meta_vars: dict[str, str] = field(default_factory=dict)
55
+
56
+
57
+ class AstGrepAnalyzer:
58
+ """Multi-language analyzer using ast-grep native Python bindings.
59
+
60
+ This analyzer provides accurate AST-based code analysis for multiple
61
+ languages without the overhead of subprocess calls. It uses declarative
62
+ patterns that are easy to maintain and extend.
63
+
64
+ Supported Languages:
65
+ python, javascript, typescript, go, rust, java, ruby, swift,
66
+ kotlin, c, cpp, csharp, php, lua, scala, elixir
67
+
68
+ Attributes:
69
+ file_path: Path to the file being analyzed.
70
+ source: Source code content.
71
+ language: Programming language identifier.
72
+ root: SgRoot instance for AST operations.
73
+
74
+ Example:
75
+ >>> source = '''
76
+ ... def greet(name: str) -> str:
77
+ ... return f"Hello, {name}"
78
+ ... '''
79
+ >>> analyzer = AstGrepAnalyzer("greet.py", source, "python")
80
+ >>> symbols = analyzer.analyze()
81
+ >>> print(symbols[0].name)
82
+ 'greet'
83
+ """
84
+
85
+ # Pattern definitions for each language
86
+ # Format: { "symbol_type": "pattern" } or { "symbol_type": {"kind": "ast_node_kind"} }
87
+ PATTERNS: dict[str, dict[str, str | dict[str, str]]] = {
88
+ "python": {
89
+ "function": {"kind": "function_definition"},
90
+ "class": {"kind": "class_definition"},
91
+ "import": "import $MODULE",
92
+ "import_from": "from $MODULE import $$$NAMES",
93
+ "async_function": {"kind": "function_definition", "pattern": "async def $NAME"},
94
+ },
95
+ "javascript": {
96
+ "function": {"kind": "function_declaration"},
97
+ "arrow": "const $NAME = ($$$ARGS) => $$$BODY",
98
+ "arrow_async": "const $NAME = async ($$$ARGS) => $$$BODY",
99
+ "class": {"kind": "class_declaration"},
100
+ "method": {"kind": "method_definition"},
101
+ "import": 'import $$$ITEMS from "$PATH"',
102
+ "import_default": 'import $NAME from "$PATH"',
103
+ "require": 'require("$PATH")',
104
+ },
105
+ "typescript": {
106
+ "function": {"kind": "function_declaration"},
107
+ "arrow": "const $NAME = ($$$ARGS): $RETURN => $$$BODY",
108
+ "class": {"kind": "class_declaration"},
109
+ "interface": {"kind": "interface_declaration"},
110
+ "type_alias": {"kind": "type_alias_declaration"},
111
+ "method": {"kind": "method_definition"},
112
+ "import": 'import $$$ITEMS from "$PATH"',
113
+ "import_type": 'import type $$$ITEMS from "$PATH"',
114
+ },
115
+ "go": {
116
+ "function": "func $NAME($$$ARGS)",
117
+ "method": "func ($RECEIVER) $NAME($$$ARGS)",
118
+ "struct": "type $NAME struct",
119
+ "interface": "type $NAME interface",
120
+ "import": 'import "$PATH"',
121
+ },
122
+ "rust": {
123
+ "function": {"kind": "function_item"},
124
+ "struct": {"kind": "struct_item"},
125
+ "enum": {"kind": "enum_item"},
126
+ "impl": {"kind": "impl_item"},
127
+ "trait": {"kind": "trait_item"},
128
+ "use": "use $PATH",
129
+ },
130
+ "java": {
131
+ "class": {"kind": "class_declaration"},
132
+ "interface": {"kind": "interface_declaration"},
133
+ "method": {"kind": "method_declaration"},
134
+ "import": "import $PATH;",
135
+ },
136
+ "ruby": {
137
+ "method": {"kind": "method"},
138
+ "class": {"kind": "class"},
139
+ "module": {"kind": "module"},
140
+ "require": 'require "$PATH"',
141
+ "require_relative": 'require_relative "$PATH"',
142
+ },
143
+ "swift": {
144
+ "function": "func $NAME($$$ARGS)",
145
+ "class": "class $NAME",
146
+ "struct": "struct $NAME",
147
+ "protocol": "protocol $NAME",
148
+ "import": "import $MODULE",
149
+ },
150
+ "kotlin": {
151
+ "function": "fun $NAME($$$ARGS)",
152
+ "class": "class $NAME",
153
+ "interface": "interface $NAME",
154
+ "import": "import $PATH",
155
+ },
156
+ "c": {
157
+ "function": {"kind": "function_definition"},
158
+ "struct": "struct $NAME",
159
+ "include": '#include "$PATH"',
160
+ "include_system": "#include <$PATH>",
161
+ },
162
+ "cpp": {
163
+ "function": {"kind": "function_definition"},
164
+ "class": {"kind": "class_specifier"},
165
+ "struct": "struct $NAME",
166
+ "namespace": "namespace $NAME",
167
+ "include": '#include "$PATH"',
168
+ },
169
+ }
170
+
171
+ # Language aliases
172
+ LANGUAGE_ALIASES = {
173
+ "js": "javascript",
174
+ "ts": "typescript",
175
+ "tsx": "typescript",
176
+ "jsx": "javascript",
177
+ "rs": "rust",
178
+ "rb": "ruby",
179
+ "kt": "kotlin",
180
+ "cs": "csharp",
181
+ }
182
+
183
+ def __init__(self, file_path: str, source: str, language: str):
184
+ """Initialize the analyzer.
185
+
186
+ Args:
187
+ file_path: Relative path to the file.
188
+ source: Source code content.
189
+ language: Programming language identifier.
190
+
191
+ Raises:
192
+ ImportError: If ast-grep-py is not installed.
193
+ """
194
+ if not HAS_AST_GREP:
195
+ raise ImportError(
196
+ "ast-grep-py is required for AstGrepAnalyzer. "
197
+ "Install with: pip install ast-grep-py"
198
+ )
199
+
200
+ self.file_path = file_path
201
+ self.source = source
202
+ self.language = self.LANGUAGE_ALIASES.get(language.lower(), language.lower())
203
+
204
+ # Parse source code
205
+ self.root: SgRoot | None = None
206
+ try:
207
+ self.root = SgRoot(source, self.language)
208
+ self._available = True
209
+ except Exception:
210
+ self._available = False
211
+ self.root = None
212
+
213
+ @property
214
+ def available(self) -> bool:
215
+ """Check if analysis is available for this language."""
216
+ return self._available and self.language in self.PATTERNS
217
+
218
+ def analyze(self) -> list[AstGrepSymbol]:
219
+ """Extract all symbols from the source code.
220
+
221
+ Returns:
222
+ List of AstGrepSymbol objects found in the file.
223
+ """
224
+ if not self.available:
225
+ return []
226
+
227
+ symbols = []
228
+ patterns = self.PATTERNS.get(self.language, {})
229
+ assert self.root is not None # guaranteed by self.available check
230
+ root_node = self.root.root()
231
+
232
+ for symbol_type, pattern_config in patterns.items():
233
+ # Skip import patterns (handled separately)
234
+ if "import" in symbol_type or symbol_type in ("require", "use", "include"):
235
+ continue
236
+
237
+ matches = self._find_matches(root_node, pattern_config)
238
+
239
+ for match in matches:
240
+ symbol = self._extract_symbol(match, symbol_type)
241
+ if symbol:
242
+ symbols.append(symbol)
243
+
244
+ return symbols
245
+
246
+ def _find_matches(self, node: Any, pattern_config) -> list[Any]:
247
+ """Find matches using pattern or kind."""
248
+ if isinstance(pattern_config, str):
249
+ # Direct pattern
250
+ return list(node.find_all(pattern=pattern_config))
251
+ elif isinstance(pattern_config, dict):
252
+ # Kind-based or combined
253
+ if "kind" in pattern_config and "pattern" in pattern_config:
254
+ # Both kind and pattern
255
+ kind_matches = list(node.find_all(kind=pattern_config["kind"]))
256
+ return [m for m in kind_matches if m.matches(pattern=pattern_config["pattern"])]
257
+ elif "kind" in pattern_config:
258
+ return list(node.find_all(kind=pattern_config["kind"]))
259
+ elif "pattern" in pattern_config:
260
+ return list(node.find_all(pattern=pattern_config["pattern"]))
261
+ return []
262
+
263
+ def _extract_symbol(self, match: Any, symbol_type: str) -> AstGrepSymbol | None:
264
+ """Extract symbol information from a match."""
265
+ try:
266
+ # Try to get name from meta-variable
267
+ name_node = match.get_match("NAME")
268
+ if name_node:
269
+ name = name_node.text()
270
+ else:
271
+ # Try field-based extraction
272
+ name_field = match.field("name")
273
+ if name_field:
274
+ name = name_field.text()
275
+ else:
276
+ # Fallback: extract first identifier
277
+ name = self._extract_name_fallback(match, symbol_type)
278
+ if not name:
279
+ return None
280
+
281
+ # Get range
282
+ rng = match.range()
283
+
284
+ # Get signature (truncated)
285
+ full_text = match.text()
286
+ signature = full_text[:150] + "..." if len(full_text) > 150 else full_text
287
+ # Clean up signature (first line only for readability)
288
+ signature = signature.split("\n")[0].strip()
289
+
290
+ return AstGrepSymbol(
291
+ name=name,
292
+ type=symbol_type,
293
+ file_path=self.file_path,
294
+ line_start=rng.start.line + 1, # Convert to 1-indexed
295
+ line_end=rng.end.line + 1,
296
+ signature=signature,
297
+ )
298
+
299
+ except Exception:
300
+ return None
301
+
302
+ def _extract_name_fallback(self, match: Any, symbol_type: str) -> str | None:
303
+ """Fallback method to extract name from AST node."""
304
+ text: str = match.text()
305
+
306
+ # Language-specific extraction
307
+ if self.language == "python":
308
+ if symbol_type in ("function", "async_function"):
309
+ # def name(...) or async def name(...)
310
+ if "def " in text:
311
+ start = text.index("def ") + 4
312
+ end = text.index("(", start)
313
+ return text[start:end].strip()
314
+ elif symbol_type == "class":
315
+ if "class " in text:
316
+ start = text.index("class ") + 6
317
+ end = text.find("(", start)
318
+ if end == -1:
319
+ end = text.find(":", start)
320
+ return text[start:end].strip()
321
+
322
+ elif self.language in ("javascript", "typescript"):
323
+ if symbol_type == "function":
324
+ if "function " in text:
325
+ start = text.index("function ") + 9
326
+ end = text.index("(", start)
327
+ return text[start:end].strip()
328
+
329
+ return None
330
+
331
+ def find_imports(self) -> list[str]:
332
+ """Extract all import statements.
333
+
334
+ Returns:
335
+ List of imported module/path strings.
336
+ """
337
+ if not self.available:
338
+ return []
339
+
340
+ imports = []
341
+ patterns = self.PATTERNS.get(self.language, {})
342
+ assert self.root is not None # guaranteed by self.available check
343
+ root_node = self.root.root()
344
+
345
+ # Collect import-related patterns
346
+ import_patterns = {
347
+ k: v
348
+ for k, v in patterns.items()
349
+ if "import" in k or k in ("require", "use", "include", "include_system")
350
+ }
351
+
352
+ for _pattern_type, pattern in import_patterns.items():
353
+ if isinstance(pattern, str):
354
+ matches = root_node.find_all(pattern=pattern)
355
+ for match in matches:
356
+ # Try common meta-variables
357
+ for var in ["MODULE", "PATH", "ITEMS"]:
358
+ node = match.get_match(var)
359
+ if node:
360
+ text = node.text().strip("\"'")
361
+ if text and text not in imports:
362
+ imports.append(text)
363
+ break
364
+
365
+ return imports
366
+
367
+ def find_classes(self) -> list[tuple[str, list[str]]]:
368
+ """Find classes with their methods.
369
+
370
+ Returns:
371
+ List of (class_name, [method_names]) tuples.
372
+ """
373
+ if not self.available:
374
+ return []
375
+
376
+ results = []
377
+ assert self.root is not None # guaranteed by self.available check
378
+ root_node = self.root.root()
379
+
380
+ # Find class patterns based on language
381
+ class_kind = {
382
+ "python": "class_definition",
383
+ "javascript": "class_declaration",
384
+ "typescript": "class_declaration",
385
+ "java": "class_declaration",
386
+ "ruby": "class",
387
+ "cpp": "class_specifier",
388
+ }.get(self.language)
389
+
390
+ if not class_kind:
391
+ return []
392
+
393
+ for class_match in root_node.find_all(kind=class_kind):
394
+ # Get class name
395
+ name_field = class_match.field("name")
396
+ class_name = name_field.text() if name_field else "Unknown"
397
+
398
+ # Find methods within this class
399
+ methods = []
400
+ method_kind = {
401
+ "python": "function_definition",
402
+ "javascript": "method_definition",
403
+ "typescript": "method_definition",
404
+ "java": "method_declaration",
405
+ "ruby": "method",
406
+ }.get(self.language)
407
+
408
+ if method_kind:
409
+ for method in class_match.find_all(kind=method_kind):
410
+ method_name_field = method.field("name")
411
+ if method_name_field:
412
+ methods.append(method_name_field.text())
413
+
414
+ results.append((class_name, methods))
415
+
416
+ return results
417
+
418
+
419
+ def analyze_with_ast_grep(
420
+ file_path: str,
421
+ source: str,
422
+ language: str,
423
+ ) -> list[AstGrepSymbol]:
424
+ """Convenience function to analyze a file with ast-grep.
425
+
426
+ Args:
427
+ file_path: Relative file path.
428
+ source: Source code content.
429
+ language: Programming language.
430
+
431
+ Returns:
432
+ List of extracted symbols.
433
+
434
+ Example:
435
+ >>> symbols = analyze_with_ast_grep("app.py", source, "python")
436
+ >>> for sym in symbols:
437
+ ... print(f"{sym.type}: {sym.name}")
438
+ """
439
+ if not HAS_AST_GREP:
440
+ return []
441
+
442
+ analyzer = AstGrepAnalyzer(file_path, source, language)
443
+ return analyzer.analyze()
444
+
445
+
446
+ def is_ast_grep_available() -> bool:
447
+ """Check if ast-grep-py is installed and available."""
448
+ return HAS_AST_GREP