tree-sitter-analyzer 1.4.1__py3-none-any.whl → 1.6.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.
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 +108 -8
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +3 -2
- tree_sitter_analyzer/cli/commands/list_files_cli.py +0 -1
- tree_sitter_analyzer/cli/commands/search_content_cli.py +3 -2
- tree_sitter_analyzer/cli_main.py +3 -1
- tree_sitter_analyzer/encoding_utils.py +3 -3
- tree_sitter_analyzer/formatters/formatter_factory.py +3 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +467 -0
- tree_sitter_analyzer/formatters/python_formatter.py +161 -20
- tree_sitter_analyzer/language_loader.py +2 -2
- tree_sitter_analyzer/languages/javascript_plugin.py +1289 -238
- tree_sitter_analyzer/languages/python_plugin.py +581 -148
- tree_sitter_analyzer/mcp/server.py +17 -2
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +106 -4
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +257 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +1 -1
- tree_sitter_analyzer/models.py +17 -0
- tree_sitter_analyzer/queries/javascript.py +592 -31
- tree_sitter_analyzer/queries/python.py +617 -58
- tree_sitter_analyzer/table_formatter.py +26 -2
- {tree_sitter_analyzer-1.4.1.dist-info → tree_sitter_analyzer-1.6.0.dist-info}/METADATA +165 -22
- {tree_sitter_analyzer-1.4.1.dist-info → tree_sitter_analyzer-1.6.0.dist-info}/RECORD +25 -23
- {tree_sitter_analyzer-1.4.1.dist-info → tree_sitter_analyzer-1.6.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.4.1.dist-info → tree_sitter_analyzer-1.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -419,14 +419,28 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
419
419
|
),
|
|
420
420
|
Tool(
|
|
421
421
|
name="analyze_code_structure",
|
|
422
|
-
description="Analyze code structure and generate tables with line positions",
|
|
422
|
+
description="Analyze code structure and generate tables with line positions, optionally save to file",
|
|
423
423
|
inputSchema={
|
|
424
424
|
"type": "object",
|
|
425
425
|
"properties": {
|
|
426
426
|
"file_path": {
|
|
427
427
|
"type": "string",
|
|
428
428
|
"description": "Path to the code file (relative to project root)",
|
|
429
|
-
}
|
|
429
|
+
},
|
|
430
|
+
"format_type": {
|
|
431
|
+
"type": "string",
|
|
432
|
+
"description": "Table format type",
|
|
433
|
+
"enum": ["full", "compact", "csv", "json"],
|
|
434
|
+
"default": "full",
|
|
435
|
+
},
|
|
436
|
+
"language": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"description": "Programming language (optional, auto-detected if not specified)",
|
|
439
|
+
},
|
|
440
|
+
"output_file": {
|
|
441
|
+
"type": "string",
|
|
442
|
+
"description": "Optional filename to save output to file (extension auto-detected based on content)",
|
|
443
|
+
},
|
|
430
444
|
},
|
|
431
445
|
"required": ["file_path"],
|
|
432
446
|
"additionalProperties": False,
|
|
@@ -522,6 +536,7 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
522
536
|
"file_path": arguments["file_path"],
|
|
523
537
|
"format_type": arguments.get("format_type", "full"),
|
|
524
538
|
"language": arguments.get("language"),
|
|
539
|
+
"output_file": arguments.get("output_file"),
|
|
525
540
|
}
|
|
526
541
|
result = await self.table_format_tool.execute(full_args)
|
|
527
542
|
|
|
@@ -22,6 +22,7 @@ from ...language_detector import detect_language_from_file
|
|
|
22
22
|
from ...table_formatter import TableFormatter
|
|
23
23
|
from ...utils import setup_logger
|
|
24
24
|
from ..utils import get_performance_monitor
|
|
25
|
+
from ..utils.file_output_manager import FileOutputManager
|
|
25
26
|
from .base_tool import BaseMCPTool
|
|
26
27
|
|
|
27
28
|
# Set up logging
|
|
@@ -40,6 +41,7 @@ class TableFormatTool(BaseMCPTool):
|
|
|
40
41
|
"""Initialize the table format tool."""
|
|
41
42
|
super().__init__(project_root)
|
|
42
43
|
self.analysis_engine = get_analysis_engine(project_root)
|
|
44
|
+
self.file_output_manager = FileOutputManager(project_root)
|
|
43
45
|
self.logger = logger
|
|
44
46
|
|
|
45
47
|
def set_project_path(self, project_path: str) -> None:
|
|
@@ -51,6 +53,7 @@ class TableFormatTool(BaseMCPTool):
|
|
|
51
53
|
"""
|
|
52
54
|
super().set_project_path(project_path)
|
|
53
55
|
self.analysis_engine = get_analysis_engine(project_path)
|
|
56
|
+
self.file_output_manager.set_project_root(project_path)
|
|
54
57
|
logger.info(f"TableFormatTool project path updated to: {project_path}")
|
|
55
58
|
|
|
56
59
|
def get_tool_schema(self) -> dict[str, Any]:
|
|
@@ -70,13 +73,17 @@ class TableFormatTool(BaseMCPTool):
|
|
|
70
73
|
"format_type": {
|
|
71
74
|
"type": "string",
|
|
72
75
|
"description": "Table format type",
|
|
73
|
-
"enum": ["full", "compact", "csv"],
|
|
76
|
+
"enum": ["full", "compact", "csv", "json"],
|
|
74
77
|
"default": "full",
|
|
75
78
|
},
|
|
76
79
|
"language": {
|
|
77
80
|
"type": "string",
|
|
78
81
|
"description": "Programming language (optional, auto-detected if not specified)",
|
|
79
82
|
},
|
|
83
|
+
"output_file": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Optional filename to save output to file (extension auto-detected based on content)",
|
|
86
|
+
},
|
|
80
87
|
},
|
|
81
88
|
"required": ["file_path"],
|
|
82
89
|
"additionalProperties": False,
|
|
@@ -111,8 +118,8 @@ class TableFormatTool(BaseMCPTool):
|
|
|
111
118
|
format_type = arguments["format_type"]
|
|
112
119
|
if not isinstance(format_type, str):
|
|
113
120
|
raise ValueError("format_type must be a string")
|
|
114
|
-
if format_type not in ["full", "compact", "csv"]:
|
|
115
|
-
raise ValueError("format_type must be one of: full, compact, csv")
|
|
121
|
+
if format_type not in ["full", "compact", "csv", "json"]:
|
|
122
|
+
raise ValueError("format_type must be one of: full, compact, csv, json")
|
|
116
123
|
|
|
117
124
|
# Validate language if provided
|
|
118
125
|
if "language" in arguments:
|
|
@@ -120,6 +127,14 @@ class TableFormatTool(BaseMCPTool):
|
|
|
120
127
|
if not isinstance(language, str):
|
|
121
128
|
raise ValueError("language must be a string")
|
|
122
129
|
|
|
130
|
+
# Validate output_file if provided
|
|
131
|
+
if "output_file" in arguments:
|
|
132
|
+
output_file = arguments["output_file"]
|
|
133
|
+
if not isinstance(output_file, str):
|
|
134
|
+
raise ValueError("output_file must be a string")
|
|
135
|
+
if not output_file.strip():
|
|
136
|
+
raise ValueError("output_file cannot be empty")
|
|
137
|
+
|
|
123
138
|
return True
|
|
124
139
|
|
|
125
140
|
def _convert_parameters(self, parameters: Any) -> list[dict[str, str]]:
|
|
@@ -286,6 +301,59 @@ class TableFormatTool(BaseMCPTool):
|
|
|
286
301
|
},
|
|
287
302
|
}
|
|
288
303
|
|
|
304
|
+
def _write_output_file(
|
|
305
|
+
self, output_file: str, content: str, format_type: str
|
|
306
|
+
) -> str:
|
|
307
|
+
"""
|
|
308
|
+
Write output content to file with automatic extension detection.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
output_file: Base filename for output
|
|
312
|
+
content: Content to write
|
|
313
|
+
format_type: Format type (full, compact, csv, json)
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Full path of the written file
|
|
317
|
+
"""
|
|
318
|
+
from pathlib import Path
|
|
319
|
+
|
|
320
|
+
# Determine file extension based on format type
|
|
321
|
+
extension_map = {
|
|
322
|
+
"full": ".md",
|
|
323
|
+
"compact": ".md",
|
|
324
|
+
"csv": ".csv",
|
|
325
|
+
"json": ".json",
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# Get the appropriate extension
|
|
329
|
+
extension = extension_map.get(format_type, ".txt")
|
|
330
|
+
|
|
331
|
+
# Add extension if not already present
|
|
332
|
+
if not output_file.endswith(extension):
|
|
333
|
+
output_file = output_file + extension
|
|
334
|
+
|
|
335
|
+
# Resolve output path relative to project root
|
|
336
|
+
output_path = self.path_resolver.resolve(output_file)
|
|
337
|
+
|
|
338
|
+
# Security validation for output path
|
|
339
|
+
is_valid, error_msg = self.security_validator.validate_file_path(output_path)
|
|
340
|
+
if not is_valid:
|
|
341
|
+
raise ValueError(f"Invalid output file path: {error_msg}")
|
|
342
|
+
|
|
343
|
+
# Ensure output directory exists
|
|
344
|
+
output_dir = Path(output_path).parent
|
|
345
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
346
|
+
|
|
347
|
+
# Write content to file
|
|
348
|
+
try:
|
|
349
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
350
|
+
f.write(content)
|
|
351
|
+
self.logger.info(f"Output written to file: {output_path}")
|
|
352
|
+
return output_path
|
|
353
|
+
except Exception as e:
|
|
354
|
+
self.logger.error(f"Failed to write output file {output_path}: {e}")
|
|
355
|
+
raise RuntimeError(f"Failed to write output file: {e}") from e
|
|
356
|
+
|
|
289
357
|
async def execute(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
290
358
|
"""Execute code structure analysis tool."""
|
|
291
359
|
try:
|
|
@@ -296,6 +364,7 @@ class TableFormatTool(BaseMCPTool):
|
|
|
296
364
|
file_path = args["file_path"]
|
|
297
365
|
format_type = args.get("format_type", "full")
|
|
298
366
|
language = args.get("language")
|
|
367
|
+
output_file = args.get("output_file")
|
|
299
368
|
|
|
300
369
|
# Resolve file path using common path resolver
|
|
301
370
|
resolved_path = self.path_resolver.resolve(file_path)
|
|
@@ -322,6 +391,12 @@ class TableFormatTool(BaseMCPTool):
|
|
|
322
391
|
language, max_length=50
|
|
323
392
|
)
|
|
324
393
|
|
|
394
|
+
# Sanitize output_file input
|
|
395
|
+
if output_file:
|
|
396
|
+
output_file = self.security_validator.sanitize_input(
|
|
397
|
+
output_file, max_length=255
|
|
398
|
+
)
|
|
399
|
+
|
|
325
400
|
# Validate file exists
|
|
326
401
|
if not Path(resolved_path).exists():
|
|
327
402
|
# Tests expect FileNotFoundError here
|
|
@@ -377,7 +452,7 @@ class TableFormatTool(BaseMCPTool):
|
|
|
377
452
|
"total_lines": stats.get("total_lines", 0),
|
|
378
453
|
}
|
|
379
454
|
|
|
380
|
-
|
|
455
|
+
result = {
|
|
381
456
|
"table_output": table_output,
|
|
382
457
|
"format_type": format_type,
|
|
383
458
|
"file_path": file_path,
|
|
@@ -385,6 +460,33 @@ class TableFormatTool(BaseMCPTool):
|
|
|
385
460
|
"metadata": metadata,
|
|
386
461
|
}
|
|
387
462
|
|
|
463
|
+
# Handle file output if requested
|
|
464
|
+
if output_file:
|
|
465
|
+
try:
|
|
466
|
+
# Generate base name from original file path if not provided
|
|
467
|
+
if not output_file or output_file.strip() == "":
|
|
468
|
+
base_name = Path(file_path).stem + "_analysis"
|
|
469
|
+
else:
|
|
470
|
+
base_name = output_file
|
|
471
|
+
|
|
472
|
+
# Save to file with automatic extension detection
|
|
473
|
+
saved_file_path = self.file_output_manager.save_to_file(
|
|
474
|
+
content=table_output,
|
|
475
|
+
base_name=base_name
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
result["output_file_path"] = saved_file_path
|
|
479
|
+
result["file_saved"] = True
|
|
480
|
+
|
|
481
|
+
self.logger.info(f"Analysis output saved to: {saved_file_path}")
|
|
482
|
+
|
|
483
|
+
except Exception as e:
|
|
484
|
+
self.logger.error(f"Failed to save output to file: {e}")
|
|
485
|
+
result["file_save_error"] = str(e)
|
|
486
|
+
result["file_saved"] = False
|
|
487
|
+
|
|
488
|
+
return result
|
|
489
|
+
|
|
388
490
|
except Exception as e:
|
|
389
491
|
self.logger.error(f"Error in code structure analysis tool: {e}")
|
|
390
492
|
raise
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
File Output Manager for MCP Tools
|
|
4
|
+
|
|
5
|
+
This module provides functionality to save analysis results to files with
|
|
6
|
+
appropriate extensions based on content type, with security validation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ...utils import setup_logger
|
|
15
|
+
|
|
16
|
+
# Set up logging
|
|
17
|
+
logger = setup_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FileOutputManager:
|
|
21
|
+
"""
|
|
22
|
+
Manages file output for analysis results with automatic extension detection
|
|
23
|
+
and security validation.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, project_root: str | None = None):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the file output manager.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
project_root: Optional project root directory for fallback output path
|
|
32
|
+
"""
|
|
33
|
+
self.project_root = project_root
|
|
34
|
+
self._output_path = None
|
|
35
|
+
self._initialize_output_path()
|
|
36
|
+
|
|
37
|
+
def _initialize_output_path(self) -> None:
|
|
38
|
+
"""Initialize the output path from environment variables or project root."""
|
|
39
|
+
# Priority 1: Environment variable TREE_SITTER_OUTPUT_PATH
|
|
40
|
+
env_output_path = os.environ.get("TREE_SITTER_OUTPUT_PATH")
|
|
41
|
+
if env_output_path and Path(env_output_path).exists():
|
|
42
|
+
self._output_path = env_output_path
|
|
43
|
+
logger.info(f"Using output path from environment: {self._output_path}")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# Priority 2: Project root if available
|
|
47
|
+
if self.project_root and Path(self.project_root).exists():
|
|
48
|
+
self._output_path = self.project_root
|
|
49
|
+
logger.info(f"Using project root as output path: {self._output_path}")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Priority 3: Current working directory as fallback
|
|
53
|
+
self._output_path = str(Path.cwd())
|
|
54
|
+
logger.warning(f"Using current directory as output path: {self._output_path}")
|
|
55
|
+
|
|
56
|
+
def get_output_path(self) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Get the current output path.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Current output path
|
|
62
|
+
"""
|
|
63
|
+
return self._output_path or str(Path.cwd())
|
|
64
|
+
|
|
65
|
+
def set_output_path(self, output_path: str) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Set a custom output path.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
output_path: New output path
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If the path doesn't exist or is not a directory
|
|
74
|
+
"""
|
|
75
|
+
path_obj = Path(output_path)
|
|
76
|
+
if not path_obj.exists():
|
|
77
|
+
raise ValueError(f"Output path does not exist: {output_path}")
|
|
78
|
+
if not path_obj.is_dir():
|
|
79
|
+
raise ValueError(f"Output path is not a directory: {output_path}")
|
|
80
|
+
|
|
81
|
+
self._output_path = str(path_obj.resolve())
|
|
82
|
+
logger.info(f"Output path updated to: {self._output_path}")
|
|
83
|
+
|
|
84
|
+
def detect_content_type(self, content: str) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Detect content type based on content structure.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
content: Content to analyze
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Detected content type ('json', 'csv', 'markdown', or 'text')
|
|
93
|
+
"""
|
|
94
|
+
content_stripped = content.strip()
|
|
95
|
+
|
|
96
|
+
# Check for JSON
|
|
97
|
+
if content_stripped.startswith(("{", "[")):
|
|
98
|
+
try:
|
|
99
|
+
json.loads(content_stripped)
|
|
100
|
+
return "json"
|
|
101
|
+
except (json.JSONDecodeError, ValueError):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Check for CSV (simple heuristic)
|
|
105
|
+
lines = content_stripped.split("\n")
|
|
106
|
+
if len(lines) >= 2:
|
|
107
|
+
# Check if first few lines have consistent comma separation
|
|
108
|
+
first_line_commas = lines[0].count(",")
|
|
109
|
+
if first_line_commas > 0:
|
|
110
|
+
# Check if at least 2 more lines have similar comma counts
|
|
111
|
+
similar_comma_lines = sum(
|
|
112
|
+
1 for line in lines[1:4] if abs(line.count(",") - first_line_commas) <= 1
|
|
113
|
+
)
|
|
114
|
+
if similar_comma_lines >= 1:
|
|
115
|
+
return "csv"
|
|
116
|
+
|
|
117
|
+
# Check for Markdown (simple heuristic)
|
|
118
|
+
markdown_indicators = ["#", "##", "###", "|", "```", "*", "-", "+"]
|
|
119
|
+
if any(content_stripped.startswith(indicator) for indicator in markdown_indicators):
|
|
120
|
+
return "markdown"
|
|
121
|
+
|
|
122
|
+
# Check for table format (pipe-separated)
|
|
123
|
+
if "|" in content and "\n" in content:
|
|
124
|
+
lines = content_stripped.split("\n")
|
|
125
|
+
pipe_lines = sum(1 for line in lines if "|" in line)
|
|
126
|
+
if pipe_lines >= 2: # At least header and one data row
|
|
127
|
+
return "markdown"
|
|
128
|
+
|
|
129
|
+
# Default to text
|
|
130
|
+
return "text"
|
|
131
|
+
|
|
132
|
+
def get_file_extension(self, content_type: str) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Get file extension for content type.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
content_type: Content type ('json', 'csv', 'markdown', 'text')
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
File extension including the dot
|
|
141
|
+
"""
|
|
142
|
+
extension_map = {
|
|
143
|
+
"json": ".json",
|
|
144
|
+
"csv": ".csv",
|
|
145
|
+
"markdown": ".md",
|
|
146
|
+
"text": ".txt"
|
|
147
|
+
}
|
|
148
|
+
return extension_map.get(content_type, ".txt")
|
|
149
|
+
|
|
150
|
+
def generate_output_filename(self, base_name: str, content: str) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Generate output filename with appropriate extension.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
base_name: Base filename (without extension)
|
|
156
|
+
content: Content to analyze for type detection
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Complete filename with extension
|
|
160
|
+
"""
|
|
161
|
+
content_type = self.detect_content_type(content)
|
|
162
|
+
extension = self.get_file_extension(content_type)
|
|
163
|
+
|
|
164
|
+
# Remove existing extension if present
|
|
165
|
+
base_name_clean = Path(base_name).stem
|
|
166
|
+
|
|
167
|
+
return f"{base_name_clean}{extension}"
|
|
168
|
+
|
|
169
|
+
def save_to_file(self, content: str, filename: str | None = None, base_name: str | None = None) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Save content to file with automatic extension detection.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
content: Content to save
|
|
175
|
+
filename: Optional specific filename (overrides base_name)
|
|
176
|
+
base_name: Optional base name for auto-generated filename
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Path to the saved file
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError: If neither filename nor base_name is provided
|
|
183
|
+
OSError: If file cannot be written
|
|
184
|
+
"""
|
|
185
|
+
if not filename and not base_name:
|
|
186
|
+
raise ValueError("Either filename or base_name must be provided")
|
|
187
|
+
|
|
188
|
+
output_path = Path(self.get_output_path())
|
|
189
|
+
|
|
190
|
+
if filename:
|
|
191
|
+
# Use provided filename as-is
|
|
192
|
+
output_file = output_path / filename
|
|
193
|
+
else:
|
|
194
|
+
# Generate filename with appropriate extension
|
|
195
|
+
generated_filename = self.generate_output_filename(base_name, content)
|
|
196
|
+
output_file = output_path / generated_filename
|
|
197
|
+
|
|
198
|
+
# Ensure output directory exists
|
|
199
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
|
|
201
|
+
# Write content to file
|
|
202
|
+
try:
|
|
203
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
204
|
+
f.write(content)
|
|
205
|
+
|
|
206
|
+
logger.info(f"Content saved to file: {output_file}")
|
|
207
|
+
return str(output_file)
|
|
208
|
+
|
|
209
|
+
except OSError as e:
|
|
210
|
+
logger.error(f"Failed to save content to file {output_file}: {e}")
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
def validate_output_path(self, path: str) -> tuple[bool, str | None]:
|
|
214
|
+
"""
|
|
215
|
+
Validate if a path is safe for output.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
path: Path to validate
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Tuple of (is_valid, error_message)
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
path_obj = Path(path).resolve()
|
|
225
|
+
|
|
226
|
+
# Check if parent directory exists or can be created
|
|
227
|
+
parent_dir = path_obj.parent
|
|
228
|
+
if not parent_dir.exists():
|
|
229
|
+
try:
|
|
230
|
+
parent_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
except OSError as e:
|
|
232
|
+
return False, f"Cannot create parent directory: {e}"
|
|
233
|
+
|
|
234
|
+
# Check if we can write to the directory
|
|
235
|
+
if not os.access(parent_dir, os.W_OK):
|
|
236
|
+
return False, f"No write permission for directory: {parent_dir}"
|
|
237
|
+
|
|
238
|
+
# Check if file already exists and is writable
|
|
239
|
+
if path_obj.exists() and not os.access(path_obj, os.W_OK):
|
|
240
|
+
return False, f"No write permission for existing file: {path_obj}"
|
|
241
|
+
|
|
242
|
+
return True, None
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
return False, f"Path validation error: {str(e)}"
|
|
246
|
+
|
|
247
|
+
def set_project_root(self, project_root: str) -> None:
|
|
248
|
+
"""
|
|
249
|
+
Update the project root and reinitialize output path if needed.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
project_root: New project root directory
|
|
253
|
+
"""
|
|
254
|
+
self.project_root = project_root
|
|
255
|
+
# Only reinitialize if we don't have an explicit output path from environment
|
|
256
|
+
if not os.environ.get("TREE_SITTER_OUTPUT_PATH"):
|
|
257
|
+
self._initialize_output_path()
|
|
@@ -52,7 +52,7 @@ def _normalize_path_cross_platform(path_str: str) -> str:
|
|
|
52
52
|
from ctypes import wintypes
|
|
53
53
|
|
|
54
54
|
# GetLongPathNameW function
|
|
55
|
-
_GetLongPathNameW = ctypes.windll.kernel32.GetLongPathNameW
|
|
55
|
+
_GetLongPathNameW = ctypes.windll.kernel32.GetLongPathNameW # type: ignore[attr-defined]
|
|
56
56
|
_GetLongPathNameW.argtypes = [
|
|
57
57
|
wintypes.LPCWSTR,
|
|
58
58
|
wintypes.LPWSTR,
|
tree_sitter_analyzer/models.py
CHANGED
|
@@ -67,6 +67,15 @@ class Function(CodeElement):
|
|
|
67
67
|
complexity_score: int = 1
|
|
68
68
|
is_abstract: bool = False
|
|
69
69
|
is_final: bool = False
|
|
70
|
+
# JavaScript-specific fields
|
|
71
|
+
is_generator: bool = False
|
|
72
|
+
is_arrow: bool = False
|
|
73
|
+
is_method: bool = False
|
|
74
|
+
framework_type: str | None = None
|
|
75
|
+
# Python-specific fields
|
|
76
|
+
is_property: bool = False
|
|
77
|
+
is_classmethod: bool = False
|
|
78
|
+
is_staticmethod: bool = False
|
|
70
79
|
|
|
71
80
|
|
|
72
81
|
@dataclass(frozen=False)
|
|
@@ -90,6 +99,14 @@ class Class(CodeElement):
|
|
|
90
99
|
implements_interfaces: list[str] = field(
|
|
91
100
|
default_factory=list
|
|
92
101
|
) # Alias for interfaces
|
|
102
|
+
# JavaScript-specific fields
|
|
103
|
+
is_react_component: bool = False
|
|
104
|
+
framework_type: str | None = None
|
|
105
|
+
is_exported: bool = False
|
|
106
|
+
# Python-specific fields
|
|
107
|
+
is_dataclass: bool = False
|
|
108
|
+
is_abstract: bool = False
|
|
109
|
+
is_exception: bool = False
|
|
93
110
|
|
|
94
111
|
|
|
95
112
|
@dataclass(frozen=False)
|