feat(languages): Expand supported languages and improve code analysis
- Add Swift and Kotlin as supported programming languages - Update language detection and file upload logic in InstantAnalysis page - Refactor language-related code to use centralized SUPPORTED_LANGUAGES constant - Enhance project detail page to handle repository scanning with more flexibility - Improve error handling and user guidance for project audits - Add language name formatting utility in ProjectDetail component - Update file input accept types to include new language extensions
This commit is contained in:
parent
d8f6b2470a
commit
a26552c86b
|
|
@ -2,15 +2,12 @@ import type { CodeAnalysisResult } from "@/shared/types";
|
||||||
import { LLMService } from '@/shared/services/llm';
|
import { LLMService } from '@/shared/services/llm';
|
||||||
import { getCurrentLLMApiKey, getCurrentLLMModel, env } from '@/shared/config/env';
|
import { getCurrentLLMApiKey, getCurrentLLMModel, env } from '@/shared/config/env';
|
||||||
import type { LLMConfig } from '@/shared/services/llm/types';
|
import type { LLMConfig } from '@/shared/services/llm/types';
|
||||||
|
import { SUPPORTED_LANGUAGES } from '@/shared/constants';
|
||||||
|
|
||||||
// 基于 LLM 的代码分析引擎
|
// 基于 LLM 的代码分析引擎
|
||||||
export class CodeAnalysisEngine {
|
export class CodeAnalysisEngine {
|
||||||
private static readonly SUPPORTED_LANGUAGES = [
|
|
||||||
'javascript', 'typescript', 'python', 'java', 'go', 'rust', 'cpp', 'csharp', 'php', 'ruby'
|
|
||||||
];
|
|
||||||
|
|
||||||
static getSupportedLanguages(): string[] {
|
static getSupportedLanguages(): string[] {
|
||||||
return [...this.SUPPORTED_LANGUAGES];
|
return [...SUPPORTED_LANGUAGES];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,65 @@ public class Example {
|
||||||
private String getData() {
|
private String getData() {
|
||||||
return "data";
|
return "data";
|
||||||
}
|
}
|
||||||
|
}`,
|
||||||
|
swift: `// 示例Swift代码 - 包含多种问题
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class UserManager {
|
||||||
|
var password = "admin123" // 硬编码密码
|
||||||
|
|
||||||
|
func validateUser(input: String) -> Bool {
|
||||||
|
if input == password { // 直接比较密码
|
||||||
|
print("User validated") // 使用print而非日志
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制解包可能导致崩溃
|
||||||
|
func processData(data: [String]?) {
|
||||||
|
let items = data! // 强制解包
|
||||||
|
for item in items {
|
||||||
|
print(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内存泄漏风险:循环引用
|
||||||
|
var closure: (() -> Void)?
|
||||||
|
func setupClosure() {
|
||||||
|
closure = {
|
||||||
|
print(self.password) // 未使用 [weak self]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
kotlin: `// 示例Kotlin代码 - 包含多种问题
|
||||||
|
class UserManager {
|
||||||
|
private val password = "admin123" // 硬编码密码
|
||||||
|
|
||||||
|
fun validateUser(input: String): Boolean {
|
||||||
|
if (input == password) { // 直接比较密码
|
||||||
|
println("User validated") // 使用println而非日志
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空指针风险
|
||||||
|
fun processData(data: List<String>?) {
|
||||||
|
val items = data!! // 强制非空断言
|
||||||
|
for (item in items) {
|
||||||
|
println(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能问题:循环中重复计算
|
||||||
|
fun inefficientLoop(items: List<String>) {
|
||||||
|
for (i in 0 until items.size) {
|
||||||
|
for (j in 0 until items.size) { // O(n²) 复杂度
|
||||||
|
println(items[i] + items[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}`
|
}`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -231,7 +290,9 @@ public class Example {
|
||||||
'hh': 'cpp',
|
'hh': 'cpp',
|
||||||
'cs': 'csharp',
|
'cs': 'csharp',
|
||||||
'php': 'php',
|
'php': 'php',
|
||||||
'rb': 'ruby'
|
'rb': 'ruby',
|
||||||
|
'swift': 'swift',
|
||||||
|
'kt': 'kotlin'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (extension && languageMap[extension]) {
|
if (extension && languageMap[extension]) {
|
||||||
|
|
@ -526,7 +587,7 @@ public class Example {
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept=".js,.jsx,.ts,.tsx,.py,.java,.go,.rs,.cpp,.c,.cc,.h,.hh,.cs,.php,.rb"
|
accept=".js,.jsx,.ts,.tsx,.py,.java,.go,.rs,.cpp,.c,.cc,.h,.hh,.cs,.php,.rb,.swift,.kt"
|
||||||
onChange={handleFileUpload}
|
onChange={handleFileUpload}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
|
|
@ -562,6 +623,24 @@ public class Example {
|
||||||
>
|
>
|
||||||
Java
|
Java
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => loadExampleCode('swift')}
|
||||||
|
disabled={analyzing}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
Swift
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => loadExampleCode('kotlin')}
|
||||||
|
disabled={analyzing}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
Kotlin
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 代码编辑器 */}
|
{/* 代码编辑器 */}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { loadZipFile } from "@/shared/utils/zipStorage";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import CreateTaskDialog from "@/components/audit/CreateTaskDialog";
|
import CreateTaskDialog from "@/components/audit/CreateTaskDialog";
|
||||||
import TerminalProgressDialog from "@/components/audit/TerminalProgressDialog";
|
import TerminalProgressDialog from "@/components/audit/TerminalProgressDialog";
|
||||||
|
import { SUPPORTED_LANGUAGES } from "@/shared/constants";
|
||||||
|
|
||||||
export default function ProjectDetail() {
|
export default function ProjectDetail() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
@ -50,9 +51,26 @@ export default function ProjectDetail() {
|
||||||
programming_languages: []
|
programming_languages: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const supportedLanguages = [
|
// 将小写语言名转换为显示格式
|
||||||
'JavaScript', 'TypeScript', 'Python', 'Java', 'Go', 'Rust', 'C++', 'C#', 'PHP', 'Ruby'
|
const formatLanguageName = (lang: string): string => {
|
||||||
];
|
const nameMap: Record<string, string> = {
|
||||||
|
'javascript': 'JavaScript',
|
||||||
|
'typescript': 'TypeScript',
|
||||||
|
'python': 'Python',
|
||||||
|
'java': 'Java',
|
||||||
|
'go': 'Go',
|
||||||
|
'rust': 'Rust',
|
||||||
|
'cpp': 'C++',
|
||||||
|
'csharp': 'C#',
|
||||||
|
'php': 'PHP',
|
||||||
|
'ruby': 'Ruby',
|
||||||
|
'swift': 'Swift',
|
||||||
|
'kotlin': 'Kotlin'
|
||||||
|
};
|
||||||
|
return nameMap[lang] || lang.charAt(0).toUpperCase() + lang.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportedLanguages = SUPPORTED_LANGUAGES.map(formatLanguageName);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -83,16 +101,18 @@ export default function ProjectDetail() {
|
||||||
const handleRunAudit = async () => {
|
const handleRunAudit = async () => {
|
||||||
if (!project || !id) return;
|
if (!project || !id) return;
|
||||||
|
|
||||||
// 如果是GitHub项目且有仓库地址,直接启动审计
|
// 检查是否有仓库地址
|
||||||
if (project.repository_type === 'github' && project.repository_url) {
|
if (project.repository_url) {
|
||||||
|
// 有仓库地址,启动仓库审计
|
||||||
try {
|
try {
|
||||||
setScanning(true);
|
setScanning(true);
|
||||||
console.log('开始启动审计任务...');
|
console.log('开始启动仓库审计任务...');
|
||||||
const taskId = await runRepositoryAudit({
|
const taskId = await runRepositoryAudit({
|
||||||
projectId: id,
|
projectId: id,
|
||||||
repoUrl: project.repository_url,
|
repoUrl: project.repository_url,
|
||||||
branch: project.default_branch || 'main',
|
branch: project.default_branch || 'main',
|
||||||
githubToken: undefined,
|
githubToken: undefined,
|
||||||
|
gitlabToken: undefined,
|
||||||
createdBy: undefined
|
createdBy: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -111,7 +131,7 @@ export default function ProjectDetail() {
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 对于ZIP项目,尝试从IndexedDB加载保存的文件
|
// 没有仓库地址,尝试从IndexedDB加载保存的ZIP文件
|
||||||
try {
|
try {
|
||||||
setScanning(true);
|
setScanning(true);
|
||||||
const file = await loadZipFile(id);
|
const file = await loadZipFile(id);
|
||||||
|
|
@ -143,14 +163,13 @@ export default function ProjectDetail() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
toast.error('未找到保存的ZIP文件,请通过"新建任务"上传');
|
toast.warning('此项目未配置仓库地址,也未上传ZIP文件。请先在项目设置中配置仓库地址,或通过"新建任务"上传ZIP文件。');
|
||||||
setShowCreateTaskDialog(true);
|
// 不自动打开对话框,让用户自己选择
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('启动审计失败:', error);
|
console.error('启动审计失败:', error);
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
toast.error('读取ZIP文件失败,请通过"新建任务"重新上传');
|
toast.error('读取ZIP文件失败,请检查项目配置');
|
||||||
setShowCreateTaskDialog(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import { saveZipFile } from "@/shared/utils/zipStorage";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import CreateTaskDialog from "@/components/audit/CreateTaskDialog";
|
import CreateTaskDialog from "@/components/audit/CreateTaskDialog";
|
||||||
|
import { SUPPORTED_LANGUAGES } from "@/shared/constants";
|
||||||
|
|
||||||
export default function Projects() {
|
export default function Projects() {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
|
@ -67,9 +68,26 @@ export default function Projects() {
|
||||||
programming_languages: []
|
programming_languages: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const supportedLanguages = [
|
// 将小写语言名转换为显示格式
|
||||||
'JavaScript', 'TypeScript', 'Python', 'Java', 'Go', 'Rust', 'C++', 'C#', 'PHP', 'Ruby'
|
const formatLanguageName = (lang: string): string => {
|
||||||
];
|
const nameMap: Record<string, string> = {
|
||||||
|
'javascript': 'JavaScript',
|
||||||
|
'typescript': 'TypeScript',
|
||||||
|
'python': 'Python',
|
||||||
|
'java': 'Java',
|
||||||
|
'go': 'Go',
|
||||||
|
'rust': 'Rust',
|
||||||
|
'cpp': 'C++',
|
||||||
|
'csharp': 'C#',
|
||||||
|
'php': 'PHP',
|
||||||
|
'ruby': 'Ruby',
|
||||||
|
'swift': 'Swift',
|
||||||
|
'kotlin': 'Kotlin'
|
||||||
|
};
|
||||||
|
return nameMap[lang] || lang.charAt(0).toUpperCase() + lang.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportedLanguages = SUPPORTED_LANGUAGES.map(formatLanguageName);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadProjects();
|
loadProjects();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue