tree-sitter-analyzer 1.9.1__py3-none-any.whl → 1.9.3__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.

Files changed (62) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/api.py +10 -6
  3. tree_sitter_analyzer/cli/argument_validator.py +1 -1
  4. tree_sitter_analyzer/cli/commands/advanced_command.py +3 -6
  5. tree_sitter_analyzer/cli/commands/query_command.py +3 -1
  6. tree_sitter_analyzer/cli/commands/table_command.py +3 -3
  7. tree_sitter_analyzer/constants.py +5 -3
  8. tree_sitter_analyzer/core/analysis_engine.py +1 -1
  9. tree_sitter_analyzer/core/cache_service.py +1 -1
  10. tree_sitter_analyzer/core/engine.py +1 -1
  11. tree_sitter_analyzer/core/query.py +0 -2
  12. tree_sitter_analyzer/exceptions.py +1 -1
  13. tree_sitter_analyzer/file_handler.py +6 -6
  14. tree_sitter_analyzer/formatters/base_formatter.py +1 -1
  15. tree_sitter_analyzer/formatters/html_formatter.py +24 -14
  16. tree_sitter_analyzer/formatters/javascript_formatter.py +28 -21
  17. tree_sitter_analyzer/formatters/language_formatter_factory.py +7 -4
  18. tree_sitter_analyzer/formatters/markdown_formatter.py +4 -4
  19. tree_sitter_analyzer/formatters/python_formatter.py +4 -4
  20. tree_sitter_analyzer/formatters/typescript_formatter.py +1 -1
  21. tree_sitter_analyzer/interfaces/mcp_adapter.py +4 -2
  22. tree_sitter_analyzer/interfaces/mcp_server.py +10 -10
  23. tree_sitter_analyzer/language_detector.py +30 -5
  24. tree_sitter_analyzer/language_loader.py +46 -26
  25. tree_sitter_analyzer/languages/css_plugin.py +6 -6
  26. tree_sitter_analyzer/languages/html_plugin.py +12 -8
  27. tree_sitter_analyzer/languages/java_plugin.py +307 -520
  28. tree_sitter_analyzer/languages/javascript_plugin.py +22 -78
  29. tree_sitter_analyzer/languages/markdown_plugin.py +277 -297
  30. tree_sitter_analyzer/languages/python_plugin.py +47 -85
  31. tree_sitter_analyzer/languages/typescript_plugin.py +48 -123
  32. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +14 -8
  33. tree_sitter_analyzer/mcp/server.py +38 -23
  34. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -7
  35. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +51 -7
  36. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +15 -2
  37. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +8 -6
  38. tree_sitter_analyzer/mcp/tools/list_files_tool.py +6 -6
  39. tree_sitter_analyzer/mcp/tools/search_content_tool.py +48 -19
  40. tree_sitter_analyzer/mcp/tools/table_format_tool.py +13 -8
  41. tree_sitter_analyzer/mcp/utils/file_output_manager.py +8 -3
  42. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +24 -12
  43. tree_sitter_analyzer/mcp/utils/path_resolver.py +2 -2
  44. tree_sitter_analyzer/models.py +16 -0
  45. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  46. tree_sitter_analyzer/plugins/base.py +66 -0
  47. tree_sitter_analyzer/queries/java.py +1 -1
  48. tree_sitter_analyzer/queries/javascript.py +3 -8
  49. tree_sitter_analyzer/queries/markdown.py +1 -1
  50. tree_sitter_analyzer/queries/python.py +2 -2
  51. tree_sitter_analyzer/security/boundary_manager.py +2 -5
  52. tree_sitter_analyzer/security/regex_checker.py +2 -2
  53. tree_sitter_analyzer/security/validator.py +5 -1
  54. tree_sitter_analyzer/table_formatter.py +4 -4
  55. tree_sitter_analyzer/utils/__init__.py +27 -116
  56. tree_sitter_analyzer/{utils.py → utils/logging.py} +2 -2
  57. tree_sitter_analyzer/utils/tree_sitter_compat.py +2 -2
  58. {tree_sitter_analyzer-1.9.1.dist-info → tree_sitter_analyzer-1.9.3.dist-info}/METADATA +70 -30
  59. tree_sitter_analyzer-1.9.3.dist-info/RECORD +110 -0
  60. tree_sitter_analyzer-1.9.1.dist-info/RECORD +0 -109
  61. {tree_sitter_analyzer-1.9.1.dist-info → tree_sitter_analyzer-1.9.3.dist-info}/WHEEL +0 -0
  62. {tree_sitter_analyzer-1.9.1.dist-info → tree_sitter_analyzer-1.9.3.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,7 @@ Architecture:
11
11
  - Data Models: Generic and language-specific code element representations
12
12
  """
13
13
 
14
- __version__ = "1.9.1"
14
+ __version__ = "1.9.3"
15
15
  __author__ = "aisheng.yu"
16
16
  __email__ = "aimasteracc@gmail.com"
17
17
 
@@ -85,7 +85,8 @@ def analyze_file(
85
85
 
86
86
  # Add elements if requested and available
87
87
  if include_elements and hasattr(analysis_result, "elements"):
88
- result["elements"] = []
88
+ elements_list: list[dict[str, Any]] = []
89
+ result["elements"] = elements_list
89
90
  for elem in analysis_result.elements:
90
91
  elem_dict = {
91
92
  "name": elem.name,
@@ -145,7 +146,7 @@ def analyze_file(
145
146
  else:
146
147
  elem_dict["class_name"] = None
147
148
 
148
- result["elements"].append(elem_dict)
149
+ elements_list.append(elem_dict)
149
150
 
150
151
  # Add query results if requested and available
151
152
  if include_queries and hasattr(analysis_result, "query_results"):
@@ -219,7 +220,8 @@ def analyze_code(
219
220
 
220
221
  # Add elements if requested and available
221
222
  if include_elements and hasattr(analysis_result, "elements"):
222
- result["elements"] = []
223
+ elements_list: list[dict[str, Any]] = []
224
+ result["elements"] = elements_list
223
225
  for elem in analysis_result.elements:
224
226
  elem_dict = {
225
227
  "name": elem.name,
@@ -279,7 +281,7 @@ def analyze_code(
279
281
  else:
280
282
  elem_dict["class_name"] = None
281
283
 
282
- result["elements"].append(elem_dict)
284
+ elements_list.append(elem_dict)
283
285
 
284
286
  # Add query results if requested and available
285
287
  if include_queries and hasattr(analysis_result, "query_results"):
@@ -454,8 +456,10 @@ def validate_file(file_path: str | Path) -> dict[str, Any]:
454
456
 
455
457
  # Check if file is readable
456
458
  try:
457
- with open(file_path, encoding="utf-8") as f:
458
- f.read(100) # Read first 100 chars to test
459
+ from .encoding_utils import read_file_safe
460
+
461
+ # Test file readability by reading it
462
+ read_file_safe(file_path)
459
463
  result["readable"] = True
460
464
  result["size"] = file_path.stat().st_size
461
465
  except Exception as e:
@@ -11,7 +11,7 @@ from typing import Any
11
11
  class CLIArgumentValidator:
12
12
  """Validator for CLI argument combinations."""
13
13
 
14
- def __init__(self):
14
+ def __init__(self) -> None:
15
15
  """Initialize the validator."""
16
16
  pass
17
17
 
@@ -49,8 +49,9 @@ class AdvancedCommand(BaseCommand):
49
49
  Dictionary containing file metrics
50
50
  """
51
51
  try:
52
- with open(file_path, encoding="utf-8") as f:
53
- content = f.read()
52
+ from ...encoding_utils import read_file_safe
53
+
54
+ content, _ = read_file_safe(file_path)
54
55
 
55
56
  lines = content.split("\n")
56
57
  total_lines = len(lines)
@@ -111,10 +112,6 @@ class AdvancedCommand(BaseCommand):
111
112
  if "-->" not in stripped:
112
113
  in_multiline_comment = True
113
114
  continue
114
- elif in_multiline_comment and "-->" in stripped:
115
- comment_lines += 1
116
- in_multiline_comment = False
117
- continue
118
115
 
119
116
  # If not a comment, it's code
120
117
  code_lines += 1
@@ -5,6 +5,8 @@ Query Command
5
5
  Handles query execution functionality.
6
6
  """
7
7
 
8
+ from typing import Any
9
+
8
10
  from ...core.query_service import QueryService
9
11
  from ...output_manager import output_data, output_error, output_info, output_json
10
12
  from .base_command import BaseCommand
@@ -13,7 +15,7 @@ from .base_command import BaseCommand
13
15
  class QueryCommand(BaseCommand):
14
16
  """Command for executing queries."""
15
17
 
16
- def __init__(self, args):
18
+ def __init__(self, args: Any) -> None:
17
19
  """Initialize the query command with QueryService."""
18
20
  super().__init__(args)
19
21
  self.query_service = QueryService()
@@ -25,7 +25,7 @@ from .base_command import BaseCommand
25
25
  class TableCommand(BaseCommand):
26
26
  """Command for generating table format output."""
27
27
 
28
- def __init__(self, args):
28
+ def __init__(self, args: Any) -> None:
29
29
  """Initialize the table command."""
30
30
  super().__init__(args)
31
31
 
@@ -56,10 +56,10 @@ class TableCommand(BaseCommand):
56
56
 
57
57
  # Create table formatter
58
58
  include_javadoc = getattr(self.args, "include_javadoc", False)
59
- formatter = create_table_formatter(
59
+ table_formatter: Any = create_table_formatter(
60
60
  self.args.table, language, include_javadoc
61
61
  )
62
- table_output = formatter.format_structure(structure_result)
62
+ table_output = table_formatter.format_structure(structure_result)
63
63
 
64
64
  # Output table
65
65
  self._output_table(table_output)
@@ -5,6 +5,8 @@ Constants for tree-sitter-analyzer
5
5
  This module defines constants used throughout the project to ensure consistency.
6
6
  """
7
7
 
8
+ from typing import Any, cast
9
+
8
10
  # Element types for unified element management system
9
11
  ELEMENT_TYPE_CLASS = "class"
10
12
  ELEMENT_TYPE_FUNCTION = "function"
@@ -34,7 +36,7 @@ LEGACY_CLASS_MAPPING = {
34
36
  }
35
37
 
36
38
 
37
- def get_element_type(element) -> str:
39
+ def get_element_type(element: Any) -> str:
38
40
  """
39
41
  Get the element type from an element object.
40
42
 
@@ -45,7 +47,7 @@ def get_element_type(element) -> str:
45
47
  Standardized element type string
46
48
  """
47
49
  if hasattr(element, "element_type"):
48
- return element.element_type
50
+ return cast(str, element.element_type)
49
51
 
50
52
  if hasattr(element, "__class__") and hasattr(element.__class__, "__name__"):
51
53
  class_name = element.__class__.__name__
@@ -54,7 +56,7 @@ def get_element_type(element) -> str:
54
56
  return "unknown"
55
57
 
56
58
 
57
- def is_element_of_type(element, element_type: str) -> bool:
59
+ def is_element_of_type(element: Any, element_type: str) -> bool:
58
60
  """
59
61
  Check if an element is of a specific type.
60
62
 
@@ -218,7 +218,7 @@ class UnifiedAnalysisEngine:
218
218
  instance = super().__new__(cls)
219
219
  cls._instances[instance_key] = instance
220
220
  # Mark as not initialized for this instance
221
- instance._initialized: bool = False
221
+ instance._initialized = False
222
222
 
223
223
  return cls._instances[instance_key]
224
224
 
@@ -18,7 +18,7 @@ from dataclasses import dataclass
18
18
  from datetime import datetime, timedelta
19
19
  from typing import Any
20
20
 
21
- from cachetools import LRUCache, TTLCache
21
+ from cachetools import LRUCache, TTLCache # type: ignore[import-untyped]
22
22
 
23
23
  from ..utils import log_debug, log_info
24
24
 
@@ -535,7 +535,7 @@ class AnalysisEngine:
535
535
  logger.error(f"Error getting extensions for {language}: {e}")
536
536
  return []
537
537
 
538
- def get_registry_info(self) -> dict:
538
+ def get_registry_info(self) -> dict[str, Any]:
539
539
  """
540
540
  Get registry information (compatibility method)
541
541
 
@@ -64,7 +64,6 @@ class QueryExecutor:
64
64
  # Validate inputs
65
65
  if tree is None:
66
66
  return self._create_error_result("Tree is None", query_name=query_name)
67
-
68
67
  if language is None:
69
68
  return self._create_error_result( # type: ignore[unreachable]
70
69
  "Language is None", query_name=query_name
@@ -166,7 +165,6 @@ class QueryExecutor:
166
165
  # Validate inputs
167
166
  if tree is None:
168
167
  return self._create_error_result("Tree is None")
169
-
170
168
  if language is None:
171
169
  return self._create_error_result("Language is None") # type: ignore[unreachable]
172
170
 
@@ -616,7 +616,7 @@ def create_mcp_error_response(
616
616
 
617
617
  def _sanitize_error_context(context: dict[str, Any]) -> dict[str, Any]:
618
618
  """Sanitize sensitive information from error context."""
619
- sanitized = {}
619
+ sanitized: dict[str, Any] = {}
620
620
  sensitive_keys = {
621
621
  "password",
622
622
  "token",
@@ -14,19 +14,19 @@ from .utils import setup_logger
14
14
  logger = setup_logger(__name__)
15
15
 
16
16
 
17
- def log_error(message: str, *args, **kwargs) -> None:
17
+ def log_error(message: str, *args: object, **kwargs: object) -> None:
18
18
  """Log error message"""
19
- logger.error(message, *args, **kwargs)
19
+ logger.error(message, *args, **kwargs) # type: ignore[arg-type]
20
20
 
21
21
 
22
- def log_info(message: str, *args, **kwargs) -> None:
22
+ def log_info(message: str, *args: object, **kwargs: object) -> None:
23
23
  """Log info message"""
24
- logger.info(message, *args, **kwargs)
24
+ logger.info(message, *args, **kwargs) # type: ignore[arg-type]
25
25
 
26
26
 
27
- def log_warning(message: str, *args, **kwargs) -> None:
27
+ def log_warning(message: str, *args: object, **kwargs: object) -> None:
28
28
  """Log warning message"""
29
- logger.warning(message, *args, **kwargs)
29
+ logger.warning(message, *args, **kwargs) # type: ignore[arg-type]
30
30
 
31
31
 
32
32
  def detect_language_from_extension(file_path: str) -> str:
@@ -13,7 +13,7 @@ class BaseFormatter(ABC):
13
13
  """Base class for language-specific formatters"""
14
14
 
15
15
  @abstractmethod
16
- def __init__(self):
16
+ def __init__(self) -> None:
17
17
  pass
18
18
 
19
19
  @abstractmethod
@@ -17,7 +17,7 @@ from .formatter_registry import IFormatter
17
17
  class HtmlFormatter(BaseFormatter, IFormatter):
18
18
  """HTML-specific formatter for MarkupElement and StyleElement"""
19
19
 
20
- def __init__(self):
20
+ def __init__(self) -> None:
21
21
  """Initialize HTML formatter"""
22
22
  pass
23
23
 
@@ -37,7 +37,7 @@ class HtmlFormatter(BaseFormatter, IFormatter):
37
37
  # Handle both CodeElement objects and dictionaries
38
38
  markup_elements = []
39
39
  style_elements = []
40
- other_elements = []
40
+ other_elements: list[dict[str, Any]] = []
41
41
 
42
42
  for e in elements:
43
43
  if isinstance(e, MarkupElement):
@@ -54,7 +54,7 @@ class HtmlFormatter(BaseFormatter, IFormatter):
54
54
  else:
55
55
  other_elements.append(e)
56
56
  else:
57
- other_elements.append(e)
57
+ other_elements.append(self._element_to_dict(e))
58
58
 
59
59
  # Format markup elements
60
60
  if markup_elements:
@@ -114,7 +114,7 @@ class HtmlFormatter(BaseFormatter, IFormatter):
114
114
  elements = analysis_result.get("elements", [])
115
115
 
116
116
  if table_type == "compact":
117
- formatter = HtmlCompactFormatter()
117
+ formatter: IFormatter = HtmlCompactFormatter()
118
118
  return formatter.format(elements)
119
119
  elif table_type == "json":
120
120
  formatter = HtmlJsonFormatter()
@@ -130,7 +130,7 @@ class HtmlFormatter(BaseFormatter, IFormatter):
130
130
  lines.append("")
131
131
 
132
132
  # Group by element class
133
- element_groups = {}
133
+ element_groups: dict[str, list[MarkupElement]] = {}
134
134
  for element in elements:
135
135
  element_class = element.element_class or "unknown"
136
136
  if element_class not in element_groups:
@@ -217,7 +217,7 @@ class HtmlFormatter(BaseFormatter, IFormatter):
217
217
  lines.append("")
218
218
 
219
219
  # Group by element class
220
- element_groups = {}
220
+ element_groups: dict[str, list[StyleElement]] = {}
221
221
  for element in elements:
222
222
  element_class = element.element_class or "unknown"
223
223
  if element_class not in element_groups:
@@ -280,30 +280,30 @@ class HtmlFormatter(BaseFormatter, IFormatter):
280
280
  lines.append("")
281
281
  return lines
282
282
 
283
- def _dict_to_markup_element(self, data: dict):
283
+ def _dict_to_markup_element(self, data: dict) -> Any:
284
284
  """Convert dictionary to MarkupElement-like object"""
285
285
 
286
286
  # Create a mock MarkupElement-like object
287
287
  class MockMarkupElement:
288
- def __init__(self, data):
288
+ def __init__(self, data: dict[str, Any]) -> None:
289
289
  self.name = data.get("name", "unknown")
290
290
  self.tag_name = data.get("tag_name", data.get("name", "unknown"))
291
291
  self.element_class = data.get("element_class", "unknown")
292
292
  self.start_line = data.get("start_line", 0)
293
293
  self.end_line = data.get("end_line", 0)
294
294
  self.attributes = data.get("attributes", {})
295
- self.children = []
295
+ self.children: list[MockMarkupElement] = []
296
296
  self.parent = None
297
297
  self.language = data.get("language", "html")
298
298
 
299
299
  return MockMarkupElement(data)
300
300
 
301
- def _dict_to_style_element(self, data: dict):
301
+ def _dict_to_style_element(self, data: dict) -> Any:
302
302
  """Convert dictionary to StyleElement-like object"""
303
303
 
304
304
  # Create a mock StyleElement-like object
305
305
  class MockStyleElement:
306
- def __init__(self, data):
306
+ def __init__(self, data: dict[str, Any]) -> None:
307
307
  self.name = data.get("name", "unknown")
308
308
  self.selector = data.get("selector", data.get("name", "unknown"))
309
309
  self.element_class = data.get("element_class", "unknown")
@@ -314,6 +314,16 @@ class HtmlFormatter(BaseFormatter, IFormatter):
314
314
 
315
315
  return MockStyleElement(data)
316
316
 
317
+ def _element_to_dict(self, element: CodeElement) -> dict[str, Any]:
318
+ """Convert generic CodeElement to dictionary"""
319
+ return {
320
+ "name": element.name,
321
+ "type": getattr(element, "element_type", "unknown"),
322
+ "start_line": element.start_line,
323
+ "end_line": element.end_line,
324
+ "language": element.language,
325
+ }
326
+
317
327
 
318
328
  class HtmlJsonFormatter(IFormatter):
319
329
  """JSON formatter specifically for HTML elements"""
@@ -324,7 +334,7 @@ class HtmlJsonFormatter(IFormatter):
324
334
 
325
335
  def format(self, elements: list[CodeElement]) -> str:
326
336
  """Format HTML elements as JSON with hierarchy"""
327
- result = {
337
+ result: dict[str, Any] = {
328
338
  "html_analysis": {
329
339
  "total_elements": len(elements),
330
340
  "markup_elements": [],
@@ -468,10 +478,10 @@ class HtmlCompactFormatter(IFormatter):
468
478
  info += f" .{attributes['class']}"
469
479
  elif "selector" in element or element_type in ["rule", "style"]:
470
480
  symbol = "🎨"
471
- info = element.get("selector", name)
481
+ info = str(element.get("selector", name))
472
482
  else:
473
483
  symbol = "📄"
474
- info = element_type
484
+ info = str(element_type)
475
485
  else:
476
486
  symbol = "📄"
477
487
  info = getattr(element, "element_type", "unknown")
@@ -15,15 +15,15 @@ from .base_formatter import BaseTableFormatter
15
15
  class JavaScriptTableFormatter(BaseTableFormatter):
16
16
  """Table formatter specialized for JavaScript"""
17
17
 
18
- def format(self, data: dict[str, Any], format_type: str = None) -> str:
18
+ def format(self, data: dict[str, Any] | None, format_type: str = "full") -> str:
19
19
  """Format data using the configured format type"""
20
- # Handle None data
20
+ # Handle None data gracefully
21
21
  if data is None:
22
- return "# No data available\n"
22
+ data = {}
23
23
 
24
24
  # Ensure data is a dictionary
25
25
  if not isinstance(data, dict):
26
- return f"# Invalid data type: {type(data)}\n"
26
+ raise TypeError(f"Expected dict, got {type(data)}")
27
27
 
28
28
  if format_type:
29
29
  # Check for supported format types
@@ -47,11 +47,8 @@ class JavaScriptTableFormatter(BaseTableFormatter):
47
47
 
48
48
  def _format_full_table(self, data: dict[str, Any]) -> str:
49
49
  """Full table format for JavaScript"""
50
- if data is None:
51
- return "# No data available\n"
52
-
53
50
  if not isinstance(data, dict):
54
- return f"# Invalid data type: {type(data)}\n"
51
+ raise TypeError(f"Expected dict, got {type(data)}")
55
52
 
56
53
  lines = []
57
54
 
@@ -243,16 +240,26 @@ class JavaScriptTableFormatter(BaseTableFormatter):
243
240
  lines.append("| Export | Type | Name | Default |")
244
241
  lines.append("|--------|------|------|---------|")
245
242
 
246
- for export in exports:
247
- export_type = self._get_export_type(export)
248
- if isinstance(export, dict):
249
- name = str(export.get("name", ""))
250
- is_default = "✓" if export.get("is_default", False) else "-"
251
- else:
252
- name = str(export)
253
- is_default = "-"
254
-
255
- lines.append(f"| {export_type} | {name} | {is_default} |")
243
+ # Handle malformed exports data
244
+ if isinstance(exports, list):
245
+ for export in exports:
246
+ try:
247
+ export_type = self._get_export_type(export)
248
+ if isinstance(export, dict):
249
+ name = str(export.get("name", ""))
250
+ is_default = "✓" if export.get("is_default", False) else "-"
251
+ else:
252
+ name = str(export)
253
+ is_default = "-"
254
+ export_type = "unknown"
255
+
256
+ lines.append(f"| {export_type} | {name} | {is_default} |")
257
+ except (TypeError, AttributeError):
258
+ # Handle malformed export data gracefully
259
+ lines.append(f"| unknown | {str(export)} | - |")
260
+ else:
261
+ # Handle case where exports is not a list (malformed data)
262
+ lines.append(f"| unknown | {str(exports)} | - |")
256
263
  lines.append("")
257
264
 
258
265
  # Trim trailing blank lines
@@ -526,7 +533,7 @@ class JavaScriptTableFormatter(BaseTableFormatter):
526
533
  else:
527
534
  return "unknown"
528
535
 
529
- def _get_export_type(self, export: dict[str, Any]) -> str:
536
+ def _get_export_type(self, export: Any) -> str:
530
537
  """Get export type"""
531
538
  if not isinstance(export, dict):
532
539
  return "unknown"
@@ -551,10 +558,10 @@ class JavaScriptTableFormatter(BaseTableFormatter):
551
558
  def _get_class_info(self, cls: dict[str, Any]) -> str:
552
559
  """Get class information as formatted string"""
553
560
  if cls is None:
554
- return "Unknown (0 methods)"
561
+ raise TypeError("Cannot format None data")
555
562
 
556
563
  if not isinstance(cls, dict):
557
- return f"{str(cls)} (0 methods)"
564
+ raise TypeError(f"Expected dict, got {type(cls)}")
558
565
 
559
566
  name = str(cls.get("name", "Unknown"))
560
567
  methods = cls.get("methods", [])
@@ -32,8 +32,7 @@ class LanguageFormatterFactory:
32
32
  formatter_class = cls._formatters.get(language.lower())
33
33
 
34
34
  if formatter_class is None:
35
- # Return None for unsupported languages
36
- return None
35
+ raise ValueError(f"Unsupported language: {language}")
37
36
 
38
37
  return formatter_class()
39
38
 
@@ -74,7 +73,7 @@ class LanguageFormatterFactory:
74
73
  return language.lower() in cls._formatters
75
74
 
76
75
 
77
- def create_language_formatter(language: str) -> BaseFormatter:
76
+ def create_language_formatter(language: str) -> BaseFormatter | None:
78
77
  """
79
78
  Create language formatter (function for compatibility)
80
79
 
@@ -84,4 +83,8 @@ def create_language_formatter(language: str) -> BaseFormatter:
84
83
  Returns:
85
84
  Language formatter or None if not supported
86
85
  """
87
- return LanguageFormatterFactory.create_formatter(language)
86
+ try:
87
+ return LanguageFormatterFactory.create_formatter(language)
88
+ except ValueError:
89
+ # Return None for unsupported languages instead of raising exception
90
+ return None
@@ -14,8 +14,7 @@ from .base_formatter import BaseFormatter
14
14
  class MarkdownFormatter(BaseFormatter):
15
15
  """Formatter specialized for Markdown documents"""
16
16
 
17
- def __init__(self):
18
- super().__init__()
17
+ def __init__(self) -> None:
19
18
  self.language = "markdown"
20
19
 
21
20
  def format_summary(self, analysis_result: dict[str, Any]) -> str:
@@ -624,8 +623,9 @@ class MarkdownFormatter(BaseFormatter):
624
623
  return counts
625
624
 
626
625
  try:
627
- with open(file_path, encoding="utf-8", errors="replace") as f:
628
- content = f.read()
626
+ from ..encoding_utils import read_file_safe
627
+
628
+ content, _ = read_file_safe(file_path)
629
629
  except Exception:
630
630
  return counts
631
631
 
@@ -30,10 +30,10 @@ class PythonTableFormatter(BaseTableFormatter):
30
30
  def _format_full_table(self, data: dict[str, Any]) -> str:
31
31
  """Full table format for Python"""
32
32
  if data is None:
33
- return "# No data available\n"
33
+ raise TypeError("Cannot format None data")
34
34
 
35
35
  if not isinstance(data, dict):
36
- return f"# Invalid data type: {type(data)}\n"
36
+ raise TypeError(f"Expected dict, got {type(data)}")
37
37
 
38
38
  lines = []
39
39
 
@@ -300,7 +300,7 @@ class PythonTableFormatter(BaseTableFormatter):
300
300
  def _create_compact_signature(self, method: dict[str, Any]) -> str:
301
301
  """Create compact method signature for Python"""
302
302
  if method is None or not isinstance(method, dict):
303
- return "(Any,Any):A"
303
+ raise TypeError(f"Expected dict, got {type(method)}")
304
304
 
305
305
  params = method.get("parameters", [])
306
306
  param_types = []
@@ -387,7 +387,7 @@ class PythonTableFormatter(BaseTableFormatter):
387
387
 
388
388
  # Single line docstring
389
389
  if stripped.count(quote_type) >= 2:
390
- return stripped.replace(quote_type, "").strip()
390
+ return str(stripped.replace(quote_type, "").strip())
391
391
 
392
392
  # Multi-line docstring
393
393
  docstring_lines = [stripped.replace(quote_type, "")]
@@ -462,7 +462,7 @@ class TypeScriptTableFormatter(BaseTableFormatter):
462
462
  elif element_type == "import":
463
463
  return "Import"
464
464
  else:
465
- return element_type.title()
465
+ return str(element_type.title())
466
466
 
467
467
  def _format_element_details(self, element: dict[str, Any]) -> str:
468
468
  """Format TypeScript-specific element details"""
@@ -32,8 +32,10 @@ def handle_mcp_resource_request(uri: str) -> dict[str, Any]:
32
32
  def read_file_safe(file_path: str) -> str:
33
33
  """Read file safely for MCP resource requests."""
34
34
  try:
35
- with open(file_path, encoding="utf-8") as f:
36
- return f.read()
35
+ from ..encoding_utils import read_file_safe
36
+
37
+ content, _ = read_file_safe(file_path)
38
+ return content
37
39
  except Exception as e:
38
40
  raise FileNotFoundError(f"Could not read file {file_path}: {e}") from e
39
41
 
@@ -25,23 +25,23 @@ except ImportError:
25
25
  MCP_AVAILABLE = False
26
26
 
27
27
  # Fallback types for development without MCP
28
- class Server:
28
+ class Server: # type: ignore
29
29
  pass
30
30
 
31
- class InitializationOptions:
31
+ class InitializationOptions: # type: ignore
32
32
  def __init__(self, **kwargs: Any) -> None:
33
33
  pass
34
34
 
35
- class Tool:
35
+ class Tool: # type: ignore
36
36
  pass
37
37
 
38
- class Resource:
38
+ class Resource: # type: ignore
39
39
  pass
40
40
 
41
- class TextContent:
41
+ class TextContent: # type: ignore
42
42
  pass
43
43
 
44
- def stdio_server() -> None:
44
+ def stdio_server() -> None: # type: ignore[misc]
45
45
  pass
46
46
 
47
47
 
@@ -337,13 +337,13 @@ class TreeSitterAnalyzerMCPServer:
337
337
  """List available resources."""
338
338
  return [
339
339
  Resource(
340
- uri="code://file/{file_path}", # type: ignore
340
+ uri="code://file/{file_path}", # type: ignore[arg-type]
341
341
  name="Code File Analysis",
342
342
  description="Access to code file content and analysis",
343
343
  mimeType="application/json",
344
344
  ),
345
345
  Resource(
346
- uri="code://stats/{stats_type}", # type: ignore
346
+ uri="code://stats/{stats_type}", # type: ignore[arg-type]
347
347
  name="Project Statistics",
348
348
  description="Access to project statistics and analysis data",
349
349
  mimeType="application/json",
@@ -389,7 +389,7 @@ class TreeSitterAnalyzerMCPServer:
389
389
 
390
390
  self.server = server
391
391
  log_info("MCP server created successfully")
392
- return server # type: ignore
392
+ return server # type: ignore[no-any-return]
393
393
 
394
394
  async def run(self) -> None:
395
395
  """Run the MCP server."""
@@ -399,7 +399,7 @@ class TreeSitterAnalyzerMCPServer:
399
399
  options = InitializationOptions(
400
400
  server_name=self.name,
401
401
  server_version=self.version,
402
- capabilities={"tools": {}, "resources": {}}, # type: ignore
402
+ capabilities={"tools": {}, "resources": {}}, # type: ignore[arg-type]
403
403
  )
404
404
 
405
405
  log_info(f"Starting MCP server: {self.name} v{self.version}")