341 lines
12 KiB
Python
341 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
NAC Lint Checker - 编译辅助检查器
|
||
自动检测违反NAC原则的代码
|
||
"""
|
||
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
from typing import List, Dict, Any, Tuple
|
||
from dataclasses import dataclass
|
||
from enum import Enum
|
||
|
||
# 记忆系统根目录
|
||
MEMORY_ROOT = Path(__file__).parent.parent
|
||
|
||
class Severity(Enum):
|
||
"""违规严重性"""
|
||
CRITICAL = "critical"
|
||
HIGH = "high"
|
||
MEDIUM = "medium"
|
||
LOW = "low"
|
||
|
||
@dataclass
|
||
class Violation:
|
||
"""违规记录"""
|
||
rule_id: str
|
||
severity: Severity
|
||
file: str
|
||
line: int
|
||
column: int
|
||
message: str
|
||
found: str
|
||
suggestion: str
|
||
reference: str
|
||
|
||
class RuleEngine:
|
||
"""规则检查引擎"""
|
||
|
||
def __init__(self):
|
||
self.rules = []
|
||
self.load_rules()
|
||
|
||
def load_rules(self):
|
||
"""从记忆系统加载规则"""
|
||
# 加载术语映射规则
|
||
terminology_file = MEMORY_ROOT / "principles" / "terminology.json"
|
||
if terminology_file.exists():
|
||
with open(terminology_file, 'r', encoding='utf-8') as f:
|
||
terminology = json.load(f)
|
||
for mapping in terminology.get('mappings', []):
|
||
self.rules.append(self._create_terminology_rule(mapping))
|
||
|
||
# 加载架构原则规则
|
||
architecture_file = MEMORY_ROOT / "principles" / "architecture.json"
|
||
if architecture_file.exists():
|
||
with open(architecture_file, 'r', encoding='utf-8') as f:
|
||
architecture = json.load(f)
|
||
for principle in architecture.get('principles', []):
|
||
if principle.get('principle_id') == 'ARCH_002':
|
||
# OpCode命名规范
|
||
self.rules.append(self._create_opcode_naming_rule(principle))
|
||
elif principle.get('principle_id') == 'ARCH_003':
|
||
# Blake3哈希规范
|
||
self.rules.append(self._create_blake3_rule(principle))
|
||
elif principle.get('principle_id') == 'ARCH_004':
|
||
# 代码保留原则
|
||
self.rules.append(self._create_no_delete_rule(principle))
|
||
|
||
def _create_terminology_rule(self, mapping: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建术语检查规则"""
|
||
wrong_term = mapping['wrong_term']
|
||
correct_term = mapping['correct_term']
|
||
|
||
# 构建正则表达式
|
||
# 支持多个错误术语(用/分隔)
|
||
terms = wrong_term.split('/')
|
||
pattern_parts = []
|
||
for term in terms:
|
||
# 匹配整个单词,不区分大小写
|
||
pattern_parts.append(rf'\b{re.escape(term.strip())}\b')
|
||
|
||
pattern = '|'.join(pattern_parts)
|
||
|
||
return {
|
||
'rule_id': mapping['mapping_id'],
|
||
'category': 'terminology',
|
||
'severity': Severity.CRITICAL,
|
||
'title': f"禁止使用{wrong_term}术语",
|
||
'description': mapping['explanation'],
|
||
'pattern': pattern,
|
||
'flags': re.IGNORECASE,
|
||
'suggestion': correct_term,
|
||
'reference': f"memory/principles/terminology.json#{mapping['mapping_id']}",
|
||
'must_not_use': mapping.get('must_not_use', [])
|
||
}
|
||
|
||
def _create_opcode_naming_rule(self, principle: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建OpCode命名规范规则"""
|
||
return {
|
||
'rule_id': 'ARCH_002',
|
||
'category': 'naming',
|
||
'severity': Severity.HIGH,
|
||
'title': 'OpCode命名必须使用UPPER_CASE',
|
||
'description': principle['description'],
|
||
'pattern': r'enum\s+OpCode\s*\{[^}]*\b([A-Z][a-z]+[A-Z][a-zA-Z]*)\b',
|
||
'flags': 0,
|
||
'suggestion': '使用UPPER_CASE命名(如PUSH1, CR_CREATE)',
|
||
'reference': 'memory/principles/architecture.json#ARCH_002'
|
||
}
|
||
|
||
def _create_blake3_rule(self, principle: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建Blake3哈希规范规则"""
|
||
return {
|
||
'rule_id': 'ARCH_003',
|
||
'category': 'cryptography',
|
||
'severity': Severity.HIGH,
|
||
'title': '必须使用Blake3哈希算法',
|
||
'description': principle['description'],
|
||
'pattern': r'\b(sha256|SHA256|keccak256|Keccak256|sha3|SHA3|md5|MD5)\b',
|
||
'flags': 0,
|
||
'suggestion': '使用Blake3哈希算法',
|
||
'reference': 'memory/principles/architecture.json#ARCH_003'
|
||
}
|
||
|
||
def _create_no_delete_rule(self, principle: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建代码保留原则规则"""
|
||
return {
|
||
'rule_id': 'ARCH_004',
|
||
'category': 'architecture',
|
||
'severity': Severity.HIGH,
|
||
'title': '禁止使用#[allow(unused)]',
|
||
'description': principle['description'],
|
||
'pattern': r'#\[allow\(unused\)\]',
|
||
'flags': 0,
|
||
'suggestion': '添加proper imports而非隐藏警告',
|
||
'reference': 'memory/principles/architecture.json#ARCH_004'
|
||
}
|
||
|
||
class CodeScanner:
|
||
"""代码扫描器"""
|
||
|
||
def __init__(self, rule_engine: RuleEngine):
|
||
self.rule_engine = rule_engine
|
||
self.violations = []
|
||
|
||
def scan_file(self, file_path: Path) -> List[Violation]:
|
||
"""扫描单个文件"""
|
||
violations = []
|
||
|
||
try:
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
lines = f.readlines()
|
||
|
||
for line_num, line in enumerate(lines, 1):
|
||
for rule in self.rule_engine.rules:
|
||
matches = re.finditer(rule['pattern'], line, rule.get('flags', 0))
|
||
for match in matches:
|
||
violation = Violation(
|
||
rule_id=rule['rule_id'],
|
||
severity=rule['severity'],
|
||
file=str(file_path),
|
||
line=line_num,
|
||
column=match.start() + 1,
|
||
message=rule['title'],
|
||
found=match.group(0),
|
||
suggestion=rule['suggestion'],
|
||
reference=rule['reference']
|
||
)
|
||
violations.append(violation)
|
||
|
||
except Exception as e:
|
||
print(f"Error scanning {file_path}: {e}", file=sys.stderr)
|
||
|
||
return violations
|
||
|
||
def scan_directory(self, directory: Path, extensions: List[str] = None) -> List[Violation]:
|
||
"""扫描目录"""
|
||
if extensions is None:
|
||
extensions = ['.rs', '.toml', '.json', '.md']
|
||
|
||
violations = []
|
||
|
||
for ext in extensions:
|
||
for file_path in directory.rglob(f'*{ext}'):
|
||
# 跳过target和node_modules目录
|
||
if 'target' in file_path.parts or 'node_modules' in file_path.parts:
|
||
continue
|
||
|
||
file_violations = self.scan_file(file_path)
|
||
violations.extend(file_violations)
|
||
|
||
return violations
|
||
|
||
class ViolationReporter:
|
||
"""违规报告器"""
|
||
|
||
@staticmethod
|
||
def report_console(violations: List[Violation], verbose: bool = True):
|
||
"""控制台输出"""
|
||
if not violations:
|
||
print("✅ No violations found. Code is compliant with NAC principles.")
|
||
return
|
||
|
||
# 按严重性分组
|
||
by_severity = {
|
||
Severity.CRITICAL: [],
|
||
Severity.HIGH: [],
|
||
Severity.MEDIUM: [],
|
||
Severity.LOW: []
|
||
}
|
||
|
||
for violation in violations:
|
||
by_severity[violation.severity].append(violation)
|
||
|
||
print("\nNAC Lint Checker v1.0.0")
|
||
print("=" * 80)
|
||
|
||
# 输出违规
|
||
for severity in [Severity.CRITICAL, Severity.HIGH, Severity.MEDIUM, Severity.LOW]:
|
||
items = by_severity[severity]
|
||
if not items:
|
||
continue
|
||
|
||
icon = "❌" if severity == Severity.CRITICAL else "⚠️ "
|
||
print(f"\n{icon} {severity.value.upper()}: {len(items)} violation(s)")
|
||
print("-" * 80)
|
||
|
||
for violation in items:
|
||
print(f"\n{violation.message} [{violation.rule_id}]")
|
||
print(f" File: {violation.file}:{violation.line}:{violation.column}")
|
||
print(f" Found: {violation.found}")
|
||
print(f" Should be: {violation.suggestion}")
|
||
if verbose:
|
||
print(f" Reference: {violation.reference}")
|
||
|
||
# 摘要
|
||
print("\n" + "=" * 80)
|
||
print("Summary:")
|
||
print(f" ❌ {len(by_severity[Severity.CRITICAL])} critical violations")
|
||
print(f" ⚠️ {len(by_severity[Severity.HIGH])} high violations")
|
||
print(f" ℹ️ {len(by_severity[Severity.MEDIUM])} medium violations")
|
||
print(f" 💡 {len(by_severity[Severity.LOW])} low violations")
|
||
|
||
if by_severity[Severity.CRITICAL]:
|
||
print("\n❌ Build blocked due to critical violations.")
|
||
return 1
|
||
else:
|
||
print("\n⚠️ Build allowed with warnings.")
|
||
return 0
|
||
|
||
@staticmethod
|
||
def report_json(violations: List[Violation], output_file: Path = None):
|
||
"""JSON输出"""
|
||
from datetime import datetime
|
||
|
||
report = {
|
||
'version': '1.0.0',
|
||
'timestamp': datetime.now().isoformat(),
|
||
'summary': {
|
||
'total_violations': len(violations),
|
||
'by_severity': {
|
||
'critical': len([v for v in violations if v.severity == Severity.CRITICAL]),
|
||
'high': len([v for v in violations if v.severity == Severity.HIGH]),
|
||
'medium': len([v for v in violations if v.severity == Severity.MEDIUM]),
|
||
'low': len([v for v in violations if v.severity == Severity.LOW])
|
||
}
|
||
},
|
||
'violations': [
|
||
{
|
||
'rule_id': v.rule_id,
|
||
'severity': v.severity.value,
|
||
'file': v.file,
|
||
'line': v.line,
|
||
'column': v.column,
|
||
'message': v.message,
|
||
'found': v.found,
|
||
'suggestion': v.suggestion,
|
||
'reference': v.reference
|
||
}
|
||
for v in violations
|
||
]
|
||
}
|
||
|
||
if output_file:
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
json.dump(report, f, ensure_ascii=False, indent=2)
|
||
print(f"✅ Report saved to {output_file}")
|
||
else:
|
||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description='NAC Lint Checker - 编译辅助检查器')
|
||
parser.add_argument('command', choices=['check', 'list-rules'], help='命令')
|
||
parser.add_argument('--path', '-p', default='.', help='扫描路径(默认当前目录)')
|
||
parser.add_argument('--output', '-o', choices=['console', 'json'], default='console', help='输出格式')
|
||
parser.add_argument('--output-file', help='JSON输出文件路径')
|
||
parser.add_argument('--verbose', '-v', action='store_true', help='详细输出')
|
||
parser.add_argument('--all', '-a', action='store_true', help='扫描所有文件(包括target)')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 初始化规则引擎
|
||
rule_engine = RuleEngine()
|
||
|
||
if args.command == 'list-rules':
|
||
print(f"\nNAC Lint Checker - Loaded {len(rule_engine.rules)} rules:")
|
||
print("=" * 80)
|
||
for rule in rule_engine.rules:
|
||
print(f"\n[{rule['rule_id']}] {rule['title']}")
|
||
print(f" Category: {rule['category']}")
|
||
print(f" Severity: {rule['severity'].value}")
|
||
print(f" Description: {rule['description']}")
|
||
return 0
|
||
|
||
# 扫描代码
|
||
scanner = CodeScanner(rule_engine)
|
||
scan_path = Path(args.path).resolve()
|
||
|
||
print(f"Scanning: {scan_path}")
|
||
|
||
if scan_path.is_file():
|
||
violations = scanner.scan_file(scan_path)
|
||
else:
|
||
violations = scanner.scan_directory(scan_path)
|
||
|
||
# 输出报告
|
||
if args.output == 'json':
|
||
output_file = Path(args.output_file) if args.output_file else None
|
||
ViolationReporter.report_json(violations, output_file)
|
||
return 0
|
||
else:
|
||
return ViolationReporter.report_console(violations, args.verbose)
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|