Files
pingtai_agent/steering_agent/src/server/extractors/naming.py
T

236 lines
8.0 KiB
Python

"""
命名规范提取器
使用 AST 解析 Python 代码,分析类名、函数名、变量名的命名模式。
"""
import os
import re
import ast
from typing import Dict, List, Any, Optional
from pathlib import Path
from collections import Counter
class NamingExtractor:
"""命名规范提取器"""
def extract(self, project_path: str) -> Dict[str, Any]:
"""
提取项目的命名规范
Args:
project_path: 项目根目录路径
Returns:
命名规范信息字典
"""
# 收集所有 Python 文件中的命名
names = self._collect_names(project_path)
return {
"files": self._analyze_file_naming(project_path),
"classes": self._analyze_pattern(names["classes"], "classes"),
"functions": self._analyze_pattern(names["functions"], "functions"),
"variables": self._analyze_pattern(names["variables"], "variables"),
"constants": self._analyze_pattern(names["constants"], "constants"),
"summary": self._generate_summary(names)
}
def _find_python_files(self, project_path: str) -> List[str]:
"""查找所有 Python 文件"""
python_files = []
ignore_dirs = {'__pycache__', '.git', '.venv', 'venv', 'node_modules', '.pytest_cache'}
for root, dirs, files in os.walk(project_path):
# 过滤忽略的目录
dirs[:] = [d for d in dirs if d not in ignore_dirs]
for f in files:
if f.endswith('.py'):
python_files.append(os.path.join(root, f))
return python_files
def _collect_names(self, project_path: str) -> Dict[str, List[str]]:
"""使用 AST 收集代码中的命名"""
names = {
"classes": [],
"functions": [],
"variables": [],
"constants": []
}
for py_file in self._find_python_files(project_path):
try:
with open(py_file, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
for node in ast.walk(tree):
# 类名
if isinstance(node, ast.ClassDef):
names["classes"].append(node.name)
# 函数名(包括方法)
elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
# 排除魔术方法
if not (node.name.startswith('__') and node.name.endswith('__')):
names["functions"].append(node.name)
# 变量和常量(模块级别的赋值)
elif isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
name = target.id
# 排除私有变量
if not name.startswith('_'):
# 全大写视为常量
if name.isupper() or (name.upper() == name and '_' in name):
names["constants"].append(name)
else:
names["variables"].append(name)
except Exception:
continue
return names
def _detect_naming_pattern(self, name: str) -> Optional[str]:
"""检测单个名称的命名模式"""
if not name:
return None
# UPPER_SNAKE_CASE (常量)
if re.match(r'^[A-Z][A-Z0-9_]*$', name):
return "UPPER_SNAKE_CASE"
# PascalCase (类名)
if re.match(r'^[A-Z][a-zA-Z0-9]*$', name):
return "PascalCase"
# snake_case
if re.match(r'^[a-z][a-z0-9_]*$', name):
return "snake_case"
# camelCase
if re.match(r'^[a-z][a-zA-Z0-9]*$', name):
return "camelCase"
# kebab-case (通常用于文件名)
if re.match(r'^[a-z][a-z0-9-]*$', name):
return "kebab-case"
return "mixed"
def _analyze_pattern(self, names: List[str], category: str) -> Dict[str, Any]:
"""分析命名模式"""
if not names:
return {
"dominant_pattern": None,
"distribution": {},
"examples": [],
"consistency": 0.0
}
# 统计各模式的数量
patterns = Counter()
for name in names:
pattern = self._detect_naming_pattern(name)
if pattern:
patterns[pattern] += 1
if not patterns:
return {
"dominant_pattern": None,
"distribution": {},
"examples": names[:5],
"consistency": 0.0
}
# 找出主导模式
dominant = patterns.most_common(1)[0][0]
total = sum(patterns.values())
consistency = patterns[dominant] / total if total > 0 else 0.0
# 获取示例
examples = []
for name in names:
if self._detect_naming_pattern(name) == dominant and name not in examples:
examples.append(name)
if len(examples) >= 5:
break
return {
"dominant_pattern": dominant,
"distribution": dict(patterns),
"examples": examples,
"consistency": round(consistency, 2)
}
def _analyze_file_naming(self, project_path: str) -> Dict[str, Any]:
"""分析文件命名规范"""
python_files = []
ignore_dirs = {'__pycache__', '.git', '.venv', 'venv', 'node_modules'}
for root, dirs, files in os.walk(project_path):
dirs[:] = [d for d in dirs if d not in ignore_dirs]
for f in files:
if f.endswith('.py') and not f.startswith('__'):
# 去掉扩展名
name = f[:-3]
python_files.append(name)
if not python_files:
return {
"dominant_pattern": None,
"examples": [],
"consistency": 0.0
}
# 分析文件名模式
patterns = Counter()
for name in python_files:
pattern = self._detect_naming_pattern(name)
if pattern:
patterns[pattern] += 1
if not patterns:
return {
"dominant_pattern": None,
"examples": python_files[:5],
"consistency": 0.0
}
dominant = patterns.most_common(1)[0][0]
total = sum(patterns.values())
consistency = patterns[dominant] / total if total > 0 else 0.0
# 获取示例(加回 .py 扩展名)
examples = [f"{name}.py" for name in python_files[:5]]
return {
"dominant_pattern": dominant,
"examples": examples,
"consistency": round(consistency, 2)
}
def _generate_summary(self, names: Dict[str, List[str]]) -> Dict[str, str]:
"""生成命名规范摘要"""
summary = {}
# 分析各类别的主导模式
for category, name_list in names.items():
if name_list:
patterns = Counter()
for name in name_list:
pattern = self._detect_naming_pattern(name)
if pattern:
patterns[pattern] += 1
if patterns:
dominant = patterns.most_common(1)[0][0]
summary[category] = dominant
return summary