auto-coder 0.1.315__py3-none-any.whl → 0.1.317__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 auto-coder might be problematic. Click here for more details.

Files changed (42) hide show
  1. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/RECORD +42 -21
  3. autocoder/auto_coder_runner.py +1 -2
  4. autocoder/commands/tools.py +30 -19
  5. autocoder/common/__init__.py +3 -0
  6. autocoder/common/auto_coder_lang.py +24 -0
  7. autocoder/common/code_auto_merge_editblock.py +2 -42
  8. autocoder/common/git_utils.py +2 -2
  9. autocoder/common/token_cost_caculate.py +103 -42
  10. autocoder/common/v2/__init__.py +0 -0
  11. autocoder/common/v2/code_auto_generate.py +199 -0
  12. autocoder/common/v2/code_auto_generate_diff.py +361 -0
  13. autocoder/common/v2/code_auto_generate_editblock.py +380 -0
  14. autocoder/common/v2/code_auto_generate_strict_diff.py +269 -0
  15. autocoder/common/v2/code_auto_merge.py +211 -0
  16. autocoder/common/v2/code_auto_merge_diff.py +354 -0
  17. autocoder/common/v2/code_auto_merge_editblock.py +523 -0
  18. autocoder/common/v2/code_auto_merge_strict_diff.py +259 -0
  19. autocoder/common/v2/code_diff_manager.py +266 -0
  20. autocoder/common/v2/code_editblock_manager.py +275 -0
  21. autocoder/common/v2/code_manager.py +238 -0
  22. autocoder/common/v2/code_strict_diff_manager.py +241 -0
  23. autocoder/dispacher/actions/action.py +16 -0
  24. autocoder/dispacher/actions/plugins/action_regex_project.py +6 -0
  25. autocoder/events/event_manager_singleton.py +2 -2
  26. autocoder/helper/__init__.py +0 -0
  27. autocoder/helper/project_creator.py +570 -0
  28. autocoder/linters/linter_factory.py +44 -25
  29. autocoder/linters/models.py +220 -0
  30. autocoder/linters/python_linter.py +1 -7
  31. autocoder/linters/reactjs_linter.py +580 -0
  32. autocoder/linters/shadow_linter.py +390 -0
  33. autocoder/linters/vue_linter.py +576 -0
  34. autocoder/memory/active_context_manager.py +0 -4
  35. autocoder/memory/active_package.py +12 -12
  36. autocoder/shadows/__init__.py +0 -0
  37. autocoder/shadows/shadow_manager.py +235 -0
  38. autocoder/version.py +1 -1
  39. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/LICENSE +0 -0
  40. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/WHEEL +0 -0
  41. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/entry_points.txt +0 -0
  42. {auto_coder-0.1.315.dist-info → auto_coder-0.1.317.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,580 @@
1
+ """
2
+ Module for linting ReactJS projects.
3
+ This module provides functionality to analyze ReactJS projects for code quality and best practices.
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import subprocess
9
+ from typing import Dict, List, Optional, Any
10
+
11
+ from autocoder.linters.base_linter import BaseLinter
12
+
13
+
14
+ class ReactJSLinter(BaseLinter):
15
+ """
16
+ A class that provides linting functionality for ReactJS projects and single files.
17
+ """
18
+
19
+ def __init__(self, verbose: bool = False):
20
+ """
21
+ Initialize the ReactJSLinter.
22
+
23
+ Args:
24
+ verbose (bool): Whether to display verbose output.
25
+ """
26
+ super().__init__(verbose)
27
+
28
+ def _check_dependencies(self) -> bool:
29
+ """
30
+ Check if required dependencies (node, npm, npx) are installed.
31
+
32
+ Returns:
33
+ bool: True if all dependencies are available, False otherwise.
34
+ """
35
+ try:
36
+ # Check if node and npm are installed
37
+ subprocess.run(["node", "--version"], check=True,
38
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
39
+ subprocess.run(["npm", "--version"], check=True,
40
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
41
+ subprocess.run(["npx", "--version"], check=True,
42
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
43
+ return True
44
+ except (subprocess.SubprocessError, FileNotFoundError):
45
+ return False
46
+
47
+ def get_supported_extensions(self) -> List[str]:
48
+ """
49
+ Get the list of file extensions supported by this linter.
50
+
51
+ Returns:
52
+ List[str]: List of supported file extensions.
53
+ """
54
+ return ['.js', '.jsx', '.ts', '.tsx']
55
+
56
+ def _detect_project_type(self, project_path: str) -> bool:
57
+ """
58
+ Detect whether the project is ReactJS.
59
+
60
+ Args:
61
+ project_path (str): Path to the project directory.
62
+
63
+ Returns:
64
+ bool: True if it's a React project, False otherwise
65
+ """
66
+ # Check for package.json
67
+ package_json_path = os.path.join(project_path, 'package.json')
68
+ if not os.path.exists(package_json_path):
69
+ return False
70
+
71
+ try:
72
+ with open(package_json_path, 'r') as f:
73
+ package_data = json.load(f)
74
+
75
+ dependencies = {
76
+ **package_data.get('dependencies', {}),
77
+ **package_data.get('devDependencies', {})
78
+ }
79
+
80
+ # Check for React
81
+ return 'react' in dependencies
82
+ except (json.JSONDecodeError, FileNotFoundError):
83
+ return False
84
+
85
+ def _detect_file_type(self, file_path: str) -> bool:
86
+ """
87
+ Detect if the file is a ReactJS file.
88
+
89
+ Args:
90
+ file_path (str): Path to the file.
91
+
92
+ Returns:
93
+ bool: True if it's a React file, False otherwise
94
+ """
95
+ if not os.path.exists(file_path) or not os.path.isfile(file_path):
96
+ return False
97
+
98
+ # Get file extension
99
+ ext = self.get_file_extension(file_path)
100
+
101
+ if ext == '.jsx' or ext == '.tsx':
102
+ return True
103
+ elif ext == '.js' or ext == '.ts':
104
+ # Check content for React imports
105
+ try:
106
+ with open(file_path, 'r', encoding='utf-8') as f:
107
+ content = f.read()
108
+ if 'import React' in content or 'from "react"' in content or "from 'react'" in content:
109
+ return True
110
+ except:
111
+ pass
112
+
113
+ return False
114
+
115
+ def _install_eslint_if_needed(self, project_path: str) -> bool:
116
+ """
117
+ Install ESLint and the appropriate React plugins if they're not already installed.
118
+
119
+ Args:
120
+ project_path (str): Path to the project directory.
121
+
122
+ Returns:
123
+ bool: True if installation was successful, False otherwise.
124
+ """
125
+ try:
126
+ # 首先尝试运行 npx eslint --version 检查是否已安装
127
+ if self.verbose:
128
+ print("Checking if ESLint is already installed...")
129
+
130
+ try:
131
+ result = subprocess.run(
132
+ ["npx", "eslint", "--version"],
133
+ cwd=project_path,
134
+ stdout=subprocess.PIPE,
135
+ stderr=subprocess.PIPE,
136
+ text=True,
137
+ check=False
138
+ )
139
+
140
+ if result.returncode == 0:
141
+ if self.verbose:
142
+ print(f"ESLint is already installed: {result.stdout.strip()}")
143
+ return True
144
+ except subprocess.SubprocessError:
145
+ if self.verbose:
146
+ print("ESLint not found via npx, will proceed with installation")
147
+
148
+ # 检查 .eslintrc.* 配置文件是否存在
149
+ eslint_configs = [
150
+ os.path.join(project_path, '.eslintrc'),
151
+ os.path.join(project_path, '.eslintrc.js'),
152
+ os.path.join(project_path, '.eslintrc.json'),
153
+ os.path.join(project_path, '.eslintrc.yml'),
154
+ os.path.join(project_path, '.eslintrc.yaml')
155
+ ]
156
+
157
+ if any(os.path.exists(config) for config in eslint_configs):
158
+ if self.verbose:
159
+ print("ESLint configuration found.")
160
+ return True
161
+
162
+ # Install eslint and React plugins
163
+ cmd = ["npm", "install", "--save-dev", "eslint", "eslint-plugin-react"]
164
+ if self.verbose:
165
+ print("Installing ESLint with React plugins...")
166
+
167
+ result = subprocess.run(
168
+ cmd,
169
+ cwd=project_path,
170
+ stdout=subprocess.PIPE if not self.verbose else None,
171
+ stderr=subprocess.PIPE if not self.verbose else None
172
+ )
173
+
174
+ # Create basic configuration
175
+ eslint_config = {
176
+ "env": {
177
+ "browser": True,
178
+ "es2021": True,
179
+ "node": True
180
+ },
181
+ "extends": ["eslint:recommended", "plugin:react/recommended"],
182
+ "plugins": ["react"],
183
+ "parserOptions": {
184
+ "ecmaFeatures": {
185
+ "jsx": True
186
+ },
187
+ "ecmaVersion": 2021,
188
+ "sourceType": "module"
189
+ }
190
+ }
191
+
192
+ # Write configuration
193
+ with open(os.path.join(project_path, '.eslintrc.json'), 'w') as f:
194
+ json.dump(eslint_config, f, indent=2)
195
+
196
+ return result.returncode == 0
197
+ except subprocess.SubprocessError:
198
+ return False
199
+
200
+ def _extract_json_from_output(self, output_text: str) -> str:
201
+ """
202
+ Extract JSON string from output text that might contain non-JSON content at the beginning.
203
+
204
+ Args:
205
+ output_text (str): The output text that may contain a JSON string after a separator.
206
+
207
+ Returns:
208
+ str: The extracted JSON string, or the original text if no separator is found.
209
+ """
210
+ if "=============" in output_text:
211
+ lines = output_text.split('\n')
212
+ json_lines = []
213
+ found_separator = False
214
+
215
+ for line in lines:
216
+ if line.startswith("============="):
217
+ found_separator = True
218
+ continue
219
+ if found_separator:
220
+ json_lines.append(line)
221
+
222
+ if json_lines:
223
+ return '\n'.join(json_lines)
224
+
225
+ return output_text
226
+
227
+ def lint_file(self, file_path: str, fix: bool = False, project_path: str = None) -> Dict[str, Any]:
228
+ """
229
+ Lint a single ReactJS file using ESLint.
230
+
231
+ Args:
232
+ file_path (str): Path to the file to lint.
233
+ fix (bool): Whether to automatically fix fixable issues.
234
+ project_path (str, optional): Path to the project directory. If not provided,
235
+ the parent directory of the file will be used.
236
+
237
+ Returns:
238
+ Dict: A dictionary containing lint results.
239
+ """
240
+ result = {
241
+ 'success': False,
242
+ 'framework': 'reactjs',
243
+ 'files_analyzed': 0,
244
+ 'error_count': 0,
245
+ 'warning_count': 0,
246
+ 'issues': []
247
+ }
248
+
249
+ # Check if file exists
250
+ if not os.path.exists(file_path) or not os.path.isfile(file_path):
251
+ result['error'] = f"File '{file_path}' does not exist or is not a file"
252
+ return result
253
+
254
+ # Check if file is supported
255
+ if not self.is_supported_file(file_path):
256
+ result['error'] = f"Unsupported file type for '{file_path}'"
257
+ return result
258
+
259
+ # Check if it's a React file
260
+ if not self._detect_file_type(file_path):
261
+ result['error'] = f"Not a React file: '{file_path}'"
262
+ return result
263
+
264
+ # Check dependencies
265
+ if not self._check_dependencies():
266
+ result['error'] = "Required dependencies (node, npm, npx) are not installed"
267
+ return result
268
+
269
+ # If project_path is not provided, use parent directory
270
+ if project_path is None:
271
+ project_path = os.path.dirname(file_path)
272
+
273
+ # Install ESLint if needed
274
+ if not self._install_eslint_if_needed(project_path):
275
+ result['error'] = "Failed to install or configure ESLint"
276
+ return result
277
+
278
+ # Run ESLint on the file
279
+ try:
280
+ cmd = ["npx", "eslint", "--format", "json"]
281
+
282
+ # Add fix flag if requested
283
+ if fix:
284
+ cmd.append("--fix")
285
+
286
+ # Add file path
287
+ cmd.append(file_path)
288
+
289
+ process = subprocess.run(
290
+ cmd,
291
+ cwd=project_path,
292
+ stdout=subprocess.PIPE,
293
+ stderr=subprocess.PIPE,
294
+ text=True
295
+ )
296
+
297
+ # Parse ESLint output
298
+ if process.stdout:
299
+ try:
300
+ output_text = process.stdout
301
+ try:
302
+ eslint_output = json.loads(output_text)
303
+ except json.JSONDecodeError:
304
+ # Try to extract JSON from output if it contains separator
305
+ json_text = self._extract_json_from_output(output_text)
306
+ eslint_output = json.loads(json_text)
307
+
308
+ # print(f"eslint_output: {json.dumps(eslint_output, indent=4,ensure_ascii=False)}")
309
+
310
+ # Count files analyzed (should be 1)
311
+ result['files_analyzed'] = len(eslint_output)
312
+
313
+ # Track overall counts
314
+ total_errors = 0
315
+ total_warnings = 0
316
+
317
+ # Process the file result
318
+ for file_result in eslint_output:
319
+ file_rel_path = os.path.relpath(
320
+ file_result['filePath'], project_path)
321
+
322
+ # Add error and warning counts
323
+ total_errors += file_result.get('errorCount', 0)
324
+ total_warnings += file_result.get('warningCount', 0)
325
+
326
+ # Process individual messages
327
+ for message in file_result.get('messages', []):
328
+ issue = {
329
+ 'file': file_rel_path,
330
+ 'line': message.get('line', 0),
331
+ 'column': message.get('column', 0),
332
+ 'severity': 'error' if message.get('severity', 1) == 2 else 'warning',
333
+ 'message': message.get('message', ''),
334
+ 'rule': message.get('ruleId', 'unknown')
335
+ }
336
+ result['issues'].append(issue)
337
+
338
+ result['error_count'] = total_errors
339
+ result['warning_count'] = total_warnings
340
+ result['success'] = True
341
+ except json.JSONDecodeError:
342
+ # Handle case where ESLint output is not valid JSON
343
+ result['error'] = "Failed to parse ESLint output"
344
+ else:
345
+ # Handle case where ESLint didn't produce any output
346
+ stderr = process.stderr.strip()
347
+ if stderr:
348
+ result['error'] = f"ESLint error: {stderr}"
349
+ else:
350
+ # No errors found
351
+ result['success'] = True
352
+ except subprocess.SubprocessError as e:
353
+ result['error'] = f"Error running ESLint: {str(e)}"
354
+
355
+ return result
356
+
357
+ def lint_project(self, project_path: str, fix: bool = False) -> Dict[str, Any]:
358
+ """
359
+ Lint a ReactJS project.
360
+
361
+ Args:
362
+ project_path (str): Path to the project directory.
363
+ fix (bool): Whether to automatically fix fixable issues.
364
+
365
+ Returns:
366
+ ProjectLintResult: A dictionary containing lint results.
367
+ """
368
+ result = {
369
+ 'success': False,
370
+ 'framework': 'reactjs',
371
+ 'files_analyzed': 0,
372
+ 'error_count': 0,
373
+ 'warning_count': 0,
374
+ 'issues': []
375
+ }
376
+
377
+ # Check if the path exists
378
+ if not os.path.exists(project_path) or not os.path.isdir(project_path):
379
+ result['error'] = f"Path '{project_path}' does not exist or is not a directory"
380
+ return result
381
+
382
+ # Check dependencies
383
+ if not self._check_dependencies():
384
+ result['error'] = "Required dependencies (node, npm, npx) are not installed"
385
+ return result
386
+
387
+ # Detect project type
388
+ if not self._detect_project_type(project_path):
389
+ result['error'] = "Not a React project"
390
+ return result
391
+
392
+ # Install ESLint if needed
393
+ if not self._install_eslint_if_needed(project_path):
394
+ result['error'] = "Failed to install or configure ESLint"
395
+ return result
396
+
397
+ # Run ESLint
398
+ try:
399
+ cmd = ["npx", "eslint", "--format", "json"]
400
+
401
+ # Add fix flag if requested
402
+ if fix:
403
+ cmd.append("--fix")
404
+
405
+ # Target React files
406
+ cmd.extend([
407
+ "src/**/*.js",
408
+ "src/**/*.jsx",
409
+ "src/**/*.ts",
410
+ "src/**/*.tsx"
411
+ ])
412
+
413
+ process = subprocess.run(
414
+ cmd,
415
+ cwd=project_path,
416
+ stdout=subprocess.PIPE,
417
+ stderr=subprocess.PIPE,
418
+ text=True
419
+ )
420
+
421
+ # Parse ESLint output
422
+ if process.stdout:
423
+ try:
424
+ try:
425
+ eslint_output = json.loads(process.stdout)
426
+ except json.JSONDecodeError:
427
+ # Try to extract JSON from output if it contains separator
428
+ json_text = self._extract_json_from_output(process.stdout)
429
+ eslint_output = json.loads(json_text)
430
+
431
+ # Count files analyzed
432
+ result['files_analyzed'] = len(eslint_output)
433
+
434
+ # Track overall counts
435
+ total_errors = 0
436
+ total_warnings = 0
437
+
438
+ # Process each file result
439
+ for file_result in eslint_output:
440
+ file_path = os.path.relpath(
441
+ file_result['filePath'], project_path)
442
+
443
+ # Add error and warning counts
444
+ total_errors += file_result.get('errorCount', 0)
445
+ total_warnings += file_result.get('warningCount', 0)
446
+
447
+ # Process individual messages
448
+ for message in file_result.get('messages', []):
449
+ issue = {
450
+ 'file': file_path,
451
+ 'line': message.get('line', 0),
452
+ 'column': message.get('column', 0),
453
+ 'severity': 'error' if message.get('severity', 1) == 2 else 'warning',
454
+ 'message': message.get('message', ''),
455
+ 'rule': message.get('ruleId', 'unknown')
456
+ }
457
+ result['issues'].append(issue)
458
+
459
+ result['error_count'] = total_errors
460
+ result['warning_count'] = total_warnings
461
+ result['success'] = True
462
+ except json.JSONDecodeError:
463
+ # Handle case where ESLint output is not valid JSON
464
+ result['error'] = "Failed to parse ESLint output"
465
+ else:
466
+ # Handle case where ESLint didn't produce any output
467
+ stderr = process.stderr.strip()
468
+ if stderr:
469
+ result['error'] = f"ESLint error: {stderr}"
470
+ else:
471
+ # No errors found
472
+ result['success'] = True
473
+ except subprocess.SubprocessError as e:
474
+ result['error'] = f"Error running ESLint: {str(e)}"
475
+
476
+ return result
477
+
478
+ def format_lint_result(self, lint_result: Dict[str, Any]) -> str:
479
+ """
480
+ Format lint results into a human-readable string.
481
+
482
+ Args:
483
+ lint_result (Dict): The lint result dictionary.
484
+
485
+ Returns:
486
+ str: A formatted string representation of the lint results.
487
+ """
488
+ if not lint_result.get('success', False):
489
+ return f"Linting failed: {lint_result.get('error', 'Unknown error')}"
490
+
491
+ files_analyzed = lint_result.get('files_analyzed', 0)
492
+ error_count = lint_result.get('error_count', 0)
493
+ warning_count = lint_result.get('warning_count', 0)
494
+
495
+ output = [
496
+ "ReactJS Lint Results",
497
+ f"{'=' * 30}",
498
+ f"Files analyzed: {files_analyzed}",
499
+ f"Errors: {error_count}",
500
+ f"Warnings: {warning_count}",
501
+ ""
502
+ ]
503
+
504
+ if error_count == 0 and warning_count == 0:
505
+ output.append("No issues found. Great job!")
506
+ else:
507
+ output.append("Issues:")
508
+ output.append(f"{'-' * 30}")
509
+
510
+ # Group issues by file
511
+ issues_by_file = {}
512
+ for issue in lint_result.get('issues', []):
513
+ file_path = issue.get('file', '')
514
+ if file_path not in issues_by_file:
515
+ issues_by_file[file_path] = []
516
+ issues_by_file[file_path].append(issue)
517
+
518
+ # Display issues grouped by file
519
+ for file_path, issues in issues_by_file.items():
520
+ output.append(f"\nFile: {file_path}")
521
+
522
+ for issue in issues:
523
+ severity = issue.get('severity', '').upper()
524
+ line = issue.get('line', 0)
525
+ column = issue.get('column', 0)
526
+ message = issue.get('message', '')
527
+ rule = issue.get('rule', 'unknown')
528
+
529
+ output.append(
530
+ f" [{severity}] Line {line}, Col {column}: {message} ({rule})")
531
+
532
+ return "\n".join(output)
533
+
534
+
535
+ def lint_project(project_path: str, fix: bool = False, verbose: bool = False) -> Dict[str, Any]:
536
+ """
537
+ Utility function to lint a ReactJS project.
538
+
539
+ Args:
540
+ project_path (str): Path to the project directory.
541
+ fix (bool): Whether to automatically fix fixable issues.
542
+ verbose (bool): Whether to display verbose output.
543
+
544
+ Returns:
545
+ Dict: A dictionary containing lint results.
546
+ """
547
+ linter = ReactJSLinter(verbose=verbose)
548
+ return linter.lint_project(project_path, fix=fix)
549
+
550
+
551
+ def lint_file(file_path: str, project_path: str = None, fix: bool = False, verbose: bool = False) -> Dict[str, Any]:
552
+ """
553
+ Utility function to lint a single ReactJS file.
554
+
555
+ Args:
556
+ file_path (str): Path to the file to lint.
557
+ project_path (str, optional): Path to the project directory. If not provided,
558
+ the parent directory of the file will be used.
559
+ fix (bool): Whether to automatically fix fixable issues.
560
+ verbose (bool): Whether to display verbose output.
561
+
562
+ Returns:
563
+ Dict: A dictionary containing lint results.
564
+ """
565
+ linter = ReactJSLinter(verbose=verbose)
566
+ return linter.lint_file(file_path, fix=fix, project_path=project_path)
567
+
568
+
569
+ def format_lint_result(lint_result: Dict[str, Any]) -> str:
570
+ """
571
+ Format lint results into a human-readable string.
572
+
573
+ Args:
574
+ lint_result (Dict): The lint result dictionary.
575
+
576
+ Returns:
577
+ str: A formatted string representation of the lint results.
578
+ """
579
+ linter = ReactJSLinter()
580
+ return linter.format_lint_result(lint_result)