tree-sitter-analyzer 1.1.2__py3-none-any.whl → 1.2.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/cli/commands/advanced_command.py +145 -6
- tree_sitter_analyzer/cli/commands/structure_command.py +23 -5
- tree_sitter_analyzer/cli/commands/summary_command.py +19 -4
- tree_sitter_analyzer/cli/commands/table_command.py +14 -6
- tree_sitter_analyzer/constants.py +68 -0
- tree_sitter_analyzer/core/analysis_engine.py +0 -5
- tree_sitter_analyzer/core/engine.py +0 -12
- tree_sitter_analyzer/interfaces/cli_adapter.py +27 -12
- tree_sitter_analyzer/interfaces/mcp_adapter.py +31 -15
- tree_sitter_analyzer/mcp/server.py +187 -35
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +42 -19
- tree_sitter_analyzer/mcp/tools/base_tool.py +90 -5
- tree_sitter_analyzer/mcp/tools/query_tool.py +73 -6
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +3 -6
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +37 -11
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +102 -22
- tree_sitter_analyzer/models.py +138 -43
- tree_sitter_analyzer/security/boundary_manager.py +29 -9
- tree_sitter_analyzer/security/validator.py +16 -3
- {tree_sitter_analyzer-1.1.2.dist-info → tree_sitter_analyzer-1.2.0.dist-info}/METADATA +298 -127
- {tree_sitter_analyzer-1.1.2.dist-info → tree_sitter_analyzer-1.2.0.dist-info}/RECORD +23 -22
- {tree_sitter_analyzer-1.1.2.dist-info → tree_sitter_analyzer-1.2.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.1.2.dist-info → tree_sitter_analyzer-1.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -11,15 +11,14 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from ...file_handler import read_file_partial
|
|
14
|
-
from ...security import SecurityValidator
|
|
15
14
|
from ...utils import setup_logger
|
|
16
|
-
from
|
|
15
|
+
from .base_tool import BaseMCPTool
|
|
17
16
|
|
|
18
17
|
# Set up logging
|
|
19
18
|
logger = setup_logger(__name__)
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
class ReadPartialTool:
|
|
21
|
+
class ReadPartialTool(BaseMCPTool):
|
|
23
22
|
"""
|
|
24
23
|
MCP Tool for reading partial content from code files.
|
|
25
24
|
|
|
@@ -29,9 +28,7 @@ class ReadPartialTool:
|
|
|
29
28
|
|
|
30
29
|
def __init__(self, project_root: str = None) -> None:
|
|
31
30
|
"""Initialize the read partial tool."""
|
|
32
|
-
|
|
33
|
-
self.project_root = project_root
|
|
34
|
-
self.path_resolver = PathResolver(project_root)
|
|
31
|
+
super().__init__(project_root)
|
|
35
32
|
logger.info("ReadPartialTool initialized with security validation")
|
|
36
33
|
|
|
37
34
|
def get_tool_schema(self) -> dict[str, Any]:
|
|
@@ -9,19 +9,26 @@ converting analysis results into structured table formats for better readability
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from ...constants import (
|
|
13
|
+
ELEMENT_TYPE_CLASS,
|
|
14
|
+
ELEMENT_TYPE_FUNCTION,
|
|
15
|
+
ELEMENT_TYPE_IMPORT,
|
|
16
|
+
ELEMENT_TYPE_PACKAGE,
|
|
17
|
+
ELEMENT_TYPE_VARIABLE,
|
|
18
|
+
is_element_of_type,
|
|
19
|
+
)
|
|
12
20
|
from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
|
|
13
21
|
from ...language_detector import detect_language_from_file
|
|
14
|
-
from ...security import SecurityValidator
|
|
15
22
|
from ...table_formatter import TableFormatter
|
|
16
23
|
from ...utils import setup_logger
|
|
17
24
|
from ..utils import get_performance_monitor
|
|
18
|
-
from
|
|
25
|
+
from .base_tool import BaseMCPTool
|
|
19
26
|
|
|
20
27
|
# Set up logging
|
|
21
28
|
logger = setup_logger(__name__)
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
class TableFormatTool:
|
|
31
|
+
class TableFormatTool(BaseMCPTool):
|
|
25
32
|
"""
|
|
26
33
|
MCP Tool for code structure analysis and table formatting.
|
|
27
34
|
|
|
@@ -31,12 +38,21 @@ class TableFormatTool:
|
|
|
31
38
|
|
|
32
39
|
def __init__(self, project_root: str = None) -> None:
|
|
33
40
|
"""Initialize the table format tool."""
|
|
34
|
-
|
|
41
|
+
super().__init__(project_root)
|
|
35
42
|
self.analysis_engine = get_analysis_engine(project_root)
|
|
36
|
-
self.security_validator = SecurityValidator(project_root)
|
|
37
|
-
self.path_resolver = PathResolver(project_root)
|
|
38
43
|
self.logger = logger
|
|
39
44
|
|
|
45
|
+
def set_project_path(self, project_path: str) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Update the project path for all components.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
project_path: New project root directory
|
|
51
|
+
"""
|
|
52
|
+
super().set_project_path(project_path)
|
|
53
|
+
self.analysis_engine = get_analysis_engine(project_path)
|
|
54
|
+
logger.info(f"TableFormatTool project path updated to: {project_path}")
|
|
55
|
+
|
|
40
56
|
def get_tool_schema(self) -> dict[str, Any]:
|
|
41
57
|
"""
|
|
42
58
|
Get the MCP tool schema for analyze_code_structure.
|
|
@@ -178,11 +194,21 @@ class TableFormatTool:
|
|
|
178
194
|
def _convert_analysis_result_to_dict(self, result: Any) -> dict[str, Any]:
|
|
179
195
|
"""Convert AnalysisResult to dictionary format expected by TableFormatter"""
|
|
180
196
|
# Extract elements by type
|
|
181
|
-
classes = [
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
classes = [
|
|
198
|
+
e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
199
|
+
]
|
|
200
|
+
methods = [
|
|
201
|
+
e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
202
|
+
]
|
|
203
|
+
fields = [
|
|
204
|
+
e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
205
|
+
]
|
|
206
|
+
imports = [
|
|
207
|
+
e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
208
|
+
]
|
|
209
|
+
packages = [
|
|
210
|
+
e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
|
|
211
|
+
]
|
|
186
212
|
|
|
187
213
|
# Convert package to expected format
|
|
188
214
|
package_info = None
|
|
@@ -3,25 +3,32 @@
|
|
|
3
3
|
Universal Analyze Tool for MCP
|
|
4
4
|
|
|
5
5
|
This tool provides universal code analysis capabilities through the MCP protocol,
|
|
6
|
-
supporting multiple languages with
|
|
6
|
+
supporting multiple programming languages with automatic language detection.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from ...constants import (
|
|
13
|
+
ELEMENT_TYPE_CLASS,
|
|
14
|
+
ELEMENT_TYPE_FUNCTION,
|
|
15
|
+
ELEMENT_TYPE_IMPORT,
|
|
16
|
+
ELEMENT_TYPE_PACKAGE,
|
|
17
|
+
ELEMENT_TYPE_VARIABLE,
|
|
18
|
+
is_element_of_type,
|
|
19
|
+
)
|
|
12
20
|
from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
|
|
13
21
|
from ...language_detector import detect_language_from_file, is_language_supported
|
|
14
|
-
from ...
|
|
22
|
+
from ...mcp.utils import get_performance_monitor
|
|
15
23
|
from ...utils import setup_logger
|
|
16
|
-
from ..utils import get_performance_monitor
|
|
17
24
|
from ..utils.error_handler import handle_mcp_errors
|
|
18
|
-
from
|
|
25
|
+
from .base_tool import BaseMCPTool
|
|
19
26
|
|
|
20
27
|
# Set up logging
|
|
21
28
|
logger = setup_logger(__name__)
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
class UniversalAnalyzeTool:
|
|
31
|
+
class UniversalAnalyzeTool(BaseMCPTool):
|
|
25
32
|
"""
|
|
26
33
|
Universal MCP Tool for code analysis across multiple languages.
|
|
27
34
|
|
|
@@ -31,12 +38,21 @@ class UniversalAnalyzeTool:
|
|
|
31
38
|
|
|
32
39
|
def __init__(self, project_root: str | None = None) -> None:
|
|
33
40
|
"""Initialize the universal analyze tool."""
|
|
34
|
-
|
|
41
|
+
super().__init__(project_root)
|
|
35
42
|
self.analysis_engine = get_analysis_engine(project_root)
|
|
36
|
-
self.security_validator = SecurityValidator(project_root)
|
|
37
|
-
self.path_resolver = PathResolver(project_root)
|
|
38
43
|
logger.info("UniversalAnalyzeTool initialized with security validation")
|
|
39
44
|
|
|
45
|
+
def set_project_path(self, project_path: str) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Update the project path for all components.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
project_path: New project root directory
|
|
51
|
+
"""
|
|
52
|
+
super().set_project_path(project_path)
|
|
53
|
+
self.analysis_engine = get_analysis_engine(project_path)
|
|
54
|
+
logger.info(f"UniversalAnalyzeTool project path updated to: {project_path}")
|
|
55
|
+
|
|
40
56
|
def get_tool_definition(self) -> dict[str, Any]:
|
|
41
57
|
"""
|
|
42
58
|
Get MCP tool definition for universal code analysis
|
|
@@ -304,31 +320,75 @@ class UniversalAnalyzeTool:
|
|
|
304
320
|
[
|
|
305
321
|
e
|
|
306
322
|
for e in analysis_result.elements
|
|
307
|
-
if e
|
|
323
|
+
if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
308
324
|
]
|
|
309
325
|
),
|
|
310
326
|
"methods": len(
|
|
311
327
|
[
|
|
312
328
|
e
|
|
313
329
|
for e in analysis_result.elements
|
|
314
|
-
if e
|
|
330
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
315
331
|
]
|
|
316
332
|
),
|
|
317
333
|
"fields": len(
|
|
318
334
|
[
|
|
319
335
|
e
|
|
320
336
|
for e in analysis_result.elements
|
|
321
|
-
if e
|
|
337
|
+
if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
322
338
|
]
|
|
323
339
|
),
|
|
324
340
|
"imports": len(
|
|
325
341
|
[
|
|
326
342
|
e
|
|
327
343
|
for e in analysis_result.elements
|
|
328
|
-
if e
|
|
344
|
+
if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
329
345
|
]
|
|
330
346
|
),
|
|
331
347
|
"annotations": len(getattr(analysis_result, "annotations", [])),
|
|
348
|
+
"packages": len(
|
|
349
|
+
[
|
|
350
|
+
e
|
|
351
|
+
for e in analysis_result.elements
|
|
352
|
+
if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
|
|
353
|
+
]
|
|
354
|
+
),
|
|
355
|
+
"total": (
|
|
356
|
+
len(
|
|
357
|
+
[
|
|
358
|
+
e
|
|
359
|
+
for e in analysis_result.elements
|
|
360
|
+
if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
361
|
+
]
|
|
362
|
+
)
|
|
363
|
+
+ len(
|
|
364
|
+
[
|
|
365
|
+
e
|
|
366
|
+
for e in analysis_result.elements
|
|
367
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
368
|
+
]
|
|
369
|
+
)
|
|
370
|
+
+ len(
|
|
371
|
+
[
|
|
372
|
+
e
|
|
373
|
+
for e in analysis_result.elements
|
|
374
|
+
if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
375
|
+
]
|
|
376
|
+
)
|
|
377
|
+
+ len(
|
|
378
|
+
[
|
|
379
|
+
e
|
|
380
|
+
for e in analysis_result.elements
|
|
381
|
+
if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
+ len(
|
|
385
|
+
[
|
|
386
|
+
e
|
|
387
|
+
for e in analysis_result.elements
|
|
388
|
+
if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
|
|
389
|
+
]
|
|
390
|
+
)
|
|
391
|
+
),
|
|
332
392
|
},
|
|
333
393
|
}
|
|
334
394
|
}
|
|
@@ -339,7 +399,9 @@ class UniversalAnalyzeTool:
|
|
|
339
399
|
|
|
340
400
|
# Add complexity metrics
|
|
341
401
|
methods = [
|
|
342
|
-
e
|
|
402
|
+
e
|
|
403
|
+
for e in analysis_result.elements
|
|
404
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
343
405
|
]
|
|
344
406
|
total_complexity = sum(
|
|
345
407
|
getattr(method, "complexity_score", 0) or 0 for method in methods
|
|
@@ -347,7 +409,7 @@ class UniversalAnalyzeTool:
|
|
|
347
409
|
|
|
348
410
|
basic["metrics"]["complexity"] = {
|
|
349
411
|
"total": total_complexity,
|
|
350
|
-
"average": (total_complexity / len(methods) if methods else 0),
|
|
412
|
+
"average": round(total_complexity / len(methods) if methods else 0, 2),
|
|
351
413
|
"max": max(
|
|
352
414
|
(getattr(method, "complexity_score", 0) or 0 for method in methods),
|
|
353
415
|
default=0,
|
|
@@ -372,7 +434,7 @@ class UniversalAnalyzeTool:
|
|
|
372
434
|
for cls in [
|
|
373
435
|
e
|
|
374
436
|
for e in analysis_result.elements
|
|
375
|
-
if e
|
|
437
|
+
if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
376
438
|
]
|
|
377
439
|
],
|
|
378
440
|
"methods": [
|
|
@@ -384,7 +446,7 @@ class UniversalAnalyzeTool:
|
|
|
384
446
|
for method in [
|
|
385
447
|
e
|
|
386
448
|
for e in analysis_result.elements
|
|
387
|
-
if e
|
|
449
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
388
450
|
]
|
|
389
451
|
],
|
|
390
452
|
"fields": [
|
|
@@ -396,7 +458,7 @@ class UniversalAnalyzeTool:
|
|
|
396
458
|
for field in [
|
|
397
459
|
e
|
|
398
460
|
for e in analysis_result.elements
|
|
399
|
-
if e
|
|
461
|
+
if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
400
462
|
]
|
|
401
463
|
],
|
|
402
464
|
"imports": [
|
|
@@ -408,7 +470,7 @@ class UniversalAnalyzeTool:
|
|
|
408
470
|
for imp in [
|
|
409
471
|
e
|
|
410
472
|
for e in analysis_result.elements
|
|
411
|
-
if e
|
|
473
|
+
if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
412
474
|
]
|
|
413
475
|
],
|
|
414
476
|
"annotations": [
|
|
@@ -446,16 +508,34 @@ class UniversalAnalyzeTool:
|
|
|
446
508
|
"lines_blank": 0, # Not available in universal analyzer
|
|
447
509
|
"elements": {
|
|
448
510
|
"classes": len(
|
|
449
|
-
[
|
|
511
|
+
[
|
|
512
|
+
e
|
|
513
|
+
for e in elements
|
|
514
|
+
if hasattr(e, "element_type") and e.element_type == "class"
|
|
515
|
+
]
|
|
450
516
|
),
|
|
451
517
|
"methods": len(
|
|
452
|
-
[
|
|
518
|
+
[
|
|
519
|
+
e
|
|
520
|
+
for e in elements
|
|
521
|
+
if hasattr(e, "element_type")
|
|
522
|
+
and e.element_type == "function"
|
|
523
|
+
]
|
|
453
524
|
),
|
|
454
525
|
"fields": len(
|
|
455
|
-
[
|
|
526
|
+
[
|
|
527
|
+
e
|
|
528
|
+
for e in elements
|
|
529
|
+
if hasattr(e, "element_type")
|
|
530
|
+
and e.element_type == "variable"
|
|
531
|
+
]
|
|
456
532
|
),
|
|
457
533
|
"imports": len(
|
|
458
|
-
[
|
|
534
|
+
[
|
|
535
|
+
e
|
|
536
|
+
for e in elements
|
|
537
|
+
if hasattr(e, "element_type") and e.element_type == "import"
|
|
538
|
+
]
|
|
459
539
|
),
|
|
460
540
|
"annotations": 0, # Not available in universal analyzer
|
|
461
541
|
},
|
tree_sitter_analyzer/models.py
CHANGED
|
@@ -11,6 +11,16 @@ from collections.abc import Callable
|
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
12
|
from typing import TYPE_CHECKING, Any
|
|
13
13
|
|
|
14
|
+
from .constants import (
|
|
15
|
+
ELEMENT_TYPE_ANNOTATION,
|
|
16
|
+
ELEMENT_TYPE_CLASS,
|
|
17
|
+
ELEMENT_TYPE_FUNCTION,
|
|
18
|
+
ELEMENT_TYPE_IMPORT,
|
|
19
|
+
ELEMENT_TYPE_PACKAGE,
|
|
20
|
+
ELEMENT_TYPE_VARIABLE,
|
|
21
|
+
is_element_of_type,
|
|
22
|
+
)
|
|
23
|
+
|
|
14
24
|
if TYPE_CHECKING:
|
|
15
25
|
pass
|
|
16
26
|
|
|
@@ -276,45 +286,64 @@ class AnalysisResult:
|
|
|
276
286
|
) # Query results for new architecture
|
|
277
287
|
source_code: str = "" # Source code for new architecture
|
|
278
288
|
package: JavaPackage | None = None
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
# Legacy fields removed - use elements list instead
|
|
290
|
+
# imports: list[JavaImport] = field(default_factory=list)
|
|
291
|
+
# classes: list[JavaClass] = field(default_factory=list)
|
|
292
|
+
# methods: list[JavaMethod] = field(default_factory=list)
|
|
293
|
+
# fields: list[JavaField] = field(default_factory=list)
|
|
294
|
+
# annotations: list[JavaAnnotation] = field(default_factory=list)
|
|
284
295
|
analysis_time: float = 0.0
|
|
285
296
|
success: bool = True
|
|
286
297
|
error_message: str | None = None
|
|
287
298
|
|
|
288
299
|
def to_dict(self) -> dict[str, Any]:
|
|
289
|
-
"""Convert analysis result to dictionary for serialization"""
|
|
300
|
+
"""Convert analysis result to dictionary for serialization using unified elements"""
|
|
301
|
+
# Use unified elements list for consistent data structure
|
|
302
|
+
elements = self.elements or []
|
|
303
|
+
|
|
304
|
+
# Extract elements by type from unified list using constants
|
|
305
|
+
classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
|
|
306
|
+
methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
|
|
307
|
+
fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
|
|
308
|
+
imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
|
|
309
|
+
packages = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)]
|
|
310
|
+
|
|
290
311
|
return {
|
|
291
312
|
"file_path": self.file_path,
|
|
292
313
|
"line_count": self.line_count,
|
|
293
|
-
"package": {"name":
|
|
314
|
+
"package": {"name": packages[0].name} if packages else None,
|
|
294
315
|
"imports": [
|
|
295
316
|
{
|
|
296
317
|
"name": imp.name,
|
|
297
|
-
"is_static": imp
|
|
298
|
-
"is_wildcard": imp
|
|
318
|
+
"is_static": getattr(imp, "is_static", False),
|
|
319
|
+
"is_wildcard": getattr(imp, "is_wildcard", False),
|
|
299
320
|
}
|
|
300
|
-
for imp in
|
|
321
|
+
for imp in imports
|
|
301
322
|
],
|
|
302
323
|
"classes": [
|
|
303
|
-
{
|
|
304
|
-
|
|
324
|
+
{
|
|
325
|
+
"name": cls.name,
|
|
326
|
+
"type": getattr(cls, "class_type", "class"),
|
|
327
|
+
"package": getattr(cls, "package_name", None),
|
|
328
|
+
}
|
|
329
|
+
for cls in classes
|
|
305
330
|
],
|
|
306
331
|
"methods": [
|
|
307
332
|
{
|
|
308
333
|
"name": method.name,
|
|
309
|
-
"return_type": method
|
|
310
|
-
"parameters": method
|
|
334
|
+
"return_type": getattr(method, "return_type", None),
|
|
335
|
+
"parameters": getattr(method, "parameters", []),
|
|
311
336
|
}
|
|
312
|
-
for method in
|
|
337
|
+
for method in methods
|
|
313
338
|
],
|
|
314
339
|
"fields": [
|
|
315
|
-
{"name": field.name, "type": field
|
|
340
|
+
{"name": field.name, "type": getattr(field, "field_type", None)}
|
|
341
|
+
for field in fields
|
|
342
|
+
],
|
|
343
|
+
"annotations": [
|
|
344
|
+
{"name": getattr(ann, "name", str(ann))}
|
|
345
|
+
for ann in getattr(self, "annotations", [])
|
|
316
346
|
],
|
|
317
|
-
"annotations": [{"name": ann.name} for ann in self.annotations],
|
|
318
347
|
"analysis_time": self.analysis_time,
|
|
319
348
|
"success": self.success,
|
|
320
349
|
"error_message": self.error_message,
|
|
@@ -322,40 +351,56 @@ class AnalysisResult:
|
|
|
322
351
|
|
|
323
352
|
def to_summary_dict(self, types: list[str] | None = None) -> dict[str, Any]:
|
|
324
353
|
"""
|
|
325
|
-
Return analysis summary as a dictionary.
|
|
354
|
+
Return analysis summary as a dictionary using unified elements.
|
|
326
355
|
Only include specified element types (e.g., 'classes', 'methods', 'fields').
|
|
327
356
|
"""
|
|
328
357
|
if types is None:
|
|
329
358
|
types = ["classes", "methods"] # default
|
|
330
359
|
|
|
331
360
|
summary: dict[str, Any] = {"file_path": self.file_path, "summary_elements": []}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"
|
|
361
|
+
elements = self.elements or []
|
|
362
|
+
|
|
363
|
+
# Map type names to element_type constants
|
|
364
|
+
type_mapping = {
|
|
365
|
+
"imports": ELEMENT_TYPE_IMPORT,
|
|
366
|
+
"classes": ELEMENT_TYPE_CLASS,
|
|
367
|
+
"methods": ELEMENT_TYPE_FUNCTION,
|
|
368
|
+
"fields": ELEMENT_TYPE_VARIABLE,
|
|
369
|
+
"annotations": ELEMENT_TYPE_ANNOTATION,
|
|
339
370
|
}
|
|
340
371
|
|
|
341
|
-
for type_name,
|
|
372
|
+
for type_name, element_type in type_mapping.items():
|
|
342
373
|
if "all" in types or type_name in types:
|
|
343
|
-
|
|
374
|
+
type_elements = [
|
|
375
|
+
e for e in elements if is_element_of_type(e, element_type)
|
|
376
|
+
]
|
|
377
|
+
for element in type_elements:
|
|
344
378
|
# Call each element model's to_summary_item()
|
|
345
379
|
summary["summary_elements"].append(element.to_summary_item())
|
|
346
380
|
|
|
347
381
|
return summary
|
|
348
382
|
|
|
349
383
|
def get_summary(self) -> dict[str, Any]:
|
|
350
|
-
"""Get analysis summary statistics"""
|
|
384
|
+
"""Get analysis summary statistics using unified elements"""
|
|
385
|
+
elements = self.elements or []
|
|
386
|
+
|
|
387
|
+
# Count elements by type from unified list using constants
|
|
388
|
+
classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
|
|
389
|
+
methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
|
|
390
|
+
fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
|
|
391
|
+
imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
|
|
392
|
+
annotations = [
|
|
393
|
+
e for e in elements if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
|
|
394
|
+
]
|
|
395
|
+
|
|
351
396
|
return {
|
|
352
397
|
"file_path": self.file_path,
|
|
353
398
|
"line_count": self.line_count,
|
|
354
|
-
"class_count": len(
|
|
355
|
-
"method_count": len(
|
|
356
|
-
"field_count": len(
|
|
357
|
-
"import_count": len(
|
|
358
|
-
"annotation_count": len(
|
|
399
|
+
"class_count": len(classes),
|
|
400
|
+
"method_count": len(methods),
|
|
401
|
+
"field_count": len(fields),
|
|
402
|
+
"import_count": len(imports),
|
|
403
|
+
"annotation_count": len(annotations),
|
|
359
404
|
"success": self.success,
|
|
360
405
|
"analysis_time": self.analysis_time,
|
|
361
406
|
}
|
|
@@ -400,7 +445,11 @@ class AnalysisResult:
|
|
|
400
445
|
"end": safe_get_attr(imp, "end_line", 0),
|
|
401
446
|
},
|
|
402
447
|
}
|
|
403
|
-
for imp in
|
|
448
|
+
for imp in [
|
|
449
|
+
e
|
|
450
|
+
for e in (self.elements or [])
|
|
451
|
+
if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
452
|
+
]
|
|
404
453
|
],
|
|
405
454
|
"classes": [
|
|
406
455
|
{
|
|
@@ -412,7 +461,11 @@ class AnalysisResult:
|
|
|
412
461
|
"end": safe_get_attr(cls, "end_line", 0),
|
|
413
462
|
},
|
|
414
463
|
}
|
|
415
|
-
for cls in
|
|
464
|
+
for cls in [
|
|
465
|
+
e
|
|
466
|
+
for e in (self.elements or [])
|
|
467
|
+
if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
468
|
+
]
|
|
416
469
|
],
|
|
417
470
|
"methods": [
|
|
418
471
|
{
|
|
@@ -424,7 +477,11 @@ class AnalysisResult:
|
|
|
424
477
|
"end": safe_get_attr(method, "end_line", 0),
|
|
425
478
|
},
|
|
426
479
|
}
|
|
427
|
-
for method in
|
|
480
|
+
for method in [
|
|
481
|
+
e
|
|
482
|
+
for e in (self.elements or [])
|
|
483
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
484
|
+
]
|
|
428
485
|
],
|
|
429
486
|
"fields": [
|
|
430
487
|
{
|
|
@@ -435,7 +492,11 @@ class AnalysisResult:
|
|
|
435
492
|
"end": safe_get_attr(field, "end_line", 0),
|
|
436
493
|
},
|
|
437
494
|
}
|
|
438
|
-
for field in
|
|
495
|
+
for field in [
|
|
496
|
+
e
|
|
497
|
+
for e in (self.elements or [])
|
|
498
|
+
if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
499
|
+
]
|
|
439
500
|
],
|
|
440
501
|
"annotations": [
|
|
441
502
|
{
|
|
@@ -445,16 +506,50 @@ class AnalysisResult:
|
|
|
445
506
|
"end": safe_get_attr(ann, "end_line", 0),
|
|
446
507
|
},
|
|
447
508
|
}
|
|
448
|
-
for ann in
|
|
509
|
+
for ann in [
|
|
510
|
+
e
|
|
511
|
+
for e in (self.elements or [])
|
|
512
|
+
if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
|
|
513
|
+
]
|
|
449
514
|
],
|
|
450
515
|
},
|
|
451
516
|
"metadata": {
|
|
452
517
|
"line_count": self.line_count,
|
|
453
|
-
"class_count": len(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
518
|
+
"class_count": len(
|
|
519
|
+
[
|
|
520
|
+
e
|
|
521
|
+
for e in (self.elements or [])
|
|
522
|
+
if is_element_of_type(e, ELEMENT_TYPE_CLASS)
|
|
523
|
+
]
|
|
524
|
+
),
|
|
525
|
+
"method_count": len(
|
|
526
|
+
[
|
|
527
|
+
e
|
|
528
|
+
for e in (self.elements or [])
|
|
529
|
+
if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
|
|
530
|
+
]
|
|
531
|
+
),
|
|
532
|
+
"field_count": len(
|
|
533
|
+
[
|
|
534
|
+
e
|
|
535
|
+
for e in (self.elements or [])
|
|
536
|
+
if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
|
|
537
|
+
]
|
|
538
|
+
),
|
|
539
|
+
"import_count": len(
|
|
540
|
+
[
|
|
541
|
+
e
|
|
542
|
+
for e in (self.elements or [])
|
|
543
|
+
if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
|
|
544
|
+
]
|
|
545
|
+
),
|
|
546
|
+
"annotation_count": len(
|
|
547
|
+
[
|
|
548
|
+
e
|
|
549
|
+
for e in (self.elements or [])
|
|
550
|
+
if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
|
|
551
|
+
]
|
|
552
|
+
),
|
|
458
553
|
"analysis_time": self.analysis_time,
|
|
459
554
|
"success": self.success,
|
|
460
555
|
"error_message": self.error_message,
|
|
@@ -39,18 +39,38 @@ class ProjectBoundaryManager:
|
|
|
39
39
|
if not project_root:
|
|
40
40
|
raise SecurityError("Project root cannot be empty")
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
try:
|
|
43
|
+
project_path = Path(project_root)
|
|
44
|
+
|
|
45
|
+
# Handle both string and Path objects
|
|
46
|
+
if isinstance(project_root, str):
|
|
47
|
+
project_path = Path(project_root)
|
|
48
|
+
elif isinstance(project_root, Path):
|
|
49
|
+
project_path = project_root
|
|
50
|
+
else:
|
|
51
|
+
raise SecurityError(f"Invalid project root type: {type(project_root)}")
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
# Ensure the path exists and is a directory
|
|
54
|
+
if not project_path.exists():
|
|
55
|
+
raise SecurityError(f"Project root does not exist: {project_root}")
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.allowed_directories: set[str] = {self.project_root}
|
|
57
|
+
if not project_path.is_dir():
|
|
58
|
+
raise SecurityError(f"Project root is not a directory: {project_root}")
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
# Store real path to prevent symlink attacks
|
|
61
|
+
self.project_root = str(project_path.resolve())
|
|
62
|
+
self.allowed_directories: set[str] = {self.project_root}
|
|
63
|
+
|
|
64
|
+
log_debug(
|
|
65
|
+
f"ProjectBoundaryManager initialized with root: {self.project_root}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
if isinstance(e, SecurityError):
|
|
70
|
+
raise
|
|
71
|
+
raise SecurityError(
|
|
72
|
+
f"Failed to initialize ProjectBoundaryManager: {e}"
|
|
73
|
+
) from e
|
|
54
74
|
|
|
55
75
|
def add_allowed_directory(self, directory: str) -> None:
|
|
56
76
|
"""
|