Files
code_scan/scanner/js_scanner.py
Dang Zerong 027cf50759 add web
2026-03-12 14:42:23 +08:00

162 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JavaScript/TypeScript 代码扫描器
使用 ESLint 进行代码质量检查
"""
import os
import json
import logging
from typing import Dict, Any, List, Optional
from scanner.base import BaseScanner
logger = logging.getLogger(__name__)
class JavaScriptScanner(BaseScanner):
"""JavaScript/TypeScript 代码扫描器"""
def __init__(self, config: Dict[str, Any]):
super().__init__(config)
self.extensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte']
def scan(self, repo_url: str, commit_id: Optional[str], branch: str, changed_files: Optional[List[str]] = None) -> Dict[str, Any]:
"""
执行 JavaScript/TypeScript 代码扫描
Args:
repo_url: 仓库 URL
commit_id: 提交 ID
branch: 分支名
changed_files: 可选的变更文件列表(来自 PR
Returns:
扫描结果
"""
result = {
'tool': 'JavaScript Scanner',
'language': 'javascript',
'status': 'success',
'issues': [],
'summary': {
'total': 0,
'error': 0,
'warning': 0,
'info': 0
},
'files_scanned': 0
}
clone_dir = None
try:
# 克隆仓库
clone_dir = self.clone_repo(repo_url, commit_id, branch)
# 获取 JavaScript/TypeScript 文件(只扫描变更的文件)
js_files = self.get_changed_files(clone_dir, self.extensions, changed_files)
result['files_scanned'] = len(js_files)
if not js_files:
logger.info('没有找到 JavaScript/TypeScript 文件')
return result
# 运行 ESLint 扫描
eslint_result = self._run_eslint(clone_dir, js_files)
# 合并结果
result['issues'] = eslint_result.get('issues', [])[:self.max_issues] if self.detailed else eslint_result.get('issues', [])
result['summary'] = self._calculate_summary(eslint_result.get('issues', []))
result['raw_output'] = eslint_result.get('raw_output', '')
except Exception as e:
logger.error(f'JavaScript 扫描失败: {str(e)}')
result['status'] = 'error'
result['error'] = str(e)
return result
def _run_eslint(self, clone_dir: str, files: List[str]) -> Dict[str, Any]:
"""运行 ESLint 扫描"""
result = {
'tool': 'eslint',
'issues': [],
'raw_output': ''
}
try:
# 尝试使用 npx 运行 eslint
cmd = ['npx', 'eslint', '--format=json', '--no-eslintrc'] + files
# 如果没有 eslint 配置,先创建默认配置
eslintrc_path = os.path.join(clone_dir, '.eslintrc.json')
if not os.path.exists(eslintrc_path):
# 创建简单的 ESLint 配置
eslint_config = {
"env": {
"browser": True,
"es2021": True,
"node": True
},
"extends": ["eslint:recommended"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}
with open(eslintrc_path, 'w') as f:
json.dump(eslint_config, f)
output = self.run_command(cmd, clone_dir, timeout=120)
result['raw_output'] = output.get('stdout', '') + output.get('stderr', '')
# 解析 JSON 输出
if output.get('stdout'):
try:
eslint_results = json.loads(output['stdout'])
for file_result in eslint_results:
file_path = file_result.get('filePath', '')
# 使用相对于 clone_dir 的路径
rel_path = os.path.relpath(file_path, clone_dir) if file_path else ''
messages = file_result.get('messages', [])
for msg in messages:
severity = 'error' if msg.get('severity', 0) == 2 else 'warning'
result['issues'].append({
'tool': 'eslint',
'type': severity,
'severity': 'Error' if msg.get('severity', 0) == 2 else 'Warning',
'message': msg.get('message', ''),
'file': rel_path,
'line': msg.get('line', 0),
'column': msg.get('column', 0),
'symbol': msg.get('ruleId', 'unknown')
})
except json.JSONDecodeError as e:
logger.warning(f'ESLint JSON 解析失败: {e}')
except Exception as e:
logger.warning(f'ESLint 运行失败: {str(e)}')
return result
def _calculate_summary(self, issues: List[Dict]) -> Dict[str, int]:
"""计算问题摘要"""
summary = {
'total': len(issues),
'error': 0,
'warning': 0,
'info': 0
}
for issue in issues:
severity = issue.get('severity', '').lower()
if severity in ['error', 'critical']:
summary['error'] += 1
elif severity in ['warning', 'moderate']:
summary['warning'] += 1
else:
summary['info'] += 1
return summary