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.

@@ -7,6 +7,13 @@ Handles advanced analysis functionality.
7
7
 
8
8
  from typing import TYPE_CHECKING
9
9
 
10
+ from ...constants import (
11
+ ELEMENT_TYPE_CLASS,
12
+ ELEMENT_TYPE_FUNCTION,
13
+ ELEMENT_TYPE_IMPORT,
14
+ ELEMENT_TYPE_VARIABLE,
15
+ get_element_type,
16
+ )
10
17
  from ...output_manager import output_data, output_json, output_section
11
18
  from .base_command import BaseCommand
12
19
 
@@ -29,6 +36,111 @@ class AdvancedCommand(BaseCommand):
29
36
 
30
37
  return 0
31
38
 
39
+ def _calculate_file_metrics(self, file_path: str, language: str) -> dict[str, int]:
40
+ """
41
+ Calculate accurate file metrics including line counts.
42
+
43
+ Args:
44
+ file_path: Path to the file to analyze
45
+ language: Programming language
46
+
47
+ Returns:
48
+ Dictionary containing file metrics
49
+ """
50
+ try:
51
+ with open(file_path, encoding="utf-8") as f:
52
+ content = f.read()
53
+
54
+ lines = content.split("\n")
55
+ total_lines = len(lines)
56
+
57
+ # Remove empty line at the end if file ends with newline
58
+ if lines and not lines[-1]:
59
+ total_lines -= 1
60
+
61
+ # Count different types of lines
62
+ code_lines = 0
63
+ comment_lines = 0
64
+ blank_lines = 0
65
+ in_multiline_comment = False
66
+
67
+ for line in lines:
68
+ stripped = line.strip()
69
+
70
+ # Check for blank lines first
71
+ if not stripped:
72
+ blank_lines += 1
73
+ continue
74
+
75
+ # Check if we're in a multi-line comment
76
+ if in_multiline_comment:
77
+ comment_lines += 1
78
+ # Check if this line ends the multi-line comment
79
+ if "*/" in stripped:
80
+ in_multiline_comment = False
81
+ continue
82
+
83
+ # Check for multi-line comment start
84
+ if stripped.startswith("/**") or stripped.startswith("/*"):
85
+ comment_lines += 1
86
+ # Check if this line also ends the comment
87
+ if "*/" not in stripped:
88
+ in_multiline_comment = True
89
+ continue
90
+
91
+ # Check for single-line comments
92
+ if stripped.startswith("//"):
93
+ comment_lines += 1
94
+ continue
95
+
96
+ # Check for JavaDoc continuation lines (lines starting with * but not */)
97
+ if stripped.startswith("*") and not stripped.startswith("*/"):
98
+ comment_lines += 1
99
+ continue
100
+
101
+ # Check for other comment types based on language
102
+ if language == "python" and stripped.startswith("#"):
103
+ comment_lines += 1
104
+ continue
105
+ elif language == "sql" and stripped.startswith("--"):
106
+ comment_lines += 1
107
+ continue
108
+ elif language in ["html", "xml"] and stripped.startswith("<!--"):
109
+ comment_lines += 1
110
+ if "-->" not in stripped:
111
+ in_multiline_comment = True
112
+ continue
113
+ elif in_multiline_comment and "-->" in stripped:
114
+ comment_lines += 1
115
+ in_multiline_comment = False
116
+ continue
117
+
118
+ # If not a comment, it's code
119
+ code_lines += 1
120
+
121
+ # Ensure the sum equals total_lines (handle any rounding errors)
122
+ calculated_total = code_lines + comment_lines + blank_lines
123
+ if calculated_total != total_lines:
124
+ # Adjust blank_lines to match total (since it's most likely to be off by 1)
125
+ blank_lines = total_lines - code_lines - comment_lines
126
+ # Ensure blank_lines is not negative
127
+ blank_lines = max(0, blank_lines)
128
+
129
+ return {
130
+ "total_lines": total_lines,
131
+ "code_lines": code_lines,
132
+ "comment_lines": comment_lines,
133
+ "blank_lines": blank_lines,
134
+ }
135
+ except Exception:
136
+ # Fallback to basic counting if file read fails
137
+ return {
138
+ "total_lines": 0,
139
+ "code_lines": 0,
140
+ "comment_lines": 0,
141
+ "blank_lines": 0,
142
+ }
143
+
32
144
  def _output_statistics(self, analysis_result: "AnalysisResult") -> None:
33
145
  """Output statistics only."""
34
146
  stats = {
@@ -57,7 +169,7 @@ class AdvancedCommand(BaseCommand):
57
169
  "elements": [
58
170
  {
59
171
  "name": getattr(element, "name", str(element)),
60
- "type": getattr(element, "__class__", type(element)).__name__,
172
+ "type": get_element_type(element),
61
173
  "start_line": getattr(element, "start_line", 0),
62
174
  "end_line": getattr(element, "end_line", 0),
63
175
  }
@@ -77,12 +189,39 @@ class AdvancedCommand(BaseCommand):
77
189
  output_data(f"Lines: {analysis_result.line_count}")
78
190
 
79
191
  element_counts: dict[str, int] = {}
192
+ complexity_scores: list[int] = []
193
+
80
194
  for element in analysis_result.elements:
81
- element_type = getattr(element, "__class__", type(element)).__name__
195
+ element_type = get_element_type(element)
82
196
  element_counts[element_type] = element_counts.get(element_type, 0) + 1
83
197
 
84
- output_data(f"Classes: {element_counts.get('Class', 0)}")
85
- output_data(f"Methods: {element_counts.get('Function', 0)}")
86
- output_data(f"Fields: {element_counts.get('Variable', 0)}")
87
- output_data(f"Imports: {element_counts.get('Import', 0)}")
198
+ # Collect complexity scores for methods
199
+ if element_type == ELEMENT_TYPE_FUNCTION:
200
+ complexity = getattr(element, "complexity_score", 1)
201
+ complexity_scores.append(complexity)
202
+
203
+ # Calculate accurate file metrics
204
+ file_metrics = self._calculate_file_metrics(
205
+ analysis_result.file_path, analysis_result.language
206
+ )
207
+
208
+ # Calculate complexity statistics
209
+ total_complexity = sum(complexity_scores) if complexity_scores else 0
210
+ avg_complexity = (
211
+ total_complexity / len(complexity_scores) if complexity_scores else 0
212
+ )
213
+ max_complexity = max(complexity_scores) if complexity_scores else 0
214
+
215
+ output_data(f"Classes: {element_counts.get(ELEMENT_TYPE_CLASS, 0)}")
216
+ output_data(f"Methods: {element_counts.get(ELEMENT_TYPE_FUNCTION, 0)}")
217
+ output_data(f"Fields: {element_counts.get(ELEMENT_TYPE_VARIABLE, 0)}")
218
+ output_data(f"Imports: {element_counts.get(ELEMENT_TYPE_IMPORT, 0)}")
88
219
  output_data("Annotations: 0")
220
+
221
+ # Add detailed metrics using accurate calculations
222
+ output_data(f"Code Lines: {file_metrics['code_lines']}")
223
+ output_data(f"Comment Lines: {file_metrics['comment_lines']}")
224
+ output_data(f"Blank Lines: {file_metrics['blank_lines']}")
225
+ output_data(f"Total Complexity: {total_complexity}")
226
+ output_data(f"Average Complexity: {avg_complexity:.2f}")
227
+ output_data(f"Max Complexity: {max_complexity}")
@@ -7,6 +7,14 @@ Handles structure analysis functionality with appropriate Japanese output.
7
7
 
8
8
  from typing import TYPE_CHECKING
9
9
 
10
+ from ...constants import (
11
+ ELEMENT_TYPE_CLASS,
12
+ ELEMENT_TYPE_FUNCTION,
13
+ ELEMENT_TYPE_IMPORT,
14
+ ELEMENT_TYPE_PACKAGE,
15
+ ELEMENT_TYPE_VARIABLE,
16
+ is_element_of_type,
17
+ )
10
18
  from ...output_manager import output_data, output_json, output_section
11
19
  from .base_command import BaseCommand
12
20
 
@@ -43,19 +51,29 @@ class StructureCommand(BaseCommand):
43
51
 
44
52
  # Extract elements by type
45
53
  classes = [
46
- e for e in analysis_result.elements if e.__class__.__name__ == "Class"
54
+ e
55
+ for e in analysis_result.elements
56
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
47
57
  ]
48
58
  methods = [
49
- e for e in analysis_result.elements if e.__class__.__name__ == "Function"
59
+ e
60
+ for e in analysis_result.elements
61
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
50
62
  ]
51
63
  fields = [
52
- e for e in analysis_result.elements if e.__class__.__name__ == "Variable"
64
+ e
65
+ for e in analysis_result.elements
66
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
53
67
  ]
54
68
  imports = [
55
- e for e in analysis_result.elements if e.__class__.__name__ == "Import"
69
+ e
70
+ for e in analysis_result.elements
71
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
56
72
  ]
57
73
  packages = [
58
- e for e in analysis_result.elements if e.__class__.__name__ == "Package"
74
+ e
75
+ for e in analysis_result.elements
76
+ if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
59
77
  ]
60
78
 
61
79
  return {
@@ -7,6 +7,13 @@ Handles summary functionality with specified element types.
7
7
 
8
8
  from typing import TYPE_CHECKING, Any
9
9
 
10
+ from ...constants import (
11
+ ELEMENT_TYPE_CLASS,
12
+ ELEMENT_TYPE_FUNCTION,
13
+ ELEMENT_TYPE_IMPORT,
14
+ ELEMENT_TYPE_VARIABLE,
15
+ is_element_of_type,
16
+ )
10
17
  from ...output_manager import output_data, output_json, output_section
11
18
  from .base_command import BaseCommand
12
19
 
@@ -38,16 +45,24 @@ class SummaryCommand(BaseCommand):
38
45
 
39
46
  # Extract elements by type
40
47
  classes = [
41
- e for e in analysis_result.elements if e.__class__.__name__ == "Class"
48
+ e
49
+ for e in analysis_result.elements
50
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
42
51
  ]
43
52
  methods = [
44
- e for e in analysis_result.elements if e.__class__.__name__ == "Function"
53
+ e
54
+ for e in analysis_result.elements
55
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
45
56
  ]
46
57
  fields = [
47
- e for e in analysis_result.elements if e.__class__.__name__ == "Variable"
58
+ e
59
+ for e in analysis_result.elements
60
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
48
61
  ]
49
62
  imports = [
50
- e for e in analysis_result.elements if e.__class__.__name__ == "Import"
63
+ e
64
+ for e in analysis_result.elements
65
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
51
66
  ]
52
67
 
53
68
  summary_data: dict[str, Any] = {
@@ -8,6 +8,14 @@ Handles table format output generation.
8
8
  import sys
9
9
  from typing import Any
10
10
 
11
+ from ...constants import (
12
+ ELEMENT_TYPE_CLASS,
13
+ ELEMENT_TYPE_FUNCTION,
14
+ ELEMENT_TYPE_IMPORT,
15
+ ELEMENT_TYPE_PACKAGE,
16
+ ELEMENT_TYPE_VARIABLE,
17
+ get_element_type,
18
+ )
11
19
  from ...output_manager import output_error
12
20
  from ...table_formatter import create_table_formatter
13
21
  from .base_command import BaseCommand
@@ -58,18 +66,18 @@ class TableCommand(BaseCommand):
58
66
  # Process each element
59
67
  for i, element in enumerate(analysis_result.elements):
60
68
  try:
61
- element_type = getattr(element, "__class__", type(element)).__name__
69
+ element_type = get_element_type(element)
62
70
  element_name = getattr(element, "name", None)
63
71
 
64
- if element_type == "Package":
72
+ if element_type == ELEMENT_TYPE_PACKAGE:
65
73
  package_name = str(element_name)
66
- elif element_type == "Class":
74
+ elif element_type == ELEMENT_TYPE_CLASS:
67
75
  classes.append(self._convert_class_element(element, i))
68
- elif element_type == "Function":
76
+ elif element_type == ELEMENT_TYPE_FUNCTION:
69
77
  methods.append(self._convert_function_element(element, language))
70
- elif element_type == "Variable":
78
+ elif element_type == ELEMENT_TYPE_VARIABLE:
71
79
  fields.append(self._convert_variable_element(element, language))
72
- elif element_type == "Import":
80
+ elif element_type == ELEMENT_TYPE_IMPORT:
73
81
  imports.append(self._convert_import_element(element))
74
82
 
75
83
  except Exception as element_error:
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Constants for tree-sitter-analyzer
4
+
5
+ This module defines constants used throughout the project to ensure consistency.
6
+ """
7
+
8
+ # Element types for unified element management system
9
+ ELEMENT_TYPE_CLASS = "class"
10
+ ELEMENT_TYPE_FUNCTION = "function"
11
+ ELEMENT_TYPE_VARIABLE = "variable"
12
+ ELEMENT_TYPE_IMPORT = "import"
13
+ ELEMENT_TYPE_PACKAGE = "package"
14
+ ELEMENT_TYPE_ANNOTATION = "annotation"
15
+
16
+ # Element type mapping for backward compatibility
17
+ ELEMENT_TYPE_MAPPING = {
18
+ "Class": ELEMENT_TYPE_CLASS,
19
+ "Function": ELEMENT_TYPE_FUNCTION,
20
+ "Variable": ELEMENT_TYPE_VARIABLE,
21
+ "Import": ELEMENT_TYPE_IMPORT,
22
+ "Package": ELEMENT_TYPE_PACKAGE,
23
+ "Annotation": ELEMENT_TYPE_ANNOTATION,
24
+ }
25
+
26
+ # Legacy class name to element type mapping
27
+ LEGACY_CLASS_MAPPING = {
28
+ "Class": ELEMENT_TYPE_CLASS,
29
+ "Function": ELEMENT_TYPE_FUNCTION,
30
+ "Variable": ELEMENT_TYPE_VARIABLE,
31
+ "Import": ELEMENT_TYPE_IMPORT,
32
+ "Package": ELEMENT_TYPE_PACKAGE,
33
+ "Annotation": ELEMENT_TYPE_ANNOTATION,
34
+ }
35
+
36
+
37
+ def get_element_type(element) -> str:
38
+ """
39
+ Get the element type from an element object.
40
+
41
+ Args:
42
+ element: Element object with element_type attribute or __class__.__name__
43
+
44
+ Returns:
45
+ Standardized element type string
46
+ """
47
+ if hasattr(element, "element_type"):
48
+ return element.element_type
49
+
50
+ if hasattr(element, "__class__") and hasattr(element.__class__, "__name__"):
51
+ class_name = element.__class__.__name__
52
+ return LEGACY_CLASS_MAPPING.get(class_name, "unknown")
53
+
54
+ return "unknown"
55
+
56
+
57
+ def is_element_of_type(element, element_type: str) -> bool:
58
+ """
59
+ Check if an element is of a specific type.
60
+
61
+ Args:
62
+ element: Element object to check
63
+ element_type: Expected element type
64
+
65
+ Returns:
66
+ True if element is of the specified type
67
+ """
68
+ return get_element_type(element) == element_type
@@ -560,11 +560,6 @@ class MockLanguagePlugin:
560
560
  source_code="// Mock source code", # 新しいアーキテクチャ用
561
561
  language=self.language, # 言語を設定
562
562
  package=None,
563
- imports=[],
564
- classes=[],
565
- methods=[],
566
- fields=[],
567
- annotations=[],
568
563
  analysis_time=0.1,
569
564
  success=True,
570
565
  error_message=None,
@@ -200,12 +200,6 @@ class AnalysisEngine:
200
200
  success=True,
201
201
  error_message=None,
202
202
  analysis_time=0.0,
203
- # Initialize empty collections for now
204
- classes=[],
205
- methods=[],
206
- fields=[],
207
- imports=[],
208
- annotations=[],
209
203
  package=None,
210
204
  )
211
205
 
@@ -458,12 +452,6 @@ class AnalysisEngine:
458
452
  source_code="",
459
453
  error_message=error,
460
454
  analysis_time=0.0,
461
- # Initialize empty collections
462
- classes=[],
463
- methods=[],
464
- fields=[],
465
- imports=[],
466
- annotations=[],
467
455
  package=None,
468
456
  )
469
457
 
@@ -150,32 +150,51 @@ class CLIAdapter:
150
150
  result = self.analyze_file(file_path, **kwargs)
151
151
 
152
152
  # 構造情報を辞書形式に変換
153
+ # Use unified elements system
154
+ from ..constants import (
155
+ ELEMENT_TYPE_ANNOTATION,
156
+ ELEMENT_TYPE_CLASS,
157
+ ELEMENT_TYPE_FUNCTION,
158
+ ELEMENT_TYPE_IMPORT,
159
+ ELEMENT_TYPE_VARIABLE,
160
+ is_element_of_type,
161
+ )
162
+
163
+ elements = result.elements or []
164
+
165
+ # Extract elements by type from unified list
166
+ imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
167
+ classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
168
+ methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
169
+ fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
170
+ annotations = [
171
+ e for e in elements if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
172
+ ]
173
+
153
174
  return {
154
175
  "file_path": result.file_path,
155
176
  "language": result.language,
156
177
  "package": result.package,
157
178
  "imports": [
158
- {"name": imp.name, "type": str(type(imp).__name__)}
159
- for imp in result.imports
179
+ {"name": imp.name, "type": str(type(imp).__name__)} for imp in imports
160
180
  ],
161
181
  "classes": [
162
- {"name": cls.name, "type": str(type(cls).__name__)}
163
- for cls in result.classes
182
+ {"name": cls.name, "type": str(type(cls).__name__)} for cls in classes
164
183
  ],
165
184
  "methods": [
166
185
  {"name": method.name, "type": str(type(method).__name__)}
167
- for method in result.methods
186
+ for method in methods
168
187
  ],
169
188
  "fields": [
170
189
  {"name": field.name, "type": str(type(field).__name__)}
171
- for field in result.fields
190
+ for field in fields
172
191
  ],
173
192
  "annotations": [
174
193
  {
175
194
  "name": getattr(ann, "name", str(ann)),
176
195
  "type": str(type(ann).__name__),
177
196
  }
178
- for ann in getattr(result, "annotations", [])
197
+ for ann in annotations
179
198
  ],
180
199
  "analysis_time": result.analysis_time,
181
200
  "elements": [
@@ -215,11 +234,7 @@ class CLIAdapter:
215
234
  error_result = AnalysisResult(
216
235
  file_path=file_path,
217
236
  package=None,
218
- imports=[],
219
- classes=[],
220
- methods=[],
221
- fields=[],
222
- annotations=[],
237
+ elements=[],
223
238
  analysis_time=0.0,
224
239
  success=False,
225
240
  error_message=str(e),
@@ -67,32 +67,53 @@ class MCPAdapter:
67
67
  ) -> dict[str, Any]:
68
68
  """Get file structure asynchronously."""
69
69
  result = await self.analyze_file_async(file_path, **kwargs)
70
+ # Use unified elements system
71
+ from ..constants import (
72
+ ELEMENT_TYPE_ANNOTATION,
73
+ ELEMENT_TYPE_CLASS,
74
+ ELEMENT_TYPE_FUNCTION,
75
+ ELEMENT_TYPE_IMPORT,
76
+ ELEMENT_TYPE_VARIABLE,
77
+ is_element_of_type,
78
+ )
79
+
80
+ elements = result.elements or []
81
+
82
+ # Extract elements by type from unified list
83
+ imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
84
+ classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
85
+ methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
86
+ fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
87
+ annotations = [
88
+ e for e in elements if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
89
+ ]
90
+
70
91
  return {
71
92
  "file_path": result.file_path,
72
93
  "language": result.language,
73
94
  "structure": {
74
95
  "classes": [
75
96
  {"name": cls.name, "type": str(type(cls).__name__)}
76
- for cls in result.classes
97
+ for cls in classes
77
98
  ],
78
99
  "methods": [
79
100
  {"name": method.name, "type": str(type(method).__name__)}
80
- for method in result.methods
101
+ for method in methods
81
102
  ],
82
103
  "fields": [
83
104
  {"name": field.name, "type": str(type(field).__name__)}
84
- for field in result.fields
105
+ for field in fields
85
106
  ],
86
107
  "imports": [
87
108
  {"name": imp.name, "type": str(type(imp).__name__)}
88
- for imp in result.imports
109
+ for imp in imports
89
110
  ],
90
111
  "annotations": [
91
112
  {
92
113
  "name": getattr(ann, "name", str(ann)),
93
114
  "type": str(type(ann).__name__),
94
115
  }
95
- for ann in getattr(result, "annotations", [])
116
+ for ann in annotations
96
117
  ],
97
118
  },
98
119
  "metadata": {
@@ -100,11 +121,11 @@ class MCPAdapter:
100
121
  "success": result.success,
101
122
  "error_message": result.error_message,
102
123
  "package": result.package,
103
- "class_count": len(result.classes),
104
- "method_count": len(result.methods),
105
- "field_count": len(result.fields),
106
- "import_count": len(result.imports),
107
- "annotation_count": len(result.annotations),
124
+ "class_count": len(classes),
125
+ "method_count": len(methods),
126
+ "field_count": len(fields),
127
+ "import_count": len(imports),
128
+ "annotation_count": len(annotations),
108
129
  },
109
130
  }
110
131
 
@@ -130,11 +151,6 @@ class MCPAdapter:
130
151
  query_results={},
131
152
  source_code="",
132
153
  package=None,
133
- imports=[],
134
- classes=[],
135
- methods=[],
136
- fields=[],
137
- annotations=[],
138
154
  analysis_time=0.0,
139
155
  success=False,
140
156
  error_message=str(e),