tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.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.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/METADATA +21 -6
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  77. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,366 +1,374 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Language Registry
5
-
6
- Manages mapping between languages, file extensions, and plugins.
7
- Provides language detection and plugin resolution services.
8
- """
9
-
10
- from pathlib import Path
11
- from typing import Dict, List, Optional, Set
12
- import logging
13
-
14
- from .base import LanguagePlugin
15
- from ..utils import log_error, log_info, log_warning, log_debug
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class LanguageRegistry:
21
- """
22
- Registry for managing language-to-plugin mappings.
23
-
24
- This class handles:
25
- - Mapping languages to plugins
26
- - Mapping file extensions to languages
27
- - Language detection from file paths
28
- - Plugin resolution for languages
29
- """
30
-
31
- def __init__(self):
32
- """Initialize the language registry."""
33
- self._plugins: Dict[str, LanguagePlugin] = {}
34
- self._extension_map: Dict[str, str] = {} # extension -> language
35
- self._language_aliases: Dict[str, str] = {} # alias -> canonical_language
36
-
37
- # Initialize common language aliases
38
- self._init_language_aliases()
39
-
40
- def _init_language_aliases(self):
41
- """Initialize common language aliases."""
42
- self._language_aliases.update({
43
- 'js': 'javascript',
44
- 'ts': 'typescript',
45
- 'py': 'python',
46
- 'rb': 'ruby',
47
- 'cpp': 'c++',
48
- 'cxx': 'c++',
49
- 'cc': 'c++',
50
- 'c++': 'cpp', # Normalize c++ to cpp
51
- })
52
-
53
- def register_plugin(self, plugin: LanguagePlugin) -> bool:
54
- """
55
- Register a language plugin.
56
-
57
- Args:
58
- plugin: Plugin instance to register
59
-
60
- Returns:
61
- True if registration was successful
62
- """
63
- try:
64
- language = plugin.get_language_name().lower()
65
- extensions = plugin.get_file_extensions()
66
-
67
- # Check for conflicts
68
- if language in self._plugins:
69
- existing_plugin = self._plugins[language]
70
- log_warning(
71
- f"Language '{language}' already registered by {existing_plugin.__class__.__name__}, "
72
- f"replacing with {plugin.__class__.__name__}"
73
- )
74
-
75
- # Register the plugin
76
- self._plugins[language] = plugin
77
-
78
- # Register file extensions
79
- for ext in extensions:
80
- ext = ext.lower()
81
- if not ext.startswith('.'):
82
- ext = '.' + ext
83
-
84
- if ext in self._extension_map:
85
- existing_lang = self._extension_map[ext]
86
- log_debug(f"Extension '{ext}' already mapped to '{existing_lang}', overriding with '{language}'")
87
-
88
- self._extension_map[ext] = language
89
-
90
- log_debug(f"Registered plugin for language '{language}' with extensions: {extensions}")
91
- return True
92
-
93
- except Exception as e:
94
- log_error(f"Failed to register plugin: {e}")
95
- return False
96
-
97
- def get_plugin(self, language: str) -> Optional[LanguagePlugin]:
98
- """
99
- Get plugin for a specific language.
100
-
101
- Args:
102
- language: Programming language name
103
-
104
- Returns:
105
- Plugin instance or None if not found
106
- """
107
- # Normalize language name
108
- language = self._normalize_language(language)
109
- return self._plugins.get(language)
110
-
111
- def detect_language_from_file(self, file_path: Path) -> Optional[str]:
112
- """
113
- Detect programming language from file path.
114
-
115
- Args:
116
- file_path: Path to the file
117
-
118
- Returns:
119
- Detected language name or None
120
- """
121
- if file_path is None:
122
- return None
123
-
124
- if isinstance(file_path, str):
125
- file_path = Path(file_path)
126
-
127
- # Get file extension
128
- extension = file_path.suffix.lower()
129
-
130
- # Look up in extension map
131
- language = self._extension_map.get(extension)
132
- if language:
133
- return language
134
-
135
- # Try compound extensions (e.g., .test.js, .spec.ts)
136
- if len(file_path.suffixes) > 1:
137
- # Try the last extension
138
- last_ext = file_path.suffixes[-1].lower()
139
- language = self._extension_map.get(last_ext)
140
- if language:
141
- return language
142
-
143
- # Special cases based on filename patterns
144
- filename = file_path.name.lower()
145
-
146
- # Common configuration files
147
- config_patterns = {
148
- 'makefile': 'make',
149
- 'dockerfile': 'dockerfile',
150
- 'vagrantfile': 'ruby',
151
- 'rakefile': 'ruby',
152
- 'gemfile': 'ruby',
153
- 'podfile': 'ruby',
154
- }
155
-
156
- for pattern, lang in config_patterns.items():
157
- if filename == pattern or filename.startswith(pattern):
158
- if lang in self._plugins:
159
- return lang
160
-
161
- log_debug(f"Could not detect language for file: {file_path}")
162
- return None
163
-
164
- def get_supported_languages(self) -> List[str]:
165
- """
166
- Get list of all supported languages.
167
-
168
- Returns:
169
- List of supported language names
170
- """
171
- return list(self._plugins.keys())
172
-
173
- def get_supported_extensions(self) -> List[str]:
174
- """
175
- Get list of all supported file extensions.
176
-
177
- Returns:
178
- List of supported file extensions
179
- """
180
- return list(self._extension_map.keys())
181
-
182
- def get_extensions_for_language(self, language: str) -> List[str]:
183
- """
184
- Get file extensions for a specific language.
185
-
186
- Args:
187
- language: Programming language name
188
-
189
- Returns:
190
- List of file extensions for the language
191
- """
192
- language = self._normalize_language(language)
193
- plugin = self._plugins.get(language)
194
-
195
- if plugin:
196
- return plugin.get_file_extensions()
197
-
198
- return []
199
-
200
- def get_language_for_extension(self, extension: str) -> Optional[str]:
201
- """
202
- Get language for a specific file extension.
203
-
204
- Args:
205
- extension: File extension (with or without leading dot)
206
-
207
- Returns:
208
- Language name or None if not found
209
- """
210
- if not extension.startswith('.'):
211
- extension = '.' + extension
212
-
213
- return self._extension_map.get(extension.lower())
214
-
215
- def is_language_supported(self, language: str) -> bool:
216
- """
217
- Check if a language is supported.
218
-
219
- Args:
220
- language: Programming language name
221
-
222
- Returns:
223
- True if the language is supported
224
- """
225
- language = self._normalize_language(language)
226
- return language in self._plugins
227
-
228
- def is_extension_supported(self, extension: str) -> bool:
229
- """
230
- Check if a file extension is supported.
231
-
232
- Args:
233
- extension: File extension (with or without leading dot)
234
-
235
- Returns:
236
- True if the extension is supported
237
- """
238
- if not extension.startswith('.'):
239
- extension = '.' + extension
240
-
241
- return extension.lower() in self._extension_map
242
-
243
- def _normalize_language(self, language: str) -> str:
244
- """
245
- Normalize language name using aliases.
246
-
247
- Args:
248
- language: Language name to normalize
249
-
250
- Returns:
251
- Normalized language name
252
- """
253
- if language is None:
254
- return ""
255
- language = language.lower().strip()
256
- return self._language_aliases.get(language, language)
257
-
258
- def add_language_alias(self, alias: str, canonical_language: str) -> bool:
259
- """
260
- Add a language alias.
261
-
262
- Args:
263
- alias: Alias name
264
- canonical_language: Canonical language name
265
-
266
- Returns:
267
- True if alias was added successfully
268
- """
269
- try:
270
- alias = alias.lower().strip()
271
- canonical_language = canonical_language.lower().strip()
272
-
273
- if canonical_language not in self._plugins:
274
- log_warning(f"Cannot add alias '{alias}' for unsupported language '{canonical_language}'")
275
- return False
276
-
277
- self._language_aliases[alias] = canonical_language
278
- log_debug(f"Added language alias: '{alias}' -> '{canonical_language}'")
279
- return True
280
-
281
- except Exception as e:
282
- log_error(f"Failed to add language alias: {e}")
283
- return False
284
-
285
- def get_registry_info(self) -> Dict[str, any]:
286
- """
287
- Get comprehensive information about the registry.
288
-
289
- "plugin_count": len(self._plugins),
290
- Returns:
291
- Dictionary containing registry information
292
- """
293
- return {
294
- "plugin_count": len(self._plugins),
295
- "supported_languages": len(self._plugins),
296
- "supported_extensions": len(self._extension_map),
297
- "language_aliases": len(self._language_aliases),
298
- "languages": list(self._plugins.keys()),
299
- "extensions": list(self._extension_map.keys()),
300
- "aliases": dict(self._language_aliases),
301
- "extension_mapping": dict(self._extension_map)
302
- }
303
-
304
- def find_plugins_for_file(self, file_path: Path) -> List[LanguagePlugin]:
305
- """
306
- Find all possible plugins for a file (useful for ambiguous cases).
307
-
308
- Args:
309
- file_path: Path to the file
310
-
311
- Returns:
312
- List of possible plugins
313
- """
314
- plugins = []
315
-
316
- # Primary detection
317
- language = self.detect_language_from_file(file_path)
318
- if language:
319
- plugin = self.get_plugin(language)
320
- if plugin:
321
- plugins.append(plugin)
322
-
323
- # Check if any plugins explicitly support this file
324
- for plugin in self._plugins.values():
325
- if hasattr(plugin, 'is_applicable') and plugin.is_applicable(str(file_path)):
326
- if plugin not in plugins:
327
- plugins.append(plugin)
328
-
329
- return plugins
330
-
331
- def clear(self):
332
- """Clear all registered plugins and mappings."""
333
- self._plugins.clear()
334
- self._extension_map.clear()
335
- # Keep language aliases as they're static
336
- log_debug("Cleared language registry")
337
-
338
- def unregister_plugin(self, language: str) -> bool:
339
- """
340
- Unregister a plugin for a specific language.
341
-
342
- Args:
343
- language: Programming language name
344
-
345
- Returns:
346
- True if unregistration was successful
347
- """
348
- language = self._normalize_language(language)
349
-
350
- if language not in self._plugins:
351
- return False
352
-
353
- # Remove the plugin
354
- plugin = self._plugins.pop(language)
355
-
356
- # Remove associated extensions
357
- extensions_to_remove = []
358
- for ext, lang in self._extension_map.items():
359
- if lang == language:
360
- extensions_to_remove.append(ext)
361
-
362
- for ext in extensions_to_remove:
363
- del self._extension_map[ext]
364
-
365
- log_debug(f"Unregistered plugin for language '{language}'")
366
- return True
1
+ #!/usr/bin/env python3
2
+ """
3
+ Language Registry
4
+
5
+ Manages mapping between languages, file extensions, and plugins.
6
+ Provides language detection and plugin resolution services.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+
12
+ from ..utils import log_debug, log_error, log_warning
13
+ from .base import LanguagePlugin
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class LanguageRegistry:
19
+ """
20
+ Registry for managing language-to-plugin mappings.
21
+
22
+ This class handles:
23
+ - Mapping languages to plugins
24
+ - Mapping file extensions to languages
25
+ - Language detection from file paths
26
+ - Plugin resolution for languages
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize the language registry."""
31
+ self._plugins: dict[str, LanguagePlugin] = {}
32
+ self._extension_map: dict[str, str] = {} # extension -> language
33
+ self._language_aliases: dict[str, str] = {} # alias -> canonical_language
34
+
35
+ # Initialize common language aliases
36
+ self._init_language_aliases()
37
+
38
+ def _init_language_aliases(self):
39
+ """Initialize common language aliases."""
40
+ self._language_aliases.update(
41
+ {
42
+ "js": "javascript",
43
+ "ts": "typescript",
44
+ "py": "python",
45
+ "rb": "ruby",
46
+ "cpp": "c++",
47
+ "cxx": "c++",
48
+ "cc": "c++",
49
+ "c++": "cpp", # Normalize c++ to cpp
50
+ }
51
+ )
52
+
53
+ def register_plugin(self, plugin: LanguagePlugin) -> bool:
54
+ """
55
+ Register a language plugin.
56
+
57
+ Args:
58
+ plugin: Plugin instance to register
59
+
60
+ Returns:
61
+ True if registration was successful
62
+ """
63
+ try:
64
+ language = plugin.get_language_name().lower()
65
+ extensions = plugin.get_file_extensions()
66
+
67
+ # Check for conflicts
68
+ if language in self._plugins:
69
+ existing_plugin = self._plugins[language]
70
+ log_warning(
71
+ f"Language '{language}' already registered by {existing_plugin.__class__.__name__}, "
72
+ f"replacing with {plugin.__class__.__name__}"
73
+ )
74
+
75
+ # Register the plugin
76
+ self._plugins[language] = plugin
77
+
78
+ # Register file extensions
79
+ for ext in extensions:
80
+ ext = ext.lower()
81
+ if not ext.startswith("."):
82
+ ext = "." + ext
83
+
84
+ if ext in self._extension_map:
85
+ existing_lang = self._extension_map[ext]
86
+ log_debug(
87
+ f"Extension '{ext}' already mapped to '{existing_lang}', overriding with '{language}'"
88
+ )
89
+
90
+ self._extension_map[ext] = language
91
+
92
+ log_debug(
93
+ f"Registered plugin for language '{language}' with extensions: {extensions}"
94
+ )
95
+ return True
96
+
97
+ except Exception as e:
98
+ log_error(f"Failed to register plugin: {e}")
99
+ return False
100
+
101
+ def get_plugin(self, language: str) -> LanguagePlugin | None:
102
+ """
103
+ Get plugin for a specific language.
104
+
105
+ Args:
106
+ language: Programming language name
107
+
108
+ Returns:
109
+ Plugin instance or None if not found
110
+ """
111
+ # Normalize language name
112
+ language = self._normalize_language(language)
113
+ return self._plugins.get(language)
114
+
115
+ def detect_language_from_file(self, file_path: Path) -> str | None:
116
+ """
117
+ Detect programming language from file path.
118
+
119
+ Args:
120
+ file_path: Path to the file
121
+
122
+ Returns:
123
+ Detected language name or None
124
+ """
125
+ if file_path is None:
126
+ return None
127
+
128
+ if isinstance(file_path, str):
129
+ file_path = Path(file_path)
130
+
131
+ # Get file extension
132
+ extension = file_path.suffix.lower()
133
+
134
+ # Look up in extension map
135
+ language = self._extension_map.get(extension)
136
+ if language:
137
+ return language
138
+
139
+ # Try compound extensions (e.g., .test.js, .spec.ts)
140
+ if len(file_path.suffixes) > 1:
141
+ # Try the last extension
142
+ last_ext = file_path.suffixes[-1].lower()
143
+ language = self._extension_map.get(last_ext)
144
+ if language:
145
+ return language
146
+
147
+ # Special cases based on filename patterns
148
+ filename = file_path.name.lower()
149
+
150
+ # Common configuration files
151
+ config_patterns = {
152
+ "makefile": "make",
153
+ "dockerfile": "dockerfile",
154
+ "vagrantfile": "ruby",
155
+ "rakefile": "ruby",
156
+ "gemfile": "ruby",
157
+ "podfile": "ruby",
158
+ }
159
+
160
+ for pattern, lang in config_patterns.items():
161
+ if filename == pattern or filename.startswith(pattern):
162
+ if lang in self._plugins:
163
+ return lang
164
+
165
+ log_debug(f"Could not detect language for file: {file_path}")
166
+ return None
167
+
168
+ def get_supported_languages(self) -> list[str]:
169
+ """
170
+ Get list of all supported languages.
171
+
172
+ Returns:
173
+ List of supported language names
174
+ """
175
+ return list(self._plugins.keys())
176
+
177
+ def get_supported_extensions(self) -> list[str]:
178
+ """
179
+ Get list of all supported file extensions.
180
+
181
+ Returns:
182
+ List of supported file extensions
183
+ """
184
+ return list(self._extension_map.keys())
185
+
186
+ def get_extensions_for_language(self, language: str) -> list[str]:
187
+ """
188
+ Get file extensions for a specific language.
189
+
190
+ Args:
191
+ language: Programming language name
192
+
193
+ Returns:
194
+ List of file extensions for the language
195
+ """
196
+ language = self._normalize_language(language)
197
+ plugin = self._plugins.get(language)
198
+
199
+ if plugin:
200
+ return plugin.get_file_extensions()
201
+
202
+ return []
203
+
204
+ def get_language_for_extension(self, extension: str) -> str | None:
205
+ """
206
+ Get language for a specific file extension.
207
+
208
+ Args:
209
+ extension: File extension (with or without leading dot)
210
+
211
+ Returns:
212
+ Language name or None if not found
213
+ """
214
+ if not extension.startswith("."):
215
+ extension = "." + extension
216
+
217
+ return self._extension_map.get(extension.lower())
218
+
219
+ def is_language_supported(self, language: str) -> bool:
220
+ """
221
+ Check if a language is supported.
222
+
223
+ Args:
224
+ language: Programming language name
225
+
226
+ Returns:
227
+ True if the language is supported
228
+ """
229
+ language = self._normalize_language(language)
230
+ return language in self._plugins
231
+
232
+ def is_extension_supported(self, extension: str) -> bool:
233
+ """
234
+ Check if a file extension is supported.
235
+
236
+ Args:
237
+ extension: File extension (with or without leading dot)
238
+
239
+ Returns:
240
+ True if the extension is supported
241
+ """
242
+ if not extension.startswith("."):
243
+ extension = "." + extension
244
+
245
+ return extension.lower() in self._extension_map
246
+
247
+ def _normalize_language(self, language: str) -> str:
248
+ """
249
+ Normalize language name using aliases.
250
+
251
+ Args:
252
+ language: Language name to normalize
253
+
254
+ Returns:
255
+ Normalized language name
256
+ """
257
+ if language is None:
258
+ return ""
259
+ language = language.lower().strip()
260
+ return self._language_aliases.get(language, language)
261
+
262
+ def add_language_alias(self, alias: str, canonical_language: str) -> bool:
263
+ """
264
+ Add a language alias.
265
+
266
+ Args:
267
+ alias: Alias name
268
+ canonical_language: Canonical language name
269
+
270
+ Returns:
271
+ True if alias was added successfully
272
+ """
273
+ try:
274
+ alias = alias.lower().strip()
275
+ canonical_language = canonical_language.lower().strip()
276
+
277
+ if canonical_language not in self._plugins:
278
+ log_warning(
279
+ f"Cannot add alias '{alias}' for unsupported language '{canonical_language}'"
280
+ )
281
+ return False
282
+
283
+ self._language_aliases[alias] = canonical_language
284
+ log_debug(f"Added language alias: '{alias}' -> '{canonical_language}'")
285
+ return True
286
+
287
+ except Exception as e:
288
+ log_error(f"Failed to add language alias: {e}")
289
+ return False
290
+
291
+ def get_registry_info(self) -> dict[str, any]:
292
+ """
293
+ Get comprehensive information about the registry.
294
+
295
+ "plugin_count": len(self._plugins),
296
+ Returns:
297
+ Dictionary containing registry information
298
+ """
299
+ return {
300
+ "plugin_count": len(self._plugins),
301
+ "supported_languages": len(self._plugins),
302
+ "supported_extensions": len(self._extension_map),
303
+ "language_aliases": len(self._language_aliases),
304
+ "languages": list(self._plugins.keys()),
305
+ "extensions": list(self._extension_map.keys()),
306
+ "aliases": dict(self._language_aliases),
307
+ "extension_mapping": dict(self._extension_map),
308
+ }
309
+
310
+ def find_plugins_for_file(self, file_path: Path) -> list[LanguagePlugin]:
311
+ """
312
+ Find all possible plugins for a file (useful for ambiguous cases).
313
+
314
+ Args:
315
+ file_path: Path to the file
316
+
317
+ Returns:
318
+ List of possible plugins
319
+ """
320
+ plugins = []
321
+
322
+ # Primary detection
323
+ language = self.detect_language_from_file(file_path)
324
+ if language:
325
+ plugin = self.get_plugin(language)
326
+ if plugin:
327
+ plugins.append(plugin)
328
+
329
+ # Check if any plugins explicitly support this file
330
+ for plugin in self._plugins.values():
331
+ if hasattr(plugin, "is_applicable") and plugin.is_applicable(
332
+ str(file_path)
333
+ ):
334
+ if plugin not in plugins:
335
+ plugins.append(plugin)
336
+
337
+ return plugins
338
+
339
+ def clear(self):
340
+ """Clear all registered plugins and mappings."""
341
+ self._plugins.clear()
342
+ self._extension_map.clear()
343
+ # Keep language aliases as they're static
344
+ log_debug("Cleared language registry")
345
+
346
+ def unregister_plugin(self, language: str) -> bool:
347
+ """
348
+ Unregister a plugin for a specific language.
349
+
350
+ Args:
351
+ language: Programming language name
352
+
353
+ Returns:
354
+ True if unregistration was successful
355
+ """
356
+ language = self._normalize_language(language)
357
+
358
+ if language not in self._plugins:
359
+ return False
360
+
361
+ # Remove the plugin
362
+ self._plugins.pop(language)
363
+
364
+ # Remove associated extensions
365
+ extensions_to_remove = []
366
+ for ext, lang in self._extension_map.items():
367
+ if lang == language:
368
+ extensions_to_remove.append(ext)
369
+
370
+ for ext in extensions_to_remove:
371
+ del self._extension_map[ext]
372
+
373
+ log_debug(f"Unregistered plugin for language '{language}'")
374
+ return True