skydeckai-code 0.1.34__py3-none-any.whl → 0.1.35__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.
- {skydeckai_code-0.1.34.dist-info → skydeckai_code-0.1.35.dist-info}/METADATA +22 -142
- {skydeckai_code-0.1.34.dist-info → skydeckai_code-0.1.35.dist-info}/RECORD +9 -11
- src/aidd/tools/__init__.py +2 -60
- src/aidd/tools/code_execution.py +5 -0
- src/aidd/tools/code_tools.py +63 -23
- src/aidd/tools/file_tools.py +2 -1
- src/aidd/tools/git_tools.py +0 -774
- src/aidd/tools/lint_tools.py +0 -590
- {skydeckai_code-0.1.34.dist-info → skydeckai_code-0.1.35.dist-info}/WHEEL +0 -0
- {skydeckai_code-0.1.34.dist-info → skydeckai_code-0.1.35.dist-info}/entry_points.txt +0 -0
- {skydeckai_code-0.1.34.dist-info → skydeckai_code-0.1.35.dist-info}/licenses/LICENSE +0 -0
src/aidd/tools/lint_tools.py
DELETED
@@ -1,590 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import re
|
3
|
-
import json
|
4
|
-
import subprocess
|
5
|
-
from typing import List, Dict, Any, Optional
|
6
|
-
|
7
|
-
from mcp.types import TextContent
|
8
|
-
from .state import state
|
9
|
-
|
10
|
-
|
11
|
-
def check_lint_tool():
|
12
|
-
return {
|
13
|
-
"name": "check_lint",
|
14
|
-
"description": "Check the codebase for linting issues using native linting tools. "
|
15
|
-
"WHEN TO USE: When you need to identify coding style issues, potential bugs, or code quality problems. "
|
16
|
-
"Similar to how VSCode reports lint issues in the Problems panel. "
|
17
|
-
"WHEN NOT TO USE: When you need to fix formatting (use appropriate formatters instead), "
|
18
|
-
"when you need detailed code analysis with custom rules, or for compiled languages where linting may not apply. "
|
19
|
-
"RETURNS: A detailed report of linting issues found in the codebase, including file paths, line numbers, "
|
20
|
-
"issue descriptions, and severity levels. Issues are grouped by file and sorted by severity. "
|
21
|
-
"Note: Respects config files like .pylintrc, .flake8, .eslintrc, and analysis_options.yaml if present.\n\n"
|
22
|
-
"EXAMPLES:\n"
|
23
|
-
"- Basic usage: {\"path\": \"src\"}\n"
|
24
|
-
"- Python with custom line length: {\"path\": \"src\", \"linters\": {\"flake8\": \"--max-line-length=120 --ignore=E501,E302\"}}\n"
|
25
|
-
"- Disable specific pylint checks: {\"linters\": {\"pylint\": \"--disable=missing-docstring,invalid-name\"}}\n"
|
26
|
-
"- TypeScript only: {\"path\": \"src\", \"languages\": [\"typescript\"], \"linters\": {\"eslint\": \"--no-eslintrc --config .eslintrc.custom.js\"}}\n"
|
27
|
-
"- Dart only: {\"path\": \"lib\", \"languages\": [\"dart\"], \"linters\": {\"dart_analyze\": \"--fatal-infos\"}}\n"
|
28
|
-
"- Disable a linter: {\"linters\": {\"flake8\": false}, \"max_issues\": 50}\n"
|
29
|
-
"- Single file check: {\"path\": \"src/main.py\"}",
|
30
|
-
"inputSchema": {
|
31
|
-
"type": "object",
|
32
|
-
"properties": {
|
33
|
-
"path": {
|
34
|
-
"type": "string",
|
35
|
-
"description": "Directory or file to lint. Can be a specific file or directory to recursively check. "
|
36
|
-
"Examples: '.' for entire codebase, 'src' for just the src directory, 'src/main.py' for a specific file.",
|
37
|
-
"default": "."
|
38
|
-
},
|
39
|
-
"languages": {
|
40
|
-
"type": "array",
|
41
|
-
"items": {
|
42
|
-
"type": "string"
|
43
|
-
},
|
44
|
-
"description": "List of languages to lint. If empty, will auto-detect based on file extensions. "
|
45
|
-
"Supported languages include: 'python', 'javascript', 'typescript', 'dart'.",
|
46
|
-
"default": []
|
47
|
-
},
|
48
|
-
"linters": {
|
49
|
-
"type": "object",
|
50
|
-
"description": "Configuration for specific linters. Each key is a linter name ('pylint', 'flake8', 'eslint', 'dart_analyze') "
|
51
|
-
"and the value is either a boolean to enable/disable or a string with CLI arguments.",
|
52
|
-
"properties": {
|
53
|
-
"pylint": {
|
54
|
-
"type": ["boolean", "string"],
|
55
|
-
"description": "Whether to use pylint or custom pylint arguments."
|
56
|
-
},
|
57
|
-
"flake8": {
|
58
|
-
"type": ["boolean", "string"],
|
59
|
-
"description": "Whether to use flake8 or custom flake8 arguments."
|
60
|
-
},
|
61
|
-
"eslint": {
|
62
|
-
"type": ["boolean", "string"],
|
63
|
-
"description": "Whether to use eslint or custom eslint arguments."
|
64
|
-
},
|
65
|
-
"dart_analyze": {
|
66
|
-
"type": ["boolean", "string"],
|
67
|
-
"description": "Whether to use 'dart analyze' or custom dart analyze arguments."
|
68
|
-
}
|
69
|
-
},
|
70
|
-
"default": {}
|
71
|
-
},
|
72
|
-
"max_issues": {
|
73
|
-
"type": "integer",
|
74
|
-
"description": "Maximum number of issues to return. Set to 0 for unlimited.",
|
75
|
-
"default": 100
|
76
|
-
}
|
77
|
-
},
|
78
|
-
"required": []
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
async def handle_check_lint(arguments: dict) -> List[TextContent]:
|
83
|
-
"""Handle linting the codebase and reporting issues using native linting tools."""
|
84
|
-
path = arguments.get("path", ".")
|
85
|
-
languages = arguments.get("languages", [])
|
86
|
-
linters_config = arguments.get("linters", {})
|
87
|
-
max_issues = arguments.get("max_issues", 100)
|
88
|
-
|
89
|
-
# Determine full path
|
90
|
-
if os.path.isabs(path):
|
91
|
-
full_path = os.path.abspath(path)
|
92
|
-
else:
|
93
|
-
full_path = os.path.abspath(os.path.join(state.allowed_directory, path))
|
94
|
-
|
95
|
-
# Security check
|
96
|
-
if not full_path.startswith(state.allowed_directory):
|
97
|
-
raise ValueError(f"Access denied: Path ({full_path}) must be within allowed directory")
|
98
|
-
|
99
|
-
if not os.path.exists(full_path):
|
100
|
-
raise ValueError(f"Path does not exist: {path}")
|
101
|
-
|
102
|
-
# Skip the entire operation if the path looks like a virtual environment or system directory
|
103
|
-
if _is_excluded_system_directory(full_path):
|
104
|
-
return [TextContent(
|
105
|
-
type="text",
|
106
|
-
text="The specified path appears to be a virtual environment, package directory, or system path.\n"
|
107
|
-
"To prevent excessive noise, linting has been skipped for this path.\n"
|
108
|
-
"Please specify a project source directory to lint instead."
|
109
|
-
)]
|
110
|
-
|
111
|
-
# Auto-detect languages if not specified
|
112
|
-
if not languages:
|
113
|
-
if os.path.isfile(full_path):
|
114
|
-
language = _detect_language_from_file(full_path)
|
115
|
-
if language:
|
116
|
-
languages = [language]
|
117
|
-
else:
|
118
|
-
# If we can't detect a supported language for the file
|
119
|
-
_, ext = os.path.splitext(full_path)
|
120
|
-
return [TextContent(
|
121
|
-
type="text",
|
122
|
-
text=f"Unsupported file type: {ext}\nThe check_lint tool only supports: .py, .js, .jsx, .ts, .tsx, .dart files.\nSupported languages are: python, javascript, typescript, dart."
|
123
|
-
)]
|
124
|
-
else:
|
125
|
-
languages = ["python", "javascript", "typescript", "dart"] # Default to common languages
|
126
|
-
|
127
|
-
# Prepare linter configurations with defaults
|
128
|
-
linter_defaults = {
|
129
|
-
"python": {
|
130
|
-
"pylint": True,
|
131
|
-
"flake8": True
|
132
|
-
},
|
133
|
-
"javascript": {
|
134
|
-
"eslint": True
|
135
|
-
},
|
136
|
-
"typescript": {
|
137
|
-
"eslint": True
|
138
|
-
},
|
139
|
-
"dart": {
|
140
|
-
"dart_analyze": True
|
141
|
-
}
|
142
|
-
}
|
143
|
-
|
144
|
-
# Validate languages if explicitly specified
|
145
|
-
if languages and os.path.isfile(full_path):
|
146
|
-
_, ext = os.path.splitext(full_path)
|
147
|
-
ext_language_map = {
|
148
|
-
'.py': 'python',
|
149
|
-
'.js': 'javascript',
|
150
|
-
'.jsx': 'javascript',
|
151
|
-
'.ts': 'typescript',
|
152
|
-
'.tsx': 'typescript',
|
153
|
-
'.dart': 'dart',
|
154
|
-
'.flutter': 'dart'
|
155
|
-
}
|
156
|
-
detected_language = ext_language_map.get(ext.lower())
|
157
|
-
|
158
|
-
# If we have a mismatch between specified language and file extension
|
159
|
-
if detected_language and not any(lang == detected_language for lang in languages):
|
160
|
-
return [TextContent(
|
161
|
-
type="text",
|
162
|
-
text=f"Language mismatch: File has {ext} extension but you specified {languages}.\n"
|
163
|
-
f"For this file extension, please use: {detected_language}"
|
164
|
-
)]
|
165
|
-
|
166
|
-
# If file type is not supported at all
|
167
|
-
if not detected_language:
|
168
|
-
return [TextContent(
|
169
|
-
type="text",
|
170
|
-
text=f"Unsupported file type: {ext}\nThe check_lint tool only supports: .py, .js, .jsx, .ts, .tsx, .dart files.\nSupported languages are: python, javascript, typescript, dart."
|
171
|
-
)]
|
172
|
-
|
173
|
-
# Process each language
|
174
|
-
all_issues = []
|
175
|
-
|
176
|
-
for language in languages:
|
177
|
-
if language in linter_defaults:
|
178
|
-
# Get default linters for this language
|
179
|
-
default_linters = linter_defaults[language]
|
180
|
-
|
181
|
-
# Override with user configuration
|
182
|
-
for linter_name, default_value in default_linters.items():
|
183
|
-
# Check if the linter is explicitly configured
|
184
|
-
if linter_name in linters_config:
|
185
|
-
linter_setting = linters_config[linter_name]
|
186
|
-
|
187
|
-
# Skip if explicitly disabled
|
188
|
-
if linter_setting is False:
|
189
|
-
continue
|
190
|
-
|
191
|
-
# Run the linter with custom args or defaults
|
192
|
-
issues = await _run_linter(
|
193
|
-
linter_name,
|
194
|
-
full_path,
|
195
|
-
custom_args=linter_setting if isinstance(linter_setting, str) else None
|
196
|
-
)
|
197
|
-
all_issues.extend(issues)
|
198
|
-
elif default_value:
|
199
|
-
# Use default if not explicitly configured
|
200
|
-
issues = await _run_linter(linter_name, full_path)
|
201
|
-
all_issues.extend(issues)
|
202
|
-
|
203
|
-
# Limit total issues if needed
|
204
|
-
if max_issues > 0 and len(all_issues) >= max_issues:
|
205
|
-
all_issues = all_issues[:max_issues]
|
206
|
-
break
|
207
|
-
|
208
|
-
# Format and return results
|
209
|
-
if not all_issues:
|
210
|
-
return [TextContent(
|
211
|
-
type="text",
|
212
|
-
text="No linting issues found."
|
213
|
-
)]
|
214
|
-
|
215
|
-
return [TextContent(
|
216
|
-
type="text",
|
217
|
-
text=_format_lint_results(all_issues)
|
218
|
-
)]
|
219
|
-
|
220
|
-
def _is_excluded_system_directory(path: str) -> bool:
|
221
|
-
"""Check if the path is a system directory or virtual environment that should be excluded from linting."""
|
222
|
-
# Common virtual environment directories
|
223
|
-
venv_indicators = [
|
224
|
-
'venv', '.venv', 'env', '.env', 'virtualenv',
|
225
|
-
'site-packages', 'dist-packages',
|
226
|
-
# Python version-specific directories often in venvs
|
227
|
-
'python3', 'python2', 'python3.', 'lib/python'
|
228
|
-
]
|
229
|
-
|
230
|
-
# System and dependency directories to exclude
|
231
|
-
system_dirs = [
|
232
|
-
'node_modules', 'bower_components',
|
233
|
-
'.git', '.svn',
|
234
|
-
'__pycache__', '.mypy_cache', '.pytest_cache', '.ruff_cache',
|
235
|
-
'.idea', '.vscode'
|
236
|
-
]
|
237
|
-
|
238
|
-
# Check if the path contains any of these indicators
|
239
|
-
path_parts = path.lower().split(os.sep)
|
240
|
-
|
241
|
-
# Check for virtual environment indicators
|
242
|
-
for indicator in venv_indicators:
|
243
|
-
if indicator in path_parts:
|
244
|
-
return True
|
245
|
-
|
246
|
-
# Check for system directories
|
247
|
-
for sys_dir in system_dirs:
|
248
|
-
if sys_dir in path_parts:
|
249
|
-
return True
|
250
|
-
|
251
|
-
return False
|
252
|
-
|
253
|
-
async def _run_linter(linter_name: str, path: str, custom_args: str = None) -> List[Dict[str, Any]]:
|
254
|
-
"""Run a specific linter as a command-line process and parse the results."""
|
255
|
-
issues = []
|
256
|
-
|
257
|
-
# Skip if the path is a system directory
|
258
|
-
if _is_excluded_system_directory(path):
|
259
|
-
return []
|
260
|
-
|
261
|
-
# Handle different linters
|
262
|
-
if linter_name == "pylint":
|
263
|
-
try:
|
264
|
-
# Build the command
|
265
|
-
cmd = ["pylint", "--output-format=json"]
|
266
|
-
|
267
|
-
if custom_args:
|
268
|
-
# Split and add custom arguments
|
269
|
-
cmd.extend(custom_args.split())
|
270
|
-
|
271
|
-
# Add the target path
|
272
|
-
if os.path.isdir(path):
|
273
|
-
# For directories, scan recursively but exclude common directories
|
274
|
-
cmd.extend([
|
275
|
-
"--recursive=y",
|
276
|
-
"--ignore=venv,.venv,env,.env,node_modules,__pycache__,.git,.svn,dist,build,target,.idea,.vscode",
|
277
|
-
])
|
278
|
-
|
279
|
-
cmd.append(path)
|
280
|
-
|
281
|
-
# Run pylint
|
282
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
283
|
-
|
284
|
-
if result.stdout.strip():
|
285
|
-
# Parse pylint JSON output
|
286
|
-
try:
|
287
|
-
pylint_issues = json.loads(result.stdout)
|
288
|
-
for issue in pylint_issues:
|
289
|
-
# Extract the file path and ensure it's within the allowed directory
|
290
|
-
file_path = issue.get("path", "")
|
291
|
-
if not os.path.isabs(file_path):
|
292
|
-
file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
|
293
|
-
|
294
|
-
# Security check for file path and exclude system directories
|
295
|
-
if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
|
296
|
-
continue
|
297
|
-
|
298
|
-
issues.append({
|
299
|
-
"file": os.path.relpath(file_path, state.allowed_directory),
|
300
|
-
"line": issue.get("line", 0),
|
301
|
-
"column": issue.get("column", 0),
|
302
|
-
"message": issue.get("message", ""),
|
303
|
-
"severity": _map_pylint_severity(issue.get("type", "")),
|
304
|
-
"source": "pylint",
|
305
|
-
"code": issue.get("symbol", "")
|
306
|
-
})
|
307
|
-
except (json.JSONDecodeError, ValueError):
|
308
|
-
# If JSON parsing fails, it might be an error message
|
309
|
-
if result.stderr:
|
310
|
-
issues.append({
|
311
|
-
"file": path if os.path.isfile(path) else "",
|
312
|
-
"line": 1,
|
313
|
-
"column": 1,
|
314
|
-
"message": f"Error running pylint: {result.stderr}",
|
315
|
-
"severity": "error",
|
316
|
-
"source": "pylint",
|
317
|
-
"code": "tool-error"
|
318
|
-
})
|
319
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
320
|
-
# Silently fail if pylint is not installed
|
321
|
-
pass
|
322
|
-
|
323
|
-
elif linter_name == "flake8":
|
324
|
-
try:
|
325
|
-
# Build the command
|
326
|
-
cmd = ["flake8", "--format=default"]
|
327
|
-
|
328
|
-
if custom_args:
|
329
|
-
cmd.extend(custom_args.split())
|
330
|
-
else:
|
331
|
-
# Add default exclusions if no custom args provided
|
332
|
-
cmd.extend(["--exclude=.venv,venv,env,.env,node_modules,__pycache__,.git,.svn,dist,build,target,.idea,.vscode"])
|
333
|
-
|
334
|
-
# Add target path
|
335
|
-
cmd.append(path)
|
336
|
-
|
337
|
-
# Run flake8
|
338
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
339
|
-
|
340
|
-
if result.stdout.strip():
|
341
|
-
# Parse flake8 output (file:line:col: code message)
|
342
|
-
flake8_pattern = r"(.+):(\d+):(\d+): ([A-Z]\d+) (.+)"
|
343
|
-
for line in result.stdout.splitlines():
|
344
|
-
match = re.match(flake8_pattern, line)
|
345
|
-
if match:
|
346
|
-
filepath, line_num, col, code, message = match.groups()
|
347
|
-
|
348
|
-
# Ensure path is absolute for security check
|
349
|
-
if not os.path.isabs(filepath):
|
350
|
-
filepath = os.path.abspath(os.path.join(os.path.dirname(path), filepath))
|
351
|
-
|
352
|
-
# Security check for file path and exclude system directories
|
353
|
-
if not filepath.startswith(state.allowed_directory) or _is_excluded_system_directory(filepath):
|
354
|
-
continue
|
355
|
-
|
356
|
-
issues.append({
|
357
|
-
"file": os.path.relpath(filepath, state.allowed_directory),
|
358
|
-
"line": int(line_num),
|
359
|
-
"column": int(col),
|
360
|
-
"message": message,
|
361
|
-
"severity": "warning", # flake8 doesn't provide severity
|
362
|
-
"source": "flake8",
|
363
|
-
"code": code
|
364
|
-
})
|
365
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
366
|
-
# Silently fail if flake8 is not installed
|
367
|
-
pass
|
368
|
-
|
369
|
-
elif linter_name == "eslint":
|
370
|
-
try:
|
371
|
-
# Build the command
|
372
|
-
cmd = ["npx", "eslint", "--format=json"]
|
373
|
-
|
374
|
-
if custom_args:
|
375
|
-
cmd.extend(custom_args.split())
|
376
|
-
else:
|
377
|
-
# Add default exclusions if no custom args provided
|
378
|
-
cmd.extend(["--ignore-pattern", "**/node_modules/**", "--ignore-pattern", "**/.git/**"])
|
379
|
-
|
380
|
-
# Add target path
|
381
|
-
cmd.append(path)
|
382
|
-
|
383
|
-
# Run ESLint
|
384
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
385
|
-
|
386
|
-
if result.stdout.strip():
|
387
|
-
# Parse ESLint JSON output
|
388
|
-
try:
|
389
|
-
eslint_results = json.loads(result.stdout)
|
390
|
-
for file_result in eslint_results:
|
391
|
-
# Get file path and ensure it's absolute
|
392
|
-
file_path = file_result.get("filePath", "")
|
393
|
-
if not os.path.isabs(file_path):
|
394
|
-
file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
|
395
|
-
|
396
|
-
# Security check for file path and exclude system directories
|
397
|
-
if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
|
398
|
-
continue
|
399
|
-
|
400
|
-
for message in file_result.get("messages", []):
|
401
|
-
issues.append({
|
402
|
-
"file": os.path.relpath(file_path, state.allowed_directory),
|
403
|
-
"line": message.get("line", 0),
|
404
|
-
"column": message.get("column", 0),
|
405
|
-
"message": message.get("message", ""),
|
406
|
-
"severity": _map_eslint_severity(message.get("severity", 1)),
|
407
|
-
"source": "eslint",
|
408
|
-
"code": message.get("ruleId", "")
|
409
|
-
})
|
410
|
-
except json.JSONDecodeError:
|
411
|
-
# Handle parsing errors or ESLint errors
|
412
|
-
if result.stderr:
|
413
|
-
issues.append({
|
414
|
-
"file": path if os.path.isfile(path) else "",
|
415
|
-
"line": 1,
|
416
|
-
"column": 1,
|
417
|
-
"message": f"Error running eslint: {result.stderr}",
|
418
|
-
"severity": "error",
|
419
|
-
"source": "eslint",
|
420
|
-
"code": "tool-error"
|
421
|
-
})
|
422
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
423
|
-
# Silently fail if eslint is not installed
|
424
|
-
pass
|
425
|
-
|
426
|
-
elif linter_name == "dart_analyze":
|
427
|
-
try:
|
428
|
-
# Build the command
|
429
|
-
cmd = ["dart", "analyze", "--format=json"]
|
430
|
-
|
431
|
-
if custom_args:
|
432
|
-
cmd.extend(custom_args.split())
|
433
|
-
|
434
|
-
# Add target path
|
435
|
-
cmd.append(path)
|
436
|
-
|
437
|
-
# Run dart analyze
|
438
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
439
|
-
|
440
|
-
if result.stdout.strip():
|
441
|
-
# Parse Dart Analyze JSON output
|
442
|
-
try:
|
443
|
-
dart_results = json.loads(result.stdout)
|
444
|
-
for file_result in dart_results.get("issues", []):
|
445
|
-
# Get file path and ensure it's absolute
|
446
|
-
file_path = file_result.get("path", "")
|
447
|
-
if not os.path.isabs(file_path):
|
448
|
-
file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
|
449
|
-
|
450
|
-
# Security check for file path and exclude system directories
|
451
|
-
if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
|
452
|
-
continue
|
453
|
-
|
454
|
-
issues.append({
|
455
|
-
"file": os.path.relpath(file_path, state.allowed_directory),
|
456
|
-
"line": file_result.get("location", {}).get("startLine", 0),
|
457
|
-
"column": file_result.get("location", {}).get("startColumn", 0),
|
458
|
-
"message": file_result.get("message", ""),
|
459
|
-
"severity": _map_dart_severity(file_result.get("severity", "")),
|
460
|
-
"source": "dart",
|
461
|
-
"code": file_result.get("code", "")
|
462
|
-
})
|
463
|
-
except json.JSONDecodeError:
|
464
|
-
# Try parsing Flutter-specific error format (from compilation errors)
|
465
|
-
try:
|
466
|
-
# For flutter/dart compilation errors which might not be in JSON format
|
467
|
-
dart_issues = []
|
468
|
-
for line in result.stdout.splitlines() + result.stderr.splitlines():
|
469
|
-
# Check if line contains a compilation error pattern
|
470
|
-
error_match = re.search(r'(.*?):(\d+):(\d+):\s+(error|warning|info):\s+(.*)', line)
|
471
|
-
if error_match:
|
472
|
-
file_path, line_num, col, severity, message = error_match.groups()
|
473
|
-
|
474
|
-
# Ensure path is absolute for security check
|
475
|
-
if not os.path.isabs(file_path):
|
476
|
-
file_path = os.path.abspath(os.path.join(os.path.dirname(path), file_path))
|
477
|
-
|
478
|
-
# Security check for file path
|
479
|
-
if not file_path.startswith(state.allowed_directory) or _is_excluded_system_directory(file_path):
|
480
|
-
continue
|
481
|
-
|
482
|
-
dart_issues.append({
|
483
|
-
"file": os.path.relpath(file_path, state.allowed_directory),
|
484
|
-
"line": int(line_num),
|
485
|
-
"column": int(col),
|
486
|
-
"message": message,
|
487
|
-
"severity": severity,
|
488
|
-
"source": "dart",
|
489
|
-
"code": "compilation-error"
|
490
|
-
})
|
491
|
-
|
492
|
-
# If we found compilation errors, add them
|
493
|
-
issues.extend(dart_issues)
|
494
|
-
except Exception:
|
495
|
-
# Handle any parsing errors
|
496
|
-
if result.stderr:
|
497
|
-
issues.append({
|
498
|
-
"file": path if os.path.isfile(path) else "",
|
499
|
-
"line": 1,
|
500
|
-
"column": 1,
|
501
|
-
"message": f"Error running dart analyze: {result.stderr}",
|
502
|
-
"severity": "error",
|
503
|
-
"source": "dart",
|
504
|
-
"code": "tool-error"
|
505
|
-
})
|
506
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
507
|
-
# Silently fail if dart is not installed
|
508
|
-
pass
|
509
|
-
|
510
|
-
return issues
|
511
|
-
|
512
|
-
def _detect_language_from_file(file_path: str) -> Optional[str]:
|
513
|
-
"""Detect the programming language based on file extension."""
|
514
|
-
_, ext = os.path.splitext(file_path)
|
515
|
-
ext = ext.lower()
|
516
|
-
|
517
|
-
language_map = {
|
518
|
-
'.py': 'python',
|
519
|
-
'.js': 'javascript',
|
520
|
-
'.jsx': 'javascript',
|
521
|
-
'.ts': 'typescript',
|
522
|
-
'.tsx': 'typescript',
|
523
|
-
'.dart': 'dart',
|
524
|
-
'.flutter': 'dart'
|
525
|
-
}
|
526
|
-
|
527
|
-
return language_map.get(ext)
|
528
|
-
|
529
|
-
def _map_pylint_severity(severity_type: str) -> str:
|
530
|
-
"""Map pylint message types to standard severity levels."""
|
531
|
-
severity_map = {
|
532
|
-
"convention": "hint",
|
533
|
-
"refactor": "info",
|
534
|
-
"warning": "warning",
|
535
|
-
"error": "error",
|
536
|
-
"fatal": "error"
|
537
|
-
}
|
538
|
-
return severity_map.get(severity_type.lower(), "info")
|
539
|
-
|
540
|
-
def _map_eslint_severity(severity: int) -> str:
|
541
|
-
"""Map ESLint severity levels to standard severity levels."""
|
542
|
-
if severity == 2:
|
543
|
-
return "error"
|
544
|
-
elif severity == 1:
|
545
|
-
return "warning"
|
546
|
-
else:
|
547
|
-
return "info"
|
548
|
-
|
549
|
-
def _map_dart_severity(severity_type: str) -> str:
|
550
|
-
"""Map dart severity levels to standard severity levels."""
|
551
|
-
severity_map = {
|
552
|
-
"info": "info",
|
553
|
-
"warning": "warning",
|
554
|
-
"error": "error",
|
555
|
-
}
|
556
|
-
return severity_map.get(severity_type.lower(), "info")
|
557
|
-
|
558
|
-
def _format_lint_results(issues: List[Dict[str, Any]]) -> str:
|
559
|
-
"""Format linting issues into a readable text output."""
|
560
|
-
if not issues:
|
561
|
-
return "No linting issues found."
|
562
|
-
|
563
|
-
# Group issues by file
|
564
|
-
issues_by_file = {}
|
565
|
-
for issue in issues:
|
566
|
-
file_path = issue["file"]
|
567
|
-
if file_path not in issues_by_file:
|
568
|
-
issues_by_file[file_path] = []
|
569
|
-
issues_by_file[file_path].append(issue)
|
570
|
-
|
571
|
-
# Format the output
|
572
|
-
output_lines = ["Linting issues found:"]
|
573
|
-
|
574
|
-
for file_path, file_issues in issues_by_file.items():
|
575
|
-
# Sort issues by severity (error -> warning -> info -> hint)
|
576
|
-
severity_order = {"error": 0, "warning": 1, "info": 2, "hint": 3}
|
577
|
-
sorted_issues = sorted(file_issues, key=lambda x: (severity_order.get(x["severity"], 4), x["line"]))
|
578
|
-
|
579
|
-
output_lines.append(f"\n{file_path}:")
|
580
|
-
|
581
|
-
for issue in sorted_issues:
|
582
|
-
severity = issue["severity"].upper()
|
583
|
-
line = issue["line"]
|
584
|
-
column = issue["column"]
|
585
|
-
message = issue["message"]
|
586
|
-
code = f"[{issue['code']}]" if issue["code"] else ""
|
587
|
-
|
588
|
-
output_lines.append(f" {severity} Line {line}:{column} {message} {code}")
|
589
|
-
|
590
|
-
return "\n".join(output_lines)
|
File without changes
|
File without changes
|
File without changes
|