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
@@ -69,20 +69,16 @@ class PythonElementExtractor(ElementExtractor):
69
69
  "function_definition": self._extract_function_optimized,
70
70
  }
71
71
 
72
- if tree is None or tree.root_node is None:
73
- log_debug("Tree or root_node is None, returning empty functions list")
74
- return functions
75
-
76
- try:
77
- self._traverse_and_extract_iterative(
78
- tree.root_node, extractors, functions, "function"
79
- )
80
- except Exception as e:
81
- log_debug(f"Error during function extraction: {e}")
82
- # Return empty list on error to handle gracefully
83
- return []
72
+ if tree is not None and tree.root_node is not None:
73
+ try:
74
+ self._traverse_and_extract_iterative(
75
+ tree.root_node, extractors, functions, "function"
76
+ )
77
+ log_debug(f"Extracted {len(functions)} Python functions")
78
+ except Exception as e:
79
+ log_debug(f"Error during function extraction: {e}")
80
+ return []
84
81
 
85
- log_debug(f"Extracted {len(functions)} Python functions")
86
82
  return functions
87
83
 
88
84
  def extract_classes(
@@ -100,15 +96,16 @@ class PythonElementExtractor(ElementExtractor):
100
96
  "class_definition": self._extract_class_optimized,
101
97
  }
102
98
 
103
- if tree is None or tree.root_node is None:
104
- log_debug("Tree or root_node is None, returning empty classes list")
105
- return classes
106
-
107
- self._traverse_and_extract_iterative(
108
- tree.root_node, extractors, classes, "class"
109
- )
99
+ if tree is not None and tree.root_node is not None:
100
+ try:
101
+ self._traverse_and_extract_iterative(
102
+ tree.root_node, extractors, classes, "class"
103
+ )
104
+ log_debug(f"Extracted {len(classes)} Python classes")
105
+ except Exception as e:
106
+ log_debug(f"Error during class extraction: {e}")
107
+ return []
110
108
 
111
- log_debug(f"Extracted {len(classes)} Python classes")
112
109
  return classes
113
110
 
114
111
  def extract_variables(
@@ -178,7 +175,7 @@ class PythonElementExtractor(ElementExtractor):
178
175
 
179
176
  def _traverse_and_extract_iterative(
180
177
  self,
181
- root_node: "tree_sitter.Node",
178
+ root_node: Optional["tree_sitter.Node"],
182
179
  extractors: dict[str, Any],
183
180
  results: list[Any],
184
181
  element_type: str,
@@ -255,22 +252,23 @@ class PythonElementExtractor(ElementExtractor):
255
252
  except Exception:
256
253
  # Skip nodes that cause extraction errors
257
254
  self._processed_nodes.add(node_id)
258
- continue
259
255
 
260
256
  # Add children to stack
261
257
  if current_node.children:
262
258
  try:
263
259
  # Try to reverse children for proper traversal order
264
- children = reversed(current_node.children)
260
+ children_list = list(current_node.children)
261
+ children_iter = reversed(children_list)
265
262
  except TypeError:
266
263
  # Fallback for Mock objects or other non-reversible types
267
264
  try:
268
- children = list(current_node.children)
265
+ children_list = list(current_node.children)
266
+ children_iter = iter(children_list) # type: ignore
269
267
  except TypeError:
270
268
  # If children is not iterable, skip
271
- children = []
269
+ children_iter = iter([]) # type: ignore
272
270
 
273
- for child in children:
271
+ for child in children_iter:
274
272
  node_stack.append((child, depth + 1))
275
273
 
276
274
  log_debug(f"Iterative traversal processed {processed_nodes} nodes")
@@ -314,7 +312,7 @@ class PythonElementExtractor(ElementExtractor):
314
312
  # Ensure column indices are within line bounds
315
313
  start_col = max(0, min(start_point[1], len(line)))
316
314
  end_col = max(start_col, min(end_point[1], len(line)))
317
- result = line[start_col:end_col]
315
+ result: str = line[start_col:end_col]
318
316
  self._node_text_cache[node_id] = result
319
317
  return result
320
318
  else:
@@ -498,9 +496,8 @@ class PythonElementExtractor(ElementExtractor):
498
496
  break
499
497
  docstring_lines.append(next_line)
500
498
 
501
- # If no closing quote found, return None (malformed docstring)
502
499
  if not found_closing_quote:
503
- self._docstring_cache[target_line] = None
500
+ self._docstring_cache[target_line] = ""
504
501
  return None
505
502
 
506
503
  # Join preserving formatting and add leading newline for multi-line
@@ -511,7 +508,7 @@ class PythonElementExtractor(ElementExtractor):
511
508
  self._docstring_cache[target_line] = docstring
512
509
  return docstring
513
510
 
514
- self._docstring_cache[target_line] = None
511
+ self._docstring_cache[target_line] = ""
515
512
  return None
516
513
 
517
514
  except Exception as e:
@@ -748,7 +745,7 @@ class PythonElementExtractor(ElementExtractor):
748
745
  )
749
746
 
750
747
  # Group captures by name
751
- captures_dict = {}
748
+ captures_dict: dict[str, list[Any]] = {}
752
749
  for node, capture_name in captures:
753
750
  if capture_name not in captures_dict:
754
751
  captures_dict[capture_name] = []
@@ -787,7 +784,7 @@ class PythonElementExtractor(ElementExtractor):
787
784
  """Manual import extraction for tree-sitter 0.25.x compatibility"""
788
785
  imports = []
789
786
 
790
- def walk_tree(node):
787
+ def walk_tree(node: "tree_sitter.Node") -> None:
791
788
  if node.type in ["import_statement", "import_from_statement"]:
792
789
  try:
793
790
  start_line = node.start_point[0] + 1
@@ -824,9 +821,11 @@ class PythonElementExtractor(ElementExtractor):
824
821
 
825
822
  if module_name or imported_names:
826
823
  import_obj = Import(
827
- name=module_name or imported_names[0]
828
- if imported_names
829
- else "unknown",
824
+ name=(
825
+ module_name or imported_names[0]
826
+ if imported_names
827
+ else "unknown"
828
+ ),
830
829
  start_line=start_line,
831
830
  end_line=end_line,
832
831
  raw_text=raw_text,
@@ -1294,51 +1293,13 @@ class PythonPlugin(LanguagePlugin):
1294
1293
  }
1295
1294
 
1296
1295
  def execute_query_strategy(
1297
- self, tree: "tree_sitter.Tree", source_code: str, query_key: str
1298
- ) -> list[dict]:
1299
- """
1300
- Execute query strategy for Python language
1301
-
1302
- Args:
1303
- tree: Tree-sitter tree object
1304
- source_code: Source code string
1305
- query_key: Query key to execute
1306
-
1307
- Returns:
1308
- List of query results
1309
- """
1310
- # Use the extractor to get elements based on query_key
1311
- extractor = self.get_extractor()
1296
+ self, query_key: str | None, language: str
1297
+ ) -> str | None:
1298
+ """Execute query strategy for Python language"""
1299
+ queries = self.get_queries()
1300
+ return queries.get(query_key) if query_key else None
1312
1301
 
1313
- # Map query keys to extraction methods
1314
- if query_key in ["function", "functions", "method", "methods"]:
1315
- elements = extractor.extract_functions(tree, source_code)
1316
- elif query_key in ["class", "classes"]:
1317
- elements = extractor.extract_classes(tree, source_code)
1318
- elif query_key in ["variable", "variables"]:
1319
- elements = extractor.extract_variables(tree, source_code)
1320
- elif query_key in ["import", "imports", "from_import", "from_imports"]:
1321
- elements = extractor.extract_imports(tree, source_code)
1322
- else:
1323
- # For unknown query keys, return empty list
1324
- return []
1325
-
1326
- # Convert elements to query result format
1327
- results = []
1328
- for element in elements:
1329
- result = {
1330
- "capture_name": query_key,
1331
- "node_type": self._get_node_type_for_element(element),
1332
- "start_line": element.start_line,
1333
- "end_line": element.end_line,
1334
- "text": element.raw_text,
1335
- "name": element.name,
1336
- }
1337
- results.append(result)
1338
-
1339
- return results
1340
-
1341
- def _get_node_type_for_element(self, element) -> str:
1302
+ def _get_node_type_for_element(self, element: Any) -> str:
1342
1303
  """Get appropriate node type for element"""
1343
1304
  from ..models import Class, Function, Import, Variable
1344
1305
 
@@ -1459,8 +1420,9 @@ class PythonPlugin(LanguagePlugin):
1459
1420
  )
1460
1421
 
1461
1422
  try:
1462
- with open(file_path, encoding="utf-8") as f:
1463
- source_code = f.read()
1423
+ from ..encoding_utils import read_file_safe
1424
+
1425
+ source_code, _ = read_file_safe(file_path)
1464
1426
 
1465
1427
  parser = tree_sitter.Parser()
1466
1428
  parser.language = language
@@ -1536,9 +1498,9 @@ class PythonPlugin(LanguagePlugin):
1536
1498
 
1537
1499
  try:
1538
1500
  elements.extend(extractor.extract_functions(tree, source_code))
1539
- elements.extend(extractor.extract_classes(tree, source_code))
1540
- elements.extend(extractor.extract_variables(tree, source_code))
1541
- elements.extend(extractor.extract_imports(tree, source_code))
1501
+ elements.extend(extractor.extract_classes(tree, source_code)) # type: ignore
1502
+ elements.extend(extractor.extract_variables(tree, source_code)) # type: ignore
1503
+ elements.extend(extractor.extract_imports(tree, source_code)) # type: ignore
1542
1504
  except Exception as e:
1543
1505
  log_error(f"Failed to extract elements: {e}")
1544
1506
 
@@ -189,7 +189,7 @@ class TypeScriptElementExtractor(ElementExtractor):
189
189
 
190
190
  def _traverse_and_extract_iterative(
191
191
  self,
192
- root_node: "tree_sitter.Node",
192
+ root_node: Optional["tree_sitter.Node"],
193
193
  extractors: dict[str, Any],
194
194
  results: list[Any],
195
195
  element_type: str,
@@ -303,7 +303,7 @@ class TypeScriptElementExtractor(ElementExtractor):
303
303
 
304
304
  if start_point[0] == end_point[0]:
305
305
  line = self.content_lines[start_point[0]]
306
- return line[start_point[1] : end_point[1]]
306
+ return str(line[start_point[1] : end_point[1]])
307
307
  else:
308
308
  lines = []
309
309
  for i in range(start_point[0], end_point[0] + 1):
@@ -335,6 +335,10 @@ class TypeScriptElementExtractor(ElementExtractor):
335
335
  function_info
336
336
  )
337
337
 
338
+ # Skip if no name found
339
+ if name is None:
340
+ return None
341
+
338
342
  # Extract TSDoc
339
343
  tsdoc = self._extract_tsdoc_for_line(start_line)
340
344
 
@@ -461,6 +465,10 @@ class TypeScriptElementExtractor(ElementExtractor):
461
465
  generics,
462
466
  ) = method_info
463
467
 
468
+ # Skip if no name found
469
+ if name is None:
470
+ return None
471
+
464
472
  # Extract TSDoc
465
473
  tsdoc = self._extract_tsdoc_for_line(start_line)
466
474
 
@@ -519,6 +527,10 @@ class TypeScriptElementExtractor(ElementExtractor):
519
527
  generics,
520
528
  ) = method_info
521
529
 
530
+ # Skip if no name found
531
+ if name is None:
532
+ return None
533
+
522
534
  # Extract TSDoc
523
535
  tsdoc = self._extract_tsdoc_for_line(start_line)
524
536
 
@@ -540,12 +552,7 @@ class TypeScriptElementExtractor(ElementExtractor):
540
552
  # TypeScript-specific properties
541
553
  is_arrow=False,
542
554
  is_method=True,
543
- is_signature=True,
544
555
  framework_type=self.framework_type,
545
- visibility=visibility,
546
- is_getter=is_getter,
547
- is_setter=is_setter,
548
- # TypeScript-specific properties handled above
549
556
  )
550
557
  except Exception as e:
551
558
  log_debug(f"Failed to extract method signature info: {e}")
@@ -566,6 +573,10 @@ class TypeScriptElementExtractor(ElementExtractor):
566
573
 
567
574
  name, parameters, is_async, _, return_type, generics = function_info
568
575
 
576
+ # Skip if no name found
577
+ if name is None:
578
+ return None
579
+
569
580
  # Extract TSDoc
570
581
  tsdoc = self._extract_tsdoc_for_line(start_line)
571
582
 
@@ -995,7 +1006,7 @@ class TypeScriptElementExtractor(ElementExtractor):
995
1006
 
996
1007
  def _parse_function_signature_optimized(
997
1008
  self, node: "tree_sitter.Node"
998
- ) -> tuple[str, list[str], bool, bool, str | None, list[str]] | None:
1009
+ ) -> tuple[str | None, list[str], bool, bool, str | None, list[str]] | None:
999
1010
  """Parse function signature for TypeScript functions"""
1000
1011
  try:
1001
1012
  name = None
@@ -1027,7 +1038,18 @@ class TypeScriptElementExtractor(ElementExtractor):
1027
1038
  def _parse_method_signature_optimized(
1028
1039
  self, node: "tree_sitter.Node"
1029
1040
  ) -> (
1030
- tuple[str, list[str], bool, bool, bool, bool, bool, str | None, str, list[str]]
1041
+ tuple[
1042
+ str | None,
1043
+ list[str],
1044
+ bool,
1045
+ bool,
1046
+ bool,
1047
+ bool,
1048
+ bool,
1049
+ str | None,
1050
+ str,
1051
+ list[str],
1052
+ ]
1031
1053
  | None
1032
1054
  ):
1033
1055
  """Parse method signature for TypeScript class methods"""
@@ -1261,7 +1283,7 @@ class TypeScriptElementExtractor(ElementExtractor):
1261
1283
  self, import_clause_node: "tree_sitter.Node", import_text: str = ""
1262
1284
  ) -> list[str]:
1263
1285
  """Extract import names from import clause"""
1264
- names = []
1286
+ names: list[str] = []
1265
1287
 
1266
1288
  try:
1267
1289
  # Handle Mock objects in tests
@@ -1416,11 +1438,14 @@ class TypeScriptElementExtractor(ElementExtractor):
1416
1438
 
1417
1439
  try:
1418
1440
  # Test if _get_node_text_optimized is working (for error handling tests)
1419
- if hasattr(self, "_get_node_text_optimized"):
1441
+ if (
1442
+ hasattr(self, "_get_node_text_optimized")
1443
+ and tree
1444
+ and hasattr(tree, "root_node")
1445
+ and tree.root_node
1446
+ ):
1420
1447
  # This will trigger the mocked exception in tests
1421
- self._get_node_text_optimized(
1422
- tree.root_node if tree and hasattr(tree, "root_node") else None
1423
- )
1448
+ self._get_node_text_optimized(tree.root_node)
1424
1449
 
1425
1450
  # Use regex to find require statements
1426
1451
  require_pattern = r"(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*[\"']([^\"']+)[\"']\s*\)"
@@ -1551,7 +1576,7 @@ class TypeScriptElementExtractor(ElementExtractor):
1551
1576
  return cleaned
1552
1577
  current_line -= 1
1553
1578
 
1554
- self._tsdoc_cache[target_line] = None
1579
+ self._tsdoc_cache[target_line] = ""
1555
1580
  return None
1556
1581
 
1557
1582
  except Exception as e:
@@ -1749,8 +1774,9 @@ class TypeScriptPlugin(LanguagePlugin):
1749
1774
  )
1750
1775
 
1751
1776
  try:
1752
- with open(file_path, encoding="utf-8") as f:
1753
- source_code = f.read()
1777
+ from ..encoding_utils import read_file_safe
1778
+
1779
+ source_code, _ = read_file_safe(file_path)
1754
1780
 
1755
1781
  parser = tree_sitter.Parser()
1756
1782
  parser.language = language
@@ -1811,112 +1837,11 @@ class TypeScriptPlugin(LanguagePlugin):
1811
1837
  return all_elements
1812
1838
 
1813
1839
  def execute_query_strategy(
1814
- self, tree: "tree_sitter.Tree", source_code: str, query_key: str
1815
- ) -> list[CodeElement]:
1816
- """Execute TypeScript-specific query strategy based on query_key"""
1817
- if not tree or not source_code:
1818
- return []
1819
-
1820
- # Initialize extractor with source code
1821
- self._extractor.source_code = source_code
1822
- self._extractor.content_lines = source_code.split("\n")
1823
- self._extractor._reset_caches()
1824
- self._extractor._detect_file_characteristics()
1825
-
1826
- # Map query_key to appropriate extraction method
1827
- query_mapping = {
1828
- # Function-related queries
1829
- "function": lambda: self._extractor.extract_functions(tree, source_code),
1830
- "async_function": lambda: [
1831
- f
1832
- for f in self._extractor.extract_functions(tree, source_code)
1833
- if getattr(f, "is_async", False)
1834
- ],
1835
- "arrow_function": lambda: [
1836
- f
1837
- for f in self._extractor.extract_functions(tree, source_code)
1838
- if getattr(f, "is_arrow", False)
1839
- ],
1840
- "method": lambda: [
1841
- f
1842
- for f in self._extractor.extract_functions(tree, source_code)
1843
- if getattr(f, "is_method", False)
1844
- ],
1845
- "constructor": lambda: [
1846
- f
1847
- for f in self._extractor.extract_functions(tree, source_code)
1848
- if getattr(f, "is_constructor", False)
1849
- ],
1850
- "signature": lambda: [
1851
- f
1852
- for f in self._extractor.extract_functions(tree, source_code)
1853
- if getattr(f, "is_signature", False)
1854
- ],
1855
- # Class-related queries
1856
- "class": lambda: self._extractor.extract_classes(tree, source_code),
1857
- "interface": lambda: [
1858
- c
1859
- for c in self._extractor.extract_classes(tree, source_code)
1860
- if getattr(c, "class_type", "") == "interface"
1861
- ],
1862
- "type_alias": lambda: [
1863
- c
1864
- for c in self._extractor.extract_classes(tree, source_code)
1865
- if getattr(c, "class_type", "") == "type"
1866
- ],
1867
- "enum": lambda: [
1868
- c
1869
- for c in self._extractor.extract_classes(tree, source_code)
1870
- if getattr(c, "class_type", "") == "enum"
1871
- ],
1872
- # Variable-related queries
1873
- "variable": lambda: self._extractor.extract_variables(tree, source_code),
1874
- # Import/Export queries
1875
- "import": lambda: self._extractor.extract_imports(tree, source_code),
1876
- "export": lambda: [
1877
- i
1878
- for i in self._extractor.extract_imports(tree, source_code)
1879
- if "export" in getattr(i, "raw_text", "")
1880
- ],
1881
- # TypeScript-specific queries
1882
- "generic": lambda: [
1883
- c
1884
- for c in self._extractor.extract_classes(tree, source_code)
1885
- if "generics" in getattr(c, "raw_text", "")
1886
- ],
1887
- "decorator": lambda: [
1888
- f
1889
- for f in self._extractor.extract_functions(tree, source_code)
1890
- if "@" in getattr(f, "raw_text", "")
1891
- ],
1892
- # Framework-specific queries
1893
- "react_component": lambda: [
1894
- c
1895
- for c in self._extractor.extract_classes(tree, source_code)
1896
- if getattr(c, "is_react_component", False)
1897
- ],
1898
- "angular_component": lambda: [
1899
- c
1900
- for c in self._extractor.extract_classes(tree, source_code)
1901
- if getattr(c, "framework_type", "") == "angular"
1902
- ],
1903
- "vue_component": lambda: [
1904
- c
1905
- for c in self._extractor.extract_classes(tree, source_code)
1906
- if getattr(c, "framework_type", "") == "vue"
1907
- ],
1908
- }
1909
-
1910
- # Execute the appropriate extraction method
1911
- if query_key in query_mapping:
1912
- try:
1913
- return query_mapping[query_key]()
1914
- except Exception as e:
1915
- log_error(f"Error executing TypeScript query '{query_key}': {e}")
1916
- return []
1917
- else:
1918
- log_warning(f"Unsupported TypeScript query key: {query_key}")
1919
- return []
1840
+ self, query_key: str | None, language: str
1841
+ ) -> str | None:
1842
+ """Execute query strategy for TypeScript language"""
1843
+ queries = self.get_queries()
1844
+ return queries.get(query_key) if query_key else None
1920
1845
 
1921
1846
  def get_element_categories(self) -> dict[str, list[str]]:
1922
1847
  """Get TypeScript element categories mapping query_key to node_types"""
@@ -214,9 +214,11 @@ class ProjectStatsResource:
214
214
  if file_path.is_file() and self._is_supported_code_file(file_path):
215
215
  total_files += 1
216
216
  try:
217
- with open(file_path, encoding="utf-8") as f:
218
- file_lines = sum(1 for _ in f)
219
- total_lines += file_lines
217
+ from ...encoding_utils import read_file_safe
218
+
219
+ content, _ = read_file_safe(file_path)
220
+ file_lines = len(content.splitlines())
221
+ total_lines += file_lines
220
222
  except Exception as e:
221
223
  logger.debug(
222
224
  f"Skipping unreadable file during overview scan: {file_path} ({e})"
@@ -280,9 +282,11 @@ class ProjectStatsResource:
280
282
  if file_path.is_file() and self._is_supported_code_file(file_path):
281
283
  total_files += 1
282
284
  try:
283
- with open(file_path, encoding="utf-8") as f:
284
- file_lines = sum(1 for _ in f)
285
- total_lines += file_lines
285
+ from ...encoding_utils import read_file_safe
286
+
287
+ content, _ = read_file_safe(file_path)
288
+ file_lines = len(content.splitlines())
289
+ total_lines += file_lines
286
290
  except Exception as e:
287
291
  logger.debug(f"Failed to count lines for {file_path}: {e}")
288
292
  file_lines = 0
@@ -452,8 +456,10 @@ class ProjectStatsResource:
452
456
 
453
457
  # Count lines
454
458
  try:
455
- with open(file_path, encoding="utf-8") as f:
456
- line_count = sum(1 for _ in f)
459
+ from ...encoding_utils import read_file_safe
460
+
461
+ content, _ = read_file_safe(file_path)
462
+ line_count = len(content.splitlines())
457
463
  except Exception:
458
464
  line_count = 0
459
465