const fs = require('fs-extra'); const path = require('path'); const glob = require('glob'); const chalk = require('chalk'); // 支持的文件类型及对应的注释模式 const SUPPORTED_EXTENSIONS = { '.js': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'javascript', priority: 1 }, '.jsx': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'jsx', priority: 1 }, '.ts': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'typescript', priority: 2 }, '.tsx': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'tsx', priority: 2 }, '.css': { single_line: null, multi_line: [/\/\*/, /\*\//], language: 'css', priority: 3 }, '.scss': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'scss', priority: 3 }, '.html': { single_line: null, multi_line: [//], language: 'html', priority: 3 }, '.py': { single_line: '#', multi_line: [/"""/, /"""/], language: 'python', priority: 1 }, '.java': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'java', priority: 1 }, '.c': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'c', priority: 1 }, '.cpp': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'cpp', priority: 1 }, '.h': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'c', priority: 1 }, '.cs': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'csharp', priority: 1 }, '.php': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'php', priority: 2 }, '.rb': { single_line: '#', multi_line: [/^=begin/, /^=end/], language: 'ruby', priority: 2 }, '.go': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'go', priority: 1 }, '.rs': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'rust', priority: 1 }, '.swift': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'swift', priority: 1 }, '.kt': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'kotlin', priority: 1 }, '.dart': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'dart', priority: 2 }, '.vue': { single_line: '//', multi_line: [/\/\*/, /\*\//], language: 'vue', priority: 2 } }; // 要忽略的目录和文件 const IGNORE_DIRS = [ 'node_modules', '.git', 'build', 'dist', 'public', 'target', 'bin', 'obj', '__pycache__', '.vscode', '.idea', '.next', '.nuxt', 'coverage', '.cache', 'tmp', 'temp' ]; const IGNORE_FILES = [ '.DS_Store', '.gitignore', 'package-lock.json', 'yarn.lock', 'README.md', 'LICENSE', '.env', '*.min.js', '*.min.css', '*.map' ]; // 性能优化配置 const OPTIMIZATION_CONFIG = { // 是否启用优化 enabled: process.env.SOFTCOPYRIGHT_NO_OPTIMIZATION !== '1', // 是否启用并发 concurrency: process.env.SOFTCOPYRIGHT_NO_CONCURRENCY !== '1', // 并发数量(默认:CPU核心数 * 2) concurrencyLimit: parseInt(process.env.SOFTCOPYRIGHT_CONCURRENCY) || require('os').cpus().length * 2, // 单个文件最大大小(字节,默认5MB) maxFileSize: parseInt(process.env.SOFTCOPYRIGHT_MAX_FILE_SIZE) || 5 * 1024 * 1024, // 是否启用缓存 cache: process.env.SOFTCOPYRIGHT_NO_CACHE !== '1' }; // 配置文件缓存 const configFileCache = new Map(); /** * 获取项目名称(优化版:缓存配置文件) * 优先级: package.json > README文件 > 目录名 * @param {string} projectPath 项目路径 * @returns {Promise} 项目名称 */ async function getProjectName(projectPath) { try { // 1. 尝试从package.json读取 const packageJsonPath = path.join(projectPath, 'package.json'); if (await checkFileExists(packageJsonPath)) { const packageData = await fs.readJson(packageJsonPath); configFileCache.set('package.json', packageData); if (packageData.name) { console.log(chalk.green(`从package.json读取项目名称: ${packageData.name}`)); return packageData.name; } } // 2. 尝试从README文件读取 const readmeFiles = ['README.md', 'README.txt', 'readme.md', 'README']; for (const readmeFile of readmeFiles) { const readmePath = path.join(projectPath, readmeFile); if (await checkFileExists(readmePath)) { const readmeContent = await fs.readFile(readmePath, 'utf8'); // 尝试多种项目名称格式 const namePatterns = [ /^#\s+(.+)/m, // # 项目名称 /^#+\s*(.+)\s*(?:项目|Project|App)/im, // # XXX项目 /(?:项目名称|Project Name)[\s::]+(.+)/i, // 项目名称: XXX /\*\*(?:项目名称|Project Name)\*\*[\s::]+(.+)/i, // **项目名称**: XXX ]; for (const pattern of namePatterns) { const nameMatch = readmeContent.match(pattern); if (nameMatch && nameMatch[1]) { const projectName = nameMatch[1].trim() .replace(/项目$/, '') .replace(/Project$/, '') .replace(/App$/, '') .trim(); if (projectName) { console.log(chalk.green(`从${readmeFile}读取项目名称: ${projectName}`)); return projectName; } } } } } // 3. 使用目录名作为最后备选 const dirName = path.basename(projectPath); console.log(chalk.yellow(`使用目录名作为项目名称: ${dirName}`)); return dirName; } catch (error) { console.warn(chalk.yellow('读取项目名称失败,使用目录名:'), error.message); return path.basename(projectPath); } } /** * 检查文件是否存在(带缓存) * @param {string} filePath 文件路径 * @returns {Promise} */ async function checkFileExists(filePath) { if (OPTIMIZATION_CONFIG.cache && configFileCache.has(filePath)) { return configFileCache.get(filePath) !== null; } const exists = await fs.pathExists(filePath); if (OPTIMIZATION_CONFIG.cache) { configFileCache.set(filePath, exists); } return exists; } /** * 扫描项目(优化版) * @param {string} projectPath 项目路径 * @returns {Promise} 项目信息 */ async function scanProject(projectPath) { try { console.log(chalk.yellow(`🔍 扫描项目: ${projectPath}`)); console.log(chalk.cyan(`⚡ 性能优化已${OPTIMIZATION_CONFIG.enabled ? '启用' : '禁用'}`)); if (OPTIMIZATION_CONFIG.enabled) { console.log(chalk.cyan(` - 并发处理: ${OPTIMIZATION_CONFIG.concurrency ? `是 (${OPTIMIZATION_CONFIG.concurrencyLimit}个并发)` : '否'}`)); console.log(chalk.cyan(` - 文件大小限制: ${(OPTIMIZATION_CONFIG.maxFileSize / 1024 / 1024).toFixed(1)}MB`)); console.log(chalk.cyan(` - 缓存: ${OPTIMIZATION_CONFIG.cache ? '启用' : '禁用'}`)); } // 检查路径是否存在 if (!await fs.pathExists(projectPath)) { throw new Error(`项目路径不存在: ${projectPath}`); } // 获取项目名称(优先从package.json,其次从README,最后使用目录名) const projectName = await getProjectName(projectPath); // 查找所有源代码文件(优化版:单次扫描) const startScan = Date.now(); const sourceFiles = await findSourceFilesOptimized(projectPath); const scanTime = Date.now() - startScan; console.log(chalk.green(`✅ 文件扫描完成,耗时: ${scanTime}ms,找到 ${sourceFiles.length} 个文件`)); // 分析文件内容(优化版:并发处理) const startAnalysis = Date.now(); const files = []; const fileStats = {}; const languages = new Set(); let totalLines = 0; let totalSize = 0; console.log(chalk.cyan('📊 分析源代码文件...')); // 过滤超大文件 const filteredFiles = sourceFiles.filter(filePath => { try { const stat = fs.statSync(filePath); if (stat.size > OPTIMIZATION_CONFIG.maxFileSize) { console.log(chalk.yellow(`⚠️ 跳过超大文件 (${(stat.size / 1024 / 1024).toFixed(2)}MB): ${path.relative(projectPath, filePath)}`)); return false; } return true; } catch (error) { return false; } }); // 智能选择并发或串行处理 // 小项目(<50文件)用串行,大项目(>=50文件)用并发 const useConcurrency = OPTIMIZATION_CONFIG.concurrency && filteredFiles.length >= 50; if (useConcurrency) { console.log(chalk.cyan(` 使用并发处理 (${OPTIMIZATION_CONFIG.concurrencyLimit}个并发)...`)); const { processFilesConcurrently } = require('./utils-optimized'); const results = await processFilesConcurrently( filteredFiles, async (filePath) => await analyzeFile(filePath, projectPath), OPTIMIZATION_CONFIG.concurrencyLimit ); for (const result of results) { if (result.success && result.data) { const fileInfo = result.data; files.push(fileInfo); const ext = fileInfo.extension; fileStats[ext] = (fileStats[ext] || 0) + 1; languages.add(fileInfo.language); totalLines += fileInfo.lines; totalSize += fileInfo.size; } } } else { // 串行处理(小项目或禁用并发) console.log(chalk.cyan(` 使用串行处理...`)); for (const filePath of filteredFiles) { try { const fileInfo = await analyzeFile(filePath, projectPath); if (fileInfo) { files.push(fileInfo); const ext = fileInfo.extension; fileStats[ext] = (fileStats[ext] || 0) + 1; languages.add(fileInfo.language); totalLines += fileInfo.lines; totalSize += fileInfo.size; } } catch (error) { console.warn(chalk.yellow(`⚠️ 跳过文件 ${filePath}: ${error.message}`)); } } } const analysisTime = Date.now() - startAnalysis; console.log(chalk.green(`✅ 文件分析完成,耗时: ${analysisTime}ms`)); // 按优先级和重要性排序文件 files.sort((a, b) => { if (a.priority !== b.priority) { return a.priority - b.priority; } return b.lines - a.lines; }); // 分析项目特征(优化版:使用缓存的配置文件) const projectFeatures = await analyzeProjectFeaturesOptimized(files, projectPath); const projectInfo = { name: projectName, path: projectPath, files, fileStats, languages: Array.from(languages), totalLines, totalSize, features: projectFeatures, scanTime: new Date().toISOString(), performance: { scanTime, analysisTime, totalTime: scanTime + analysisTime, filesProcessed: files.length, filesSkipped: sourceFiles.length - files.length } }; console.log(chalk.green(`✅ 扫描完成: ${files.length} 个文件, ${totalLines} 行代码`)); console.log(chalk.cyan(`⚡ 总耗时: ${scanTime + analysisTime}ms (扫描: ${scanTime}ms, 分析: ${analysisTime}ms)`)); return projectInfo; } catch (error) { throw new Error(`扫描项目失败: ${error.message}`); } } /** * 分析单个文件 * @param {string} filePath 文件路径 * @param {string} projectPath 项目路径 * @returns {Promise} 文件信息 */ async function analyzeFile(filePath, projectPath) { try { const fileContent = await fs.readFile(filePath, 'utf8'); const ext = path.extname(filePath); const langInfo = SUPPORTED_EXTENSIONS[ext]; if (!langInfo) return null; const relativePath = path.relative(projectPath, filePath); const stats = await fs.stat(filePath); // 统计有效代码行数(排除空行和注释) const codeLines = countCodeLines(fileContent, ext); // 分析文件类型 return { path: relativePath, fullPath: filePath, extension: ext, language: langInfo.language, size: stats.size, lines: codeLines, content: fileContent, priority: langInfo.priority }; } catch (error) { console.warn(chalk.yellow(`⚠️ 分析文件失败 ${filePath}: ${error.message}`)); return null; } } /** * 查找源代码文件(优化版:单次扫描) * @param {string} projectPath 项目路径 * @returns {Promise} 文件路径列表 */ async function findSourceFilesOptimized(projectPath) { const supportedExtensions = Object.keys(SUPPORTED_EXTENSIONS); // 合并所有扩展名为一个 glob 模式 const pattern = supportedExtensions.length === 1 ? `**/*${supportedExtensions[0]}` : `**/*{${supportedExtensions.join(',')}}`; try { const files = await glob.glob(pattern, { cwd: projectPath, ignore: [ ...IGNORE_DIRS.map(dir => `${dir}/**`), ...IGNORE_DIRS.map(dir => `**/${dir}/**`), ...IGNORE_FILES ], absolute: true, nodir: true }); // 去重并排序 return [...new Set(files)].sort(); } catch (error) { console.error(chalk.red('文件扫描失败:'), error.message); throw error; } } /** * 查找源代码文件(旧版本,保持向后兼容) * @param {string} projectPath 项目路径 * @returns {Promise} 文件路径列表 */ async function findSourceFiles(projectPath) { if (OPTIMIZATION_CONFIG.enabled) { return findSourceFilesOptimized(projectPath); } // 原始实现 const supportedExtensions = Object.keys(SUPPORTED_EXTENSIONS); const patterns = supportedExtensions.map(ext => `**/*${ext}`); const allFiles = []; for (const pattern of patterns) { const files = glob.sync(pattern, { cwd: projectPath, ignore: [ ...IGNORE_DIRS.map(dir => `${dir}/**`), ...IGNORE_FILES ], absolute: true }); allFiles.push(...files); } // 去重并排序 return [...new Set(allFiles)].sort(); } /** * 统计有效代码行数 * @param {string} content 文件内容 * @param {string} extension 文件扩展名 * @returns {number} 有效代码行数 */ function countCodeLines(content, extension) { const langInfo = SUPPORTED_EXTENSIONS[extension]; if (!langInfo) return 0; let lines = content.split('\n'); let codeLines = 0; let inMultiLineComment = false; for (let line of lines) { const trimmedLine = line.trim(); // 跳过空行 if (!trimmedLine) continue; // 处理多行注释 if (langInfo.multi_line) { const [startPattern, endPattern] = langInfo.multi_line; if (inMultiLineComment) { if (endPattern.test(trimmedLine)) { inMultiLineComment = false; } continue; } if (startPattern.test(trimmedLine)) { if (!endPattern.test(trimmedLine)) { inMultiLineComment = true; } continue; } } // 跳过单行注释 if (langInfo.single_line && trimmedLine.startsWith(langInfo.single_line)) { continue; } // 特殊处理HTML注释 if (extension === '.html' && trimmedLine.startsWith('