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

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (29) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/api.py +542 -542
  3. tree_sitter_analyzer/cli/commands/base_command.py +181 -181
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -139
  5. tree_sitter_analyzer/cli/info_commands.py +124 -124
  6. tree_sitter_analyzer/cli_main.py +327 -327
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/query_service.py +162 -162
  9. tree_sitter_analyzer/file_handler.py +212 -212
  10. tree_sitter_analyzer/formatters/base_formatter.py +169 -169
  11. tree_sitter_analyzer/interfaces/cli.py +535 -535
  12. tree_sitter_analyzer/mcp/__init__.py +1 -1
  13. tree_sitter_analyzer/mcp/resources/__init__.py +0 -1
  14. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +16 -5
  15. tree_sitter_analyzer/mcp/server.py +655 -655
  16. tree_sitter_analyzer/mcp/tools/__init__.py +28 -30
  17. tree_sitter_analyzer/mcp/utils/__init__.py +1 -2
  18. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
  19. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -414
  20. tree_sitter_analyzer/output_manager.py +257 -257
  21. tree_sitter_analyzer/project_detector.py +330 -330
  22. tree_sitter_analyzer/security/boundary_manager.py +260 -260
  23. tree_sitter_analyzer/security/validator.py +257 -257
  24. tree_sitter_analyzer/table_formatter.py +710 -710
  25. tree_sitter_analyzer/utils.py +335 -335
  26. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/METADATA +12 -12
  27. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/RECORD +29 -29
  28. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/WHEEL +0 -0
  29. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/entry_points.txt +0 -0
@@ -1,330 +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 logging
9
- from pathlib import Path
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 = Path(file_path).resolve()
97
- if abs_path.is_file():
98
- start_dir = abs_path.parent
99
- else:
100
- start_dir = abs_path
101
-
102
- return self._traverse_upward(str(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(str(Path.cwd()))
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 = str(Path(start_dir).resolve())
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
- current_path = Path(current_dir)
163
- parent_path = current_path.parent
164
- if parent_path == current_path: # Reached filesystem root
165
- break
166
- current_dir = str(parent_path)
167
-
168
- # Return the best candidate if any found
169
- if candidates:
170
- # Sort by score (descending) and return the best
171
- candidates.sort(key=lambda x: x[1], reverse=True)
172
- best_candidate = candidates[0]
173
- logger.debug(
174
- f"Selected project root: {best_candidate[0]} (score: {best_candidate[1]}, markers: {best_candidate[2]})"
175
- )
176
- return best_candidate[0]
177
-
178
- logger.debug(f"No project root detected from {start_dir}")
179
- return None
180
-
181
- def _find_markers_in_dir(self, directory: str) -> list[str]:
182
- """
183
- Find project markers in a directory.
184
-
185
- Args:
186
- directory: Directory to search in
187
-
188
- Returns:
189
- List of found marker names
190
- """
191
- found_markers = []
192
-
193
- try:
194
- dir_path = Path(directory)
195
-
196
- for marker in PROJECT_MARKERS:
197
- if "*" in marker:
198
- # Handle glob patterns using pathlib
199
- if list(dir_path.glob(marker)):
200
- found_markers.append(marker)
201
- else:
202
- # Handle exact matches
203
- if (dir_path / marker).exists():
204
- found_markers.append(marker)
205
-
206
- except (OSError, PermissionError) as e:
207
- logger.debug(f"Cannot access directory {directory}: {e}")
208
-
209
- return found_markers
210
-
211
- def _calculate_score(self, markers: list[str]) -> int:
212
- """
213
- Calculate a score for project root candidates based on markers found.
214
-
215
- Args:
216
- markers: List of found markers
217
-
218
- Returns:
219
- Score (higher is better)
220
- """
221
- score = 0
222
-
223
- # High-priority markers
224
- high_priority = [
225
- ".git",
226
- "pyproject.toml",
227
- "package.json",
228
- "pom.xml",
229
- "Cargo.toml",
230
- "go.mod",
231
- ]
232
- medium_priority = ["setup.py", "requirements.txt", "CMakeLists.txt", "Makefile"]
233
-
234
- for marker in markers:
235
- if marker in high_priority:
236
- score += 100
237
- elif marker in medium_priority:
238
- score += 50
239
- else:
240
- score += 10
241
-
242
- # Bonus for multiple markers
243
- if len(markers) > 1:
244
- score += len(markers) * 5
245
-
246
- return score
247
-
248
- def get_fallback_root(self, file_path: str) -> str:
249
- """
250
- Get fallback project root when detection fails.
251
-
252
- Args:
253
- file_path: Original file path
254
-
255
- Returns:
256
- Fallback directory (file's directory or cwd)
257
- """
258
- try:
259
- if file_path:
260
- path = Path(file_path)
261
- if path.exists():
262
- if path.is_file():
263
- return str(path.resolve().parent)
264
- else:
265
- return str(path.resolve())
266
- return str(Path.cwd())
267
- except Exception:
268
- return str(Path.cwd())
269
-
270
-
271
- def detect_project_root(
272
- file_path: str | None = None, explicit_root: str | None = None
273
- ) -> str:
274
- """
275
- Unified project root detection with priority handling.
276
-
277
- Priority order:
278
- 1. explicit_root parameter (highest priority)
279
- 2. Auto-detection from file_path
280
- 3. Auto-detection from current working directory
281
- 4. Fallback to file directory or cwd
282
-
283
- Args:
284
- file_path: Path to a file within the project
285
- explicit_root: Explicitly specified project root
286
-
287
- Returns:
288
- Project root directory path
289
- """
290
- detector = ProjectRootDetector()
291
-
292
- # Priority 1: Explicit root
293
- if explicit_root:
294
- explicit_path = Path(explicit_root)
295
- if explicit_path.exists() and explicit_path.is_dir():
296
- logger.debug(f"Using explicit project root: {explicit_root}")
297
- return str(explicit_path.resolve())
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}")
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
+ from pathlib import Path
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 = Path(file_path).resolve()
97
+ if abs_path.is_file():
98
+ start_dir = abs_path.parent
99
+ else:
100
+ start_dir = abs_path
101
+
102
+ return self._traverse_upward(str(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(str(Path.cwd()))
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 = str(Path(start_dir).resolve())
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
+ current_path = Path(current_dir)
163
+ parent_path = current_path.parent
164
+ if parent_path == current_path: # Reached filesystem root
165
+ break
166
+ current_dir = str(parent_path)
167
+
168
+ # Return the best candidate if any found
169
+ if candidates:
170
+ # Sort by score (descending) and return the best
171
+ candidates.sort(key=lambda x: x[1], reverse=True)
172
+ best_candidate = candidates[0]
173
+ logger.debug(
174
+ f"Selected project root: {best_candidate[0]} (score: {best_candidate[1]}, markers: {best_candidate[2]})"
175
+ )
176
+ return best_candidate[0]
177
+
178
+ logger.debug(f"No project root detected from {start_dir}")
179
+ return None
180
+
181
+ def _find_markers_in_dir(self, directory: str) -> list[str]:
182
+ """
183
+ Find project markers in a directory.
184
+
185
+ Args:
186
+ directory: Directory to search in
187
+
188
+ Returns:
189
+ List of found marker names
190
+ """
191
+ found_markers = []
192
+
193
+ try:
194
+ dir_path = Path(directory)
195
+
196
+ for marker in PROJECT_MARKERS:
197
+ if "*" in marker:
198
+ # Handle glob patterns using pathlib
199
+ if list(dir_path.glob(marker)):
200
+ found_markers.append(marker)
201
+ else:
202
+ # Handle exact matches
203
+ if (dir_path / marker).exists():
204
+ found_markers.append(marker)
205
+
206
+ except (OSError, PermissionError) as e:
207
+ logger.debug(f"Cannot access directory {directory}: {e}")
208
+
209
+ return found_markers
210
+
211
+ def _calculate_score(self, markers: list[str]) -> int:
212
+ """
213
+ Calculate a score for project root candidates based on markers found.
214
+
215
+ Args:
216
+ markers: List of found markers
217
+
218
+ Returns:
219
+ Score (higher is better)
220
+ """
221
+ score = 0
222
+
223
+ # High-priority markers
224
+ high_priority = [
225
+ ".git",
226
+ "pyproject.toml",
227
+ "package.json",
228
+ "pom.xml",
229
+ "Cargo.toml",
230
+ "go.mod",
231
+ ]
232
+ medium_priority = ["setup.py", "requirements.txt", "CMakeLists.txt", "Makefile"]
233
+
234
+ for marker in markers:
235
+ if marker in high_priority:
236
+ score += 100
237
+ elif marker in medium_priority:
238
+ score += 50
239
+ else:
240
+ score += 10
241
+
242
+ # Bonus for multiple markers
243
+ if len(markers) > 1:
244
+ score += len(markers) * 5
245
+
246
+ return score
247
+
248
+ def get_fallback_root(self, file_path: str) -> str:
249
+ """
250
+ Get fallback project root when detection fails.
251
+
252
+ Args:
253
+ file_path: Original file path
254
+
255
+ Returns:
256
+ Fallback directory (file's directory or cwd)
257
+ """
258
+ try:
259
+ if file_path:
260
+ path = Path(file_path)
261
+ if path.exists():
262
+ if path.is_file():
263
+ return str(path.resolve().parent)
264
+ else:
265
+ return str(path.resolve())
266
+ return str(Path.cwd())
267
+ except Exception:
268
+ return str(Path.cwd())
269
+
270
+
271
+ def detect_project_root(
272
+ file_path: str | None = None, explicit_root: str | None = None
273
+ ) -> str:
274
+ """
275
+ Unified project root detection with priority handling.
276
+
277
+ Priority order:
278
+ 1. explicit_root parameter (highest priority)
279
+ 2. Auto-detection from file_path
280
+ 3. Auto-detection from current working directory
281
+ 4. Fallback to file directory or cwd
282
+
283
+ Args:
284
+ file_path: Path to a file within the project
285
+ explicit_root: Explicitly specified project root
286
+
287
+ Returns:
288
+ Project root directory path
289
+ """
290
+ detector = ProjectRootDetector()
291
+
292
+ # Priority 1: Explicit root
293
+ if explicit_root:
294
+ explicit_path = Path(explicit_root)
295
+ if explicit_path.exists() and explicit_path.is_dir():
296
+ logger.debug(f"Using explicit project root: {explicit_root}")
297
+ return str(explicit_path.resolve())
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}")