tree-sitter-analyzer 1.9.2__py3-none-any.whl → 1.9.4__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 +216 -8
- tree_sitter_analyzer/cli/argument_validator.py +1 -1
- tree_sitter_analyzer/cli/commands/advanced_command.py +3 -6
- tree_sitter_analyzer/cli/commands/query_command.py +3 -1
- tree_sitter_analyzer/cli/commands/table_command.py +3 -3
- tree_sitter_analyzer/constants.py +5 -3
- tree_sitter_analyzer/core/analysis_engine.py +1 -1
- tree_sitter_analyzer/core/cache_service.py +1 -1
- tree_sitter_analyzer/core/engine.py +34 -10
- tree_sitter_analyzer/core/query.py +82 -2
- tree_sitter_analyzer/encoding_utils.py +64 -0
- tree_sitter_analyzer/exceptions.py +1 -1
- tree_sitter_analyzer/file_handler.py +49 -33
- tree_sitter_analyzer/formatters/base_formatter.py +1 -1
- tree_sitter_analyzer/formatters/html_formatter.py +24 -14
- tree_sitter_analyzer/formatters/javascript_formatter.py +28 -21
- tree_sitter_analyzer/formatters/language_formatter_factory.py +7 -4
- tree_sitter_analyzer/formatters/markdown_formatter.py +4 -4
- tree_sitter_analyzer/formatters/python_formatter.py +4 -4
- tree_sitter_analyzer/formatters/typescript_formatter.py +1 -1
- tree_sitter_analyzer/interfaces/mcp_adapter.py +4 -2
- tree_sitter_analyzer/interfaces/mcp_server.py +10 -10
- tree_sitter_analyzer/language_detector.py +30 -5
- tree_sitter_analyzer/language_loader.py +46 -26
- tree_sitter_analyzer/languages/css_plugin.py +6 -6
- tree_sitter_analyzer/languages/html_plugin.py +12 -8
- tree_sitter_analyzer/languages/java_plugin.py +330 -520
- tree_sitter_analyzer/languages/javascript_plugin.py +22 -78
- tree_sitter_analyzer/languages/markdown_plugin.py +277 -297
- tree_sitter_analyzer/languages/python_plugin.py +47 -85
- tree_sitter_analyzer/languages/typescript_plugin.py +48 -123
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +14 -8
- tree_sitter_analyzer/mcp/server.py +38 -23
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -7
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +51 -7
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +11 -7
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +8 -6
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +6 -6
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +48 -15
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +13 -8
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +8 -3
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +24 -12
- tree_sitter_analyzer/mcp/utils/path_resolver.py +2 -2
- tree_sitter_analyzer/models.py +16 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/plugins/base.py +66 -0
- tree_sitter_analyzer/queries/java.py +9 -3
- tree_sitter_analyzer/queries/javascript.py +3 -8
- tree_sitter_analyzer/queries/markdown.py +1 -1
- tree_sitter_analyzer/queries/python.py +2 -2
- tree_sitter_analyzer/security/boundary_manager.py +2 -5
- tree_sitter_analyzer/security/regex_checker.py +2 -2
- tree_sitter_analyzer/security/validator.py +5 -1
- tree_sitter_analyzer/table_formatter.py +4 -4
- tree_sitter_analyzer/utils/__init__.py +27 -116
- tree_sitter_analyzer/{utils.py → utils/logging.py} +2 -2
- tree_sitter_analyzer/utils/tree_sitter_compat.py +2 -2
- {tree_sitter_analyzer-1.9.2.dist-info → tree_sitter_analyzer-1.9.4.dist-info}/METADATA +87 -45
- tree_sitter_analyzer-1.9.4.dist-info/RECORD +111 -0
- tree_sitter_analyzer-1.9.2.dist-info/RECORD +0 -109
- {tree_sitter_analyzer-1.9.2.dist-info → tree_sitter_analyzer-1.9.4.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.9.2.dist-info → tree_sitter_analyzer-1.9.4.dist-info}/entry_points.txt +0 -0
|
@@ -374,8 +374,9 @@ class TableFormatTool(BaseMCPTool):
|
|
|
374
374
|
|
|
375
375
|
# Write content to file
|
|
376
376
|
try:
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
from ...encoding_utils import write_file_safe
|
|
378
|
+
|
|
379
|
+
write_file_safe(output_path, content)
|
|
379
380
|
self.logger.info(f"Output written to file: {output_path}")
|
|
380
381
|
return output_path
|
|
381
382
|
except Exception as e:
|
|
@@ -471,19 +472,23 @@ class TableFormatTool(BaseMCPTool):
|
|
|
471
472
|
try:
|
|
472
473
|
if FormatterRegistry.is_format_supported(format_type):
|
|
473
474
|
# Use new FormatterRegistry
|
|
474
|
-
|
|
475
|
-
|
|
475
|
+
registry_formatter = FormatterRegistry.get_formatter(
|
|
476
|
+
format_type
|
|
477
|
+
)
|
|
478
|
+
table_output = registry_formatter.format(
|
|
479
|
+
structure_result.elements
|
|
480
|
+
)
|
|
476
481
|
else:
|
|
477
482
|
# Fallback to legacy TableFormatter for backward compatibility
|
|
478
|
-
|
|
479
|
-
table_output =
|
|
483
|
+
legacy_formatter: Any = TableFormatter(format_type)
|
|
484
|
+
table_output = legacy_formatter.format_structure(structure_dict)
|
|
480
485
|
except Exception as e:
|
|
481
486
|
# If FormatterRegistry fails, fallback to legacy TableFormatter
|
|
482
487
|
logger.warning(
|
|
483
488
|
f"FormatterRegistry failed, using legacy formatter: {e}"
|
|
484
489
|
)
|
|
485
|
-
|
|
486
|
-
table_output =
|
|
490
|
+
fallback_formatter: Any = TableFormatter(format_type)
|
|
491
|
+
table_output = fallback_formatter.format_structure(structure_dict)
|
|
487
492
|
|
|
488
493
|
# Ensure output format matches CLI exactly
|
|
489
494
|
# Fix line ending differences: normalize to Unix-style LF (\n)
|
|
@@ -36,7 +36,7 @@ class FileOutputManager:
|
|
|
36
36
|
project_root: Optional project root directory for fallback output path
|
|
37
37
|
"""
|
|
38
38
|
self.project_root = project_root
|
|
39
|
-
self._output_path = None
|
|
39
|
+
self._output_path: str | None = None
|
|
40
40
|
self._initialize_output_path()
|
|
41
41
|
|
|
42
42
|
@classmethod
|
|
@@ -252,6 +252,10 @@ class FileOutputManager:
|
|
|
252
252
|
output_file = output_path / filename
|
|
253
253
|
else:
|
|
254
254
|
# Generate filename with appropriate extension
|
|
255
|
+
if base_name is None:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
"base_name cannot be None when filename is not provided"
|
|
258
|
+
)
|
|
255
259
|
generated_filename = self.generate_output_filename(base_name, content)
|
|
256
260
|
output_file = output_path / generated_filename
|
|
257
261
|
|
|
@@ -260,8 +264,9 @@ class FileOutputManager:
|
|
|
260
264
|
|
|
261
265
|
# Write content to file
|
|
262
266
|
try:
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
from ...encoding_utils import write_file_safe
|
|
268
|
+
|
|
269
|
+
write_file_safe(output_file, content)
|
|
265
270
|
|
|
266
271
|
logger.info(f"Content saved to file: {output_file}")
|
|
267
272
|
return str(output_file)
|
|
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
class GitignoreDetector:
|
|
17
17
|
"""Detects .gitignore interference with file searches"""
|
|
18
18
|
|
|
19
|
-
def __init__(self):
|
|
19
|
+
def __init__(self) -> None:
|
|
20
20
|
self.common_ignore_patterns = {
|
|
21
21
|
# Directory patterns that commonly cause search issues
|
|
22
22
|
"build/*",
|
|
@@ -116,8 +116,10 @@ class GitignoreDetector:
|
|
|
116
116
|
current_search_dir: Directory where the search is being performed
|
|
117
117
|
"""
|
|
118
118
|
try:
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
from ...encoding_utils import read_file_safe
|
|
120
|
+
|
|
121
|
+
content, _ = read_file_safe(gitignore_file)
|
|
122
|
+
lines = content.splitlines()
|
|
121
123
|
|
|
122
124
|
for line in lines:
|
|
123
125
|
line = line.strip()
|
|
@@ -257,14 +259,14 @@ class GitignoreDetector:
|
|
|
257
259
|
|
|
258
260
|
def get_detection_info(
|
|
259
261
|
self, roots: list[str], project_root: str | None = None
|
|
260
|
-
) -> dict:
|
|
262
|
+
) -> dict[str, object]:
|
|
261
263
|
"""
|
|
262
264
|
Get detailed information about gitignore detection
|
|
263
265
|
|
|
264
266
|
Returns:
|
|
265
267
|
Dictionary with detection details for debugging/logging
|
|
266
268
|
"""
|
|
267
|
-
info = {
|
|
269
|
+
info: dict[str, object] = {
|
|
268
270
|
"should_use_no_ignore": False,
|
|
269
271
|
"detected_gitignore_files": [],
|
|
270
272
|
"interfering_patterns": [],
|
|
@@ -294,13 +296,21 @@ class GitignoreDetector:
|
|
|
294
296
|
gitignore_file, gitignore_dir, project_path
|
|
295
297
|
)
|
|
296
298
|
if patterns:
|
|
297
|
-
info
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
existing_patterns = info.get("interfering_patterns", [])
|
|
300
|
+
if isinstance(existing_patterns, list):
|
|
301
|
+
info["interfering_patterns"] = existing_patterns + patterns
|
|
302
|
+
else:
|
|
303
|
+
info["interfering_patterns"] = patterns
|
|
304
|
+
|
|
305
|
+
interfering_patterns = info.get("interfering_patterns", [])
|
|
306
|
+
if interfering_patterns:
|
|
300
307
|
info["should_use_no_ignore"] = True
|
|
301
|
-
|
|
302
|
-
|
|
308
|
+
pattern_count = (
|
|
309
|
+
len(interfering_patterns)
|
|
310
|
+
if isinstance(interfering_patterns, list)
|
|
311
|
+
else 0
|
|
303
312
|
)
|
|
313
|
+
info["reason"] = f"Found {pattern_count} interfering patterns"
|
|
304
314
|
|
|
305
315
|
except Exception as e:
|
|
306
316
|
info["reason"] = f"Error during detection: {e}"
|
|
@@ -314,8 +324,10 @@ class GitignoreDetector:
|
|
|
314
324
|
interfering = []
|
|
315
325
|
|
|
316
326
|
try:
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
from ...encoding_utils import read_file_safe
|
|
328
|
+
|
|
329
|
+
content, _ = read_file_safe(gitignore_file)
|
|
330
|
+
lines = content.splitlines()
|
|
319
331
|
|
|
320
332
|
for line in lines:
|
|
321
333
|
line = line.strip()
|
|
@@ -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
|
|
56
56
|
_GetLongPathNameW.argtypes = [
|
|
57
57
|
wintypes.LPCWSTR,
|
|
58
58
|
wintypes.LPWSTR,
|
|
@@ -110,7 +110,7 @@ class PathResolver:
|
|
|
110
110
|
project_root: Optional project root directory for resolving relative paths
|
|
111
111
|
"""
|
|
112
112
|
self.project_root = None
|
|
113
|
-
self._cache = {} # Simple cache for resolved paths
|
|
113
|
+
self._cache: dict[str, str] = {} # Simple cache for resolved paths
|
|
114
114
|
self._cache_size_limit = 100 # Limit cache size to prevent memory issues
|
|
115
115
|
|
|
116
116
|
if project_root:
|
tree_sitter_analyzer/models.py
CHANGED
|
@@ -45,6 +45,14 @@ class CodeElement(ABC):
|
|
|
45
45
|
raw_text: str = ""
|
|
46
46
|
language: str = "unknown"
|
|
47
47
|
docstring: str | None = None # JavaDoc/docstring for this element
|
|
48
|
+
element_type: str = "unknown"
|
|
49
|
+
|
|
50
|
+
def to_summary_item(self) -> dict[str, Any]:
|
|
51
|
+
return {
|
|
52
|
+
"name": self.name,
|
|
53
|
+
"type": self.element_type,
|
|
54
|
+
"lines": {"start": self.start_line, "end": self.end_line},
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
|
|
50
58
|
@dataclass(frozen=False)
|
|
@@ -341,6 +349,14 @@ class JavaPackage:
|
|
|
341
349
|
start_line: int = 0
|
|
342
350
|
end_line: int = 0
|
|
343
351
|
|
|
352
|
+
def to_summary_item(self) -> dict[str, Any]:
|
|
353
|
+
"""Return dictionary for summary item"""
|
|
354
|
+
return {
|
|
355
|
+
"name": self.name,
|
|
356
|
+
"type": "package",
|
|
357
|
+
"lines": {"start": self.start_line, "end": self.end_line},
|
|
358
|
+
}
|
|
359
|
+
|
|
344
360
|
|
|
345
361
|
@dataclass(frozen=False)
|
|
346
362
|
class AnalysisResult:
|
|
@@ -34,6 +34,10 @@ class ElementExtractor(ABC):
|
|
|
34
34
|
meaningful code elements like functions, classes, variables, etc.
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""Initialize the element extractor."""
|
|
39
|
+
self.current_file: str = "" # Current file being processed
|
|
40
|
+
|
|
37
41
|
@abstractmethod
|
|
38
42
|
def extract_functions(
|
|
39
43
|
self, tree: "tree_sitter.Tree", source_code: str
|
|
@@ -98,6 +102,36 @@ class ElementExtractor(ABC):
|
|
|
98
102
|
"""
|
|
99
103
|
pass
|
|
100
104
|
|
|
105
|
+
def extract_packages(self, tree: "tree_sitter.Tree", source_code: str) -> list[Any]:
|
|
106
|
+
"""
|
|
107
|
+
Extract package declarations from the syntax tree.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
tree: Tree-sitter AST
|
|
111
|
+
source_code: Original source code
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of extracted package objects
|
|
115
|
+
"""
|
|
116
|
+
# Default implementation returns empty list
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
def extract_annotations(
|
|
120
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
121
|
+
) -> list[Any]:
|
|
122
|
+
"""
|
|
123
|
+
Extract annotations from the syntax tree.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
tree: Tree-sitter AST
|
|
127
|
+
source_code: Original source code
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of extracted annotation objects
|
|
131
|
+
"""
|
|
132
|
+
# Default implementation returns empty list
|
|
133
|
+
return []
|
|
134
|
+
|
|
101
135
|
def extract_all_elements(
|
|
102
136
|
self, tree: "tree_sitter.Tree", source_code: str
|
|
103
137
|
) -> list[CodeElement]:
|
|
@@ -123,6 +157,38 @@ class ElementExtractor(ABC):
|
|
|
123
157
|
|
|
124
158
|
return elements
|
|
125
159
|
|
|
160
|
+
def extract_html_elements(
|
|
161
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
162
|
+
) -> list[Any]:
|
|
163
|
+
"""
|
|
164
|
+
Extract HTML elements from the syntax tree.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
tree: Tree-sitter AST
|
|
168
|
+
source_code: Original source code
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of extracted HTML elements
|
|
172
|
+
"""
|
|
173
|
+
# Default implementation returns empty list
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
def extract_css_rules(
|
|
177
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
178
|
+
) -> list[Any]:
|
|
179
|
+
"""
|
|
180
|
+
Extract CSS rules from the syntax tree.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
tree: Tree-sitter AST
|
|
184
|
+
source_code: Original source code
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of extracted CSS rules
|
|
188
|
+
"""
|
|
189
|
+
# Default implementation returns empty list
|
|
190
|
+
return []
|
|
191
|
+
|
|
126
192
|
|
|
127
193
|
class LanguagePlugin(ABC):
|
|
128
194
|
"""
|
|
@@ -107,10 +107,16 @@ JAVA_QUERIES: dict[str, str] = {
|
|
|
107
107
|
name: (identifier) @name
|
|
108
108
|
body: (block) @body) @method_with_body
|
|
109
109
|
""",
|
|
110
|
+
# Fixed: Match methods WITH annotations (at least one required)
|
|
111
|
+
# Uses alternation [(annotation) (marker_annotation)] to match both types:
|
|
112
|
+
# - marker_annotation: Annotations without parameters (e.g., @Override)
|
|
113
|
+
# - annotation: Annotations with parameters (e.g., @SuppressWarnings("unchecked"))
|
|
114
|
+
# The + quantifier requires at least one annotation
|
|
110
115
|
"method_with_annotations": """
|
|
111
116
|
(method_declaration
|
|
112
|
-
(modifiers
|
|
113
|
-
|
|
117
|
+
(modifiers
|
|
118
|
+
[(annotation) (marker_annotation)]+ @annotation)
|
|
119
|
+
name: (identifier) @name) @method
|
|
114
120
|
""",
|
|
115
121
|
# --- Inheritance Relations ---
|
|
116
122
|
"extends_clause": """
|
|
@@ -181,7 +187,7 @@ JAVA_QUERIES: dict[str, str] = {
|
|
|
181
187
|
# --- Structural Information Extraction Queries ---
|
|
182
188
|
"javadoc_comment": """
|
|
183
189
|
(block_comment) @javadoc_comment
|
|
184
|
-
(#match? @javadoc_comment "
|
|
190
|
+
(#match? @javadoc_comment "^/\\\\\\\\\\\\*\\\\\\\\\\\\*")
|
|
185
191
|
""",
|
|
186
192
|
"class_with_javadoc": """
|
|
187
193
|
(class_declaration
|
|
@@ -56,13 +56,13 @@ JAVASCRIPT_QUERIES: dict[str, str] = {
|
|
|
56
56
|
"class_declaration": """
|
|
57
57
|
(class_declaration
|
|
58
58
|
name: (identifier) @class_name
|
|
59
|
-
|
|
59
|
+
(class_heritage)? @superclass
|
|
60
60
|
body: (class_body) @body) @class_declaration
|
|
61
61
|
""",
|
|
62
62
|
"class_expression": """
|
|
63
63
|
(class_expression
|
|
64
64
|
name: (identifier)? @class_name
|
|
65
|
-
|
|
65
|
+
(class_heritage)? @superclass
|
|
66
66
|
body: (class_body) @body) @class_expression
|
|
67
67
|
""",
|
|
68
68
|
"class_method": """
|
|
@@ -542,13 +542,8 @@ FUNCTIONS = """
|
|
|
542
542
|
CLASSES = """
|
|
543
543
|
(class_declaration
|
|
544
544
|
name: (identifier) @class.name
|
|
545
|
-
|
|
545
|
+
(class_heritage)? @class.superclass
|
|
546
546
|
body: (class_body) @class.body) @class.declaration
|
|
547
|
-
|
|
548
|
-
(class_expression
|
|
549
|
-
name: (identifier)? @class.name
|
|
550
|
-
superclass: (class_heritage)? @class.superclass
|
|
551
|
-
body: (class_body) @class.body) @class.expression
|
|
552
547
|
"""
|
|
553
548
|
|
|
554
549
|
VARIABLES = """
|
|
@@ -176,7 +176,7 @@ def get_available_queries() -> list[str]:
|
|
|
176
176
|
return sorted(queries + aliases)
|
|
177
177
|
|
|
178
178
|
|
|
179
|
-
def get_query_info(query_name: str) -> dict[str, str]:
|
|
179
|
+
def get_query_info(query_name: str) -> dict[str, str | bool]:
|
|
180
180
|
"""
|
|
181
181
|
Get information about a query
|
|
182
182
|
|
|
@@ -40,7 +40,7 @@ IMPORTS = """
|
|
|
40
40
|
|
|
41
41
|
(import_from_statement
|
|
42
42
|
module_name: (dotted_name)? @import.module
|
|
43
|
-
name: (
|
|
43
|
+
name: (aliased_import) @import.aliased_item) @import.from_aliased
|
|
44
44
|
|
|
45
45
|
(aliased_import
|
|
46
46
|
name: (dotted_name) @import.name
|
|
@@ -335,7 +335,7 @@ PYTHON_QUERIES: dict[str, str] = {
|
|
|
335
335
|
"import_from_list": """
|
|
336
336
|
(import_from_statement
|
|
337
337
|
module_name: (dotted_name)? @module_name
|
|
338
|
-
name: (
|
|
338
|
+
name: (aliased_import) @import_item) @import_from_list
|
|
339
339
|
""",
|
|
340
340
|
"aliased_import": """
|
|
341
341
|
(aliased_import
|
|
@@ -45,8 +45,6 @@ class ProjectBoundaryManager:
|
|
|
45
45
|
# Handle both string and Path objects
|
|
46
46
|
if isinstance(project_root, str):
|
|
47
47
|
project_path = Path(project_root)
|
|
48
|
-
elif isinstance(project_root, Path):
|
|
49
|
-
project_path = project_root
|
|
50
48
|
else:
|
|
51
49
|
raise SecurityError(f"Invalid project root type: {type(project_root)}")
|
|
52
50
|
|
|
@@ -150,18 +148,17 @@ class ProjectBoundaryManager:
|
|
|
150
148
|
real_path = Path(file_path).resolve()
|
|
151
149
|
try:
|
|
152
150
|
rel_path = real_path.relative_to(Path(self.project_root))
|
|
153
|
-
rel_path = str(rel_path)
|
|
154
151
|
except ValueError:
|
|
155
152
|
# Path is not relative to project root
|
|
156
153
|
log_warning(f"Path not relative to project root: {file_path}")
|
|
157
154
|
return None
|
|
158
155
|
|
|
159
156
|
# Ensure relative path doesn't start with ..
|
|
160
|
-
if rel_path.startswith(".."):
|
|
157
|
+
if str(rel_path).startswith(".."):
|
|
161
158
|
log_warning(f"Relative path calculation failed: {rel_path}")
|
|
162
159
|
return None
|
|
163
160
|
|
|
164
|
-
return rel_path
|
|
161
|
+
return str(rel_path)
|
|
165
162
|
|
|
166
163
|
except Exception as e:
|
|
167
164
|
log_warning(f"Relative path calculation error: {e}")
|
|
@@ -219,9 +219,9 @@ class RegexSafetyChecker:
|
|
|
219
219
|
|
|
220
220
|
# Calculate complexity score
|
|
221
221
|
metrics["complexity_score"] = (
|
|
222
|
-
metrics["length"] * 0.1
|
|
222
|
+
int(metrics["length"] * 0.1)
|
|
223
223
|
+ metrics["quantifiers"] * 2
|
|
224
|
-
+ metrics["groups"] * 1.5
|
|
224
|
+
+ int(metrics["groups"] * 1.5)
|
|
225
225
|
+ metrics["alternations"] * 3
|
|
226
226
|
+ metrics["character_classes"] * 1
|
|
227
227
|
)
|
|
@@ -36,6 +36,8 @@ class SecurityValidator:
|
|
|
36
36
|
Args:
|
|
37
37
|
project_root: Optional project root directory for boundary checks
|
|
38
38
|
"""
|
|
39
|
+
self.boundary_manager: ProjectBoundaryManager | None
|
|
40
|
+
|
|
39
41
|
# Ensure project_root is properly resolved if provided
|
|
40
42
|
if project_root:
|
|
41
43
|
try:
|
|
@@ -488,7 +490,9 @@ class SecurityValidator:
|
|
|
488
490
|
or "CI" in os.environ
|
|
489
491
|
or "GITHUB_ACTIONS" in os.environ
|
|
490
492
|
or any(
|
|
491
|
-
"test" in arg.lower()
|
|
493
|
+
"test" in arg.lower()
|
|
494
|
+
for arg in getattr(getattr(os, "sys", None), "argv", [])
|
|
495
|
+
if hasattr(os, "sys")
|
|
492
496
|
)
|
|
493
497
|
)
|
|
494
498
|
|
|
@@ -665,7 +665,7 @@ class TableFormatter:
|
|
|
665
665
|
|
|
666
666
|
# Map<String,Object> -> M<S,O>
|
|
667
667
|
if "Map<" in type_name:
|
|
668
|
-
return (
|
|
668
|
+
return str(
|
|
669
669
|
type_name.replace("Map<", "M<")
|
|
670
670
|
.replace("String", "S")
|
|
671
671
|
.replace("Object", "O")
|
|
@@ -673,17 +673,17 @@ class TableFormatter:
|
|
|
673
673
|
|
|
674
674
|
# List<String> -> L<S>
|
|
675
675
|
if "List<" in type_name:
|
|
676
|
-
return type_name.replace("List<", "L<").replace("String", "S")
|
|
676
|
+
return str(type_name.replace("List<", "L<").replace("String", "S"))
|
|
677
677
|
|
|
678
678
|
# String[] -> S[]
|
|
679
679
|
if "[]" in type_name:
|
|
680
680
|
base_type = type_name.replace("[]", "")
|
|
681
681
|
if base_type:
|
|
682
|
-
return type_mapping.get(base_type, base_type[0].upper()) + "[]"
|
|
682
|
+
return str(type_mapping.get(base_type, base_type[0].upper())) + "[]"
|
|
683
683
|
else:
|
|
684
684
|
return "O[]"
|
|
685
685
|
|
|
686
|
-
return type_mapping.get(type_name, type_name)
|
|
686
|
+
return str(type_mapping.get(type_name, type_name))
|
|
687
687
|
|
|
688
688
|
def _convert_visibility(self, visibility: str) -> str:
|
|
689
689
|
"""Convert visibility to symbol"""
|
|
@@ -3,132 +3,38 @@
|
|
|
3
3
|
Utilities package for tree_sitter_analyzer.
|
|
4
4
|
|
|
5
5
|
This package contains utility modules for various functionality
|
|
6
|
-
including tree-sitter API compatibility.
|
|
6
|
+
including tree-sitter API compatibility and logging.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
# Import from tree-sitter compatibility module
|
|
10
10
|
from .tree_sitter_compat import TreeSitterQueryCompat, get_node_text_safe, log_api_info
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
utils_module.log_debug,
|
|
32
|
-
utils_module.log_error,
|
|
33
|
-
utils_module.log_warning,
|
|
34
|
-
utils_module.log_info,
|
|
35
|
-
utils_module.log_performance,
|
|
36
|
-
utils_module.QuietMode,
|
|
37
|
-
utils_module.safe_print,
|
|
38
|
-
utils_module.LoggingContext,
|
|
39
|
-
utils_module.setup_performance_logger,
|
|
40
|
-
utils_module.create_performance_logger,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# Import logging functions
|
|
45
|
-
try:
|
|
46
|
-
(
|
|
47
|
-
setup_logger,
|
|
48
|
-
log_debug,
|
|
49
|
-
log_error,
|
|
50
|
-
log_warning,
|
|
51
|
-
log_info,
|
|
52
|
-
log_performance,
|
|
53
|
-
QuietMode,
|
|
54
|
-
safe_print,
|
|
55
|
-
LoggingContext,
|
|
56
|
-
setup_performance_logger,
|
|
57
|
-
create_performance_logger,
|
|
58
|
-
) = _import_logging_functions()
|
|
59
|
-
except Exception:
|
|
60
|
-
# Fallback logging functions if import fails
|
|
61
|
-
def setup_logger(name="tree_sitter_analyzer", level=30):
|
|
62
|
-
import logging
|
|
63
|
-
|
|
64
|
-
logger = logging.getLogger(name)
|
|
65
|
-
if not logger.handlers:
|
|
66
|
-
handler = logging.StreamHandler()
|
|
67
|
-
formatter = logging.Formatter(
|
|
68
|
-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
69
|
-
)
|
|
70
|
-
handler.setFormatter(formatter)
|
|
71
|
-
logger.addHandler(handler)
|
|
72
|
-
logger.setLevel(level)
|
|
73
|
-
return logger
|
|
74
|
-
|
|
75
|
-
def log_debug(msg, *args, **kwargs):
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
def log_error(msg, *args, **kwargs):
|
|
79
|
-
print(f"ERROR: {msg}", *args)
|
|
80
|
-
|
|
81
|
-
def log_warning(msg, *args, **kwargs):
|
|
82
|
-
print(f"WARNING: {msg}", *args)
|
|
83
|
-
|
|
84
|
-
def log_info(msg, *args, **kwargs):
|
|
85
|
-
print(f"INFO: {msg}", *args)
|
|
86
|
-
|
|
87
|
-
def log_performance(operation, execution_time=None, details=None):
|
|
88
|
-
pass
|
|
89
|
-
|
|
90
|
-
# Fallback QuietMode class
|
|
91
|
-
class QuietMode:
|
|
92
|
-
def __init__(self, enabled=True):
|
|
93
|
-
self.enabled = enabled
|
|
94
|
-
|
|
95
|
-
def __enter__(self):
|
|
96
|
-
return self
|
|
97
|
-
|
|
98
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
# Fallback LoggingContext class
|
|
102
|
-
class LoggingContext:
|
|
103
|
-
def __init__(self, enabled=True, level=None):
|
|
104
|
-
self.enabled = enabled
|
|
105
|
-
self.level = level
|
|
106
|
-
|
|
107
|
-
def __enter__(self):
|
|
108
|
-
return self
|
|
109
|
-
|
|
110
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
111
|
-
pass
|
|
112
|
-
|
|
113
|
-
def setup_performance_logger():
|
|
114
|
-
import logging
|
|
115
|
-
|
|
116
|
-
return logging.getLogger("performance")
|
|
117
|
-
|
|
118
|
-
def create_performance_logger(name):
|
|
119
|
-
import logging
|
|
120
|
-
|
|
121
|
-
return logging.getLogger(f"{name}.performance")
|
|
122
|
-
|
|
123
|
-
def safe_print(message, level="info", quiet=False):
|
|
124
|
-
if not quiet:
|
|
125
|
-
print(message)
|
|
126
|
-
|
|
12
|
+
# Import logging functions directly from logging module
|
|
13
|
+
from .logging import (
|
|
14
|
+
LoggingContext,
|
|
15
|
+
QuietMode,
|
|
16
|
+
SafeStreamHandler,
|
|
17
|
+
create_performance_logger,
|
|
18
|
+
log_debug,
|
|
19
|
+
log_error,
|
|
20
|
+
log_info,
|
|
21
|
+
log_performance,
|
|
22
|
+
log_warning,
|
|
23
|
+
logger,
|
|
24
|
+
perf_logger,
|
|
25
|
+
safe_print,
|
|
26
|
+
setup_logger,
|
|
27
|
+
setup_performance_logger,
|
|
28
|
+
setup_safe_logging_shutdown,
|
|
29
|
+
suppress_output,
|
|
30
|
+
)
|
|
127
31
|
|
|
128
32
|
__all__ = [
|
|
33
|
+
# Tree-sitter compatibility
|
|
129
34
|
"TreeSitterQueryCompat",
|
|
130
35
|
"get_node_text_safe",
|
|
131
36
|
"log_api_info",
|
|
37
|
+
# Logging functionality
|
|
132
38
|
"setup_logger",
|
|
133
39
|
"log_debug",
|
|
134
40
|
"log_error",
|
|
@@ -140,4 +46,9 @@ __all__ = [
|
|
|
140
46
|
"LoggingContext",
|
|
141
47
|
"setup_performance_logger",
|
|
142
48
|
"create_performance_logger",
|
|
49
|
+
"SafeStreamHandler",
|
|
50
|
+
"setup_safe_logging_shutdown",
|
|
51
|
+
"suppress_output",
|
|
52
|
+
"logger",
|
|
53
|
+
"perf_logger",
|
|
143
54
|
]
|
|
@@ -154,7 +154,7 @@ class SafeStreamHandler(logging.StreamHandler):
|
|
|
154
154
|
A StreamHandler that safely handles closed streams
|
|
155
155
|
"""
|
|
156
156
|
|
|
157
|
-
def __init__(self, stream=None):
|
|
157
|
+
def __init__(self, stream: Any = None) -> None:
|
|
158
158
|
# Default to sys.stderr to keep stdout clean for MCP stdio
|
|
159
159
|
super().__init__(stream if stream is not None else sys.stderr)
|
|
160
160
|
|
|
@@ -449,4 +449,4 @@ class LoggingContext:
|
|
|
449
449
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
450
450
|
if self.enabled and self.old_level is not None:
|
|
451
451
|
# Always restore the saved level
|
|
452
|
-
self.target_logger.setLevel(self.old_level)
|
|
452
|
+
self.target_logger.setLevel(self.old_level)
|
|
@@ -235,7 +235,7 @@ def get_node_text_safe(node: Any, source_code: str, encoding: str = "utf-8") ->
|
|
|
235
235
|
line = lines[start_point[0]]
|
|
236
236
|
start_col = max(0, min(start_point[1], len(line)))
|
|
237
237
|
end_col = max(start_col, min(end_point[1], len(line)))
|
|
238
|
-
return line[start_col:end_col]
|
|
238
|
+
return str(line[start_col:end_col])
|
|
239
239
|
else:
|
|
240
240
|
# Multiple lines
|
|
241
241
|
result_lines = []
|
|
@@ -258,7 +258,7 @@ def get_node_text_safe(node: Any, source_code: str, encoding: str = "utf-8") ->
|
|
|
258
258
|
return ""
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
def log_api_info():
|
|
261
|
+
def log_api_info() -> None:
|
|
262
262
|
"""Log information about available tree-sitter APIs."""
|
|
263
263
|
try:
|
|
264
264
|
import tree_sitter
|