tree-sitter-analyzer 0.9.2__py3-none-any.whl → 0.9.4__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 (37) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/cli/commands/base_command.py +2 -3
  3. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  5. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  6. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  7. tree_sitter_analyzer/cli/info_commands.py +121 -121
  8. tree_sitter_analyzer/cli_main.py +307 -303
  9. tree_sitter_analyzer/core/analysis_engine.py +584 -576
  10. tree_sitter_analyzer/core/cache_service.py +6 -5
  11. tree_sitter_analyzer/core/query.py +502 -502
  12. tree_sitter_analyzer/encoding_utils.py +6 -2
  13. tree_sitter_analyzer/exceptions.py +400 -406
  14. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  15. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  16. tree_sitter_analyzer/interfaces/cli.py +1 -1
  17. tree_sitter_analyzer/interfaces/cli_adapter.py +3 -3
  18. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  19. tree_sitter_analyzer/language_detector.py +398 -398
  20. tree_sitter_analyzer/language_loader.py +224 -224
  21. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  22. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  23. tree_sitter_analyzer/mcp/server.py +30 -9
  24. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  25. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  26. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  27. tree_sitter_analyzer/models.py +470 -470
  28. tree_sitter_analyzer/project_detector.py +330 -317
  29. tree_sitter_analyzer/security/__init__.py +22 -22
  30. tree_sitter_analyzer/security/boundary_manager.py +243 -237
  31. tree_sitter_analyzer/security/regex_checker.py +297 -292
  32. tree_sitter_analyzer/table_formatter.py +703 -652
  33. tree_sitter_analyzer/utils.py +53 -22
  34. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/METADATA +13 -13
  35. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/RECORD +37 -37
  36. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/WHEEL +0 -0
  37. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/entry_points.txt +0 -0
@@ -1,317 +1,330 @@
1
- #!/usr/bin/env python3
2
- """
3
- Project Root Detection
4
-
5
- Intelligent detection of project root directories based on common project markers.
6
- """
7
-
8
- import os
9
- from pathlib import Path
10
- from typing import Optional, List, Tuple
11
- import logging
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
- # Common project root indicators (in priority order)
16
- PROJECT_MARKERS = [
17
- # Version control
18
- '.git',
19
- '.hg',
20
- '.svn',
21
-
22
- # Python projects
23
- 'pyproject.toml',
24
- 'setup.py',
25
- 'setup.cfg',
26
- 'requirements.txt',
27
- 'Pipfile',
28
- 'poetry.lock',
29
- 'conda.yaml',
30
- 'environment.yml',
31
-
32
- # JavaScript/Node.js projects
33
- 'package.json',
34
- 'package-lock.json',
35
- 'yarn.lock',
36
- 'node_modules',
37
-
38
- # Java projects
39
- 'pom.xml',
40
- 'build.gradle',
41
- 'build.gradle.kts',
42
- 'gradlew',
43
- 'mvnw',
44
-
45
- # C/C++ projects
46
- 'CMakeLists.txt',
47
- 'Makefile',
48
- 'configure.ac',
49
- 'configure.in',
50
-
51
- # Rust projects
52
- 'Cargo.toml',
53
- 'Cargo.lock',
54
-
55
- # Go projects
56
- 'go.mod',
57
- 'go.sum',
58
-
59
- # .NET projects
60
- '*.sln',
61
- '*.csproj',
62
- '*.vbproj',
63
- '*.fsproj',
64
-
65
- # Other common markers
66
- 'README.md',
67
- 'README.rst',
68
- 'README.txt',
69
- 'LICENSE',
70
- 'CHANGELOG.md',
71
- '.gitignore',
72
- '.dockerignore',
73
- 'Dockerfile',
74
- 'docker-compose.yml',
75
- '.editorconfig',
76
- ]
77
-
78
-
79
- class ProjectRootDetector:
80
- """Intelligent project root directory detection."""
81
-
82
- def __init__(self, max_depth: int = 10):
83
- """
84
- Initialize project root detector.
85
-
86
- Args:
87
- max_depth: Maximum directory levels to traverse upward
88
- """
89
- self.max_depth = max_depth
90
-
91
- def detect_from_file(self, file_path: str) -> Optional[str]:
92
- """
93
- Detect project root from a file path.
94
-
95
- Args:
96
- file_path: Path to a file within the project
97
-
98
- Returns:
99
- Project root directory path, or None if not detected
100
- """
101
- if not file_path:
102
- return None
103
-
104
- try:
105
- # Convert to absolute path and get directory
106
- abs_path = os.path.abspath(file_path)
107
- if os.path.isfile(abs_path):
108
- start_dir = os.path.dirname(abs_path)
109
- else:
110
- start_dir = abs_path
111
-
112
- return self._traverse_upward(start_dir)
113
-
114
- except Exception as e:
115
- logger.warning(f"Error detecting project root from {file_path}: {e}")
116
- return None
117
-
118
- def detect_from_cwd(self) -> Optional[str]:
119
- """
120
- Detect project root from current working directory.
121
-
122
- Returns:
123
- Project root directory path, or None if not detected
124
- """
125
- try:
126
- return self._traverse_upward(os.getcwd())
127
- except Exception as e:
128
- logger.warning(f"Error detecting project root from cwd: {e}")
129
- return None
130
-
131
- def _traverse_upward(self, start_dir: str) -> Optional[str]:
132
- """
133
- Traverse upward from start directory looking for project markers.
134
-
135
- Args:
136
- start_dir: Directory to start traversal from
137
-
138
- Returns:
139
- Project root directory path, or None if not found
140
- """
141
- current_dir = os.path.abspath(start_dir)
142
- candidates = []
143
-
144
- for depth in range(self.max_depth):
145
- # Check for project markers in current directory
146
- markers_found = self._find_markers_in_dir(current_dir)
147
-
148
- if markers_found:
149
- # Calculate score based on marker priority and count
150
- score = self._calculate_score(markers_found)
151
- candidates.append((current_dir, score, markers_found))
152
-
153
- # If we find high-priority markers, we can stop early
154
- if any(marker in ['.git', 'pyproject.toml', 'package.json', 'pom.xml', 'Cargo.toml', 'go.mod']
155
- for marker in markers_found):
156
- logger.debug(f"Found high-priority project root: {current_dir} (markers: {markers_found})")
157
- return current_dir
158
-
159
- # Move up one directory
160
- parent_dir = os.path.dirname(current_dir)
161
- if parent_dir == current_dir: # Reached filesystem root
162
- break
163
- current_dir = parent_dir
164
-
165
- # Return the best candidate if any found
166
- if candidates:
167
- # Sort by score (descending) and return the best
168
- candidates.sort(key=lambda x: x[1], reverse=True)
169
- best_candidate = candidates[0]
170
- logger.debug(f"Selected project root: {best_candidate[0]} (score: {best_candidate[1]}, markers: {best_candidate[2]})")
171
- return best_candidate[0]
172
-
173
- logger.debug(f"No project root detected from {start_dir}")
174
- return None
175
-
176
- def _find_markers_in_dir(self, directory: str) -> List[str]:
177
- """
178
- Find project markers in a directory.
179
-
180
- Args:
181
- directory: Directory to search in
182
-
183
- Returns:
184
- List of found marker names
185
- """
186
- found_markers = []
187
-
188
- try:
189
- dir_contents = os.listdir(directory)
190
-
191
- for marker in PROJECT_MARKERS:
192
- if '*' in marker:
193
- # Handle glob patterns
194
- import glob
195
- pattern = os.path.join(directory, marker)
196
- if glob.glob(pattern):
197
- found_markers.append(marker)
198
- else:
199
- # Handle exact matches
200
- if marker in dir_contents:
201
- found_markers.append(marker)
202
-
203
- except (OSError, PermissionError) as e:
204
- logger.debug(f"Cannot access directory {directory}: {e}")
205
-
206
- return found_markers
207
-
208
- def _calculate_score(self, markers: List[str]) -> int:
209
- """
210
- Calculate a score for project root candidates based on markers found.
211
-
212
- Args:
213
- markers: List of found markers
214
-
215
- Returns:
216
- Score (higher is better)
217
- """
218
- score = 0
219
-
220
- # High-priority markers
221
- high_priority = ['.git', 'pyproject.toml', 'package.json', 'pom.xml', 'Cargo.toml', 'go.mod']
222
- medium_priority = ['setup.py', 'requirements.txt', 'CMakeLists.txt', 'Makefile']
223
-
224
- for marker in markers:
225
- if marker in high_priority:
226
- score += 100
227
- elif marker in medium_priority:
228
- score += 50
229
- else:
230
- score += 10
231
-
232
- # Bonus for multiple markers
233
- if len(markers) > 1:
234
- score += len(markers) * 5
235
-
236
- return score
237
-
238
- def get_fallback_root(self, file_path: str) -> str:
239
- """
240
- Get fallback project root when detection fails.
241
-
242
- Args:
243
- file_path: Original file path
244
-
245
- Returns:
246
- Fallback directory (file's directory or cwd)
247
- """
248
- try:
249
- if file_path and os.path.exists(file_path):
250
- if os.path.isfile(file_path):
251
- return os.path.dirname(os.path.abspath(file_path))
252
- else:
253
- return os.path.abspath(file_path)
254
- else:
255
- return os.getcwd()
256
- except Exception:
257
- return os.getcwd()
258
-
259
-
260
- def detect_project_root(file_path: Optional[str] = None,
261
- explicit_root: Optional[str] = None) -> str:
262
- """
263
- Unified project root detection with priority handling.
264
-
265
- Priority order:
266
- 1. explicit_root parameter (highest priority)
267
- 2. Auto-detection from file_path
268
- 3. Auto-detection from current working directory
269
- 4. Fallback to file directory or cwd
270
-
271
- Args:
272
- file_path: Path to a file within the project
273
- explicit_root: Explicitly specified project root
274
-
275
- Returns:
276
- Project root directory path
277
- """
278
- detector = ProjectRootDetector()
279
-
280
- # Priority 1: Explicit root
281
- if explicit_root:
282
- if os.path.exists(explicit_root) and os.path.isdir(explicit_root):
283
- logger.info(f"Using explicit project root: {explicit_root}")
284
- return os.path.abspath(explicit_root)
285
- else:
286
- logger.warning(f"Explicit project root does not exist: {explicit_root}")
287
-
288
- # Priority 2: Auto-detection from file path
289
- if file_path:
290
- detected_root = detector.detect_from_file(file_path)
291
- if detected_root:
292
- logger.info(f"Auto-detected project root from file: {detected_root}")
293
- return detected_root
294
-
295
- # Priority 3: Auto-detection from cwd
296
- detected_root = detector.detect_from_cwd()
297
- if detected_root:
298
- logger.info(f"Auto-detected project root from cwd: {detected_root}")
299
- return detected_root
300
-
301
- # Priority 4: Fallback
302
- fallback_root = detector.get_fallback_root(file_path)
303
- logger.info(f"Using fallback project root: {fallback_root}")
304
- return fallback_root
305
-
306
-
307
- if __name__ == "__main__":
308
- # Test the detector
309
- import sys
310
-
311
- if len(sys.argv) > 1:
312
- test_path = sys.argv[1]
313
- result = detect_project_root(test_path)
314
- print(f"Project root for '{test_path}': {result}")
315
- else:
316
- result = detect_project_root()
317
- print(f"Project root from cwd: {result}")
1
+ #!/usr/bin/env python3
2
+ """
3
+ Project Root Detection
4
+
5
+ Intelligent detection of project root directories based on common project markers.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Common project root indicators (in priority order)
14
+ PROJECT_MARKERS = [
15
+ # Version control
16
+ ".git",
17
+ ".hg",
18
+ ".svn",
19
+ # Python projects
20
+ "pyproject.toml",
21
+ "setup.py",
22
+ "setup.cfg",
23
+ "requirements.txt",
24
+ "Pipfile",
25
+ "poetry.lock",
26
+ "conda.yaml",
27
+ "environment.yml",
28
+ # JavaScript/Node.js projects
29
+ "package.json",
30
+ "package-lock.json",
31
+ "yarn.lock",
32
+ "node_modules",
33
+ # Java projects
34
+ "pom.xml",
35
+ "build.gradle",
36
+ "build.gradle.kts",
37
+ "gradlew",
38
+ "mvnw",
39
+ # C/C++ projects
40
+ "CMakeLists.txt",
41
+ "Makefile",
42
+ "configure.ac",
43
+ "configure.in",
44
+ # Rust projects
45
+ "Cargo.toml",
46
+ "Cargo.lock",
47
+ # Go projects
48
+ "go.mod",
49
+ "go.sum",
50
+ # .NET projects
51
+ "*.sln",
52
+ "*.csproj",
53
+ "*.vbproj",
54
+ "*.fsproj",
55
+ # Other common markers
56
+ "README.md",
57
+ "README.rst",
58
+ "README.txt",
59
+ "LICENSE",
60
+ "CHANGELOG.md",
61
+ ".gitignore",
62
+ ".dockerignore",
63
+ "Dockerfile",
64
+ "docker-compose.yml",
65
+ ".editorconfig",
66
+ ]
67
+
68
+
69
+ class ProjectRootDetector:
70
+ """Intelligent project root directory detection."""
71
+
72
+ def __init__(self, max_depth: int = 10):
73
+ """
74
+ Initialize project root detector.
75
+
76
+ Args:
77
+ max_depth: Maximum directory levels to traverse upward
78
+ """
79
+ self.max_depth = max_depth
80
+
81
+ def detect_from_file(self, file_path: str) -> str | None:
82
+ """
83
+ Detect project root from a file path.
84
+
85
+ Args:
86
+ file_path: Path to a file within the project
87
+
88
+ Returns:
89
+ Project root directory path, or None if not detected
90
+ """
91
+ if not file_path:
92
+ return None
93
+
94
+ try:
95
+ # Convert to absolute path and get directory
96
+ abs_path = os.path.abspath(file_path)
97
+ if os.path.isfile(abs_path):
98
+ start_dir = os.path.dirname(abs_path)
99
+ else:
100
+ start_dir = abs_path
101
+
102
+ return self._traverse_upward(start_dir)
103
+
104
+ except Exception as e:
105
+ logger.warning(f"Error detecting project root from {file_path}: {e}")
106
+ return None
107
+
108
+ def detect_from_cwd(self) -> str | None:
109
+ """
110
+ Detect project root from current working directory.
111
+
112
+ Returns:
113
+ Project root directory path, or None if not detected
114
+ """
115
+ try:
116
+ return self._traverse_upward(os.getcwd())
117
+ except Exception as e:
118
+ logger.warning(f"Error detecting project root from cwd: {e}")
119
+ return None
120
+
121
+ def _traverse_upward(self, start_dir: str) -> str | None:
122
+ """
123
+ Traverse upward from start directory looking for project markers.
124
+
125
+ Args:
126
+ start_dir: Directory to start traversal from
127
+
128
+ Returns:
129
+ Project root directory path, or None if not found
130
+ """
131
+ current_dir = os.path.abspath(start_dir)
132
+ candidates = []
133
+
134
+ for _depth in range(self.max_depth):
135
+ # Check for project markers in current directory
136
+ markers_found = self._find_markers_in_dir(current_dir)
137
+
138
+ if markers_found:
139
+ # Calculate score based on marker priority and count
140
+ score = self._calculate_score(markers_found)
141
+ candidates.append((current_dir, score, markers_found))
142
+
143
+ # If we find high-priority markers, we can stop early
144
+ if any(
145
+ marker
146
+ in [
147
+ ".git",
148
+ "pyproject.toml",
149
+ "package.json",
150
+ "pom.xml",
151
+ "Cargo.toml",
152
+ "go.mod",
153
+ ]
154
+ for marker in markers_found
155
+ ):
156
+ logger.debug(
157
+ f"Found high-priority project root: {current_dir} (markers: {markers_found})"
158
+ )
159
+ return current_dir
160
+
161
+ # Move up one directory
162
+ parent_dir = os.path.dirname(current_dir)
163
+ if parent_dir == current_dir: # Reached filesystem root
164
+ break
165
+ current_dir = parent_dir
166
+
167
+ # Return the best candidate if any found
168
+ if candidates:
169
+ # Sort by score (descending) and return the best
170
+ candidates.sort(key=lambda x: x[1], reverse=True)
171
+ best_candidate = candidates[0]
172
+ logger.debug(
173
+ f"Selected project root: {best_candidate[0]} (score: {best_candidate[1]}, markers: {best_candidate[2]})"
174
+ )
175
+ return best_candidate[0]
176
+
177
+ logger.debug(f"No project root detected from {start_dir}")
178
+ return None
179
+
180
+ def _find_markers_in_dir(self, directory: str) -> list[str]:
181
+ """
182
+ Find project markers in a directory.
183
+
184
+ Args:
185
+ directory: Directory to search in
186
+
187
+ Returns:
188
+ List of found marker names
189
+ """
190
+ found_markers = []
191
+
192
+ try:
193
+ dir_contents = os.listdir(directory)
194
+
195
+ for marker in PROJECT_MARKERS:
196
+ if "*" in marker:
197
+ # Handle glob patterns
198
+ import glob
199
+
200
+ pattern = os.path.join(directory, marker)
201
+ if glob.glob(pattern):
202
+ found_markers.append(marker)
203
+ else:
204
+ # Handle exact matches
205
+ if marker in dir_contents:
206
+ found_markers.append(marker)
207
+
208
+ except (OSError, PermissionError) as e:
209
+ logger.debug(f"Cannot access directory {directory}: {e}")
210
+
211
+ return found_markers
212
+
213
+ def _calculate_score(self, markers: list[str]) -> int:
214
+ """
215
+ Calculate a score for project root candidates based on markers found.
216
+
217
+ Args:
218
+ markers: List of found markers
219
+
220
+ Returns:
221
+ Score (higher is better)
222
+ """
223
+ score = 0
224
+
225
+ # High-priority markers
226
+ high_priority = [
227
+ ".git",
228
+ "pyproject.toml",
229
+ "package.json",
230
+ "pom.xml",
231
+ "Cargo.toml",
232
+ "go.mod",
233
+ ]
234
+ medium_priority = ["setup.py", "requirements.txt", "CMakeLists.txt", "Makefile"]
235
+
236
+ for marker in markers:
237
+ if marker in high_priority:
238
+ score += 100
239
+ elif marker in medium_priority:
240
+ score += 50
241
+ else:
242
+ score += 10
243
+
244
+ # Bonus for multiple markers
245
+ if len(markers) > 1:
246
+ score += len(markers) * 5
247
+
248
+ return score
249
+
250
+ def get_fallback_root(self, file_path: str) -> str:
251
+ """
252
+ Get fallback project root when detection fails.
253
+
254
+ Args:
255
+ file_path: Original file path
256
+
257
+ Returns:
258
+ Fallback directory (file's directory or cwd)
259
+ """
260
+ try:
261
+ if file_path and os.path.exists(file_path):
262
+ if os.path.isfile(file_path):
263
+ return os.path.dirname(os.path.abspath(file_path))
264
+ else:
265
+ return os.path.abspath(file_path)
266
+ else:
267
+ return os.getcwd()
268
+ except Exception:
269
+ return os.getcwd()
270
+
271
+
272
+ def detect_project_root(
273
+ file_path: str | None = None, explicit_root: str | None = None
274
+ ) -> str:
275
+ """
276
+ Unified project root detection with priority handling.
277
+
278
+ Priority order:
279
+ 1. explicit_root parameter (highest priority)
280
+ 2. Auto-detection from file_path
281
+ 3. Auto-detection from current working directory
282
+ 4. Fallback to file directory or cwd
283
+
284
+ Args:
285
+ file_path: Path to a file within the project
286
+ explicit_root: Explicitly specified project root
287
+
288
+ Returns:
289
+ Project root directory path
290
+ """
291
+ detector = ProjectRootDetector()
292
+
293
+ # Priority 1: Explicit root
294
+ if explicit_root:
295
+ if os.path.exists(explicit_root) and os.path.isdir(explicit_root):
296
+ logger.debug(f"Using explicit project root: {explicit_root}")
297
+ return os.path.abspath(explicit_root)
298
+ else:
299
+ logger.warning(f"Explicit project root does not exist: {explicit_root}")
300
+
301
+ # Priority 2: Auto-detection from file path
302
+ if file_path:
303
+ detected_root = detector.detect_from_file(file_path)
304
+ if detected_root:
305
+ logger.debug(f"Auto-detected project root from file: {detected_root}")
306
+ return detected_root
307
+
308
+ # Priority 3: Auto-detection from cwd
309
+ detected_root = detector.detect_from_cwd()
310
+ if detected_root:
311
+ logger.debug(f"Auto-detected project root from cwd: {detected_root}")
312
+ return detected_root
313
+
314
+ # Priority 4: Fallback
315
+ fallback_root = detector.get_fallback_root(file_path)
316
+ logger.debug(f"Using fallback project root: {fallback_root}")
317
+ return fallback_root
318
+
319
+
320
+ if __name__ == "__main__":
321
+ # Test the detector
322
+ import sys
323
+
324
+ if len(sys.argv) > 1:
325
+ test_path = sys.argv[1]
326
+ result = detect_project_root(test_path)
327
+ print(f"Project root for '{test_path}': {result}")
328
+ else:
329
+ result = detect_project_root()
330
+ print(f"Project root from cwd: {result}")