tree-sitter-analyzer 1.7.5__py3-none-any.whl → 1.8.2__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.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/api.py +26 -32
- tree_sitter_analyzer/cli/argument_validator.py +77 -0
- tree_sitter_analyzer/cli/commands/table_command.py +7 -2
- tree_sitter_analyzer/cli_main.py +17 -3
- tree_sitter_analyzer/core/cache_service.py +15 -5
- tree_sitter_analyzer/core/query.py +33 -22
- tree_sitter_analyzer/core/query_service.py +179 -154
- tree_sitter_analyzer/exceptions.py +334 -0
- tree_sitter_analyzer/file_handler.py +16 -1
- tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
- tree_sitter_analyzer/formatters/html_formatter.py +462 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
- tree_sitter_analyzer/interfaces/mcp_server.py +3 -1
- tree_sitter_analyzer/language_detector.py +91 -7
- tree_sitter_analyzer/languages/css_plugin.py +390 -0
- tree_sitter_analyzer/languages/html_plugin.py +395 -0
- tree_sitter_analyzer/languages/java_plugin.py +116 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
- tree_sitter_analyzer/languages/python_plugin.py +176 -33
- tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +68 -3
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +32 -7
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +10 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +9 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +100 -52
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +98 -14
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +9 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +37 -13
- tree_sitter_analyzer/models.py +53 -0
- tree_sitter_analyzer/output_manager.py +1 -1
- tree_sitter_analyzer/plugins/base.py +50 -0
- tree_sitter_analyzer/plugins/manager.py +5 -1
- tree_sitter_analyzer/queries/css.py +634 -0
- tree_sitter_analyzer/queries/html.py +556 -0
- tree_sitter_analyzer/queries/markdown.py +54 -164
- tree_sitter_analyzer/query_loader.py +16 -3
- tree_sitter_analyzer/security/validator.py +343 -46
- tree_sitter_analyzer/utils/__init__.py +113 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
- tree_sitter_analyzer/utils.py +62 -24
- {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/METADATA +136 -14
- {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/RECORD +47 -38
- {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/entry_points.txt +2 -0
- {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/WHEEL +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
tree_sitter_analyzer/api.py
CHANGED
|
@@ -11,6 +11,7 @@ import logging
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
+
from . import __version__
|
|
14
15
|
from .core.engine import AnalysisEngine
|
|
15
16
|
from .utils import log_error
|
|
16
17
|
|
|
@@ -57,30 +58,20 @@ def analyze_file(
|
|
|
57
58
|
include_complexity: Whether to include complexity metrics (backward compatibility)
|
|
58
59
|
|
|
59
60
|
Returns:
|
|
60
|
-
Analysis results dictionary
|
|
61
|
-
- success: Whether the analysis was successful
|
|
62
|
-
- file_info: Basic file information
|
|
63
|
-
- language_info: Detected/specified language information
|
|
64
|
-
- ast_info: Abstract syntax tree information
|
|
65
|
-
- query_results: Results from executed queries (if include_queries=True)
|
|
66
|
-
- elements: Extracted code elements (if include_elements=True)
|
|
67
|
-
- error: Error message (if success=False)
|
|
61
|
+
Analysis results dictionary
|
|
68
62
|
"""
|
|
69
63
|
try:
|
|
70
64
|
engine = get_engine()
|
|
71
65
|
|
|
72
66
|
# Perform the analysis
|
|
73
67
|
analysis_result = engine.analyze_file(file_path, language)
|
|
74
|
-
|
|
75
|
-
# Convert AnalysisResult to expected API format
|
|
68
|
+
|
|
69
|
+
# Convert AnalysisResult to expected API format (same as analyze_code)
|
|
76
70
|
result = {
|
|
77
71
|
"success": analysis_result.success,
|
|
78
72
|
"file_info": {
|
|
79
73
|
"path": str(file_path),
|
|
80
|
-
"exists":
|
|
81
|
-
"size": (
|
|
82
|
-
Path(file_path).stat().st_size if Path(file_path).exists() else 0
|
|
83
|
-
),
|
|
74
|
+
"exists": True,
|
|
84
75
|
},
|
|
85
76
|
"language_info": {
|
|
86
77
|
"language": analysis_result.language,
|
|
@@ -176,21 +167,14 @@ def analyze_file(
|
|
|
176
167
|
except FileNotFoundError as e:
|
|
177
168
|
# Re-raise FileNotFoundError for tests that expect it
|
|
178
169
|
raise e
|
|
179
|
-
except (ValueError, TypeError, OSError) as e:
|
|
180
|
-
# Handle specific expected errors
|
|
181
|
-
log_error(f"API analyze_file failed with {type(e).__name__}: {e}")
|
|
182
|
-
return {
|
|
183
|
-
"success": False,
|
|
184
|
-
"error": f"{type(e).__name__}: {str(e)}",
|
|
185
|
-
"file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
|
|
186
|
-
}
|
|
187
170
|
except Exception as e:
|
|
188
|
-
|
|
189
|
-
log_error(f"API analyze_file failed with unexpected error: {e}")
|
|
171
|
+
log_error(f"API analyze_file failed: {e}")
|
|
190
172
|
return {
|
|
191
173
|
"success": False,
|
|
192
|
-
"error":
|
|
193
|
-
"file_info": {"path": str(file_path), "exists":
|
|
174
|
+
"error": str(e),
|
|
175
|
+
"file_info": {"path": str(file_path), "exists": False},
|
|
176
|
+
"language_info": {"language": language or "unknown", "detected": False},
|
|
177
|
+
"ast_info": {"node_count": 0, "line_count": 0},
|
|
194
178
|
}
|
|
195
179
|
|
|
196
180
|
|
|
@@ -377,7 +361,7 @@ def is_language_supported(language: str) -> bool:
|
|
|
377
361
|
return False
|
|
378
362
|
|
|
379
363
|
|
|
380
|
-
def detect_language(file_path: str | Path) -> str
|
|
364
|
+
def detect_language(file_path: str | Path) -> str:
|
|
381
365
|
"""
|
|
382
366
|
Detect programming language from file path.
|
|
383
367
|
|
|
@@ -385,15 +369,25 @@ def detect_language(file_path: str | Path) -> str | None:
|
|
|
385
369
|
file_path: Path to the file
|
|
386
370
|
|
|
387
371
|
Returns:
|
|
388
|
-
Detected language name
|
|
372
|
+
Detected language name - 常に有効な文字列を返す
|
|
389
373
|
"""
|
|
390
374
|
try:
|
|
375
|
+
# Handle invalid input
|
|
376
|
+
if not file_path:
|
|
377
|
+
return "unknown"
|
|
378
|
+
|
|
391
379
|
engine = get_engine()
|
|
392
380
|
# Use language_detector instead of language_registry
|
|
393
|
-
|
|
381
|
+
result = engine.language_detector.detect_from_extension(str(file_path))
|
|
382
|
+
|
|
383
|
+
# Ensure result is valid
|
|
384
|
+
if not result or result.strip() == "":
|
|
385
|
+
return "unknown"
|
|
386
|
+
|
|
387
|
+
return result
|
|
394
388
|
except Exception as e:
|
|
395
389
|
log_error(f"Failed to detect language for {file_path}: {e}")
|
|
396
|
-
return
|
|
390
|
+
return "unknown"
|
|
397
391
|
|
|
398
392
|
|
|
399
393
|
def get_file_extensions(language: str) -> list[str]:
|
|
@@ -500,7 +494,7 @@ def get_framework_info() -> dict[str, Any]:
|
|
|
500
494
|
|
|
501
495
|
return {
|
|
502
496
|
"name": "tree-sitter-analyzer",
|
|
503
|
-
"version":
|
|
497
|
+
"version": __version__,
|
|
504
498
|
"supported_languages": engine.get_supported_languages(),
|
|
505
499
|
"total_languages": len(engine.get_supported_languages()),
|
|
506
500
|
"plugin_info": {
|
|
@@ -521,7 +515,7 @@ def get_framework_info() -> dict[str, Any]:
|
|
|
521
515
|
}
|
|
522
516
|
except Exception as e:
|
|
523
517
|
log_error(f"Failed to get framework info: {e}")
|
|
524
|
-
return {"name": "tree-sitter-analyzer", "version":
|
|
518
|
+
return {"name": "tree-sitter-analyzer", "version": __version__, "error": str(e)}
|
|
525
519
|
|
|
526
520
|
|
|
527
521
|
def execute_query(
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLI Argument Validator
|
|
4
|
+
|
|
5
|
+
Validates CLI argument combinations and provides clear error messages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CLIArgumentValidator:
|
|
12
|
+
"""Validator for CLI argument combinations."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize the validator."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def validate_arguments(self, args: Any) -> Optional[str]:
|
|
19
|
+
"""
|
|
20
|
+
Validate CLI argument combinations.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
args: Parsed command line arguments
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
None if valid, error message string if invalid
|
|
27
|
+
"""
|
|
28
|
+
# Check for --table and --query-key combination
|
|
29
|
+
table_specified = hasattr(args, 'table') and args.table is not None and args.table != ""
|
|
30
|
+
query_key_specified = hasattr(args, 'query_key') and args.query_key is not None and args.query_key != ""
|
|
31
|
+
|
|
32
|
+
if table_specified and query_key_specified:
|
|
33
|
+
return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
|
|
34
|
+
|
|
35
|
+
# All validations passed
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def validate_table_query_exclusivity(self, args: Any) -> Optional[str]:
|
|
39
|
+
"""
|
|
40
|
+
Validate that --table and --query-key are mutually exclusive.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
args: Parsed command line arguments
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
None if valid, error message string if invalid
|
|
47
|
+
"""
|
|
48
|
+
table_specified = hasattr(args, 'table') and args.table is not None and args.table != ""
|
|
49
|
+
query_key_specified = hasattr(args, 'query_key') and args.query_key is not None and args.query_key != ""
|
|
50
|
+
|
|
51
|
+
if table_specified and query_key_specified:
|
|
52
|
+
return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
|
|
53
|
+
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def get_usage_examples(self) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Get usage examples for correct argument combinations.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
String containing usage examples
|
|
62
|
+
"""
|
|
63
|
+
return """
|
|
64
|
+
Correct usage examples:
|
|
65
|
+
|
|
66
|
+
# Use table format only:
|
|
67
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --table full
|
|
68
|
+
|
|
69
|
+
# Use query-key only:
|
|
70
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods
|
|
71
|
+
|
|
72
|
+
# Use query-key with filter:
|
|
73
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "name=main"
|
|
74
|
+
|
|
75
|
+
# Invalid combination (will cause error):
|
|
76
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --table full --query-key methods
|
|
77
|
+
"""
|
|
@@ -16,7 +16,7 @@ from ...constants import (
|
|
|
16
16
|
ELEMENT_TYPE_VARIABLE,
|
|
17
17
|
get_element_type,
|
|
18
18
|
)
|
|
19
|
-
from ...output_manager import output_error
|
|
19
|
+
from ...output_manager import output_error, output_info
|
|
20
20
|
from ...table_formatter import create_table_formatter
|
|
21
21
|
from ...formatters.language_formatter_factory import create_language_formatter
|
|
22
22
|
from .base_command import BaseCommand
|
|
@@ -25,10 +25,14 @@ from .base_command import BaseCommand
|
|
|
25
25
|
class TableCommand(BaseCommand):
|
|
26
26
|
"""Command for generating table format output."""
|
|
27
27
|
|
|
28
|
+
def __init__(self, args):
|
|
29
|
+
"""Initialize the table command."""
|
|
30
|
+
super().__init__(args)
|
|
31
|
+
|
|
28
32
|
async def execute_async(self, language: str) -> int:
|
|
29
33
|
"""Execute table format generation."""
|
|
30
34
|
try:
|
|
31
|
-
# Perform analysis
|
|
35
|
+
# Perform standard analysis
|
|
32
36
|
analysis_result = await self.analyze_file(language)
|
|
33
37
|
if not analysis_result:
|
|
34
38
|
return 1
|
|
@@ -64,6 +68,7 @@ class TableCommand(BaseCommand):
|
|
|
64
68
|
output_error(f"An error occurred during table format analysis: {e}")
|
|
65
69
|
return 1
|
|
66
70
|
|
|
71
|
+
|
|
67
72
|
def _convert_to_formatter_format(self, analysis_result: Any) -> dict[str, Any]:
|
|
68
73
|
"""Convert AnalysisResult to format expected by formatters."""
|
|
69
74
|
return {
|
tree_sitter_analyzer/cli_main.py
CHANGED
|
@@ -25,6 +25,7 @@ from .cli.info_commands import (
|
|
|
25
25
|
)
|
|
26
26
|
from .output_manager import output_error, output_info, output_list
|
|
27
27
|
from .query_loader import query_loader
|
|
28
|
+
from .cli.argument_validator import CLIArgumentValidator
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class CLICommandFactory:
|
|
@@ -33,6 +34,14 @@ class CLICommandFactory:
|
|
|
33
34
|
@staticmethod
|
|
34
35
|
def create_command(args: argparse.Namespace) -> Any:
|
|
35
36
|
"""Create appropriate command based on arguments."""
|
|
37
|
+
|
|
38
|
+
# Validate argument combinations first
|
|
39
|
+
validator = CLIArgumentValidator()
|
|
40
|
+
validation_error = validator.validate_arguments(args)
|
|
41
|
+
if validation_error:
|
|
42
|
+
output_error(validation_error)
|
|
43
|
+
output_info(validator.get_usage_examples())
|
|
44
|
+
return None
|
|
36
45
|
|
|
37
46
|
# Information commands (no file analysis required)
|
|
38
47
|
if args.list_queries:
|
|
@@ -62,6 +71,7 @@ class CLICommandFactory:
|
|
|
62
71
|
if hasattr(args, "partial_read") and args.partial_read:
|
|
63
72
|
return PartialReadCommand(args)
|
|
64
73
|
|
|
74
|
+
# Handle table command with or without query-key
|
|
65
75
|
if hasattr(args, "table") and args.table:
|
|
66
76
|
return TableCommand(args)
|
|
67
77
|
|
|
@@ -274,14 +284,18 @@ def main() -> None:
|
|
|
274
284
|
if "--quiet" in sys.argv:
|
|
275
285
|
os.environ["LOG_LEVEL"] = "ERROR"
|
|
276
286
|
else:
|
|
277
|
-
# Set default log level to
|
|
278
|
-
os.environ
|
|
287
|
+
# Set default log level to ERROR to prevent log output in CLI
|
|
288
|
+
os.environ["LOG_LEVEL"] = "ERROR"
|
|
279
289
|
|
|
280
290
|
parser = create_argument_parser()
|
|
281
291
|
args = parser.parse_args()
|
|
282
292
|
|
|
283
|
-
# Configure
|
|
293
|
+
# Configure all logging to ERROR level to prevent output contamination
|
|
294
|
+
logging.getLogger().setLevel(logging.ERROR)
|
|
295
|
+
logging.getLogger("tree_sitter_analyzer").setLevel(logging.ERROR)
|
|
284
296
|
logging.getLogger("tree_sitter_analyzer.performance").setLevel(logging.ERROR)
|
|
297
|
+
logging.getLogger("tree_sitter_analyzer.plugins").setLevel(logging.ERROR)
|
|
298
|
+
logging.getLogger("tree_sitter_analyzer.plugins.manager").setLevel(logging.ERROR)
|
|
285
299
|
|
|
286
300
|
# Configure logging for table output
|
|
287
301
|
if hasattr(args, "table") and args.table:
|
|
@@ -225,7 +225,10 @@ class CacheService:
|
|
|
225
225
|
for key in self._stats:
|
|
226
226
|
self._stats[key] = 0
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
# Only log if not in quiet mode (check log level)
|
|
229
|
+
import logging
|
|
230
|
+
if logging.getLogger("tree_sitter_analyzer").level <= logging.INFO:
|
|
231
|
+
log_info("All caches cleared")
|
|
229
232
|
|
|
230
233
|
def size(self) -> int:
|
|
231
234
|
"""
|
|
@@ -315,7 +318,14 @@ class CacheService:
|
|
|
315
318
|
def __del__(self) -> None:
|
|
316
319
|
"""デストラクタ - リソースクリーンアップ"""
|
|
317
320
|
try:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
# Only clear if not in shutdown mode
|
|
322
|
+
import sys
|
|
323
|
+
if sys.meta_path is not None: # Check if Python is not shutting down
|
|
324
|
+
# Clear caches without logging to avoid shutdown issues
|
|
325
|
+
with self._lock:
|
|
326
|
+
self._l1_cache.clear()
|
|
327
|
+
self._l2_cache.clear()
|
|
328
|
+
self._l3_cache.clear()
|
|
329
|
+
except Exception:
|
|
330
|
+
# Silently ignore all errors during shutdown to prevent ImportError
|
|
331
|
+
pass
|
|
@@ -13,6 +13,7 @@ from typing import Any
|
|
|
13
13
|
from tree_sitter import Language, Node, Tree
|
|
14
14
|
|
|
15
15
|
from ..query_loader import get_query_loader
|
|
16
|
+
from ..utils.tree_sitter_compat import TreeSitterQueryCompat, get_node_text_safe
|
|
16
17
|
|
|
17
18
|
# Configure logging
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
@@ -69,18 +70,33 @@ class QueryExecutor:
|
|
|
69
70
|
"Language is None", query_name=query_name
|
|
70
71
|
)
|
|
71
72
|
|
|
72
|
-
# Get the query string
|
|
73
|
-
language_name =
|
|
73
|
+
# Get the query string with robust language name handling
|
|
74
|
+
language_name = None
|
|
75
|
+
if language:
|
|
76
|
+
# Try multiple ways to get language name
|
|
77
|
+
language_name = getattr(language, "name", None)
|
|
78
|
+
if not language_name:
|
|
79
|
+
language_name = getattr(language, "_name", None)
|
|
80
|
+
if not language_name:
|
|
81
|
+
language_name = str(language).split('.')[-1] if hasattr(language, '__class__') else None
|
|
82
|
+
|
|
83
|
+
# Ensure we have a valid language name
|
|
84
|
+
if not language_name or language_name.strip() == "" or language_name == "None":
|
|
85
|
+
language_name = "unknown"
|
|
86
|
+
else:
|
|
87
|
+
language_name = language_name.strip().lower()
|
|
88
|
+
|
|
74
89
|
query_string = self._query_loader.get_query(language_name, query_name)
|
|
75
90
|
if query_string is None:
|
|
76
91
|
return self._create_error_result(
|
|
77
92
|
f"Query '{query_name}' not found", query_name=query_name
|
|
78
93
|
)
|
|
79
94
|
|
|
80
|
-
# Create and execute the query
|
|
95
|
+
# Create and execute the query using modern API
|
|
81
96
|
try:
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
captures = TreeSitterQueryCompat.safe_execute_query(
|
|
98
|
+
language, query_string, tree.root_node, fallback_result=[]
|
|
99
|
+
)
|
|
84
100
|
|
|
85
101
|
# Process captures
|
|
86
102
|
try:
|
|
@@ -146,10 +162,11 @@ class QueryExecutor:
|
|
|
146
162
|
if language is None:
|
|
147
163
|
return self._create_error_result("Language is None") # type: ignore[unreachable]
|
|
148
164
|
|
|
149
|
-
# Create and execute the query
|
|
165
|
+
# Create and execute the query using modern API
|
|
150
166
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
captures = TreeSitterQueryCompat.safe_execute_query(
|
|
168
|
+
language, query_string, tree.root_node, fallback_result=[]
|
|
169
|
+
)
|
|
153
170
|
|
|
154
171
|
# Process captures
|
|
155
172
|
try:
|
|
@@ -223,14 +240,13 @@ class QueryExecutor:
|
|
|
223
240
|
try:
|
|
224
241
|
for capture in captures:
|
|
225
242
|
try:
|
|
226
|
-
# Handle
|
|
227
|
-
if isinstance(capture,
|
|
228
|
-
# New Tree-sitter API format
|
|
229
|
-
node = capture.get("node")
|
|
230
|
-
name = capture.get("name", "unknown")
|
|
231
|
-
elif isinstance(capture, tuple) and len(capture) == 2:
|
|
232
|
-
# Old Tree-sitter API format
|
|
243
|
+
# Handle tuple format from modern API
|
|
244
|
+
if isinstance(capture, tuple) and len(capture) == 2:
|
|
233
245
|
node, name = capture
|
|
246
|
+
# Handle dictionary format (legacy API compatibility)
|
|
247
|
+
elif isinstance(capture, dict) and "node" in capture and "name" in capture:
|
|
248
|
+
node = capture["node"]
|
|
249
|
+
name = capture["name"]
|
|
234
250
|
else:
|
|
235
251
|
logger.warning(f"Unexpected capture format: {type(capture)}")
|
|
236
252
|
continue
|
|
@@ -265,13 +281,8 @@ class QueryExecutor:
|
|
|
265
281
|
Dictionary containing node information
|
|
266
282
|
"""
|
|
267
283
|
try:
|
|
268
|
-
# Extract node text
|
|
269
|
-
node_text =
|
|
270
|
-
if hasattr(node, "text") and node.text:
|
|
271
|
-
try:
|
|
272
|
-
node_text = node.text.decode("utf-8", errors="replace")
|
|
273
|
-
except Exception:
|
|
274
|
-
node_text = str(node.text)
|
|
284
|
+
# Extract node text using safe utility
|
|
285
|
+
node_text = get_node_text_safe(node, source_code)
|
|
275
286
|
|
|
276
287
|
return {
|
|
277
288
|
"capture_name": capture_name,
|