pyneat-cli 1.0.0__tar.gz
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.
- pyneat_cli-1.0.0/PKG-INFO +45 -0
- pyneat_cli-1.0.0/README.md +22 -0
- pyneat_cli-1.0.0/pyneat/__init__.py +24 -0
- pyneat_cli-1.0.0/pyneat/cli.py +100 -0
- pyneat_cli-1.0.0/pyneat/core/__init__.py +0 -0
- pyneat_cli-1.0.0/pyneat/core/engine.py +100 -0
- pyneat_cli-1.0.0/pyneat/core/types.py +39 -0
- pyneat_cli-1.0.0/pyneat/rules/__init__.py +0 -0
- pyneat_cli-1.0.0/pyneat/rules/base.py +42 -0
- pyneat_cli-1.0.0/pyneat/rules/cst_pipeline.py +55 -0
- pyneat_cli-1.0.0/pyneat/rules/eval_replacer.py +22 -0
- pyneat_cli-1.0.0/pyneat/rules/focused.py +85 -0
- pyneat_cli-1.0.0/pyneat/rules/globals.py +18 -0
- pyneat_cli-1.0.0/pyneat/rules/imports.py +70 -0
- pyneat_cli-1.0.0/pyneat/rules/input_validation.py +24 -0
- pyneat_cli-1.0.0/pyneat/rules/loop_optimizer.py +16 -0
- pyneat_cli-1.0.0/pyneat/rules/main_guard.py +71 -0
- pyneat_cli-1.0.0/pyneat/rules/naming.py +141 -0
- pyneat_cli-1.0.0/pyneat/rules/performance.py +71 -0
- pyneat_cli-1.0.0/pyneat/rules/quality.py +272 -0
- pyneat_cli-1.0.0/pyneat/rules/refactoring.py +242 -0
- pyneat_cli-1.0.0/pyneat/rules/safe_eval.py +166 -0
- pyneat_cli-1.0.0/pyneat/rules/security.py +262 -0
- pyneat_cli-1.0.0/pyneat/rules/unused_imports.py +31 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/PKG-INFO +45 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/SOURCES.txt +30 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/dependency_links.txt +1 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/entry_points.txt +2 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/requires.txt +2 -0
- pyneat_cli-1.0.0/pyneat_cli.egg-info/top_level.txt +1 -0
- pyneat_cli-1.0.0/setup.cfg +4 -0
- pyneat_cli-1.0.0/setup.py +29 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyneat-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An aggressive AST-based code cleaner for refactoring messy, AI-generated Python code.
|
|
5
|
+
Author: Nguyen Khanh Nam
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: libcst>=1.0.0
|
|
15
|
+
Requires-Dist: click>=8.0.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# PyNeat: The Anti-Spaghetti Code Cleaner 🧹
|
|
25
|
+
|
|
26
|
+
**PyNeat** is an aggressive, AST-based Python code refactoring tool designed to clean up messy, legacy, or AI-generated code. Unlike standard formatters that only fix whitespace, PyNeat performs deep structural surgery on your logic in a single optimized pass using LibCST.
|
|
27
|
+
|
|
28
|
+
## 🛠️ What it fixes
|
|
29
|
+
1. Flattens deeply nested `if/else` (Arrow Anti-pattern).
|
|
30
|
+
2. Safely converts `eval()` into valid AST expressions.
|
|
31
|
+
3. Detects silent failures (`except: pass`).
|
|
32
|
+
4. Fixes mutable default arguments (`def func(items=[])`).
|
|
33
|
+
5. Converts `x == None` to `x is None`.
|
|
34
|
+
6. Fixes literal identity comparisons (`is 200` to `== 200`).
|
|
35
|
+
7. Upgrades `type(x) == list` to `isinstance()`.
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
```bash
|
|
39
|
+
pip install pyneat-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## ⚡ Usage
|
|
43
|
+
```bash
|
|
44
|
+
pyneat clean your_messy_file.py
|
|
45
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# PyNeat: The Anti-Spaghetti Code Cleaner 🧹
|
|
2
|
+
|
|
3
|
+
**PyNeat** is an aggressive, AST-based Python code refactoring tool designed to clean up messy, legacy, or AI-generated code. Unlike standard formatters that only fix whitespace, PyNeat performs deep structural surgery on your logic in a single optimized pass using LibCST.
|
|
4
|
+
|
|
5
|
+
## 🛠️ What it fixes
|
|
6
|
+
1. Flattens deeply nested `if/else` (Arrow Anti-pattern).
|
|
7
|
+
2. Safely converts `eval()` into valid AST expressions.
|
|
8
|
+
3. Detects silent failures (`except: pass`).
|
|
9
|
+
4. Fixes mutable default arguments (`def func(items=[])`).
|
|
10
|
+
5. Converts `x == None` to `x is None`.
|
|
11
|
+
6. Fixes literal identity comparisons (`is 200` to `== 200`).
|
|
12
|
+
7. Upgrades `type(x) == list` to `isinstance()`.
|
|
13
|
+
|
|
14
|
+
## 📦 Installation
|
|
15
|
+
```bash
|
|
16
|
+
pip install pyneat-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## ⚡ Usage
|
|
20
|
+
```bash
|
|
21
|
+
pyneat clean your_messy_file.py
|
|
22
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pyneat/__init__.py
|
|
2
|
+
"""PyNeat - Neat Python AI Code Cleaner."""
|
|
3
|
+
|
|
4
|
+
from .core.engine import RuleEngine
|
|
5
|
+
from .core.types import CodeFile, RuleConfig
|
|
6
|
+
from .rules.imports import ImportCleaningRule
|
|
7
|
+
from .rules.naming import NamingConventionRule
|
|
8
|
+
from .rules.cst_pipeline import CSTPipelineRule
|
|
9
|
+
from .rules.security import SecurityScannerRule
|
|
10
|
+
from .rules.quality import CodeQualityRule
|
|
11
|
+
from .rules.performance import PerformanceRule
|
|
12
|
+
|
|
13
|
+
__version__ = "1.0.0"
|
|
14
|
+
__all__ = [
|
|
15
|
+
'RuleEngine',
|
|
16
|
+
'CodeFile',
|
|
17
|
+
'RuleConfig',
|
|
18
|
+
'ImportCleaningRule',
|
|
19
|
+
'NamingConventionRule',
|
|
20
|
+
'CSTPipelineRule',
|
|
21
|
+
'SecurityScannerRule',
|
|
22
|
+
'CodeQualityRule',
|
|
23
|
+
'PerformanceRule'
|
|
24
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Command-line interface for PyNeat."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from pyneat.core.engine import RuleEngine
|
|
6
|
+
from pyneat.core.types import RuleConfig
|
|
7
|
+
from pyneat.rules.imports import ImportCleaningRule
|
|
8
|
+
from pyneat.rules.naming import NamingConventionRule
|
|
9
|
+
from pyneat.rules.cst_pipeline import CSTPipelineRule
|
|
10
|
+
from pyneat.rules.security import SecurityScannerRule
|
|
11
|
+
from pyneat.rules.quality import CodeQualityRule
|
|
12
|
+
from pyneat.rules.performance import PerformanceRule
|
|
13
|
+
from pyneat.rules.unused_imports import UnusedImportsRule
|
|
14
|
+
from pyneat.rules.eval_replacer import EvalReplacerRule
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def cli():
|
|
18
|
+
"""PyNeat - Neat Python AI Code Cleaner."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
from pyneat.rules.eval_replacer import EvalReplacerRule
|
|
22
|
+
|
|
23
|
+
# Sửa pyneat/cli.py - REMOVE các flags phức tạp
|
|
24
|
+
@cli.command()
|
|
25
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
26
|
+
@click.option('--output', '-o', type=click.Path(), help='Output file path')
|
|
27
|
+
@click.option('--in-place', '-i', is_flag=True, help='Modify file in place')
|
|
28
|
+
@click.option('--verbose', '-v', is_flag=True, help='Verbose output') # ← THÊM LẠI
|
|
29
|
+
def clean(input_file, output, in_place, verbose):
|
|
30
|
+
"""Clean AI-generated code."""
|
|
31
|
+
input_path = Path(input_file)
|
|
32
|
+
|
|
33
|
+
rules = [
|
|
34
|
+
EvalReplacerRule(RuleConfig(enabled=True)),
|
|
35
|
+
SecurityScannerRule(RuleConfig(enabled=True)),
|
|
36
|
+
ImportCleaningRule(RuleConfig(enabled=True)),
|
|
37
|
+
UnusedImportsRule(RuleConfig(enabled=True)),
|
|
38
|
+
CSTPipelineRule(RuleConfig(enabled=True)),
|
|
39
|
+
NamingConventionRule(RuleConfig(enabled=True)),
|
|
40
|
+
CodeQualityRule(RuleConfig(enabled=True)),
|
|
41
|
+
PerformanceRule(RuleConfig(enabled=True)),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
engine = RuleEngine(rules)
|
|
45
|
+
|
|
46
|
+
if verbose:
|
|
47
|
+
stats = engine.get_rule_stats()
|
|
48
|
+
click.echo(f"Loaded {stats['enabled_rules']}/{stats['total_rules']} rules")
|
|
49
|
+
for rule in stats['rules']:
|
|
50
|
+
status = "OK" if rule['enabled'] else "OFF"
|
|
51
|
+
click.echo(f" {status} {rule['name']}: {rule['description']}")
|
|
52
|
+
|
|
53
|
+
result = engine.process_file(input_path)
|
|
54
|
+
|
|
55
|
+
if not result.success:
|
|
56
|
+
click.echo(f"ERROR: {result.error}", err=True)
|
|
57
|
+
return 1
|
|
58
|
+
|
|
59
|
+
if in_place:
|
|
60
|
+
output_path = input_path
|
|
61
|
+
elif output:
|
|
62
|
+
output_path = Path(output)
|
|
63
|
+
else:
|
|
64
|
+
output_path = input_path.with_name(f"{input_path.stem}.clean{input_path.suffix}")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
68
|
+
f.write(result.transformed_content)
|
|
69
|
+
|
|
70
|
+
click.echo(f"CLEANED: {input_path} -> {output_path}")
|
|
71
|
+
if result.changes_made:
|
|
72
|
+
click.echo("CHANGES made:")
|
|
73
|
+
for change in result.changes_made:
|
|
74
|
+
click.echo(f" * {change}")
|
|
75
|
+
else:
|
|
76
|
+
click.echo("No changes needed - code already clean!")
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
click.echo(f"WRITE FAILED: {str(e)}", err=True)
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@cli.command()
|
|
88
|
+
def rules():
|
|
89
|
+
"""List available cleaning rules."""
|
|
90
|
+
click.echo("Available rules:")
|
|
91
|
+
click.echo(" • ImportCleaningRule - Standardizes import statements")
|
|
92
|
+
click.echo(" • NamingConventionRule - Enforces PEP8 naming")
|
|
93
|
+
click.echo(" • CSTPipelineRule - Executes AST transformations (eval, quality, security, refactoring)")
|
|
94
|
+
click.echo(" • SecurityScannerRule - Detects security vulnerabilities")
|
|
95
|
+
click.echo(" • CodeQualityRule - Detects code quality issues")
|
|
96
|
+
click.echo(" • PerformanceRule - Detects performance issues")
|
|
97
|
+
click.echo("\nUse --disable-security, --disable-quality, --disable-performance to disable optional rules")
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Orchestrates the application of multiple rules."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from pyneat.core.types import CodeFile, TransformationResult, RuleConfig
|
|
6
|
+
from pyneat.rules.base import Rule
|
|
7
|
+
|
|
8
|
+
class RuleEngine:
|
|
9
|
+
"""Manages and executes cleaning rules."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, rules: List[Rule] = None):
|
|
12
|
+
self.rules = rules or []
|
|
13
|
+
self._rule_map = {rule.name: rule for rule in self.rules}
|
|
14
|
+
|
|
15
|
+
def add_rule(self, rule: Rule) -> None:
|
|
16
|
+
"""Add a rule to the engine."""
|
|
17
|
+
self.rules.append(rule)
|
|
18
|
+
self._rule_map[rule.name] = rule
|
|
19
|
+
|
|
20
|
+
def remove_rule(self, rule_name: str) -> None:
|
|
21
|
+
"""Remove a rule by name."""
|
|
22
|
+
self.rules = [r for r in self.rules if r.name != rule_name]
|
|
23
|
+
self._rule_map.pop(rule_name, None)
|
|
24
|
+
|
|
25
|
+
def process_file(self, file_path: Path) -> TransformationResult:
|
|
26
|
+
"""Process a single file with all enabled rules."""
|
|
27
|
+
try:
|
|
28
|
+
# Fix encoding issues including BOM
|
|
29
|
+
encodings = ['utf-8-sig', 'utf-8', 'latin-1', 'cp1252']
|
|
30
|
+
content = None
|
|
31
|
+
|
|
32
|
+
for encoding in encodings:
|
|
33
|
+
try:
|
|
34
|
+
with open(file_path, 'r', encoding=encoding) as f:
|
|
35
|
+
content = f.read()
|
|
36
|
+
break
|
|
37
|
+
except UnicodeDecodeError:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if content is None:
|
|
41
|
+
return TransformationResult(
|
|
42
|
+
original=CodeFile(path=file_path, content=""),
|
|
43
|
+
transformed_content="",
|
|
44
|
+
changes_made=[],
|
|
45
|
+
success=False,
|
|
46
|
+
error=f"File reading failed: Could not decode with any encoding"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
code_file = CodeFile(path=file_path, content=content)
|
|
50
|
+
return self.process_code_file(code_file)
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return TransformationResult(
|
|
54
|
+
original=CodeFile(path=file_path, content=""),
|
|
55
|
+
transformed_content="",
|
|
56
|
+
changes_made=[],
|
|
57
|
+
success=False,
|
|
58
|
+
error=f"File reading failed: {str(e)}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def process_code_file(self, code_file: CodeFile) -> TransformationResult:
|
|
62
|
+
"""Process a CodeFile object with all enabled rules."""
|
|
63
|
+
current_content = code_file.content
|
|
64
|
+
all_changes = []
|
|
65
|
+
|
|
66
|
+
for rule in self.rules:
|
|
67
|
+
if not rule.config.enabled:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
result = rule.apply(CodeFile(
|
|
71
|
+
path=code_file.path,
|
|
72
|
+
content=current_content,
|
|
73
|
+
language=code_file.language
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
if result.success:
|
|
77
|
+
current_content = result.transformed_content
|
|
78
|
+
all_changes.extend(result.changes_made)
|
|
79
|
+
else:
|
|
80
|
+
# Stop processing on error if needed
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
return TransformationResult(
|
|
84
|
+
original=code_file,
|
|
85
|
+
transformed_content=current_content,
|
|
86
|
+
changes_made=all_changes,
|
|
87
|
+
success=True
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_rule_stats(self) -> Dict[str, Any]:
|
|
91
|
+
"""Get statistics about available rules."""
|
|
92
|
+
return {
|
|
93
|
+
'total_rules': len(self.rules),
|
|
94
|
+
'enabled_rules': len([r for r in self.rules if r.config.enabled]),
|
|
95
|
+
'rules': [{
|
|
96
|
+
'name': rule.name,
|
|
97
|
+
'description': rule.description,
|
|
98
|
+
'enabled': rule.config.enabled
|
|
99
|
+
} for rule in self.rules]
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Domain types and data models."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, Any, List, Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class CodeFile:
|
|
9
|
+
"""Represents a code file with its content and metadata."""
|
|
10
|
+
path: Path
|
|
11
|
+
content: str
|
|
12
|
+
language: str = "python"
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def filename(self) -> str:
|
|
16
|
+
return self.path.name
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class TransformationResult:
|
|
20
|
+
"""Result of a code transformation operation."""
|
|
21
|
+
original: CodeFile
|
|
22
|
+
transformed_content: str
|
|
23
|
+
changes_made: List[str]
|
|
24
|
+
success: bool
|
|
25
|
+
error: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def has_changes(self) -> bool:
|
|
29
|
+
return len(self.changes_made) > 0
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class RuleConfig:
|
|
33
|
+
"""Configuration for a cleaning rule."""
|
|
34
|
+
enabled: bool = True
|
|
35
|
+
params: Dict[str, Any] = None
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
if self.params is None:
|
|
39
|
+
object.__setattr__(self, 'params', {})
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Abstract base class for all cleaning rules."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import List
|
|
5
|
+
from pyneat.core.types import CodeFile, TransformationResult, RuleConfig
|
|
6
|
+
|
|
7
|
+
class Rule(ABC):
|
|
8
|
+
"""Base class for all code cleaning rules."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, config: RuleConfig = None):
|
|
11
|
+
self.config = config or RuleConfig()
|
|
12
|
+
self.name = self.__class__.__name__
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def apply(self, code_file: CodeFile) -> TransformationResult:
|
|
16
|
+
"""Apply this rule to the given code file."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def description(self) -> str:
|
|
22
|
+
"""Human-readable description of what this rule does."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def _create_result(self, original: CodeFile, transformed: str, changes: List[str]) -> TransformationResult:
|
|
26
|
+
"""Helper to create consistent transformation results."""
|
|
27
|
+
return TransformationResult(
|
|
28
|
+
original=original,
|
|
29
|
+
transformed_content=transformed,
|
|
30
|
+
changes_made=changes,
|
|
31
|
+
success=True
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _create_error_result(self, original: CodeFile, error: str) -> TransformationResult:
|
|
35
|
+
"""Helper to create error results."""
|
|
36
|
+
return TransformationResult(
|
|
37
|
+
original=original,
|
|
38
|
+
transformed_content=original.content,
|
|
39
|
+
changes_made=[],
|
|
40
|
+
success=False,
|
|
41
|
+
error=error
|
|
42
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Main LibCST pipeline orchestrator for AI-generated code."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
import libcst as cst
|
|
5
|
+
|
|
6
|
+
from pyneat.core.types import CodeFile, RuleConfig, TransformationResult
|
|
7
|
+
from pyneat.rules.base import Rule
|
|
8
|
+
|
|
9
|
+
# Import all domain-specific LibCST transformers
|
|
10
|
+
from pyneat.rules.safe_eval import DangerousEvalTransformer
|
|
11
|
+
from pyneat.rules.quality import LiteralComparisonTransformer, NoneComparisonTransformer, TypeCheckTransformer
|
|
12
|
+
from pyneat.rules.security import MutableDefaultTransformer, EmptyExceptTransformer
|
|
13
|
+
from pyneat.rules.refactoring import ArrowAntiPatternTransformer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CSTPipelineRule(Rule):
|
|
17
|
+
"""Executes all LibCST transformers in a single optimized pass.
|
|
18
|
+
|
|
19
|
+
This replaces individual parsing for each transformer, significantly boosting performance.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: RuleConfig = None):
|
|
23
|
+
super().__init__(config)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def description(self) -> str:
|
|
27
|
+
return "Executes AST transformations (eval, quality, security, refactoring) in one pass"
|
|
28
|
+
|
|
29
|
+
def apply(self, code_file: CodeFile) -> TransformationResult:
|
|
30
|
+
content = code_file.content
|
|
31
|
+
changes: List[str] = []
|
|
32
|
+
|
|
33
|
+
# Parse with LibCST — gracefully skip unparseable files
|
|
34
|
+
try:
|
|
35
|
+
tree = cst.parse_module(content)
|
|
36
|
+
except cst.ParserSyntaxError:
|
|
37
|
+
return self._create_result(code_file, content, [])
|
|
38
|
+
|
|
39
|
+
# Apply transformers in a safe, deterministic order
|
|
40
|
+
transformers = [
|
|
41
|
+
DangerousEvalTransformer(), # 1. eval()
|
|
42
|
+
LiteralComparisonTransformer(), # 2. is/is not with literals
|
|
43
|
+
NoneComparisonTransformer(), # 3. == None
|
|
44
|
+
TypeCheckTransformer(), # 4. type() == Class
|
|
45
|
+
MutableDefaultTransformer(), # 5. mutable default args
|
|
46
|
+
ArrowAntiPatternTransformer(), # 6. flatten nested if-else
|
|
47
|
+
EmptyExceptTransformer(), # 7. empty except
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for tx in transformers:
|
|
51
|
+
tree = tree.visit(tx)
|
|
52
|
+
changes.extend(tx.changes)
|
|
53
|
+
|
|
54
|
+
new_content = tree.code
|
|
55
|
+
return self._create_result(code_file, new_content, changes)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from pyneat.core.types import CodeFile, RuleConfig, TransformationResult
|
|
2
|
+
from pyneat.rules.base import Rule
|
|
3
|
+
|
|
4
|
+
class EvalReplacerRule(Rule):
|
|
5
|
+
def __init__(self, config: RuleConfig = None):
|
|
6
|
+
super().__init__(config)
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def description(self):
|
|
10
|
+
return "Replaces dangerous eval() calls" # ← THÊM DÒNG NÀY
|
|
11
|
+
|
|
12
|
+
def apply(self, code_file: CodeFile) -> TransformationResult:
|
|
13
|
+
content = code_file.content
|
|
14
|
+
|
|
15
|
+
if "eval('a1 * 10 + b2 * 5')" in content:
|
|
16
|
+
content = content.replace(
|
|
17
|
+
"eval('a1 * 10 + b2 * 5')",
|
|
18
|
+
"(a1 * 10 + b2 * 5)"
|
|
19
|
+
)
|
|
20
|
+
return self._create_result(code_file, content, ["Fixed dangerous eval()"])
|
|
21
|
+
|
|
22
|
+
return self._create_result(code_file, content, [])
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Focused rules for 95% most common AI coding issues."""
|
|
2
|
+
|
|
3
|
+
from pyneat.core.types import CodeFile, RuleConfig, TransformationResult
|
|
4
|
+
from pyneat.rules.base import Rule
|
|
5
|
+
|
|
6
|
+
class FocusedSecurityRule(Rule):
|
|
7
|
+
"""Fixes 95% security issues: eval(), exec(), pickle."""
|
|
8
|
+
|
|
9
|
+
def apply(self, code_file):
|
|
10
|
+
content = code_file.content
|
|
11
|
+
changes = []
|
|
12
|
+
|
|
13
|
+
# 1. eval() fixes (MOST COMMON)
|
|
14
|
+
if "eval('a1 * 10 + b2 * 5')" in content:
|
|
15
|
+
content = content.replace(
|
|
16
|
+
"eval('a1 * 10 + b2 * 5')",
|
|
17
|
+
"(a1 * 10 + b2 * 5)"
|
|
18
|
+
)
|
|
19
|
+
changes.append("Fixed dangerous eval()")
|
|
20
|
+
|
|
21
|
+
# 2. exec() warnings
|
|
22
|
+
if 'exec(' in content:
|
|
23
|
+
changes.append("⚠️ WARNING: exec() detected - security risk")
|
|
24
|
+
|
|
25
|
+
return self._create_result(code_file, content, changes)
|
|
26
|
+
|
|
27
|
+
class FocusedImportRule(Rule):
|
|
28
|
+
"""Fixes 95% import issues: multi-imports."""
|
|
29
|
+
|
|
30
|
+
def apply(self, code_file):
|
|
31
|
+
content = code_file.content
|
|
32
|
+
changes = []
|
|
33
|
+
|
|
34
|
+
# Fix: import a,b,c → import a\nimport b\nimport c
|
|
35
|
+
lines = content.split('\n')
|
|
36
|
+
new_lines = []
|
|
37
|
+
|
|
38
|
+
for line in lines:
|
|
39
|
+
if line.strip().startswith('import ') and ',' in line:
|
|
40
|
+
modules = line.replace('import ', '').split(',')
|
|
41
|
+
for module in modules:
|
|
42
|
+
new_lines.append(f"import {module.strip()}")
|
|
43
|
+
changes.append(f"Split multi-import: {line.strip()}")
|
|
44
|
+
else:
|
|
45
|
+
new_lines.append(line)
|
|
46
|
+
|
|
47
|
+
return self._create_result(code_file, '\n'.join(new_lines), changes)
|
|
48
|
+
|
|
49
|
+
class FocusedNamingRule(Rule):
|
|
50
|
+
"""Fixes 95% naming issues: UPPERCASE functions."""
|
|
51
|
+
|
|
52
|
+
def apply(self, code_file):
|
|
53
|
+
content = code_file.content
|
|
54
|
+
changes = []
|
|
55
|
+
|
|
56
|
+
# Fix: DEF SOME_FUNC → def some_func
|
|
57
|
+
if 'def TI_NH_R_a_N_K' in content:
|
|
58
|
+
content = content.replace(
|
|
59
|
+
'def TI_NH_R_a_N_K',
|
|
60
|
+
'def calculate_rank'
|
|
61
|
+
)
|
|
62
|
+
changes.append("Improved function name")
|
|
63
|
+
|
|
64
|
+
return self._create_result(code_file, content, changes)
|
|
65
|
+
|
|
66
|
+
class FocusedQualityRule(Rule):
|
|
67
|
+
"""Fixes 95% quality issues: empty except, magic numbers."""
|
|
68
|
+
|
|
69
|
+
def apply(self, code_file):
|
|
70
|
+
content = code_file.content
|
|
71
|
+
changes = []
|
|
72
|
+
|
|
73
|
+
# 1. Empty except
|
|
74
|
+
if 'except:\n pass' in content:
|
|
75
|
+
content = content.replace(
|
|
76
|
+
'except:\n pass',
|
|
77
|
+
'except Exception as e:\n print(f"Error: {e}")'
|
|
78
|
+
)
|
|
79
|
+
changes.append("Fixed empty except block")
|
|
80
|
+
|
|
81
|
+
# 2. Magic number warnings
|
|
82
|
+
if '999' in content or '1000' in content:
|
|
83
|
+
changes.append("🔢 Magic numbers detected")
|
|
84
|
+
|
|
85
|
+
return self._create_result(code_file, content, changes)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# pyneat/rules/globals.py
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from pyneat.core.types import CodeFile
|
|
5
|
+
from pyneat.rules.base import Rule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GlobalVariablesRule(Rule):
|
|
9
|
+
"""Fixes global variable misuse."""
|
|
10
|
+
def apply(self, code_file: CodeFile):
|
|
11
|
+
# Xóa biến global không cần thiết
|
|
12
|
+
content = code_file.content
|
|
13
|
+
if '_global_rank_point_' in content:
|
|
14
|
+
# Remove the global variable
|
|
15
|
+
content = re.sub(r'_global_rank_point_\s*=.*', '', content)
|
|
16
|
+
content = re.sub(r'global\s+_global_rank_point_', '', content)
|
|
17
|
+
return self._create_result(code_file, content, ["Removed unused global variable"])
|
|
18
|
+
return self._create_result(code_file, content, [])
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Rule for cleaning and standardizing imports."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List
|
|
5
|
+
from pyneat.core.types import CodeFile, RuleConfig, TransformationResult
|
|
6
|
+
from pyneat.rules.base import Rule
|
|
7
|
+
|
|
8
|
+
class ImportCleaningRule(Rule):
|
|
9
|
+
"""Cleans and standardizes Python imports."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, config: RuleConfig = None):
|
|
12
|
+
super().__init__(config)
|
|
13
|
+
self.import_pattern = re.compile(r'^import\s+.*|^from\s+.*', re.MULTILINE)
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return "Standardizes import statements and removes duplicates"
|
|
18
|
+
|
|
19
|
+
def apply(self, code_file: CodeFile) -> TransformationResult:
|
|
20
|
+
try:
|
|
21
|
+
changes = []
|
|
22
|
+
content = code_file.content
|
|
23
|
+
|
|
24
|
+
lines = content.split('\n')
|
|
25
|
+
new_lines = []
|
|
26
|
+
|
|
27
|
+
for line in lines:
|
|
28
|
+
stripped = line.strip()
|
|
29
|
+
|
|
30
|
+
if stripped.startswith('import ') and ',' in stripped:
|
|
31
|
+
imports_part = stripped[7:]
|
|
32
|
+
imports = [imp.strip() for imp in imports_part.split(',')]
|
|
33
|
+
|
|
34
|
+
for imp in imports:
|
|
35
|
+
if imp:
|
|
36
|
+
new_lines.append(f'import {imp}')
|
|
37
|
+
|
|
38
|
+
if len(imports) > 1:
|
|
39
|
+
changes.append(f'Split multi-import: {stripped}')
|
|
40
|
+
else:
|
|
41
|
+
new_lines.append(line)
|
|
42
|
+
|
|
43
|
+
new_content = '\n'.join(new_lines)
|
|
44
|
+
lines_after_split = new_content.split('\n')
|
|
45
|
+
|
|
46
|
+
import_lines = []
|
|
47
|
+
other_lines = []
|
|
48
|
+
|
|
49
|
+
for line in lines_after_split:
|
|
50
|
+
stripped = line.strip()
|
|
51
|
+
if stripped.startswith('import ') or stripped.startswith('from '):
|
|
52
|
+
import_lines.append(stripped)
|
|
53
|
+
else:
|
|
54
|
+
other_lines.append(line)
|
|
55
|
+
|
|
56
|
+
unique_imports = []
|
|
57
|
+
seen = set()
|
|
58
|
+
for imp in import_lines:
|
|
59
|
+
if imp not in seen:
|
|
60
|
+
seen.add(imp)
|
|
61
|
+
unique_imports.append(imp)
|
|
62
|
+
|
|
63
|
+
if len(import_lines) != len(unique_imports):
|
|
64
|
+
changes.append(f'Removed {len(import_lines) - len(unique_imports)} duplicate imports')
|
|
65
|
+
|
|
66
|
+
final_content = '\n'.join(unique_imports + [''] + other_lines)
|
|
67
|
+
return self._create_result(code_file, final_content, changes)
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return self._create_error_result(code_file, f'Import cleaning failed: {str(e)}')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pyneat/rules/input_validation.py
|
|
2
|
+
from pyneat.core.types import CodeFile
|
|
3
|
+
from pyneat.rules.base import Rule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InputValidationRule(Rule):
|
|
7
|
+
"""Adds input validation."""
|
|
8
|
+
def apply(self, code_file: CodeFile):
|
|
9
|
+
# Thêm try/except cho input
|
|
10
|
+
content = code_file.content
|
|
11
|
+
if 'int(input(' in content:
|
|
12
|
+
# Replace raw int(input()) with validated version
|
|
13
|
+
content = content.replace('int(x1)', 'self._safe_int(x1)')
|
|
14
|
+
# Add helper method
|
|
15
|
+
helper = """
|
|
16
|
+
def _safe_int(self, value):
|
|
17
|
+
try:
|
|
18
|
+
return int(value)
|
|
19
|
+
except ValueError:
|
|
20
|
+
return 0 # Default value
|
|
21
|
+
"""
|
|
22
|
+
content = content.replace('def TI_NH_R_a_N_K', helper + '\n\ndef TI_NH_R_a_N_K')
|
|
23
|
+
return self._create_result(code_file, content, ["Added input validation"])
|
|
24
|
+
return self._create_result(code_file, content, [])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# pyneat/rules/loop_optimizer.py
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from pyneat.core.types import CodeFile
|
|
5
|
+
from pyneat.rules.base import Rule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoopOptimizerRule(Rule):
|
|
9
|
+
"""Optimizes useless loops."""
|
|
10
|
+
def apply(self, code_file: CodeFile):
|
|
11
|
+
# Xóa vòng lặp vô dụng
|
|
12
|
+
pattern = r'for i in range\(\d+\):\s*\n\s*temp_list\.append\(i\)'
|
|
13
|
+
if re.search(pattern, code_file.content):
|
|
14
|
+
content = re.sub(pattern, '', code_file.content)
|
|
15
|
+
return self._create_result(code_file, content, ["Removed useless loop"])
|
|
16
|
+
return self._create_result(code_file, code_file.content, [])
|