tree-sitter-analyzer 1.9.17.1__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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Gitignore Detection Utility
4
+
5
+ Intelligently detects when .gitignore rules might interfere with file searches
6
+ and suggests using --no-ignore option when appropriate.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from pathlib import Path
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class GitignoreDetector:
17
+ """Detects .gitignore interference with file searches"""
18
+
19
+ def __init__(self) -> None:
20
+ self.common_ignore_patterns = {
21
+ # Directory patterns that commonly cause search issues
22
+ "build/*",
23
+ "dist/*",
24
+ "node_modules/*",
25
+ "__pycache__/*",
26
+ "target/*",
27
+ ".git/*",
28
+ ".svn/*",
29
+ ".hg/*",
30
+ "code/*",
31
+ "src/*",
32
+ "lib/*",
33
+ "app/*", # Added code/* which is our case
34
+ }
35
+
36
+ def should_use_no_ignore(
37
+ self, roots: list[str], project_root: str | None = None
38
+ ) -> bool:
39
+ """
40
+ Determine if --no-ignore should be used based on search context
41
+
42
+ Args:
43
+ roots: List of root directories to search
44
+ project_root: Optional project root directory
45
+
46
+ Returns:
47
+ True if --no-ignore should be used
48
+ """
49
+ # Only apply auto-detection for root directory searches
50
+ if not (len(roots) == 1 and roots[0] in [".", "./"]):
51
+ return False
52
+
53
+ if not project_root:
54
+ return False
55
+
56
+ try:
57
+ project_path = Path(project_root).resolve()
58
+
59
+ # Check for .gitignore files that might interfere
60
+ gitignore_files = self._find_gitignore_files(project_path)
61
+
62
+ for gitignore_file in gitignore_files:
63
+ # Use the directory containing the .gitignore as the reference point
64
+ gitignore_dir = gitignore_file.parent
65
+ if self._has_interfering_patterns(
66
+ gitignore_file, gitignore_dir, project_path
67
+ ):
68
+ logger.debug(
69
+ f"Found interfering .gitignore patterns in {gitignore_file}"
70
+ )
71
+ return True
72
+
73
+ return False
74
+
75
+ except Exception as e:
76
+ logger.warning(f"Error detecting .gitignore interference: {e}")
77
+ return False
78
+
79
+ def _find_gitignore_files(self, project_path: Path) -> list[Path]:
80
+ """Find .gitignore files in project hierarchy"""
81
+ gitignore_files = []
82
+
83
+ # Check current directory and parent directories
84
+ current = project_path
85
+ max_depth = 3 # Limit search depth
86
+
87
+ # For temporary directories (like test directories), only check the current directory
88
+ # to avoid finding .gitignore files in parent directories that are not part of the test
89
+ if "tmp" in str(current).lower() or "temp" in str(current).lower():
90
+ gitignore_path = current / ".gitignore"
91
+ if gitignore_path.exists():
92
+ gitignore_files.append(gitignore_path)
93
+ return gitignore_files
94
+
95
+ for _ in range(max_depth):
96
+ gitignore_path = current / ".gitignore"
97
+ if gitignore_path.exists():
98
+ gitignore_files.append(gitignore_path)
99
+
100
+ parent = current.parent
101
+ if parent == current: # Reached root
102
+ break
103
+ current = parent
104
+
105
+ return gitignore_files
106
+
107
+ def _has_interfering_patterns(
108
+ self, gitignore_file: Path, gitignore_dir: Path, current_search_dir: Path
109
+ ) -> bool:
110
+ """
111
+ Check if .gitignore file has patterns that might interfere with searches
112
+
113
+ Args:
114
+ gitignore_file: Path to the .gitignore file
115
+ gitignore_dir: Directory containing the .gitignore file
116
+ current_search_dir: Directory where the search is being performed
117
+ """
118
+ try:
119
+ from ...encoding_utils import read_file_safe
120
+
121
+ content, _ = read_file_safe(gitignore_file)
122
+ lines = content.splitlines()
123
+
124
+ for line in lines:
125
+ line = line.strip()
126
+
127
+ # Skip comments and empty lines
128
+ if not line or line.startswith("#"):
129
+ continue
130
+
131
+ # Check for patterns that commonly cause search issues
132
+ if self._is_interfering_pattern(
133
+ line, gitignore_dir, current_search_dir
134
+ ):
135
+ logger.debug(f"Found interfering pattern: {line}")
136
+ return True
137
+
138
+ return False
139
+
140
+ except Exception as e:
141
+ logger.warning(f"Error reading .gitignore file {gitignore_file}: {e}")
142
+ return False
143
+
144
+ def _is_interfering_pattern(
145
+ self, pattern: str, gitignore_dir: Path, current_search_dir: Path
146
+ ) -> bool:
147
+ """
148
+ Check if a gitignore pattern is likely to interfere with searches
149
+
150
+ Args:
151
+ pattern: The gitignore pattern
152
+ gitignore_dir: Directory containing the .gitignore file
153
+ current_search_dir: Directory where the search is being performed
154
+ """
155
+ # Remove leading slash
156
+ pattern = pattern.lstrip("/")
157
+
158
+ # Check for broad directory exclusions that contain searchable files
159
+ if pattern.endswith("/*") or pattern.endswith("/"):
160
+ dir_name = pattern.rstrip("/*")
161
+
162
+ # Check if the pattern affects the current search directory
163
+ pattern_dir = gitignore_dir / dir_name
164
+
165
+ # If we're searching in a subdirectory that would be ignored by this pattern
166
+ if self._is_search_dir_affected_by_pattern(
167
+ current_search_dir, pattern_dir, gitignore_dir
168
+ ):
169
+ # For testing purposes, consider it interfering if the directory exists
170
+ if pattern_dir.exists() and pattern_dir.is_dir():
171
+ logger.debug(
172
+ f"Pattern '{pattern}' interferes with search - directory exists"
173
+ )
174
+ return True
175
+
176
+ # Check for patterns that ignore entire source directories
177
+ source_dirs = [
178
+ "code",
179
+ "src",
180
+ "lib",
181
+ "app",
182
+ "main",
183
+ "java",
184
+ "python",
185
+ "js",
186
+ "ts",
187
+ ]
188
+ pattern_dir_name = pattern.rstrip("/*")
189
+ if pattern_dir_name in source_dirs:
190
+ # Always consider source directory patterns as interfering
191
+ logger.debug(
192
+ f"Pattern '{pattern}' interferes with search - ignores source directory"
193
+ )
194
+ return True
195
+
196
+ # Check for leading slash patterns (absolute paths from repo root)
197
+ if pattern.startswith("/") or "/" in pattern:
198
+ logger.debug(
199
+ f"Pattern '{pattern}' interferes with search - absolute path pattern"
200
+ )
201
+ return True
202
+
203
+ return False
204
+
205
+ def _is_search_dir_affected_by_pattern(
206
+ self, search_dir: Path, pattern_dir: Path, gitignore_dir: Path
207
+ ) -> bool:
208
+ """Check if the search directory would be affected by a gitignore pattern"""
209
+ try:
210
+ # Check if paths exist before resolving
211
+ if not search_dir.exists() or not pattern_dir.exists():
212
+ logger.debug(
213
+ f"Path does not exist: {search_dir} or {pattern_dir}, assuming affected"
214
+ )
215
+ return True
216
+
217
+ # If search_dir is the same as pattern_dir or is a subdirectory of pattern_dir
218
+ search_resolved = search_dir.resolve()
219
+ pattern_resolved = pattern_dir.resolve()
220
+
221
+ # Check if we're searching in the directory that would be ignored
222
+ return search_resolved == pattern_resolved or str(
223
+ search_resolved
224
+ ).startswith(str(pattern_resolved) + os.sep)
225
+ except (OSError, ValueError, RuntimeError):
226
+ # If path resolution fails, assume it could be affected
227
+ logger.debug(
228
+ f"Path resolution failed for {search_dir} or {pattern_dir}, assuming affected"
229
+ )
230
+ return True
231
+
232
+ def _directory_has_searchable_files(self, directory: Path) -> bool:
233
+ """Check if directory contains files that users typically want to search"""
234
+ searchable_extensions = {
235
+ ".java",
236
+ ".py",
237
+ ".js",
238
+ ".ts",
239
+ ".cpp",
240
+ ".c",
241
+ ".h",
242
+ ".cs",
243
+ ".go",
244
+ ".rs",
245
+ }
246
+
247
+ try:
248
+ # Quick check - look for any files with searchable extensions
249
+ for file_path in directory.rglob("*"):
250
+ if (
251
+ file_path.is_file()
252
+ and file_path.suffix.lower() in searchable_extensions
253
+ ):
254
+ return True
255
+ return False
256
+ except Exception:
257
+ # If we can't scan, assume it might have searchable files
258
+ return True
259
+
260
+ def get_detection_info(
261
+ self, roots: list[str], project_root: str | None = None
262
+ ) -> dict[str, object]:
263
+ """
264
+ Get detailed information about gitignore detection
265
+
266
+ Returns:
267
+ Dictionary with detection details for debugging/logging
268
+ """
269
+ info: dict[str, object] = {
270
+ "should_use_no_ignore": False,
271
+ "detected_gitignore_files": [],
272
+ "interfering_patterns": [],
273
+ "reason": "No interference detected",
274
+ }
275
+
276
+ if not (len(roots) == 1 and roots[0] in [".", "./"]):
277
+ info["reason"] = "Not a root directory search"
278
+ return info
279
+
280
+ if not project_root:
281
+ info["reason"] = "No project root specified"
282
+ return info
283
+
284
+ try:
285
+ project_path = Path(project_root).resolve()
286
+ # Check if project path exists
287
+ if not project_path.exists():
288
+ raise FileNotFoundError(f"Project root does not exist: {project_root}")
289
+
290
+ gitignore_files = self._find_gitignore_files(project_path)
291
+ info["detected_gitignore_files"] = [str(f) for f in gitignore_files]
292
+
293
+ for gitignore_file in gitignore_files:
294
+ gitignore_dir = gitignore_file.parent
295
+ patterns = self._get_interfering_patterns(
296
+ gitignore_file, gitignore_dir, project_path
297
+ )
298
+ if patterns:
299
+ existing_patterns = info.get("interfering_patterns", [])
300
+ if isinstance(existing_patterns, list):
301
+ info["interfering_patterns"] = existing_patterns + patterns
302
+ else:
303
+ info["interfering_patterns"] = patterns
304
+
305
+ interfering_patterns = info.get("interfering_patterns", [])
306
+ if interfering_patterns:
307
+ info["should_use_no_ignore"] = True
308
+ pattern_count = (
309
+ len(interfering_patterns)
310
+ if isinstance(interfering_patterns, list)
311
+ else 0
312
+ )
313
+ info["reason"] = f"Found {pattern_count} interfering patterns"
314
+
315
+ except Exception as e:
316
+ info["reason"] = f"Error during detection: {e}"
317
+
318
+ return info
319
+
320
+ def _get_interfering_patterns(
321
+ self, gitignore_file: Path, gitignore_dir: Path, current_search_dir: Path
322
+ ) -> list[str]:
323
+ """Get list of interfering patterns from a gitignore file"""
324
+ interfering = []
325
+
326
+ try:
327
+ from ...encoding_utils import read_file_safe
328
+
329
+ content, _ = read_file_safe(gitignore_file)
330
+ lines = content.splitlines()
331
+
332
+ for line in lines:
333
+ line = line.strip()
334
+ if (
335
+ line
336
+ and not line.startswith("#")
337
+ and self._is_interfering_pattern(
338
+ line, gitignore_dir, current_search_dir
339
+ )
340
+ ):
341
+ interfering.append(line)
342
+
343
+ except Exception as e:
344
+ logger.warning(f"Error reading .gitignore file {gitignore_file}: {e}")
345
+
346
+ return interfering
347
+
348
+
349
+ # Global instance for easy access
350
+ _default_detector = None
351
+
352
+
353
+ def get_default_detector() -> GitignoreDetector:
354
+ """Get the default gitignore detector instance"""
355
+ global _default_detector
356
+ if _default_detector is None:
357
+ _default_detector = GitignoreDetector()
358
+ return _default_detector